Decompiled source of ForgeUI v0.1.0
ForgeUI.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.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.Permissions; using System.Text; using System.Text.Json; using AIGraph; using Agents; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Unity.IL2CPP; using BoosterImplants; using CellMenu; using ChainedPuzzles; using Enemies; using ForgeCore; using GameData; using Gear; using HarmonyLib; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Il2CppSystem.Reflection; using LevelGeneration; using Localization; using Microsoft.CodeAnalysis; using Player; using SNetwork; using TMPro; using TenCC.Utils; using UnityEngine; using UnityEngine.Events; using UnityEngine.Rendering; using UnityEngine.TextCore.LowLevel; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("ForgeUI")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0+66ec6b43e1a1603bac84a4b9a226674300b2fc7d")] [assembly: AssemblyProduct("ForgeUI")] [assembly: AssemblyTitle("ForgeUI")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ForgeUI { public static class Bearing { public static float Relative(float forwardX, float forwardZ, float toX, float toZ) { if (forwardX * forwardX + forwardZ * forwardZ < 1E-12f || toX * toX + toZ * toZ < 1E-12f) { return float.NaN; } float num = MathF.Atan2(forwardX, forwardZ); return Wrap((MathF.Atan2(toX, toZ) - num) * 180f / (float)Math.PI); } public static float Wrap(float degrees) { if (!float.IsFinite(degrees)) { return degrees; } degrees %= 360f; if (degrees > 180f) { degrees -= 360f; } else if (degrees <= -180f) { degrees += 360f; } return degrees; } public static float HalfHorizontalFov(float verticalFov, float aspect) { return MathF.Atan(MathF.Tan(Math.Clamp(verticalFov, 1f, 179f) * 0.5f * (float)Math.PI / 180f) * Math.Max(0.1f, aspect)) * 180f / (float)Math.PI; } public static bool InView(float azimuth, float halfFov) { return Between(azimuth, 0f) <= halfFov; } public static float Between(float a, float b) { return MathF.Abs(Wrap(a - b)); } public static (float U, float V, float Normal) EdgePoint(float azimuth, float aspect) { aspect = Math.Max(0.1f, aspect); float x = azimuth * (float)Math.PI / 180f; float num = MathF.Sin(x); float num2 = MathF.Cos(x); float num3 = aspect / 2f; float num4 = ((MathF.Abs(num) < 1E-06f) ? float.MaxValue : (num3 / MathF.Abs(num))); float num5 = ((MathF.Abs(num2) < 1E-06f) ? float.MaxValue : (0.5f / MathF.Abs(num2))); float num6; float num7; float item; if (num4 < num5) { num6 = (float)MathF.Sign(num) * num3; num7 = num2 * num4; item = ((num > 0f) ? 180f : 0f); } else { num6 = num * num5; num7 = (float)MathF.Sign(num2) * 0.5f; item = ((num2 > 0f) ? 270f : 90f); } return (U: num6 / aspect + 0.5f, V: num7 + 0.5f, Normal: item); } public static (float X, float Y, bool Inside) ArcPoint(float azimuth, float radius, float span) { span = Math.Clamp(span, 0f, 360f); float num = span / 2f; float num2 = Wrap(azimuth); bool flag = span >= 360f || MathF.Abs(num2) <= num; if (!flag) { num2 = (float)MathF.Sign(num2) * num; } float x = num2 * (float)Math.PI / 180f; return (X: MathF.Sin(x) * radius, Y: MathF.Cos(x) * radius, Inside: flag); } public static int Quadrant(float azimuth) { float num = Wrap(azimuth); if (float.IsNaN(num)) { return 0; } float num2 = MathF.Abs(num); if (num2 <= 45f) { return 0; } if (num2 >= 135f) { return 1; } if (!(num < 0f)) { return 3; } return 2; } } public sealed class BootMessageBeats { public float IgniteEnd { get; init; } = 0.12f; public float BracketsStart { get; init; } = 0.04f; public float BracketsEnd { get; init; } = 0.2f; public float HeaderStart { get; init; } = 0.08f; public float HeaderEnd { get; init; } = 0.32f; public float[] FillTimes { get; init; } = new float[5] { 0.15f, 0.5f, 0.75f, 1.15f, 1.3f }; public float[] FillValues { get; init; } = new float[5] { 0f, 0.35f, 0.48f, 0.92f, 1f }; public float StallStart { get; init; } = 0.5f; public float StallEnd { get; init; } = 0.78f; public float BarFlash { get; init; } = 0.08f; public float[] CheckStarts { get; init; } = new float[5] { 0.42f, 0.58f, 0.74f, 0.9f, 1.06f }; public float CheckType { get; init; } = 0.1f; public float CheckOk { get; init; } = 0.12f; public float CheckStamp { get; init; } = 0.08f; public float ChecksFade { get; init; } = 1.6f; public float ChecksGone { get; init; } = 1.8f; public float MessageStart { get; init; } = 1.32f; public float MessageLock { get; init; } = 1.5f; public float LockFlash { get; init; } = 0.07f; public float LockBurst { get; init; } = 0.25f; public float LockShake { get; init; } = 0.12f; public float FoldStart { get; init; } = 1.9f; public float Duration { get; init; } = 2.2f; public float DownStart { get; init; } = 0.06f; public float DownDecoded { get; init; } = 0.28f; public float DownEnd { get; init; } = 0.8f; public float BarStart => FillTimes[0]; public float BarFull => FillTimes[FillTimes.Length - 1]; public float BarGone => BarFull + BarFlash; public float BarFill(float s) { if (s <= FillTimes[0]) { return 0f; } for (int i = 1; i < FillTimes.Length; i++) { if (!(s > FillTimes[i])) { float num = (s - FillTimes[i - 1]) / Math.Max(0.0001f, FillTimes[i] - FillTimes[i - 1]); return FillValues[i - 1] + (FillValues[i] - FillValues[i - 1]) * num * num * (3f - 2f * num); } } return 1f; } } public sealed class BootMessageLook { public HudColour Accent { get; init; } = new HudColour(0.8f, 0.94f, 1f, 1f); public HudColour Bright { get; init; } = new HudColour(0.957f, 0.965f, 0.957f, 1f); public HudColour White { get; init; } = new HudColour(1f, 1f, 1f, 1f); public HudColour SplitLeft { get; init; } = new HudColour(1f, 0.29f, 0.23f, 1f); public HudColour SplitRight { get; init; } = new HudColour(0.25f, 0.85f, 1f, 1f); public HudColour Stall { get; init; } = new HudColour(1f, 0.56f, 0.17f, 1f); public HudColour Backing { get; init; } = new HudColour(0.016f, 0.03f, 0.047f, 0.62f); public HudColour Down { get; init; } = new HudColour(1f, 0.25f, 0.2f, 1f); public float MessagePx { get; init; } = 50f; public float HeaderPx { get; init; } = 16f; public float CheckPx { get; init; } = 16f; public float CheckRow { get; init; } = 20f; public float BarWidth { get; init; } = 420f; public int Cells { get; init; } = 24; public float CellHeight { get; init; } = 6f; public float CellGap { get; init; } = 3f; public float HeaderY { get; init; } = 18f; public float CheckBase { get; init; } = 44f; public float LineY { get; init; } public float DownPx { get; init; } = 64f; public HudColour? Status { get; init; } public float MessageLift { get; init; } public HudBox? Keepout { get; init; } public float KeepoutGap { get; init; } = 12f; public float OriginRise { get; init; } = 150f; public float Width { get; init; } = 500f; public float Height { get; init; } = 320f; } public sealed class BootMessageWords { public string Header { get; init; } = ""; public string Stall { get; init; } = ""; public string[] Checks { get; init; } = Array.Empty<string>(); public string Ok { get; init; } = ""; public string Message { get; init; } = ""; public string Down { get; init; } = ""; } public enum BootMessageItemKind : byte { Rect, Cell, Line, Plate, Text } public struct BootMessageItem { public BootMessageItemKind Kind; public bool Bold; public byte Align; public float X; public float Y; public float W; public float H; public float T; public float Px; public HudColour Colour; public string? Text; } public sealed class BootMessage { public const int Capacity = 192; private const string Glyphs = "#%$&@*/\\<>=+0123456789ABCDEFXZ"; private const int Variants = 3; private static readonly string[] Percents = MakePercents(); public readonly BootMessageItem[] Items = new BootMessageItem[192]; private string[] _checks = Array.Empty<string>(); private string[][] _typed = Array.Empty<string[]>(); private string[][] _messageDecode = Array.Empty<string[]>(); private string[][] _headerDecode = Array.Empty<string[]>(); private string[][] _downDecode = Array.Empty<string[]>(); private string _message = ""; private string _ok = ""; private string _stall = ""; private string _downWord = ""; private float _oy; private float _lineY = float.NaN; private float _roomWidth = float.NaN; private float _roomHeight = float.NaN; public BootMessageBeats Beats { get; } public BootMessageLook Look { get; } public int Count { get; private set; } public float Width { get; private set; } public float Height { get; private set; } public Func<string, float, float>? Measure { get; set; } private float MaxDrop => (Beats.ChecksGone - Beats.ChecksFade) * 40f; public float LineY { get { if (!float.IsNaN(_lineY)) { return _lineY; } float num = Look.LineY; HudBox? keepout = Look.Keepout; if (keepout.HasValue) { HudBox valueOrDefault = keepout.GetValueOrDefault(); HudBox hudBox = Extent(num); HudBox hudBox2 = new HudBox(hudBox.Left, hudBox.Bottom + Look.OriginRise, hudBox.Right, hudBox.Top + Look.OriginRise); if (hudBox2.Overlaps(valueOrDefault)) { num += valueOrDefault.Top + Look.KeepoutGap - hudBox2.Bottom; } } return _lineY = num; } } public BootMessage(BootMessageBeats beats, BootMessageLook look) { Beats = beats ?? throw new ArgumentNullException("beats"); Look = look ?? throw new ArgumentNullException("look"); if (beats.FillTimes.Length < 2 || beats.FillTimes.Length != beats.FillValues.Length) { throw new ArgumentException("the bar needs at least two fill keys, as many times as values", "beats"); } if (look.Cells < 1) { throw new ArgumentException("the bar needs a cell", "look"); } } public static float TextWidth(string text, float px) { float num = 0f; foreach (char c in text) { num += ((c >= '⺀') ? px : (px * 0.6f)); } return num; } private float W(string text, float px) { if (text.Length != 0) { return Measure?.Invoke(text, px) ?? TextWidth(text, px); } return 0f; } public void Prepare(BootMessageWords words) { if (words == null) { throw new ArgumentNullException("words"); } _message = words.Message ?? ""; _ok = words.Ok ?? ""; _stall = words.Stall ?? ""; int num = Math.Min(words.Checks.Length, Beats.CheckStarts.Length); _checks = new string[num]; _typed = new string[num][]; for (int i = 0; i < num; i++) { string text = (_checks[i] = words.Checks[i] ?? ""); string[] array = new string[text.Length + 1]; for (int j = 0; j <= text.Length; j++) { array[j] = text.Substring(0, j); } _typed[i] = array; } _messageDecode = Decodings(_message, 17); _headerDecode = Decodings(words.Header ?? "", 5); _downWord = words.Down ?? ""; _downDecode = Decodings(_downWord, 29); _lineY = (_roomWidth = (_roomHeight = float.NaN)); } public HudBox Extent(float lineY) { BootMessageLook look = Look; float num = look.BarWidth / 2f + 15.5f; float num2 = 30f; float num3 = ((_checks.Length != 0) ? (0f - look.CheckBase - (float)(_checks.Length - 1) * look.CheckRow - 12f - MaxDrop) : (-14f)); if (_message.Length > 0) { float num4 = W(_message, look.MessagePx); float val = Math.Max(num4 / 2f * 1.35f + 18f, num4 / 2f + 40f) + 1.5f; float num5 = look.MessagePx * 0.75f * 1.35f + 1.5f; num = Math.Max(num, val); num2 = Math.Max(num2, look.MessageLift + num5); num3 = Math.Min(num3, look.MessageLift - num5); } return new HudBox(0f - num, lineY + num3, num, lineY + num2); } private static string[][] Decodings(string text, int seed) { Random random = new Random(seed); string[][] array = new string[text.Length + 1][]; char[] array2 = new char[text.Length]; for (int i = 0; i <= text.Length; i++) { array[i] = new string[3]; for (int j = 0; j < 3; j++) { for (int k = 0; k < text.Length; k++) { array2[k] = ((k < i || char.IsWhiteSpace(text[k])) ? text[k] : "#%$&@*/\\<>=+0123456789ABCDEFXZ"[random.Next("#%$&@*/\\<>=+0123456789ABCDEFXZ".Length)]); } array[i][j] = new string(array2); } } return array; } private static string[] MakePercents() { string[] array = new string[101]; for (int i = 0; i <= 100; i++) { array[i] = i.ToString("000", CultureInfo.InvariantCulture) + "%"; } return array; } private static float Smooth(float t) { t = Math.Clamp(t, 0f, 1f); return t * t * (3f - 2f * t); } private static string Decoded(string[][] steps, float k, float clock, float rate) { if (steps.Length == 0) { return ""; } int num = steps.Length - 1; int num2 = Math.Clamp((int)((float)num * Math.Clamp(k, 0f, 1f)), 0, num); return steps[num2][(int)(Math.Max(0f, clock) * rate) % 3]; } public void Build(float boot, float down) { Count = 0; _oy = 0f; if (down >= 0f) { DownWord(down); } else if (boot >= 0f && boot < Beats.Duration) { float num = (_oy = LineY); Group(boot); _oy = num + Look.MessageLift; Message(boot); } if (Count <= 0) { float width = (Height = 0f); Width = width; return; } if (down >= 0f) { Width = Look.Width; Height = Look.Height; return; } if (float.IsNaN(_roomWidth)) { HudBox hudBox = Extent(LineY); _roomWidth = Math.Max(Look.Width, 2f * Math.Max(0f - hudBox.Left, hudBox.Right)); _roomHeight = Math.Max(Look.Height, 2f * Math.Max(0f - hudBox.Bottom, hudBox.Top)); } Width = _roomWidth; Height = _roomHeight; } private void Group(float b) { BootMessageBeats beats = Beats; BootMessageLook look = Look; float num = (0f - look.BarWidth) / 2f; float num2 = look.BarWidth / 2f; bool flag = b < beats.BarGone; bool flag2 = _checks.Length != 0 && b >= beats.CheckStarts[0] && b < beats.ChecksGone; float num3 = ((b > beats.ChecksFade) ? (1f - Smooth((b - beats.ChecksFade) / Math.Max(0.0001f, beats.ChecksGone - beats.ChecksFade))) : 1f); float num4 = ((b > beats.ChecksFade) ? ((b - beats.ChecksFade) * 40f) : 0f); int num5 = 0; for (int i = 0; i < _checks.Length; i++) { if (b >= beats.CheckStarts[i]) { num5++; } } if (b >= beats.HeaderStart && (flag || flag2)) { float num6 = (flag ? (look.HeaderY + 12f) : (0f - look.CheckBase + 13f - num4)); float num7 = ((flag2 && num5 > 0) ? (0f - look.CheckBase - (float)(num5 - 1) * look.CheckRow - num4 - 12f) : (-14f)); Plate(0f, (num6 + num7) / 2f, look.BarWidth + 24f, num6 - num7, 10f, look.Backing, flag2 ? num3 : 1f); } if (flag) { Rect(0f, 0f, look.BarWidth * Smooth(b / Math.Max(0.0001f, beats.IgniteEnd)), 2f, look.Accent, 0.6f); if (b >= beats.BracketsStart) { float num8 = Smooth((b - beats.BracketsStart) / Math.Max(0.0001f, beats.BracketsEnd - beats.BracketsStart)); for (int j = -1; j <= 1; j += 2) { float num9 = ((j < 0) ? (num - 14f) : (num2 + 14f)); float num10 = (float)(-j) * 22f * num8; Line(num9, 26f, num9, -12f, 2.5f, look.Accent, 0.86f); Line(num9, 26f, num9 + num10, 26f, 2.5f, look.Accent, 0.86f); Line(num9, -12f, num9 + num10, -12f, 2.5f, look.Accent, 0.86f); } } float num11 = beats.BarFill(b); if (b >= beats.HeaderStart) { float k = Smooth((b - beats.HeaderStart) / Math.Max(0.0001f, beats.HeaderEnd - beats.HeaderStart)); Text(Decoded(_headerDecode, k, b, 40f), num, look.HeaderY, look.HeaderPx, 0, look.Accent, 0.95f, bold: false); Text(Percents[Math.Clamp((int)MathF.Round(num11 * 100f), 0, 100)], num2, look.HeaderY, look.HeaderPx, 2, look.Bright, 0.95f, bold: false); if (b > beats.StallStart && b < beats.StallEnd && (int)(b * 12f) % 2 == 0) { Text(_stall, 0f, look.HeaderY, look.HeaderPx - 2f, 1, look.Stall, 0.9f, bold: true); } } if (b >= beats.BarStart) { int cells = look.Cells; float num12 = (look.BarWidth - look.CellGap * (float)(cells - 1)) / (float)cells; float num13 = num11 * (float)cells; float num14 = ((b >= beats.BarFull) ? (1f - (b - beats.BarFull) / Math.Max(0.0001f, beats.BarFlash)) : 0f); for (int l = 0; l < cells; l++) { float cx = num + (float)l * (num12 + look.CellGap) + num12 / 2f; if ((float)(l + 1) <= num13) { Cell(cx, 0f, num12, look.CellHeight, (num14 > 0f || ((float)(l + 1) > num13 - 1f && b < beats.BarFull)) ? look.Bright : look.Accent, 0.92f); } else if ((float)l < num13) { Cell(cx, 0f, num12, look.CellHeight, look.Bright, 0.92f * (num13 - (float)l)); } else { Cell(cx, 0f, num12, look.CellHeight, look.Accent, 0.15f); } } } } if (!flag2) { return; } for (int m = 0; m < num5; m++) { float num15 = beats.CheckStarts[m]; float num16 = 0f - look.CheckBase - (float)m * look.CheckRow - num4; string text = _checks[m]; string[] obj = _typed[m]; int num17 = (int)((float)text.Length * Math.Min(1f, (b - num15) / Math.Max(0.0001f, beats.CheckType))); string text2 = obj[Math.Clamp(num17, 0, text.Length)]; Text(text2, num, num16, look.CheckPx, 0, look.Accent, 0.9f * num3, bold: false); if (num17 < text.Length) { Rect(num + W(text2, look.CheckPx) + 6f, num16, 8f, look.CheckPx * 0.85f, look.Bright, 0.85f * num3); } if (!(b < num15 + beats.CheckOk)) { float num18 = W(_ok, look.CheckPx); Line(num + W(text, look.CheckPx) + 10f, num16, num2 - num18 - 14f, num16, 1f, look.Accent, 0.28f * num3); float num19 = 1f - Math.Clamp((b - num15 - beats.CheckOk) / Math.Max(0.0001f, beats.CheckStamp), 0f, 1f); Text(_ok, num2, num16, look.CheckPx, 2, (num19 > 0f) ? look.White : (look.Status ?? look.Accent), num3, bold: true); if (num19 > 0f) { float num20 = num2 - num18 - 7f; float num21 = num2 + 5f; float num22 = num16 - 11f; float num23 = num16 + 11f; Line(num20, num22, num21, num22, 2f, look.White, num19); Line(num21, num22, num21, num23, 2f, look.White, num19); Line(num21, num23, num20, num23, 2f, look.White, num19); Line(num20, num23, num20, num22, 2f, look.White, num19); } } } } private void Message(float b) { BootMessageBeats beats = Beats; BootMessageLook look = Look; if (b < beats.MessageStart || _message.Length == 0) { return; } float messagePx = look.MessagePx; float num = W(_message, messagePx); if (b < beats.MessageLock) { float num2 = Smooth((b - beats.MessageStart) / Math.Max(0.0001f, beats.MessageLock - beats.MessageStart)); string text = Decoded(_messageDecode, num2, b, 60f); float num3 = 10f * (1f - num2); Text(text, 0f - num3, 0f, messagePx, 1, look.SplitLeft, 0.55f, bold: true); Text(text, num3, 0f, messagePx, 1, look.SplitRight, 0.55f, bold: true); Text(text, 0f, 0f, messagePx, 1, look.Accent, 0.95f, bold: true); } else if (b < beats.FoldStart) { float num4 = b - beats.MessageLock; float x = 3f * Math.Max(0f, 1f - num4 / Math.Max(0.0001f, beats.LockShake)) * (((int)(num4 * 90f) % 2 == 1) ? 1f : (-1f)); float t = Math.Max(0f, 1f - num4 / Math.Max(0.0001f, beats.LockFlash)); Text(_message, x, 0f, messagePx * 1.06f, 1, look.Accent, 0.2f, bold: true); Text(_message, x, 0f, messagePx, 1, look.Accent.Mix(look.White, t), 1f, bold: true); if (num4 < beats.LockBurst) { float num5 = 1f + 0.35f * Smooth(num4 / beats.LockBurst); float num6 = num / 2f * num5 + 18f; float num7 = messagePx * 0.75f * num5; float alpha = 1f - num4 / beats.LockBurst; Line(0f - num6, 0f - num7, num6, 0f - num7, 2.5f, look.Bright, alpha); Line(num6, 0f - num7, num6, num7, 2.5f, look.Bright, alpha); Line(num6, num7, 0f - num6, num7, 2.5f, look.Bright, alpha); Line(0f - num6, num7, 0f - num6, 0f - num7, 2.5f, look.Bright, alpha); if (num4 < 0.06f) { Line((0f - num) / 2f - 40f, (0f - messagePx) * 0.62f, num / 2f + 40f, (0f - messagePx) * 0.62f, 3f, look.White, 1f); } } for (int i = -1; i <= 1; i += 2) { float num8 = (float)i * (num / 2f + 26f); float num9 = messagePx * 0.55f; Line(num8, 0f - num9, num8, num9, 2.5f, look.Accent, 0.8f); Line(num8, num9, num8 - (float)i * 14f, num9, 2.5f, look.Accent, 0.8f); Line(num8, 0f - num9, num8 - (float)i * 14f, 0f - num9, 2.5f, look.Accent, 0.8f); } } else { float num10 = (b - beats.FoldStart) / Math.Max(0.0001f, beats.Duration - beats.FoldStart); if (num10 < 0.4f) { float num11 = 1f - Smooth(num10 / 0.4f); Text(_message, 0f, 0f, messagePx, 1, look.Accent, num11, bold: true); Rect(0f, 0f, num, 2f + 6f * (1f - num11), look.Bright, 1f); } else { float num12 = num / 2f * (1f - Smooth((num10 - 0.4f) / 0.5f)); Rect(0f, 0f, 2f * num12 + 6f, 4f, look.Bright, 1f); } } } private ref BootMessageItem Next() { if (Count >= 192) { throw new InvalidOperationException("the power-on message is full"); } ref BootMessageItem reference = ref Items[Count++]; reference = default(BootMessageItem); return ref reference; } private void Rect(float cx, float cy, float w, float h, HudColour c, float alpha) { if (!(alpha <= 0f) && !(w <= 0f)) { ref BootMessageItem reference = ref Next(); reference.Kind = BootMessageItemKind.Rect; reference.X = cx; reference.Y = cy + _oy; reference.W = w; reference.H = h; reference.Colour = c.Faded(alpha); } } private void Cell(float cx, float cy, float w, float h, HudColour c, float alpha) { if (!(alpha <= 0f)) { ref BootMessageItem reference = ref Next(); reference.Kind = BootMessageItemKind.Cell; reference.X = cx; reference.Y = cy + _oy; reference.W = w; reference.H = h; reference.Colour = c.Faded(alpha); } } private void Plate(float cx, float cy, float w, float h, float cut, HudColour c, float alpha) { if (!(alpha <= 0f) && !(h <= 0f)) { ref BootMessageItem reference = ref Next(); reference.Kind = BootMessageItemKind.Plate; reference.X = cx; reference.Y = cy + _oy; reference.W = w; reference.H = h; reference.T = cut; reference.Colour = c.Faded(alpha); } } private void Line(float x0, float y0, float x1, float y1, float t, HudColour c, float alpha) { if (!(alpha <= 0f)) { ref BootMessageItem reference = ref Next(); reference.Kind = BootMessageItemKind.Line; reference.X = x0; reference.Y = y0 + _oy; reference.W = x1; reference.H = y1 + _oy; reference.T = t; reference.Colour = c.Faded(alpha); } } private void Text(string text, float x, float y, float px, byte align, HudColour c, float alpha, bool bold) { if (text.Length != 0 && !(alpha <= 0f)) { ref BootMessageItem reference = ref Next(); reference.Kind = BootMessageItemKind.Text; reference.X = x; reference.Y = y + _oy; reference.Px = px; reference.Align = align; reference.Colour = c.Faded(alpha); reference.Text = text; reference.Bold = bold; } } private void DownWord(float d) { BootMessageBeats beats = Beats; BootMessageLook look = Look; if (_downWord.Length != 0 && !(d < beats.DownStart) && !(d >= beats.DownEnd)) { float downPx = look.DownPx; float num = W(_downWord, downPx); float num2 = Smooth((d - beats.DownStart) / Math.Max(0.0001f, beats.DownDecoded - beats.DownStart)); string text = Decoded(_downDecode, num2, d, 50f); float num3 = d - beats.DownDecoded; float num4 = ((num3 > 0f && (int)(num3 * 22f) % 7 == 3) ? 0.35f : 1f); float num5 = num / 2f + 30f; float num6 = downPx * 0.72f; Plate(0f, 0f, 2f * num5 + 16f, 2f * num6, 10f, look.Backing, num4); float num7 = 7f * (1f - num2) + 2f; Text(text, 0f - num7, 0f, downPx, 1, look.Down.Mix(look.White, 0.25f), 0.45f * num4, bold: true); Text(text, num7, 0f, downPx, 1, look.Down.Mix(look.Backing, 0.5f), 0.6f * num4, bold: true); Text(text, 0f, 0f, downPx, 1, look.Down, num4, bold: true); for (int i = -1; i <= 1; i += 2) { float num8 = (float)i * num5; Line(num8, 0f - num6, num8, num6, 3f, look.Down, 0.9f * num4); Line(num8, num6, num8 - (float)i * 18f, num6, 3f, look.Down, 0.9f * num4); Line(num8, 0f - num6, num8 - (float)i * 18f, 0f - num6, 3f, look.Down, 0.9f * num4); } } } } public static class ChatDockMath { public const float Pad = 10f; public const float InputRow = 25f; public const float Line = 20f; public const int DefaultLines = 6; public const int MaxLines = 20; public const float FadeInSeconds = 0.12f; public const float FadeOutSeconds = 0.2f; public const float IdleFadeSeconds = 1.5f; public static int Lines(int lines) { return Math.Clamp(lines, 1, 20); } public static float SlotHeight(int lines, float line, float inputRow) { return 20f + Math.Max(0f, inputRow) + (float)Lines(lines) * Math.Max(0f, line); } public static float Ceiling(HudBox slot) { return slot.Top - 10f; } public static float IdleCeiling(HudBox slot, float inputRow, float line, int idleLines) { return Math.Min(Ceiling(slot), Corner(slot).Y + Math.Max(0f, inputRow) + (float)Lines(idleLines) * Math.Max(0f, line)); } public static bool Overflows(float ceiling, float lineTop) { return lineTop > ceiling + 0.5f; } public static float IdleAlpha(float since, float idleSeconds, float opacity) { float num = Math.Clamp(opacity, 0f, 1f); float num2 = Math.Max(0f, since) - Math.Max(0f, idleSeconds); if (!(num2 <= 0f)) { return num * Math.Clamp(1f - num2 / 1.5f, 0f, 1f); } return num; } public static (float X, float Y) Corner(HudBox slot) { return (X: slot.Left + 10f, Y: slot.Bottom + 10f); } public static HudBox Plate(HudBox slot, float contentTop, float inputRow) { float item = Corner(slot).Y; float num = Math.Max(Math.Max(0f, inputRow), contentTop - item); float num2 = Math.Min(slot.Height, num + 20f); return new HudBox(slot.Left, slot.Bottom, slot.Right, slot.Bottom + num2); } public static float TextSize(float hudSize, float px, float scale, float line) { float num = hudSize * px / 20f * scale; if (!(num > 0f)) { return 0f; } return Math.Min(num, 0.95f * line); } public static float Fade(float alpha, bool typing, float dt) { float num = Math.Max(0f, dt) / (typing ? 0.12f : 0.2f); return Math.Clamp(typing ? (alpha + num) : (alpha - num), 0f, 1f); } } public readonly struct CommandState { public int Uses { get; } public int CooldownSeconds { get; } public bool Cooling => CooldownSeconds > 0; public bool Empty => Uses == 0; public bool Ready { get { if (!Cooling) { return !Empty; } return false; } } public CommandState(int uses, int cooldownSeconds) { Uses = uses; CooldownSeconds = cooldownSeconds; } } public static class CommandCode { public static bool StartsWith<T>(IReadOnlyList<T> code, IReadOnlyList<T> typed) { if (typed.Count > code.Count) { return false; } EqualityComparer<T> equalityComparer = EqualityComparer<T>.Default; for (int i = 0; i < typed.Count; i++) { if (!equalityComparer.Equals(code[i], typed[i])) { return false; } } return true; } } public static class CommandGeometry { public const float IdleRow = 30f; public const float IdleGap = 4f; public const float TableRow = 38f; public const float TableGap = 5f; public const float Header = 26f; public const float Hint = 28f; public const float Pad = 10f; public const float Block = 6f; public const float BlockGap = 8f; public const float NameTail = 16f; public const float IdleCell = 52f; public const float TableCell = 62f; public const float CellInset = 3f; public const float Cut = 8f; public const float Key = 26f; public const float KeyStep = 30f; public const float KeyPad = 8f; public const float KeyGap = 16f; public const float IdleName = 16f; public const float TableName = 19f; public static float Row(float table) { return 30f + 8f * table; } public static float Gap(float table) { return 4f + 1f * table; } public static float Cell(float table) { return 52f + 10f * table; } public static float NamePx(float table) { return 16f + 3f * table; } public static float Codes(int steps) { if (steps > 0) { return 8f + (float)steps * 30f - 4f + 16f; } return 0f; } public static float Width(float codes, float name, float table) { return codes + 10f + 6f + 8f + name + 16f + Cell(table); } } public sealed class CommandMotion { public const float Rate = 18f; private const float Settle = 0.002f; private readonly float[] _shown; private readonly float[] _shownTarget; private int _count; private bool _primed; private float _tableTarget; private float _noteTarget; public float Table { get; private set; } public float Note { get; private set; } public float Codes { get; private set; } public int Count => _count; public bool Settled { get; private set; } = true; public float Height => HeightOf(Table, Note, _shown); public float TargetHeight => HeightOf(_tableTarget, _noteTarget, _shownTarget); public CommandMotion(int maxRows) { _shown = new float[Math.Max(1, maxRows)]; _shownTarget = new float[_shown.Length]; } public float Shown(int index) { if ((uint)index >= (uint)_count) { return 0f; } return _shown[index]; } public void Reset() { _primed = false; } public void Step(bool table, int count, int held, float codes, bool note, float dt) { count = Math.Clamp(count, 0, _shown.Length); _tableTarget = (table ? 1f : 0f); _noteTarget = (note ? 1f : 0f); for (int i = 0; i < count; i++) { _shownTarget[i] = ((held < 0 || i == held) ? 1f : 0f); } for (int j = _count; j < count; j++) { _shown[j] = _shownTarget[j]; } _count = count; if (!_primed) { _primed = true; Table = _tableTarget; Note = _noteTarget; Codes = codes; for (int k = 0; k < count; k++) { _shown[k] = _shownTarget[k]; } Settled = true; } else { bool settled = true; Table = Approach(Table, _tableTarget, dt, ref settled); Note = Approach(Note, _noteTarget, dt, ref settled); Codes = Approach(Codes, codes, dt, ref settled, 0.05f); for (int l = 0; l < count; l++) { _shown[l] = Approach(_shown[l], _shownTarget[l], dt, ref settled); } Settled = settled; } } private static float Approach(float value, float target, float dt, ref bool settled, float snap = 0.002f) { float num = Ease.Damp(value, target, 18f, dt); if (Math.Abs(num - target) <= snap) { return target; } settled = false; return num; } private float HeightOf(float table, float note, float[] shown) { float num = 0f; float num2 = CommandGeometry.Row(table) + CommandGeometry.Gap(table); for (int i = 0; i < _count; i++) { num += num2 * shown[i]; } if (num > 0f) { num -= CommandGeometry.Gap(table); } return 26f * table + 28f * Math.Max(table, note) + num; } public static float TallestHeight(int count) { return 54f + (float)Math.Max(0, count) * 43f - ((count > 0) ? 5f : 0f); } } public static class CommandDock { public const float Gap = 12f; public static void Limits(ReadOnlySpan<HudBox> obstacles, float left, float right, float bottom, float top, out float floor, out float ceiling, out int floorBy, out int ceilingBy) { floor = bottom; ceiling = top; floorBy = (ceilingBy = -1); for (int i = 0; i < obstacles.Length; i++) { HudBox hudBox = obstacles[i]; if (hudBox.Width <= 0f || hudBox.Height <= 0f || hudBox.Right <= left || hudBox.Left >= right) { continue; } if (hudBox.Bottom + hudBox.Top <= 0f) { if (hudBox.Top + 12f > floor) { floor = hudBox.Top + 12f; floorBy = i; } } else if (hudBox.Bottom - 12f < ceiling) { ceiling = hudBox.Bottom - 12f; ceilingBy = i; } } } public static float Right(ReadOnlySpan<HudBox> obstacles, float right, float bottom, float top, float width, float height) { for (int i = 0; i < 8; i++) { Limits(obstacles, right - width, right, bottom, top, out var floor, out var ceiling, out var _, out var j); if (ceiling - floor >= height) { return right; } float num = right; ReadOnlySpan<HudBox> readOnlySpan = obstacles; for (j = 0; j < readOnlySpan.Length; j++) { HudBox hudBox = readOnlySpan[j]; if (hudBox.Width > 0f && hudBox.Height > 0f && hudBox.Left < right && hudBox.Right > right - width && hudBox.Bottom < height / 2f + 12f && hudBox.Top > (0f - height) / 2f - 12f) { num = Math.Min(num, hudBox.Left - 12f); } } if (num >= right) { return right; } right = num; } return right; } public static float Centre(float height, float floor, float ceiling) { float num = 0f; if (num - height / 2f < floor) { num = floor + height / 2f; } if (num + height / 2f > ceiling) { num = Math.Max(floor + height / 2f, ceiling - height / 2f); } return num; } } public enum CompassPointKind : byte { Here, Enemy, Ammo, Health, Tool, Supply, Item, Container, Terminal, Door, Generator, Caution, Sign, Hsu, Locator, TerminalPing, Teammate } public readonly struct CompassPoint { public CompassPointKind Kind { get; } public float Azimuth { get; } public float Distance { get; } public int Slot { get; } public bool PlayerColour { get; } public bool Mod { get; } public float R { get; } public float G { get; } public float B { get; } public CompassPoint(CompassPointKind kind, float azimuth, float distance, int slot, bool playerColour, float r, float g, float b, bool mod = false) { Kind = kind; Azimuth = azimuth; Distance = distance; Slot = slot; PlayerColour = playerColour; R = r; G = g; B = b; Mod = mod; } } public static class CompassPoints { public const int Kinds = 17; private static readonly float[][] Glyphs = new float[17][] { new float[16] { 0f, 1f, 1f, 0f, 1f, 0f, 0f, -1f, 0f, -1f, -1f, 0f, -1f, 0f, 0f, 1f }, new float[8] { -1f, -1f, 1f, 1f, -1f, 1f, 1f, -1f }, new float[12] { -0.6f, -1f, -0.6f, 0.7f, 0f, -1f, 0f, 1f, 0.6f, -1f, 0.6f, 0.7f }, new float[8] { 0f, -1f, 0f, 1f, -1f, 0f, 1f, 0f }, new float[8] { -1f, 1f, 1f, 1f, 0f, 1f, 0f, -1f }, new float[24] { 0f, 1f, 0.87f, 0.5f, 0.87f, 0.5f, 0.87f, -0.5f, 0.87f, -0.5f, 0f, -1f, 0f, -1f, -0.87f, -0.5f, -0.87f, -0.5f, -0.87f, 0.5f, -0.87f, 0.5f, 0f, 1f }, new float[16] { -0.8f, -0.8f, 0.8f, -0.8f, 0.8f, -0.8f, 0.8f, 0.8f, 0.8f, 0.8f, -0.8f, 0.8f, -0.8f, 0.8f, -0.8f, -0.8f }, new float[20] { -0.8f, -0.8f, 0.8f, -0.8f, 0.8f, -0.8f, 0.8f, 0.8f, 0.8f, 0.8f, -0.8f, 0.8f, -0.8f, 0.8f, -0.8f, -0.8f, -0.8f, 0.2f, 0.8f, 0.2f }, new float[24] { -1f, 1f, 1f, 1f, 1f, 1f, 1f, -0.3f, 1f, -0.3f, -1f, -0.3f, -1f, -0.3f, -1f, 1f, 0f, -0.3f, 0f, -1f, -0.5f, -1f, 0.5f, -1f }, new float[16] { -0.8f, -1f, -0.8f, 1f, -0.8f, 1f, 0.8f, 1f, 0.8f, 1f, 0.8f, -1f, 0f, 1f, 0f, -1f }, new float[12] { 0.3f, 1f, -0.4f, 0f, -0.4f, 0f, 0.4f, 0f, 0.4f, 0f, -0.3f, -1f }, new float[16] { 0f, 1f, 1f, -0.8f, 1f, -0.8f, -1f, -0.8f, -1f, -0.8f, 0f, 1f, 0f, 0.4f, 0f, -0.3f }, new float[12] { -0.7f, -1f, -0.7f, 1f, -0.7f, 1f, 0.8f, 0.6f, 0.8f, 0.6f, -0.7f, 0.2f }, new float[20] { -0.5f, -1f, 0.5f, -1f, 0.5f, -1f, 0.5f, 1f, 0.5f, 1f, -0.5f, 1f, -0.5f, 1f, -0.5f, -1f, -0.5f, 0.4f, 0.5f, 0.4f }, new float[32] { 0f, 1f, 0f, 0.55f, 0f, -1f, 0f, -0.55f, 1f, 0f, 0.55f, 0f, -1f, 0f, -0.55f, 0f, 0f, 0.3f, 0.3f, 0f, 0.3f, 0f, 0f, -0.3f, 0f, -0.3f, -0.3f, 0f, -0.3f, 0f, 0f, 0.3f }, new float[32] { 0f, 1f, 1f, 0f, 1f, 0f, 0f, -1f, 0f, -1f, -1f, 0f, -1f, 0f, 0f, 1f, 0f, 0.4f, 0.4f, 0f, 0.4f, 0f, 0f, -0.4f, 0f, -0.4f, -0.4f, 0f, -0.4f, 0f, 0f, 0.4f }, new float[16] { -0.8f, -0.7f, 0f, 0.9f, 0f, 0.9f, 0.8f, -0.7f, 0.8f, -0.7f, 0f, -0.2f, 0f, -0.2f, -0.8f, -0.7f } }; public static bool Shown(CompassPointKind kind, bool teammates) { if (!teammates) { return kind != CompassPointKind.Teammate; } return true; } public static ReadOnlySpan<float> Glyph(CompassPointKind kind) { return ((uint)kind < (uint)Glyphs.Length) ? Glyphs[(uint)kind] : Glyphs[0]; } } public sealed class CompassReading { private readonly float[] _x; private readonly float[] _y; private readonly float[] _z; private readonly float[] _r; private readonly float[] _g; private readonly float[] _b; private readonly CompassPointKind[] _kind; private readonly bool[] _player; private readonly bool[] _mod; private readonly int[] _slot; private readonly CompassPoint[] _points; public int Capacity => _x.Length; public int Count { get; private set; } public CompassReading(int capacity) { capacity = Math.Max(1, capacity); _x = new float[capacity]; _y = new float[capacity]; _z = new float[capacity]; _r = new float[capacity]; _g = new float[capacity]; _b = new float[capacity]; _kind = new CompassPointKind[capacity]; _player = new bool[capacity]; _mod = new bool[capacity]; _slot = new int[capacity]; _points = new CompassPoint[capacity]; } public void Begin() { Count = 0; } public bool Add(float x, float y, float z, CompassPointKind kind, int slot, bool playerColour, float r, float g, float b, bool mod = false) { if (Count >= _x.Length || !float.IsFinite(x) || !float.IsFinite(y) || !float.IsFinite(z)) { return false; } int num = Count++; _x[num] = x; _y[num] = y; _z[num] = z; _kind[num] = kind; _slot[num] = slot; _player[num] = playerColour; _mod[num] = mod; if (playerColour || mod) { _r[num] = r; _g[num] = g; _b[num] = b; } else { _r[num] = 1f; _g[num] = 1f; _b[num] = 1f; } return true; } public void Locate(float eyeX, float eyeY, float eyeZ, float forwardX, float forwardZ) { for (int i = 0; i < Count; i++) { float num = _x[i] - eyeX; float num2 = _y[i] - eyeY; float num3 = _z[i] - eyeZ; float azimuth = Bearing.Relative(forwardX, forwardZ, num, num3); float distance = MathF.Sqrt(num * num + num2 * num2 + num3 * num3); _points[i] = new CompassPoint(_kind[i], azimuth, distance, _slot[i], _player[i], _r[i], _g[i], _b[i], _mod[i]); } } public CompassPoint Get(int index) { if ((uint)index >= (uint)Count) { throw new ArgumentOutOfRangeException("index"); } return _points[index]; } } public enum ConfirmAnswer : byte { Confirm, Cancel, Dismissed } public sealed class ConfirmQueue<T> { private readonly struct Entry { internal readonly int Ticket; internal readonly string Owner; internal readonly T Question; internal Entry(int ticket, string owner, T question) { Ticket = ticket; Owner = owner; Question = question; } } public const int Pending = 4; private readonly Action<T, ConfirmAnswer> _answered; private readonly List<Entry> _entries = new List<Entry>(5); private int _nextTicket; public bool Showing => _entries.Count > 0; public T Current { get { if (_entries.Count <= 0) { throw new InvalidOperationException("no question is showing"); } return _entries[0].Question; } } public int Count => _entries.Count; public int Version { get; private set; } public ConfirmQueue(Action<T, ConfirmAnswer> answered) { _answered = answered ?? throw new ArgumentNullException("answered"); } public int Ask(string owner, T question) { if (_entries.Count > 4) { _answered(question, ConfirmAnswer.Dismissed); return 0; } _nextTicket = ((_nextTicket == int.MaxValue) ? 1 : (_nextTicket + 1)); _entries.Add(new Entry(_nextTicket, owner, question)); if (_entries.Count == 1) { Version++; } return _nextTicket; } public bool Answer(ConfirmAnswer answer) { if (_entries.Count == 0) { return false; } End(0, answer); return true; } public bool Withdraw(int ticket) { for (int i = 0; i < _entries.Count; i++) { if (_entries[i].Ticket == ticket) { End(i, ConfirmAnswer.Dismissed); return true; } } return false; } public void WithdrawOwner(string owner) { for (int num = _entries.Count - 1; num >= 0; num--) { if (string.Equals(_entries[num].Owner, owner, StringComparison.Ordinal)) { End(num, ConfirmAnswer.Dismissed); } } } private void End(int index, ConfirmAnswer answer) { Entry entry = _entries[index]; _entries.RemoveAt(index); if (index == 0) { Version++; } _answered(entry.Question, answer); } } public static class TooltipLayout { public static (float X, float Y) Place(float anchorX, float anchorY, float anchorHalfHeight, float width, float height, float gap, float boundsX, float boundsY, float boundsHalfWidth, float boundsHalfHeight, out bool below) { float num = boundsY + boundsHalfHeight; float num2 = boundsY - boundsHalfHeight; float num3 = anchorY + anchorHalfHeight + gap + height / 2f; below = num3 + height / 2f > num && anchorY - anchorHalfHeight - gap - height >= num2; float item = (below ? (anchorY - anchorHalfHeight - gap - height / 2f) : num3); float num4 = boundsX - boundsHalfWidth + width / 2f; float num5 = boundsX + boundsHalfWidth - width / 2f; return (X: (num4 > num5) ? boundsX : Math.Clamp(anchorX, num4, num5), Y: item); } } public static class CooldownMath { public const int PieSteps = 48; public static float Remaining01(float remaining, float total) { if (!(total > 0f)) { return 0f; } return Ease.Clamp01(remaining / total); } public static int PieStep(float remaining01) { if (remaining01 <= 0f) { return 0; } return Math.Clamp((int)MathF.Ceiling(remaining01 * 48f - 0.0001f), 1, 48); } } public static class NumberText { private const int WholeLimit = 1000; private static readonly string[] Whole = new string[1000]; private static readonly string[] Tenths = new string[100]; private static readonly string[] Percents = new string[101]; private static readonly string[] MeterTexts = new string[1000]; public static string Tabular(string digits) { return "<mspace=0.6em>" + digits + "</mspace>"; } private static string Make(int n) { return n.ToString(CultureInfo.InvariantCulture); } public static string Count(int n) { if (n < 0 || n >= 1000) { return Make(n); } ref string reference = ref Whole[n]; return reference ?? (reference = Make(n)); } public static string Left(float seconds) { if (seconds <= 0f) { return Count(0); } int num = (int)MathF.Ceiling(seconds * 10f - 0.0001f); if (num >= 100) { return Count((int)MathF.Ceiling(seconds - 0.0001f)); } ref string reference = ref Tenths[num]; return reference ?? (reference = Make(num / 10) + "." + Make(num % 10)); } public static string Damage(float amount) { int num = (int)MathF.Round(Math.Max(0f, amount) * 10f); if (num >= 100) { return Count((int)MathF.Round(amount)); } ref string reference = ref Tenths[num]; return reference ?? (reference = Make(num / 10) + "." + Make(num % 10)); } public static string Percent(int n) { n = Math.Clamp(n, 0, 100); ref string reference = ref Percents[n]; return reference ?? (reference = Make(n) + "%"); } public static string Meters(int n) { if (n < 0) { n = 0; } if (n >= 1000) { return Make(n) + " m"; } ref string reference = ref MeterTexts[n]; return reference ?? (reference = Make(n) + " m"); } } public static class TextWidth { public static float Estimate(string text, float px) { float num = 0f; foreach (char c in text) { num += ((c < '⺀') ? 0.6f : 1f); } return num * px; } } internal static class Countdown { internal const float UrgentSeconds = 3f; internal const string UrgentHex = "FF5A3C"; internal const string NameHex = "D5DEE4"; internal const float NearReach = 2.4f; internal static decimal Tenth(float remaining) { return decimal.Round((decimal)Math.Max(0f, remaining), 1, MidpointRounding.AwayFromZero); } internal static bool Urgent(float remaining) { return remaining <= 3f; } internal static string NamePart(string name) { return "<color=#D5DEE4><size=78%><cspace=0.06em><noparse>" + name.ToUpperInvariant() + "</noparse></cspace></size></color>"; } internal static string Digits(float remaining) { string text = Math.Max(0f, remaining).ToString("0.0", CultureInfo.InvariantCulture); int num = text.IndexOf('.'); return "<size=" + (Urgent(remaining) ? "150" : "128") + "%><b><mspace=0.58em>" + text.Substring(0, num) + "</mspace>.<mspace=0.58em>" + text.Substring(num + 1) + "</mspace></b></size>"; } internal static string Label(string namePart, float remaining, string accentHex, string seconds) { return namePart + "<space=0.8em><color=#" + (Urgent(remaining) ? "FF5A3C" : accentHex) + "><size=74%>" + seconds + "</size></color>"; } internal static (float X, float Y, float Z) Beside(float eyeX, float eyeY, float eyeZ, float forwardX, float forwardZ, float x, float y, float z, float gap) { float num = x - eyeX; float num2 = z - eyeZ; float num3 = MathF.Sqrt(num * num + num2 * num2); float num4; float num5; if (num3 > 0.05f) { num4 = num / num3; num5 = num2 / num3; } else { float num6 = MathF.Sqrt(forwardX * forwardX + forwardZ * forwardZ); if (num6 * num6 > 0.0001f) { num4 = forwardX / num6; num5 = forwardZ / num6; } else { num4 = 0f; num5 = 1f; } } float num7 = MathF.Max(num3, 2.4f); float num8 = num5; float num9 = 0f - num4; float num10 = Math.Clamp(eyeY - y - 0.15f, 1f, 2.4f) + num7 * 0.02f; float num11 = MathF.Max(0.5f, num7 * gap); return (X: eyeX + num4 * num7 + num8 * num11, Y: y + num10, Z: eyeZ + num5 * num7 + num9 * num11); } } public static class Ease { public static float Clamp01(float t) { if (!float.IsNaN(t)) { if (!(t < 0f)) { if (!(t > 1f)) { return t; } return 1f; } return 0f; } return 0f; } public static float Lerp(float from, float to, float t) { return from + (to - from) * Clamp01(t); } public static float OutCubic(float t) { t = Clamp01(t); float num = 1f - t; return 1f - num * num * num; } public static float InCubic(float t) { t = Clamp01(t); return t * t * t; } public static float InOutCubic(float t) { t = Clamp01(t); if (!(t < 0.5f)) { return 1f - MathF.Pow(-2f * t + 2f, 3f) / 2f; } return 4f * t * t * t; } public static float OutBack(float t) { t = Clamp01(t) - 1f; return 1f + t * t * (2.70158f * t + 1.70158f); } public static float Blink(float seconds, float hertz) { if (!(seconds * hertz % 1f < 0.5f)) { return 0f; } return 1f; } public static float Pulse(float seconds, float hertz) { return 0.5f - 0.5f * MathF.Cos(seconds * hertz * ((float)Math.PI * 2f)); } public static float Damp(float current, float target, float rate, float dt) { return current + (target - current) * (1f - MathF.Exp((0f - Math.Max(0f, rate)) * Math.Max(0f, dt))); } } public readonly struct ClipTransform { private readonly float _00; private readonly float _01; private readonly float _02; private readonly float _03; private readonly float _10; private readonly float _11; private readonly float _12; private readonly float _13; private readonly float _20; private readonly float _21; private readonly float _22; private readonly float _23; private readonly float _30; private readonly float _31; private readonly float _32; private readonly float _33; public ClipTransform(float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33) { _00 = m00; _01 = m01; _02 = m02; _03 = m03; _10 = m10; _11 = m11; _12 = m12; _13 = m13; _20 = m20; _21 = m21; _22 = m22; _23 = m23; _30 = m30; _31 = m31; _32 = m32; _33 = m33; } public static ClipTransform Combine(in ClipTransform view, in ClipTransform projection) { ClipTransform clipTransform = projection; ClipTransform clipTransform2 = view; return new ClipTransform(Dot(clipTransform._00, clipTransform._01, clipTransform._02, clipTransform._03, clipTransform2._00, clipTransform2._10, clipTransform2._20, clipTransform2._30), Dot(clipTransform._00, clipTransform._01, clipTransform._02, clipTransform._03, clipTransform2._01, clipTransform2._11, clipTransform2._21, clipTransform2._31), Dot(clipTransform._00, clipTransform._01, clipTransform._02, clipTransform._03, clipTransform2._02, clipTransform2._12, clipTransform2._22, clipTransform2._32), Dot(clipTransform._00, clipTransform._01, clipTransform._02, clipTransform._03, clipTransform2._03, clipTransform2._13, clipTransform2._23, clipTransform2._33), Dot(clipTransform._10, clipTransform._11, clipTransform._12, clipTransform._13, clipTransform2._00, clipTransform2._10, clipTransform2._20, clipTransform2._30), Dot(clipTransform._10, clipTransform._11, clipTransform._12, clipTransform._13, clipTransform2._01, clipTransform2._11, clipTransform2._21, clipTransform2._31), Dot(clipTransform._10, clipTransform._11, clipTransform._12, clipTransform._13, clipTransform2._02, clipTransform2._12, clipTransform2._22, clipTransform2._32), Dot(clipTransform._10, clipTransform._11, clipTransform._12, clipTransform._13, clipTransform2._03, clipTransform2._13, clipTransform2._23, clipTransform2._33), Dot(clipTransform._20, clipTransform._21, clipTransform._22, clipTransform._23, clipTransform2._00, clipTransform2._10, clipTransform2._20, clipTransform2._30), Dot(clipTransform._20, clipTransform._21, clipTransform._22, clipTransform._23, clipTransform2._01, clipTransform2._11, clipTransform2._21, clipTransform2._31), Dot(clipTransform._20, clipTransform._21, clipTransform._22, clipTransform._23, clipTransform2._02, clipTransform2._12, clipTransform2._22, clipTransform2._32), Dot(clipTransform._20, clipTransform._21, clipTransform._22, clipTransform._23, clipTransform2._03, clipTransform2._13, clipTransform2._23, clipTransform2._33), Dot(clipTransform._30, clipTransform._31, clipTransform._32, clipTransform._33, clipTransform2._00, clipTransform2._10, clipTransform2._20, clipTransform2._30), Dot(clipTransform._30, clipTransform._31, clipTransform._32, clipTransform._33, clipTransform2._01, clipTransform2._11, clipTransform2._21, clipTransform2._31), Dot(clipTransform._30, clipTransform._31, clipTransform._32, clipTransform._33, clipTransform2._02, clipTransform2._12, clipTransform2._22, clipTransform2._32), Dot(clipTransform._30, clipTransform._31, clipTransform._32, clipTransform._33, clipTransform2._03, clipTransform2._13, clipTransform2._23, clipTransform2._33)); static float Dot(float a0, float a1, float a2, float a3, float b0, float b1, float b2, float b3) { return a0 * b0 + a1 * b1 + a2 * b2 + a3 * b3; } } public bool ToViewport(float x, float y, float z, out float viewportX, out float viewportY) { float num = _30 * x + _31 * y + _32 * z + _33; viewportX = (viewportY = 0f); if (num <= 0.0001f) { return false; } viewportX = ((_00 * x + _01 * y + _02 * z + _03) / num + 1f) * 0.5f; viewportY = ((_10 * x + _11 * y + _12 * z + _13) / num + 1f) * 0.5f; return true; } public bool ToViewportBehind(float x, float y, float z, out float viewportX, out float viewportY) { float num = _30 * x + _31 * y + _32 * z + _33; viewportX = (viewportY = 0f); if (num >= -0.0001f) { return false; } viewportX = ((_00 * x + _01 * y + _02 * z + _03) / num + 1f) * 0.5f; viewportY = ((_10 * x + _11 * y + _12 * z + _13) / num + 1f) * 0.5f; return true; } } public sealed class EnemyHealthBarStyle { public bool Bars { get; init; } = true; public int MaxBars { get; init; } = 6; public float Linger { get; init; } = 3f; public float Range { get; init; } = 45f; internal string? Problem() { if (MaxBars < 1 || MaxBars > 12) { return "MaxBars must be 1.." + 12; } if (!(Linger >= 0.5f) || !(Linger <= 30f)) { return "Linger must be 0.5..30 seconds"; } if (!(Range >= 5f) || !(Range <= 200f)) { return "Range must be 5..200 metres"; } return null; } } public sealed class EnemyBarBook { public struct Bar { public long Key; public bool Used; public float Fraction; public MeterTrail Trail; public float LastHit; public float LastDrop; public bool InSight; public float NextSight; } public const int Capacity = 12; public const float FlashSeconds = 0.12f; public const float FadeSeconds = 0.4f; public readonly Bar[] Bars = new Bar[12]; public int Find(long key) { for (int i = 0; i < 12; i++) { if (Bars[i].Used && Bars[i].Key == key) { return i; } } return -1; } public int Hit(long key, float now, float before, float after, float max, int limit, out long evictedKey) { evictedKey = 0L; limit = Math.Clamp(limit, 1, 12); float num = Math.Max(0f, before - after); int num2 = Find(key); if (num2 < 0) { for (int i = 0; i < limit; i++) { if (num2 >= 0) { break; } if (!Bars[i].Used) { num2 = i; } } if (num2 < 0) { num2 = 0; for (int j = 1; j < limit; j++) { if (Bars[j].LastHit < Bars[num2].LastHit) { num2 = j; } } evictedKey = Bars[num2].Key; } float num3 = ((max > 0f) ? Ease.Clamp01(before / max) : 0f); Bars[num2] = new Bar { Key = key, Used = true, Fraction = num3, LastDrop = float.NegativeInfinity, InSight = true, NextSight = now + 0.1f }; Bars[num2].Trail.Reset(num3); } ref Bar reference = ref Bars[num2]; reference.Fraction = ((max > 0f) ? Ease.Clamp01(after / max) : 0f); reference.LastHit = now; if (num > 0f) { reference.LastDrop = now; } return num2; } public void Sync(int slot, float fraction) { if (Bars[slot].Used) { Bars[slot].Fraction = Ease.Clamp01(fraction); } } public void Remove(long key) { int num = Find(key); if (num >= 0) { Bars[num] = default(Bar); } } public void Tick(float now, float dt, float linger) { for (int i = 0; i < 12; i++) { ref Bar reference = ref Bars[i]; if (reference.Used) { if (now - reference.LastHit > linger + 0.4f) { reference = default(Bar); } else { reference.Trail.Update(reference.Fraction, dt); } } } } public static float Alpha(in Bar bar, float now, float linger) { float num = now - bar.LastHit - linger; if (!(num <= 0f)) { return Math.Max(0f, 1f - num / 0.4f); } return 1f; } public static bool Flashing(in Bar bar, float now) { return now - bar.LastDrop < 0.12f; } public void Clear() { Array.Clear(Bars); } public int DueSight(float now, int[] due, int max, ref int cursor, float every) { int num = 0; int num2 = cursor; for (int i = 0; i < 12; i++) { if (num >= max) { break; } int num3 = (num2 + i) % 12; if (Bars[num3].Used && !(now < Bars[num3].NextSight)) { Bars[num3].NextSight = now + every; due[num++] = num3; cursor = (num3 + 1) % 12; } } return num; } } [Flags] public enum GameHudPart { None = 0, Vitals = 1, WeaponList = 2, Objectives = 4, Crosshair = 8, Interaction = 0x10, NavMarkers = 0x20, Chat = 0x40, Subtitles = 0x80, Teammates = 0x100, Compass = 0x200, CrosshairFeedback = 0x400, ScreenFlash = 0x800, GlassShatter = 0x1000, GlassReflection = 0x2000, Stamina = 0x4000, Watermark = 0x8000, All = 0xFFFF } public enum GameHudRelease { LevelEnded, LeftLevel, PlayerDied, Disconnected } public sealed class GameHudClaims { public sealed class Claim { public string Id { get; } public bool IsChatMove { get; } public int Priority { get; } public bool Active { get; internal set; } public GameHudPart Hides { get; internal set; } public GameHudPart Keep => GameHudPart.All & ~Hides; internal Claim(string id, bool chat, int priority) { Id = id; IsChatMove = chat; Priority = priority; } } public const GameHudPart DefaultKeep = GameHudPart.NavMarkers | GameHudPart.Chat | GameHudPart.CrosshairFeedback; private readonly List<Claim> _claims = new List<Claim>(); public GameHudPart Hidden { get { GameHudPart gameHudPart = GameHudPart.None; for (int i = 0; i < _claims.Count; i++) { Claim claim = _claims[i]; if (claim.Active && !claim.IsChatMove) { gameHudPart |= claim.Hides; } } return gameHudPart; } } public Claim? ChatWinner { get { Claim claim = null; for (int i = 0; i < _claims.Count; i++) { Claim claim2 = _claims[i]; if (claim2.Active && claim2.IsChatMove && (claim == null || claim2.Priority > claim.Priority || (claim2.Priority == claim.Priority && string.CompareOrdinal(claim2.Id, claim.Id) < 0))) { claim = claim2; } } return claim; } } public bool AnyActive { get { for (int i = 0; i < _claims.Count; i++) { if (_claims[i].Active) { return true; } } return false; } } public Claim AddHide(string id, GameHudPart keep) { Claim claim = new Claim(id, chat: false, 0) { Hides = (GameHudPart.All & ~keep) }; _claims.Add(claim); return claim; } public Claim AddHideOnly(string id, GameHudPart parts) { Claim claim = new Claim(id, chat: false, 0) { Hides = (parts & GameHudPart.All) }; _claims.Add(claim); return claim; } public Claim AddChatMove(string id, int priority) { Claim claim = new Claim(id, chat: true, priority); _claims.Add(claim); return claim; } public void Remove(Claim claim) { _claims.Remove(claim); } public void SetActive(Claim claim, bool active) { claim.Active = active; } public void SetKeep(Claim claim, GameHudPart keep) { claim.Hides = GameHudPart.All & ~keep; } public void SetHides(Claim claim, GameHudPart parts) { claim.Hides = parts & GameHudPart.All; } public void ReleaseAll(Action<Claim> released) { for (int i = 0; i < _claims.Count; i++) { Claim claim = _claims[i]; if (claim.Active) { claim.Active = false; released(claim); } } } } public enum MeterPulse : byte { Pulse, FlashThenSteady, None, FlashOnLoss } public enum ArcSide : byte { Left, Right } public static class GaugeMath { public const float FlashSeconds = 0.6f; public static float Segment(float value, int index, int count) { if (count < 1) { count = 1; } return Ease.Clamp01(Ease.Clamp01(value) * (float)count - (float)index); } public static float CriticalAlpha(MeterPulse mode, float since, float now) { if (since < 0f) { return 1f; } return mode switch { MeterPulse.Pulse => 0.55f + 0.45f * Ease.Pulse(now, 2.5f), MeterPulse.FlashThenSteady => (since < 0.6f) ? (0.35f + 0.65f * Ease.Blink(since, 6f)) : 1f, _ => 1f, }; } public static float ArcAngle(ArcSide side, float sweep, float t) { float num = Math.Clamp(sweep, 1f, 180f) / 2f; float num2 = 0f - num + Ease.Clamp01(t) * 2f * num; if (side != ArcSide.Right) { return 180f - num2; } return num2; } public static float Refill(float from, float progress) { return Ease.Lerp(Ease.Clamp01(from), 1f, Ease.OutCubic(progress)); } public static int DamageBand(float damage) { if (!(damage < 0.34f)) { if (!(damage < 0.67f)) { return 2; } return 1; } return 0; } public static float Droop(float t, float curve) { float num = 2f * Ease.Clamp01(t) - 1f; return curve * num * num; } public static HudBox ArcExtent(ArcSide side, float radius, float sweep, float thickness, float readoutWidth, float labelWidth) { float num = ((side == ArcSide.Right) ? 1f : (-1f)); float num2 = radius + thickness / 2f; float num3 = radius - thickness - 5f - thickness / 2f; float near = float.MaxValue; float far = float.MinValue; float low = float.MaxValue; float high = float.MinValue; for (int i = 0; i <= 8; i++) { float x = ArcAngle(side, sweep, (float)i / 8f) * (float)Math.PI / 180f; Add(MathF.Cos(x) * num2, MathF.Sin(x) * num2); Add(MathF.Cos(x) * num3, MathF.Sin(x) * num3); } float x2 = (ArcAngle(side, sweep, 0f) - num * 6f) * (float)Math.PI / 180f; float num4 = MathF.Cos(x2) * (radius + 12f); float num5 = MathF.Sin(x2) * (radius + 12f); Add(num4, num5 - 9f); Add(num4 + num * readoutWidth, num5 + 9f); float x3 = (ArcAngle(side, sweep, 1f) + num * 7f) * (float)Math.PI / 180f; float num6 = MathF.Cos(x3) * (radius + 10f); float num7 = MathF.Sin(x3) * (radius + 10f); Add(num6 - 7f, num7 - 7f); Add(num6 + 7f, num7 + 7f); if (labelWidth > 0f) { Add(num * (radius + 16f), -7f); Add(num * (radius + 16f + labelWidth), 7f); } return new HudBox(near, low, far, high); void Add(float val, float y) { near = Math.Min(near, val); far = Math.Max(far, val); low = Math.Min(low, y); high = Math.Max(high, y); } } public static int ArcCells(int cells, int maxCells) { if (cells > 0) { if (maxCells <= 0 || cells <= maxCells) { return cells; } return maxCells; } return 0; } public static int CellState(float value, float warnBelow) { if (!(value <= 0f)) { return (value < warnBelow) ? 1 : 0; } return 2; } public static float DistanceFade(float distance, float start, float end) { if (end <= 0f || end <= start) { return 1f; } return 1f - Ease.Clamp01((distance - start) / (end - start)); } } public sealed class SegmentBarMotion { public const float Snap = 0.005f; private float _flashAt = float.NegativeInfinity; private int _segment = int.MinValue; private int _number = int.MinValue; private bool _critical; private bool _started; public float Shown { get; private set; } public float Alarm { get; private set; } public string Readout { get; private set; } = ""; public void RollUp() { _started = true; Shown = 0f; } public void Restart() { _started = (_critical = false); _flashAt = float.NegativeInfinity; _segment = (_number = int.MinValue); Alarm = 0f; } public void Update(float value, float dt, float now, float easeRate, int segments, bool critical, float failAt, float readoutMax) { value = Ease.Clamp01(value); if (!_started) { _started = true; Shown = value; } else { Shown = Ease.Damp(Shown, value, easeRate, dt); if (Math.Abs(Shown - value) < 0.005f) { Shown = value; } } int num = (int)MathF.Ceiling(value * (float)Math.Max(1, segments) - 0.001f); if (critical && (!_critical || num < _segment)) { _flashAt = now; } _critical = critical; _segment = num; float num2 = now - _flashAt; Alarm = ((!critical) ? 0f : ((value < failAt) ? (0.5f + 0.5f * MathF.Cos(now * 22f)) : ((num2 >= 0f && num2 < 0.6f) ? (0.5f + 0.5f * MathF.Cos(num2 * 20f)) : 0f))); if (!(readoutMax <= 0f)) { int num3 = (int)MathF.Ceiling(Shown * readoutMax - 0.001f); if (num3 != _number) { _number = num3; Readout = num3.ToString(CultureInfo.InvariantCulture); } } } } public sealed class LayerHits { public const float HitSeconds = 0.18f; public const float BreakSeconds = 0.3f; public const float MinDrop = 0.002f; private float _last = -1f; private float _hitAt = float.NegativeInfinity; private float _brokeAt = float.NegativeInfinity; public void Update(float value, bool shown, float now) { value = Ease.Clamp01(value); if (shown && _last >= 0f && value < _last - 0.002f) { _hitAt = now; if (value <= 0f) { _brokeAt = now; } } _last = value; } public float Flash(float now) { float num = now - _brokeAt; if (num >= 0f && num < 0.3f) { return 0.9f * (1f - num / 0.3f); } float num2 = now - _hitAt; if (!(num2 >= 0f) || !(num2 < 0.18f)) { return 0f; } return 0.6f * (1f - num2 / 0.18f); } public void Restart() { _last = -1f; _hitAt = (_brokeAt = float.NegativeInfinity); } } public sealed class DamageShares { private readonly float[] _taken; public int Layers => _taken.Length; public DamageShares(int layers) { _taken = new float[Math.Max(1, layers)]; } public void Reset() { Array.Clear(_taken, 0, _taken.Length); } public void Add(int layer, float amount) { if ((uint)layer < (uint)_taken.Length && amount > 0f) { _taken[layer] += amount; } } public float Shown(int layer, float loss) { if ((uint)layer >= (uint)_taken.Length) { return 0f; } float num = 0f; float[] taken = _taken; foreach (float val in taken) { num = Math.Max(num, val); } if (!(num > 0f)) { return 0f; } return Ease.Clamp01(loss) * _taken[layer] / num; } } public struct ArcFlare { private float _until; private MeterTone _tone; private bool _refilling; public void Flare(float now, float seconds) { _until = now + Math.Max(0f, seconds); } public float Alpha(MeterTone tone, bool refilling, float now, float steady, float seconds) { if ((int)tone > (int)_tone || (refilling && !_refilling)) { Flare(now, seconds); } _tone = tone; _refilling = refilling; if (tone != MeterTone.Critical && !(now < _until)) { return Ease.Clamp01(steady); } return 1f; } } public sealed class BearingContacts { private readonly float[] _azimuth; private readonly float[] _strength; private readonly EnemyAlert[] _state; public int Capacity => _azimuth.Length; public int Count { get; private set; } public BearingContacts(int capacity) { capacity = Math.Max(1, capacity); _azimuth = new float[capacity]; _strength = new float[capacity]; _state = (EnemyAlert[])(object)new EnemyAlert[capacity]; } public void Begin() { Count = 0; } public bool Add(float azimuth, float strength, EnemyAlert state) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected I4, but got Unknown if (Count >= _azimuth.Length || !float.IsFinite(azimuth)) { return false; } _azimuth[Count] = Bearing.Wrap(azimuth); _strength[Count] = Ease.Clamp01(strength); _state[Count] = (EnemyAlert)(int)state; Count++; return true; } public float Azimuth(int i) { return _azimuth[i]; } public float Strength(int i) { return _strength[i]; } public EnemyAlert State(int i) { return _state[i]; } public int Behind(EnemyAlert state, float halfArc) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Invalid comparison between I4 and Unknown int num = 0; for (int i = 0; i < Count; i++) { if ((int)_state[i] == (int)state && Bearing.Between(_azimuth[i], 180f) <= halfArc) { num++; } } return num; } } public sealed class FamiliarFade { public const float UsedDelay = 1.5f; public const float AttentionSeconds = 4f; public const float Rate = 4f; private float _shownAt = float.NaN; private float _usedAt = float.NaN; private float _attentionUntil = float.MinValue; private float _alpha = 1f; public float IdleSeconds { get; } public float FadedAlpha { get; } public FamiliarFade(float idleSeconds, float fadedAlpha) { IdleSeconds = Math.Max(0f, idleSeconds); FadedAlpha = Ease.Clamp01(fadedAlpha); } public void Hidden() { _shownAt = float.NaN; } public void Used(float now) { _usedAt = now; } public void Attention(float now) { _attentionUntil = now + 4f; } public float Update(float now, float dt, bool holding) { if (float.IsNaN(_shownAt)) { _shownAt = now; _usedAt = float.NaN; _alpha = 1f; } bool flag = now - _shownAt >= IdleSeconds || (!float.IsNaN(_usedAt) && now - _usedAt >= 1.5f); float target = ((holding || now < _attentionUntil || !flag) ? 1f : FadedAlpha); _alpha = Ease.Damp(_alpha, target, 4f, dt); return _alpha; } } internal sealed class GearNameRule { private readonly Func<string> _text; private readonly List<string> _known = new List<string>(); internal object Owner { get; } internal string Find { get; } internal bool ByPacket { get; } internal string Shown { get; private set; } = ""; internal IReadOnlyList<string> Known => _known; internal GearNameRule(object owner, string find, Func<string> text, bool byPacket) { Owner = owner; Find = find; _text = text; ByPacket = byPacket; _known.Add(find); } internal void AddKnown(string text) { _known.Add(text); } internal void Take() { string text = (Shown = _text()); if (text.Length != 0 && !_known.Contains(text)) { _known.Add(text); } } } internal sealed class GearNameBook { private readonly List<GearNameRule> _rules = new List<GearNameRule>(); private readonly Dictionary<string, GearNameRule> _byFind = new Dictionary<string, GearNameRule>(StringComparer.Ordinal); private readonly Dictionary<string, GearNameRule> _byPacket = new Dictionary<string, GearNameRule>(StringComparer.Ordinal); private int _version = -1; internal bool IsEmpty => _rules.Count == 0; internal bool HasPackets => _byPacket.Count != 0; internal string? Add(object owner, string find, UITexts texts, string key, bool byPacket) { if (string.IsNullOrEmpty(key)) { return "the language key is empty"; } return Add(owner, find, () => texts.Text(key), byPacket); } internal string? Add(object owner, string find, Func<string> text, bool byPacket) { if (string.IsNullOrEmpty(find)) { return "the text that stands for the gear is empty"; } if (_byFind.TryGetValue(find, out GearNameRule value)) { if (value.Owner != owner) { return "'" + find + "' is already registered by another mod"; } _rules.Remove(value); _byPacket.Remove(find); } GearNameRule gearNameRule = new GearNameRule(owner, find, text, byPacket); if (value != null) { foreach (string item in value.Known) { if (item != find) { gearNameRule.AddKnown(item); } } } _rules.Add(gearNameRule); _byFind[find] = gearNameRule; if (byPacket) { _byPacket[find] = gearNameRule; } gearNameRule.Take(); return null; } internal void Sync() { if (_version == UITexts.Version) { return; } _version = UITexts.Version; foreach (GearNameRule rule in _rules) { rule.Take(); } } internal string Display(string? text) { if (string.IsNullOrEmpty(text) || _rules.Count == 0) { return text ?? ""; } Sync(); foreach (GearNameRule rule in _rules) { string shown = rule.Shown; if (shown.Length == 0 || text.Contains(shown, StringComparison.Ordinal)) { continue; } IReadOnlyList<string> known = rule.Known; for (int i = 0; i < known.Count; i++) { if (known[i] != shown && text.Contains(known[i], StringComparison.Ordinal)) { text = text.Replace(known[i], shown, StringComparison.Ordinal); } } } return text; } internal GearNameRule? ForPacket(string? packetName) { if (string.IsNullOrEmpty(packetName) || !_byPacket.TryGetValue(packetName, out GearNameRule value)) { return null; } Sync(); return value; } } public readonly record struct SlotFacts(bool Selected, int Clip, int ClipSize, bool Infinite); public readonly record struct HeldMagazine(bool Limited, int Clip, int Size) { public static HeldMagazine Unlimited => default(HeldMagazine); public float Fraction { get { if (Limited) { if (Size <= 0) { if (Clip <= 0) { return 0f; } return 1f; } return Math.Clamp((float)Clip / (float)Size, 0f, 1f); } return 1f; } } public static HeldMagazine Of(in SlotFacts slot) { if (!slot.Infinite && slot.Clip >= 0) { return new HeldMagazine(Limited: true, slot.Clip, slot.ClipSize); } return Unlimited; } public static HeldMagazine Pick(ReadOnlySpan<SlotFacts> slots) { for (int i = 0; i < slots.Length; i++) { if (slots[i].Selected) { return Of(in slots[i]); } } return Unlimited; } } public static class CrosshairRules { public static bool Drawn(bool visible, bool hideWhileAiming, bool aiming) { if (visible) { return !(hideWhileAiming && aiming); } return false; } public static int Graphics(bool visible, bool hideWhileAiming, bool aiming, bool ring, bool dot, int ticks, bool mark) { if (!Drawn(visible, hideWhileAiming, aiming)) { return 0; } return (ring ? 1 : 0) + (dot ? 1 : 0) + Math.Max(0, ticks) + (mark ? 1 : 0); } } public enum HitKind : byte { Hit, Critical, Kill } public sealed class FloatingNumbers { public struct Number { public long Key; public bool Used; public float Amount; public float Born; public float Last; public HitKind Kind; public float X; public float Y; public float Z; public bool World; public int Frame; } public const int Limit = 32; private readonly Number[] _numbers; public int Capacity => _numbers.Length; public float MergeSeconds { get; set; } = 0.45f; public float Seconds { get; set; } = 0.8f; public ref readonly Number this[int slot] => ref _numbers[slot]; public FloatingNumbers(int capacity) { _numbers = new Number[Math.Clamp(capacity, 1, 32)]; } public int Add(long key, float now, float amount, HitKind kind, float x, float y, float z, bool world, int frame) { if (key != 0L) { for (int i = 0; i < _numbers.Length; i++) { ref Number reference = ref _numbers[i]; if (reference.Used && reference.Key == key && !(now - reference.Last > MergeSeconds)) { reference.Amount += amount; reference.Last = now; reference.Frame = frame; if ((int)kind > (int)reference.Kind) { reference.Kind = kind; } return i; } } } int num = -1; for (int j = 0; j < _numbers.Length; j++) { if (num >= 0) { break; } if (!_numbers[j].Used) { num = j; } } if (num < 0) { num = 0; for (int k = 1; k < _numbers.Length; k++) { if (_numbers[k].Born < _numbers[num].Born) { num = k; } } } _numbers[num] = new Number { Key = key, Used = true, Amount = amount, Born = now, Last = now, Kind = kind, X = x, Y = y, Z = z, World = world, Frame = frame }; return num; } public void MarkCritical(long key, int frame) { for (int i = 0; i < _numbers.Length; i++) { ref Number reference = ref _numbers[i]; if (reference.Used && reference.Key == key && reference.Frame == frame && reference.Kind == HitKind.Hit) { reference.Kind = HitKind.Critical; } } } public void Tick(float now) { for (int i = 0; i < _numbers.Length; i++) { if (_numbers[i].Used && now - _numbers[i].Last > Seconds) { _numbers[i] = default(Number); } } } public void Motion(in Number n, float now, out float rise, out float fade) { rise = Ease.OutCubic(Ease.Clamp01((now - n.Born) / Seconds)); fade = Ease.Clamp01((now - n.Last) / Seconds); } public void Clear() { Array.Clear(_numbers); } } public sealed class HitFeedbackBook { public struct Kill { public bool Used; public float Born; public float X; public float Y; public float Z; } public const int Kills = 4; public const float HitSeconds = 0.22f; public const float KillPulseSeconds = 0.5f; public const float KillMarkSeconds = 0.35f; public const float ConfirmSeconds = 0.3f; public readonly Kill[] Marks = new Kill[4]; private int _nextKill; private int _pulseFrame = int.MinValue; public FloatingNumbers Numbers { get; } public HitKind PulseKind { get; private set; } public float PulseStart { get; private set; } = float.NegativeInfinity; public float LastKill { get; private set; } = float.NegativeInfinity; public HitFeedbackBook(int numbers) { Numbers = new FloatingNumbers(numbers); } public void Pulse(HitKind kind, float now, int frame) { if (frame != _pulseFrame || (int)kind > (int)PulseKind) { _pulseFrame = frame; PulseKind = kind; PulseStart = now; } } public float PulseProgress(float now) { return (now - PulseStart) / ((PulseKind == HitKind.Kill) ? 0.5f : 0.22f); } public void Killed(float now, float x, float y, float z) { Marks[_nextKill] = new Kill { Used = true, Born = now, X = x, Y = y, Z = z }; _nextKill = (_nextKill + 1) % 4; LastKill = now; } public void Tick(float now) { Numbers.Tick(now); for (int i = 0; i < 4; i++) { if (Marks[i].Used && now - Marks[i].Born > 0.35f) { Marks[i] = default(Kill); } } } public void Clear() { Numbers.Clear(); Array.Clear(Marks); float lastKill = (PulseStart = float.NegativeInfinity); LastKill = lastKill; _pulseFrame = int.MinValue; } } public sealed class HoldTimer { private float _held; private bool _fired; public float Seconds { get; set; } public float ReleaseSeconds { get; set; } public float Progress => Ease.Clamp01(_held / Seconds); public bool Completed => _fired; public HoldTimer(float seconds, float releaseSeconds = 0.25f) { Seconds = Math.Max(0.01f, seconds); ReleaseSeconds = Math.Max(0.01f, releaseSeconds); } public bool Update(bool down, float dt) { if (down) { _held = Math.Min(Seconds, _held + dt); if (_held >= Seconds && !_fired) { _fired = true; return true; } return false; } _held = Math.Max(0f, _held - dt * Seconds / ReleaseSeconds); if (_held <= 0f) { _fired = false; } return false; } public void Reset() { _held = 0f; _fired = false; } } public sealed class HudGlowSettings { public bool Enabled { get; set; } = true; public float Radius { get; set; } = 5f; public float Strength { get; set; } = 0.55f; public float Effective(float playerScale) { if (!Enabled || !(Radius > 0f)) { return 0f; } return Math.Clamp(Strength, 0f, 1f) * Math.Max(0f, playerScale); } } public sealed class HudFringeSettings { private float _pulseSeconds; private float _pulseStart = float.NaN; private float _pulseBoost; public bool Enabled { get; set; } = true; public float Pixels { get; set; } = 1f; public float Strength { get; set; } = 0.16f; public void Pulse(float seconds, float boost = 3f) { if (seconds > 0f && boost > 1f) { _pulseSeconds = seconds; _pulseBoost = boost; _pulseStart = float.NaN; } } public float Boost(float now) { if (_pulseSeconds <= 0f) { return 1f; } if (float.IsNaN(_pulseStart)) { _pulseStart = now; } float num = (now - _pulseStart) / _pulseSeconds; if (num >= 1f || num < 0f) { _pulseSeconds = 0f; return 1f; } return 1f + (_pulseBoost - 1f) * (1f - num); } public float Effective(bool playerOn, float now) { if (!(Enabled && playerOn) || !(Pixels > 0f)) { return 0f; } return Math.Clamp(Strength * Boost(now), 0f, 1f); } } public enum HudRegion : byte { WeaponList, Vitals, CenterBanner, TopCenter, Notices, RightColumn, BottomLeft, BottomRight, World, TopRight, TopLeft } public readonly struct HudSlot { public HudRegion Region { get; } public int Priority { get; } public float Width { get; } public float Height { get; } public int Row { get; } public float Space { get; } public HudSlot(HudRegion region, int priority = 0, float width = 0f, float height = 0f, int row = 0, float space = 0f) { Region = region; Priority = priority; Width = Math.Max(0f, width); Height = Math.Max(0f, height); Row = row; Space = Math.Max(0f, space); } public HudSlot With(int priority, float width, float height) { return new HudSlot(Region, priority, width, height, Row, Space); } } public readonly struct HudBox { public float Left { get; } public float Bottom { get; } public float Right { get; } public float Top { get; } public float Width => Right - Left; public float Height => Top - Bottom; public HudBox(float left, float bottom, float right, float top) { Left = left; Bottom = bottom; Right = right; Top = top; } public bool Overlaps(HudBox other) { if (Left < other.Right && other.Left < Right && Bottom < other.Top) { return other.Bottom < Top; } return false; } public override string ToString() { return "(" + N(Left) + ", " + N(Bottom) + ")-(" + N(Right) + ", " + N(Top) + ")"; } internal static string N(float n) { return n.ToString("0.#", CultureInfo.InvariantCulture); } } public readonly struct RegionFrame { public float X { get; } public float Y { get; } public int FlowX { get; } public int FlowY { get; } public float Limit { get; } public RegionFrame(float x, float y, int flowX, int flowY, float limit) { X = x; Y = y; FlowX = flowX; FlowY = flowY; Limit = limit; } } public struct HudScene { public HudBox Screen; public float Margin; public float OffsetX; public float OffsetY; public HudBox? WeaponList; public HudBox? Vitals; public float BarCentre; public bool WeaponListFree; public bool VitalsFree; } public static class HudRegions { public const float Gap = 12f; public const float VitalsGap = 8f; public const float BuildLabel = 30f; public const float BannerRise = 150f; public const float NoticesDrop = 150f; public static (int X, int Y) Flow(HudRegion region) { return region switch { HudRegion.WeaponList => (X: -1, Y: -1), HudRegion.Vitals => (X: 0, Y: 1), HudRegion.TopCenter => (X: 0, Y: -1), HudRegion.Notices => (X: 1, Y: -1), HudRegion.RightColumn => (X: -1, Y: -1), HudRegion.BottomLeft => (X: 1, Y: 1), HudRegion.BottomRight => (X: -1, Y: 1), HudRegion.TopRight => (X: -1, Y: -1), HudRegion.TopLeft => (X: 1, Y: -1), _ => (X: 0, Y: 0), }; } public static RegionFrame? Frame(HudRegion region, in HudScene scene) { HudBox screen = scene.Screen; float margin = scene.Margin; float x = (screen.Left + screen.Right) / 2f; float num = (screen.Bottom + screen.Top) / 2f; float num2 = screen.Height / 2f; var (flowX, flowY) = Flow(region); switch (region) { case HudRegion.WeaponList: { HudBox? vitals = scene.WeaponList; if (vitals.HasValue) { HudBox valueOrDefault2 = vitals.GetValueOrDefault(); return new RegionFrame(valueOrDefault2.Right + scene.OffsetX, valueOrDefault2.Bottom - 12f + scene.OffsetY, flowX, flowY, screen.Bottom + margin); } return null; } case HudRegion.Vitals: { HudBox? vitals = scene.Vitals; if (vitals.HasValue) { HudBox valueOrDefault = vitals.GetValueOrDefault(); return new RegionFrame(scene.BarCentre + scene.OffsetX, valueOrDefault.Top + 8f + scene.OffsetY, flowX, flowY, num); } return null; } case HudRegion.CenterBanner: return new RegionFrame(x, num + 150f, flowX, flowY, num + 150f); case HudRegion.TopCenter: return new RegionFrame(x, screen.Top - margin, flowX, flowY, num + 150f + 70f); case HudRegion.Notices: return new RegionFrame(screen.Left + margin, screen.Top - margin - 150f, flowX, flowY, num); case HudRegion.RightColumn: return new RegionFrame(screen.Right - margin, num + 0.25f * num2, flowX, flowY, num - 0.45f * num2); case HudRegion.BottomLeft: return new RegionFrame(screen.Left + margin, screen.Bottom + margin, flowX, flowY, num - 0.1f * num2); case HudRegion.BottomRight: return new RegionFrame(screen.Right - margin, screen.Bottom + margin + 30f, flowX, flowY, num + 0.5f * num2); case HudRegion.TopRight: return new RegionFrame(screen.Right - margin, screen.Top - margin, flowX, flowY, num + 220f); case HudRegion.TopLeft: return new RegionFrame(screen.Left + margin, screen.Top - margin, flowX, flowY, num + 220f); default: return new RegionFrame(x, num, 0, 0, num); } } public static HudBox Box(float x, float y, int fx, int fy, float width, float height) { float num = ((fx < 0) ? (x - width) : ((fx > 0) ? x : (x - width / 2f))); float num2 = ((fy < 0) ? (y - height) : ((fy > 0) ? y : (y - height / 2f))); return new HudBox(num, num2, num + width, num2 + height); } public static (float X, float Y) Root(HudBox box, int fx, int fy) { return (X: (fx < 0) ? box.Right : ((fx > 0) ? box.Left : ((box.Left + box.Right) / 2f)), Y: (fy < 0) ? box.Top : ((fy > 0) ? box.Bottom : ((box.Bottom + box.Top) / 2f))); } } public enum LayoutState : byte { Hidden, Placed, Collapsed, Unavailable, Free } public sealed class LayoutEntry { internal int Run; public string Id { get; } public HudSlot Slot { get; set; } public bool Visible { get; set; } public bool WeaponListStandIn { get; set; } public HudRegion Home { get; internal set; } public bool BesideStandIn => Home != Slot.Region; public float DrawnWidth { get; set; } public float DrawnHeight { get; set; } public LayoutState State { get; internal set; } public float RootX { get; internal set; } public float RootY { get; internal set; } public HudBox Box { get; internal set; } internal float Width => Math.Max(Slot.Width, DrawnWidth); internal float Height => Math.Max(Slot.Height, DrawnHeight); public LayoutEntry(string id, HudSlot slot) { Id = id; Slot = slot; } } public sealed class HudLayout { public const string GameWeaponList = "game/weapon list"; public const string GameVitals = "game/vitals"; private const int MaxSteps = 32; private readonly List<LayoutEntry> _entries = new List<LayoutEntry>(); private readonly List<LayoutEntry> _order = new List<LayoutEntry>(); private readonly List<LayoutEntry> _row = new List<LayoutEntry>(); private readonly List<HudBox> _occupied = new List<HudBox>(); private readonly float[] _cursor = new float[Enum.GetValues(typeof(HudRegion)).Length]; private readonly LayoutEntry?[] _slotHolder = new LayoutEntry[Enum.GetValues(typeof(HudRegion)).Length]; private bool _orderDirty = true; private int _run; public IReadOnlyList<LayoutEntry> Entries => _entries; public void Add(LayoutEntry entry) { _entries.Add(entry); _orderDirty = true; } public void Remove(LayoutEntry entry) { if (_entries.Remove(entry)) { _orderDirty = true; } } public void Reorder() { _orderDirty = true; } public void Run(in HudScene scene) { if (_orderDirty) { Sort(); } _run++; _occupied.Clear(); HudBox? weaponList = scene.WeaponList; if (weaponList.HasValue) { HudBox valueOrDefault = weaponList.GetValueOrDefault(); if (!scene.WeaponListFree) { _occupied.Add(valueOrDefault); } } weaponList = scene.Vitals; if (weaponList.HasValue) { HudBox valueOrDefault2 = weaponList.GetValueOrDefault(); if (!scene.VitalsFree) { _occupied.Add(valueOrDefault2); } } for (int i = 0; i < _cursor.Length; i++) { _cursor[i] = float.NaN; _slotHolder[i] = null; } LayoutEntry layoutEntry = null; if (scene.WeaponListFree) { foreach (LayoutEntry item in _order) { if (item.WeaponListStandIn && item.Visible && item.Slot.Region != HudRegion.WeaponList) { layoutEntry = item; break; } } } foreach (LayoutEntry item2 in _order) { if (layoutEntry == null || item2.Slot.Region != HudRegion.WeaponList) { Place(item2, in scene, item2.Slot.Region); } } if (layoutEntry == null) { return; } HudRegion home = ((layoutEntry.State == LayoutState.Placed) ? layoutEntry.Slot.Region : HudRegion.WeaponList); foreach (LayoutEntry item3 in _order) { if (item3.Slot.Region == HudRegion.WeaponList) { Place(item3, in scene, home); } } } private void Sort() { _order.Clear(); _order.AddRange(_entries); _order.Sort((LayoutEntry a, LayoutEntry b) => (a.Slot.Priority == b.Slot.Priority) ? string.CompareOrdinal(a.Id, b.Id) : b.Slot.Priority.CompareTo(a.Slot.Priority)); _orderDirty = false; } private void Place(LayoutEntry entry, in HudScene scene, HudRegion home) { if (entry.Run == _run) { return; } entry.Run = _run; entry.Home = home; if (!entry.Visible) { entry.State = LayoutState.Hidden; return; } RegionFrame? regionFrame = HudRegions.Frame(home, in scene); if (regionFrame.HasValue) { RegionFrame frame = regionFrame.GetValueOrDefault(); HudBox box2; if (home == HudRegion.World) { entry.State = LayoutState.Free; entry.RootX = frame.X; entry.RootY = frame.Y; entry.Box = new HudBox(frame.X, frame.Y, frame.X, frame.Y); } else if (frame.FlowY == 0) { if (_slotHolder[(uint)home] != null) { Collapse(entry); return; } HudBox box = HudRegions.Box(frame.X, frame.Y, frame.FlowX, 0, entry.Width, entry.Height); if (Blocker(box) >= 0) { Collapse(entry); return; } _slotHolder[(uint)home] = entry; Accept(entry, box); } else if (entry.Slot.Row != 0) { PlaceRow(entry, in frame, home); } else if (!Find((int)home, in frame, entry.Width, entry.Height, entry.Slot.Space, out box2)) { Collapse(entry); } else { Accept(entry, box2); } } else { entry.State = LayoutState.Unavailable; } } private void PlaceRow(LayoutEntry first, in RegionFrame frame, HudRegion home) { _row.Clear(); float num = 0f; float num2 = 0f; float num3 = 0f; foreach (LayoutEntry item in _order) { if (item.Slot.Region != first.Slot.Region || item.Slot.Row != first.Slot.Row || !item.Visible) { continue; } if (item != first) { if (item.Run == _run) { continue; } item.Run = _run; item.Home = home; } num += ((_row.Count > 0) ? 12f : 0f) + item.Width; num2 = Math.Max(num2, item.Height); num3 = Math.Max(num3, item.Slot.Space); _row.Add(item); } if (!Find((int)home, in frame, num, num2, num3, out var box)) { foreach (LayoutEntry item2 in _row) { Collapse(item2); } return; } float num4 = ((frame.FlowX < 0) ? box.Right : box.Left); foreach (LayoutEntry item3 in _row) { float width = item3.Width; float height = item3.Height; float num5 = ((frame.FlowX < 0) ? (num4 - width) : num4); float num6 = ((frame.FlowY < 0) ? (box.Top - height) : box.Bottom); Accept(item3, new HudBox(num5, num6, num5 + width, num6 + height)); num4 = ((frame.FlowX < 0) ? (num5 - 12f) : (num5 + width + 12f)); } } private bool Find(int index, in RegionFrame frame, float width, float height, float space, out HudBox box) { float y = (float.IsNaN(_cursor[index]) ? frame.Y : _cursor[index]) + ((frame.FlowY < 0) ? (0f - space) : space); for (int i = 0; i < 32; i++) { box = HudRegions.Box(frame.X, y, frame.FlowX, frame.FlowY, width, height); if ((frame.FlowY < 0) ? (box.Bottom < frame.Limit) : (box.Top > frame.Limit)) { return false; } int num = Blocker(box); if (num < 0) { _cursor[index] = ((frame.FlowY < 0) ? (box.Bottom - 12f) : (box.Top + 12f)); return true; } HudBox hudBox = _occupied[num]; y = ((frame.FlowY < 0) ? (hudBox.Bottom - 12f - space) : (hudBox.Top + 12f + space)); } box = default(HudBox); return false; } private int Blocker(HudBox box) { if (box.Width <= 0f || box.Height <= 0f) { return -1; } for (int i = 0; i < _occupied.Count; i++) { if (_occupied[i].Overlaps(box)) { return i; } } return -1; } private void Accept(LayoutEntry entry, HudBox box) { (int X, int Y) tuple = HudRegions.Flow(entry.Slot.Region); int item = tuple.X; int item2 = tuple.Y; (float X, float Y) tuple2 = HudRegions.Root(box, item, item2); float item3 = tuple2.X; float item4 = tuple2.Y; entry.State = LayoutState.Placed; entry.RootX = item3; entry.RootY = item4; entry.Box = box; if (box.Width > 0f && box.Height > 0f) { _occupied.Add(box); } } private static void Collapse(LayoutEntry entry) { entry.State = LayoutState.Collapsed; } } public static class HudReveal { public const float Seconds = 0.42f; public const float TraceEnd = 0.14f; public const float SweepStart = 0.1f; public const float SweepEnd = 0.3f; public const float LockStart = 0.34f; public static bool Running(float age) { if (age >= 0f) { return age < 0.42f; } return false; } public static float Trace(float age) { return Ease.OutCubic(age / 0.14f); } public static float Sweep(float age) { return Ease.InOutCubic((age - 0.1f) / 0.20000002f); } public static float Lock(float age) { if (!(age < 0.34f)) { return 1f - Ease.Clamp01((age - 0.34f) / 0.07999998f); } return 0f; } public static float Burst(float age) { if (!(age < 0.34f)) { return Ease.OutCubic((age - 0.34f) / 0.07999998f); } return 0f; } public static void Extent(int flow, float size, out float low, out float high) { size = Math.Max(0f, size); if (flow > 0) { low = 0f; high = size; } else if (flow < 0) { low = 0f - size; high = 0f; } else { low = (0f - size) / 2f; high = size / 2f; } } public static float Front(int flow, float low, float high, float sweep, out float other) { sweep = Ease.Clamp01(sweep); if (flow > 0) { other = float.NaN; return low + (high - low) * sweep; } if (flow < 0) { other = float.NaN; return high - (high - low) * sweep; } float num = (low + high) / 2f; float num2 = (high - low) / 2f * sweep; other = num - num2; return num + num2; } public static float Passed(int flow, float low, float high, float sweep, float left, float right) { if (sweep >= 1f) { return 1f; } if (sweep <= 0f) { return 0f; } float num = right - left; float other; float num2 = Front(flow, low, high, sweep, out other); if (num <= 0.001f) { if (flow <= 0) { if (flow >= 0) { if (!(left <= num2) || !(left >= other)) { return 0f; } return 1f; } if (!(left >= num2)) { return 0f; } return 1f; } if (!(left <= num2)) { return 0f; } return 1f; } if (flow > 0) { return Ease.Clamp01((num2 - left) / num); } if (flow < 0) { return Ease.Clamp01((right - num2) / num); } return Ease.Clamp01(Math.Max(0f, Math.Min(right, num2) - Math.Max(left, other)) / num); } } public sealed record GameIcon(string Item, string Summary) { public string Name => IconNames.Join("Game", Item); } public static class GameIcons { public static readonly IReadOnlyList<GameIcon> All = new GameIcon[4] { new GameIcon("symbol_hostile", "The hollow triangle on an enemy's tag marker."), new GameIcon("cellUI_SmallTriangleFill", "A small filled triangle."), new GameIcon("CircleBigMiddleSize_Fill", "A filled circle."), new GameIcon("GUIX_Detail_BoxFilled_AA_Centered", "A filled square.") }; public static bool Has(string item) { foreach (GameIcon item2 in All) { if (string.Equals(item2.Item, item, StringComparison.Ordinal)) { return true; } } return false; } } public static class IconNames { public const string Game = "Game"; public static string? CheckPack(string? pack) { if (string.IsNullOrEmpty(pack)) { return "the pack name is empty"; } foreach (char c in pack) { if (!IsNameChar(c) || c == '.') { return "the pack name '" + pack + "' may only hold letters, digits, '_' and '-'"; } } if (string.Equals(pack, "Game", StringComparison.OrdinalIgnoreCase)) { return "'Game' is the game's own icon catalog"; } return null; } public static string? CheckItem(string? item) { if (string.IsNullOrEmpty(item)) { return "the icon name is empty"; } for (int i = 0; i < item.Length; i++) { if (!IsNameChar(item[i])) { return "the icon name '" + item + "' may only hold letters, digits, '_', '-' and '.'"; } } return null; } public static string Join(string pack, string item) { return pack + ":" + item; } public static bool TrySplit(string? name, out string pack, out string item, out string? why) { pack = (item = ""); why = null; if (string.IsNullOrEmpty(name)) { why = "the icon name is empty"; return false; } int num = name.IndexOf(':'); if (num <= 0 || num == name.Length - 1 || name.IndexOf(':', num + 1) >= 0) { why = "'" + name + "' is not Pack:item"; return false; } pack = name.Substring(0, num); item = name.Substring(num + 1); why = CheckItem(item); string text = pack; foreach (char c in text) { if ((!IsNameChar(c) || c == '.') && why == null) { why = "the pack name '" + pack + "' may only hold letters, digits, '_' and '-'"; } } return why == null; } private static bool IsNameChar(c