Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of FarLands v1.1.1
FarLands.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using PerfGate.Shared; using Unity.Collections; using Unity.Profiling; using Unity.Profiling.LowLevel.Unsafe; using UnityEngine; using UnityEngine.Experimental.Rendering; using UnityEngine.PostProcessing; using UnityEngine.Rendering; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("FarLands")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.1.1.0")] [assembly: AssemblyInformationalVersion("1.1.1+b16c62b6984bcd59a8f70fe51881f64b24b9d1a7")] [assembly: AssemblyProduct("FarLands")] [assembly: AssemblyTitle("FarLands")] [assembly: AssemblyVersion("1.1.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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 PerfGate.Shared { internal static class FrameCounters { private const string GpuName = "GPU Frame Time"; private const string MainName = "CPU Main Thread Frame Time"; private static ProfilerRecorder _gpu; private static ProfilerRecorder _main; private static bool _opened; public static double GpuMs => Read(ref _gpu); public static double MainMs => Read(ref _main); private static double Read(ref ProfilerRecorder r) { Open(); if (!((ProfilerRecorder)(ref r)).Valid || ((ProfilerRecorder)(ref r)).LastValue <= 0) { return double.NaN; } return (double)((ProfilerRecorder)(ref r)).LastValue / 1000000.0; } private static void Open() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) if (_opened) { return; } _opened = true; List<ProfilerRecorderHandle> list = new List<ProfilerRecorderHandle>(); ProfilerRecorderHandle.GetAvailable(list); foreach (ProfilerRecorderHandle item in list) { ProfilerRecorderDescription description = ProfilerRecorderHandle.GetDescription(item); if (((ProfilerRecorderDescription)(ref description)).Name == "GPU Frame Time") { _gpu = ProfilerRecorder.StartNew(((ProfilerRecorderDescription)(ref description)).Category, "GPU Frame Time", 1, (ProfilerRecorderOptions)24); } else if (((ProfilerRecorderDescription)(ref description)).Name == "CPU Main Thread Frame Time") { _main = ProfilerRecorder.StartNew(((ProfilerRecorderDescription)(ref description)).Category, "CPU Main Thread Frame Time", 1, (ProfilerRecorderOptions)24); } } } } } namespace FarLands { internal sealed class ConfigurationManagerAttributes { public int? Order; public string Category; public string DispName; public bool? Browsable; } internal static class ConfigLayout { private static readonly string[] Sections = new string[4] { "General", "Detail", "Buildings", "Air" }; private static readonly Dictionary<string, int> Bound = new Dictionary<string, int>(); internal static ConfigEntry<T> BindListed<T>(this ConfigFile cfg, string section, string key, T value, string description) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown return cfg.BindListed(section, key, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>())); } internal static ConfigEntry<T> BindListed<T>(this ConfigFile cfg, string section, string key, T value, ConfigDescription description) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown return cfg.Bind<T>(section, key, value, new ConfigDescription(description.Description, description.AcceptableValues, description.Tags.Append(Attributes(section, key)).ToArray())); } private static ConfigurationManagerAttributes Attributes(string section, string key) { Bound.TryGetValue(section, out var value); Bound[section] = value + 1; return new ConfigurationManagerAttributes { Category = Category(section), Order = 1000 - value, DispName = Words(key) }; } internal static string Category(string section) { int num = Array.IndexOf(Sections, section); if (num >= 0) { return num + 1 + " - " + Words(section); } return Words(section); } internal static string Words(string key) { if (key.Contains(" ")) { return key; } List<string> list = new List<string>(); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < key.Length; i++) { char c = key[i]; if (i > 0 && ((char.IsUpper(c) && !char.IsUpper(key[i - 1])) || (char.IsDigit(c) && !char.IsDigit(key[i - 1])))) { list.Add(stringBuilder.ToString()); stringBuilder.Clear(); } stringBuilder.Append(c); } list.Add(stringBuilder.ToString()); for (int j = 0; j < list.Count; j++) { if (j > 0 && list[j].Length > 1 && !list[j].All(char.IsUpper)) { list[j] = char.ToLowerInvariant(list[j][0]) + list[j].Substring(1); } } return string.Join(" ", list); } } public static class AirDefaults { public const int SettingsVersion = 1; public const float NearHaze = 0.6f; public const float NearHazeDistance = 180f; public const float FarLandsHaze = 0.6f; public static readonly float[] FormerNearHazeDistances = new float[1] { 50f }; public static readonly float[] FormerFarLandsHazes = new float[2] { 0.17f, 1f }; public static float OnOldFile(float value, float current, float[] former) { foreach (float num in former) { if (Math.Abs(value - num) <= 0.0001f) { return current; } } return value; } } internal sealed class AirEase { public const float Seconds = 8f; private bool _have; public float View { get; private set; } public float Height { get; private set; } public float Mist { get; private set; } public float Sea { get; private set; } public void Step(float view, float height, float mist, float dt, float sea = 0f) { if (!float.IsNaN(view) && !float.IsNaN(height) && !float.IsNaN(mist) && !float.IsNaN(sea) && view > 0f) { if (!_have) { View = view; Height = height; Mist = mist; Sea = sea; _have = true; } else if (dt >= 0f) { float num = 1f - (float)Math.Exp((0f - Math.Min(dt, 10000f)) / 8f); View = (float)Math.Exp(Math.Log(View) + (Math.Log(view) - Math.Log(View)) * (double)num); Height += (height - Height) * num; Mist += (mist - Mist) * num; Sea += (sea - Sea) * num; } } } public void Reset() { _have = false; } } public sealed class BackgroundQueue<TJob, TResult> : IDisposable { private readonly Func<TJob, TResult> _work; private readonly object _lock = new object(); private readonly Queue<TJob> _jobs = new Queue<TJob>(); private readonly ConcurrentQueue<TResult> _results = new ConcurrentQueue<TResult>(); private readonly Thread _thread; private volatile bool _stop; private volatile Exception _failure; public Exception Failure => _failure; public BackgroundQueue(Func<TJob, TResult> work, string name) { _work = work; _thread = new Thread(Run) { IsBackground = true, Priority = ThreadPriority.BelowNormal, Name = name }; _thread.Start(); } public void Post(TJob job) { lock (_lock) { _jobs.Enqueue(job); Monitor.Pulse(_lock); } } public bool TryTake(out TResult result) { return _results.TryDequeue(out result); } private void Run() { try { while (!_stop) { TJob arg; lock (_lock) { while (_jobs.Count == 0 && !_stop) { Monitor.Wait(_lock); } if (_stop) { break; } arg = _jobs.Dequeue(); } TResult item = _work(arg); if (!_stop) { _results.Enqueue(item); continue; } break; } } catch (Exception failure) { _failure = failure; } } public void Dispose() { _stop = true; lock (_lock) { _jobs.Clear(); Monitor.PulseAll(_lock); } } } public struct PlacedPiece { public int Prefab; public float X; public float Y; public float Z; public float Qx; public float Qy; public float Qz; public float Qw; } public sealed class ZoneBuildings { public readonly int ZoneX; public readonly int ZoneZ; public readonly uint Print; public readonly PlacedPiece[] Pieces; public readonly ZoneEdit Edit; public readonly byte[][] Compilers; public bool IsEmpty { get { if (Pieces.Length == 0 && (Edit == null || Edit.IsEmpty)) { return Compilers == null; } return false; } } public ZoneBuildings(int zoneX, int zoneZ, uint print, PlacedPiece[] pieces, ZoneEdit edit) : this(zoneX, zoneZ, print, pieces, edit, null) { } private ZoneBuildings(int zoneX, int zoneZ, uint print, PlacedPiece[] pieces, ZoneEdit edit, byte[][] compilers) { ZoneX = zoneX; ZoneZ = zoneZ; Print = print; Pieces = pieces; Edit = edit; Compilers = compilers; } public static ZoneBuildings Unparsed(int zoneX, int zoneZ, uint print, PlacedPiece[] pieces, byte[][] compilers) { return new ZoneBuildings(zoneX, zoneZ, print, pieces, null, (compilers != null && compilers.Length != 0) ? compilers : null); } public ZoneBuildings Parsed(out int bad) { bad = 0; if (Compilers == null) { return this; } List<ZoneEdit> list = new List<ZoneEdit>(Compilers.Length); byte[][] compilers = Compilers; foreach (byte[] compressed in compilers) { try { list.Add(TerrainCompData.Parse(ZoneX, ZoneZ, compressed)); } catch (InvalidDataException) { bad++; } } ZoneEdit zoneEdit = TerrainCompData.Pick(list); return new ZoneBuildings(ZoneX, ZoneZ, Print, Pieces, (zoneEdit != null && !zoneEdit.IsEmpty) ? zoneEdit : null); } public static uint PrintOf(List<(long User, uint Id, PlacedPiece Piece)> pieces, List<(long User, uint Id, uint Revision)> compilers) { if (pieces.Count == 0 && compilers.Count == 0) { return 0u; } pieces.Sort(((long User, uint Id, PlacedPiece Piece) p, (long User, uint Id, PlacedPiece Piece) q) => (p.User == q.User) ? p.Id.CompareTo(q.Id) : p.User.CompareTo(q.User)); compilers.Sort(); uint h = 2166136261u; foreach (var piece in pieces) { h = Id(h, piece.User, piece.Id); PlacedPiece item = piece.Piece; h = Mix(h, (uint)item.Prefab); h = Mix(h, Bits(item.X)); h = Mix(h, Bits(item.Y)); h = Mix(h, Bits(item.Z)); h = Mix(h, Bits(item.Qx)); h = Mix(h, Bits(item.Qy)); h = Mix(h, Bits(item.Qz)); h = Mix(h, Bits(item.Qw)); } h = Mix(h, 12648430u); foreach (var compiler in compilers) { h = Id(h, compiler.User, compiler.Id); h = Mix(h, compiler.Revision); } if (h != 0) { return h; } return 1u; } private static uint Id(uint h, long user, uint id) { return Mix(Mix(Mix(h, (uint)user), (uint)(user >> 32)), id); } private static uint Bits(float f) { return (uint)BitConverter.SingleToInt32Bits(f); } private static uint Mix(uint h, uint v) { for (int i = 0; i < 4; i++) { h = (h ^ ((v >> 8 * i) & 0xFF)) * 16777619; } return h; } } public sealed class BuildingCell { public readonly TileKey Key; public readonly IReadOnlyDictionary<(int X, int Z), ZoneBuildings> Zones; public IEnumerable<ZoneEdit> Edits => from z in Zones.Values where z.Edit != null && !z.Edit.IsEmpty select z.Edit; private BuildingCell(TileKey key, Dictionary<(int X, int Z), ZoneBuildings> zones) { Key = key; Zones = zones; } public static BuildingCell Empty(TileKey key) { return new BuildingCell(key, new Dictionary<(int, int), ZoneBuildings>()); } public BuildingCell With(IEnumerable<ZoneBuildings> zones) { Dictionary<(int, int), ZoneBuildings> dictionary = new Dictionary<(int, int), ZoneBuildings>(); foreach (KeyValuePair<(int, int), ZoneBuildings> zone in Zones) { dictionary[zone.Key] = zone.Value; } foreach (ZoneBuildings zone2 in zones) { if (!EditMap.CellOf(zone2.ZoneX, zone2.ZoneZ).Equals(Key)) { throw new ArgumentException($"zone ({zone2.ZoneX}, {zone2.ZoneZ}) is not in cell {Key}"); } if (zone2.Compilers != null) { throw new ArgumentException($"zone ({zone2.ZoneX}, {zone2.ZoneZ}) has its land compilers not parsed"); } if (zone2.IsEmpty) { dictionary.Remove((zone2.ZoneX, zone2.ZoneZ)); } else { dictionary[(zone2.ZoneX, zone2.ZoneZ)] = zone2; } } return new BuildingCell(Key, dictionary); } } public sealed class BuildingCodec : ITileCodec<BuildingCell> { public const int Version = 1; public static readonly BuildingCodec Instance = new BuildingCodec(); public TileKey KeyOf(BuildingCell cell) { return cell.Key; } public void Encode(BuildingCell cell, Stream to) { using DeflateStream output = new DeflateStream(to, CompressionLevel.Fastest, leaveOpen: true); using BinaryWriter binaryWriter = new BinaryWriter(output); binaryWriter.Write(1); binaryWriter.Write(cell.Key.Level); binaryWriter.Write(cell.Key.X); binaryWriter.Write(cell.Key.Z); binaryWriter.Write(cell.Zones.Count); foreach (ZoneBuildings item in from v in cell.Zones.Values orderby v.ZoneZ, v.ZoneX select v) { binaryWriter.Write(item.ZoneX); binaryWriter.Write(item.ZoneZ); binaryWriter.Write(item.Print); binaryWriter.Write(item.Pieces.Length); PlacedPiece[] pieces = item.Pieces; for (int num = 0; num < pieces.Length; num++) { PlacedPiece placedPiece = pieces[num]; binaryWriter.Write(placedPiece.Prefab); binaryWriter.Write(placedPiece.X); binaryWriter.Write(placedPiece.Y); binaryWriter.Write(placedPiece.Z); binaryWriter.Write(placedPiece.Qx); binaryWriter.Write(placedPiece.Qy); binaryWriter.Write(placedPiece.Qz); binaryWriter.Write(placedPiece.Qw); } ZoneEdit edit = item.Edit; binaryWriter.Write(edit != null); if (edit == null) { continue; } binaryWriter.Write(edit.Operations); int num2 = 0; int num3 = 0; for (int num4 = 0; num4 < edit.Delta.Length; num4++) { if (edit.Delta[num4] != 0f) { num2++; } if (edit.Painted[num4]) { num3++; } } binaryWriter.Write(num2); for (int num5 = 0; num5 < edit.Delta.Length; num5++) { if (edit.Delta[num5] != 0f) { binaryWriter.Write((ushort)num5); binaryWriter.Write(edit.Delta[num5]); } } binaryWriter.Write(num3); for (int num6 = 0; num6 < edit.Painted.Length; num6++) { if (edit.Painted[num6]) { binaryWriter.Write((ushort)num6); binaryWriter.Write(edit.Paint[num6]); } } } } public BuildingCell Decode(byte[] data, int length) { try { using DeflateStream input = new DeflateStream(new MemoryStream(data, 0, length, writable: false), CompressionMode.Decompress); using BinaryReader binaryReader = new BinaryReader(input); int num = binaryReader.ReadInt32(); if (num != 1) { throw new InvalidDataException($"building cell format {num}, expected {1}"); } TileKey key = new TileKey(binaryReader.ReadInt32(), binaryReader.ReadInt32(), binaryReader.ReadInt32()); int num2 = binaryReader.ReadInt32(); if (num2 < 0 || num2 > 64) { throw new InvalidDataException($"building cell of {num2} zones"); } List<ZoneBuildings> list = new List<ZoneBuildings>(num2); int num3 = 4225; for (int i = 0; i < num2; i++) { int zoneX = binaryReader.ReadInt32(); int zoneZ = binaryReader.ReadInt32(); uint print = binaryReader.ReadUInt32(); int num4 = binaryReader.ReadInt32(); if (num4 < 0 || num4 > 1048576) { throw new InvalidDataException($"building zone of {num4} pieces"); } PlacedPiece[] array = new PlacedPiece[num4]; for (int j = 0; j < num4; j++) { array[j] = new PlacedPiece { Prefab = binaryReader.ReadInt32(), X = binaryReader.ReadSingle(), Y = binaryReader.ReadSingle(), Z = binaryReader.ReadSingle(), Qx = binaryReader.ReadSingle(), Qy = binaryReader.ReadSingle(), Qz = binaryReader.ReadSingle(), Qw = binaryReader.ReadSingle() }; } ZoneEdit edit = null; if (binaryReader.ReadBoolean()) { int operations = binaryReader.ReadInt32(); float[] array2 = new float[num3]; uint[] array3 = new uint[num3]; bool[] array4 = new bool[num3]; int num5 = binaryReader.ReadInt32(); if (num5 < 0 || num5 > num3) { throw new InvalidDataException($"zone edit of {num5} deltas"); } for (int k = 0; k < num5; k++) { int num6 = binaryReader.ReadUInt16(); if (num6 >= num3) { throw new InvalidDataException("zone edit vertex out of range"); } array2[num6] = binaryReader.ReadSingle(); } int num7 = binaryReader.ReadInt32(); if (num7 < 0 || num7 > num3) { throw new InvalidDataException($"zone edit of {num7} paints"); } for (int l = 0; l < num7; l++) { int num8 = binaryReader.ReadUInt16(); if (num8 >= num3) { throw new InvalidDataException("zone edit vertex out of range"); } array4[num8] = true; array3[num8] = binaryReader.ReadUInt32(); } edit = new ZoneEdit(zoneX, zoneZ, operations, array2, array3, array4); } list.Add(new ZoneBuildings(zoneX, zoneZ, print, array, edit)); } return BuildingCell.Empty(key).With(list); } catch (InvalidDataException) { throw; } catch (Exception ex2) when (ex2 is EndOfStreamException || ex2 is IOException || ex2 is ArgumentException) { throw new InvalidDataException("damaged building cell: " + ex2.Message, ex2); } } } public static class BuildingLevels { public const float Hysteresis = 32f; public const float MinHandoff = 64f; public static bool Ours(bool wasOurs, float nearest, float handoff) { if (!(nearest >= handoff)) { if (wasOurs) { return nearest >= handoff - 32f; } return false; } return true; } public static bool Ours(bool wasOurs, float nearest, float handoff, float pastLod) { if (!Ours(wasOurs, nearest, handoff)) { if (Ours(wasOurs, nearest, 64f)) { return Ours(wasOurs, pastLod, 0f); } return false; } return true; } public static float LodScale(float lodBias, float fovDegrees) { return lodBias / (2f * (float)Math.Tan((double)fovDegrees * 0.5 * Math.PI / 180.0)); } public static float PastLod(float camX, float camY, float camZ, LodBox[] boxes, float scale) { float num = float.NegativeInfinity; if (boxes == null) { return num; } for (int i = 0; i < boxes.Length; i++) { LodBox lodBox = boxes[i]; float num2 = Math.Max(camX - lodBox.MinX, lodBox.MaxX - camX); float num3 = Math.Max(camY - lodBox.MinY, lodBox.MaxY - camY); float num4 = Math.Max(camZ - lodBox.MinZ, lodBox.MaxZ - camZ); num = Math.Max(num, (float)Math.Sqrt(num2 * num2 + num3 * num3 + num4 * num4) - lodBox.LodSize * scale); } return num; } public static float Nearest(float camX, float camZ, float minX, float minZ, float maxX, float maxZ) { float num = Math.Max(Math.Max(minX - camX, camX - maxX), 0f); float num2 = Math.Max(Math.Max(minZ - camZ, camZ - maxZ), 0f); return (float)Math.Sqrt(num * num + num2 * num2); } public static bool CellLevel(float nearestOfCell, float cellDistance) { return nearestOfCell >= cellDistance; } public static float ZoneMergesFrom(float handoff, float areaEdge, float lead) { return Math.Min(handoff - 32f, areaEdge) - lead; } public static bool RemergeNow(bool hasMerge, bool gameDraws, float nearest, float handoff, float pastLod = float.NegativeInfinity) { if (hasMerge && gameDraws) { return Ours(wasOurs: true, nearest, handoff, pastLod); } return true; } } public struct LodBox { public float MinX; public float MinY; public float MinZ; public float MaxX; public float MaxY; public float MaxZ; public float LodSize; } public static class GameArea { public static bool Near(int dx, int dz, int near, bool classic) { int num = Math.Max(Math.Abs(dx), Math.Abs(dz)); if (num == 0) { return true; } if (num > near) { return false; } if (!classic) { return 4 * ((long)dx * (long)dx + (long)dz * (long)dz) < (2L * (long)near + 1) * (2L * (long)near + 1); } return true; } public static float Edge(int near, bool classic) { near = Math.Max(0, near); float num = float.MaxValue; for (int i = 0; i <= near + 1; i++) { for (int j = 0; j <= near + 1; j++) { if (!Near(i, j, near, classic)) { num = Math.Min(num, Gap(Math.Max(0, i - 1), Math.Max(0, j - 1))); } } } return num; } public static float Reach(int near, bool classic) { near = Math.Max(0, near); float num = 0f; for (int i = 0; i <= near; i++) { for (int j = 0; j <= near; j++) { if (Near(i, j, near, classic)) { num = Math.Max(num, Gap(i, j)); } } } return num; } private static float Gap(int zonesX, int zonesZ) { return (float)Math.Sqrt((double)zonesX * (double)zonesX + (double)zonesZ * (double)zonesZ) * 64f; } } public static class CanopyShade { public const float MinSunSine = 0.087f; public const double SkyFitA = 0.874; public const double SkyFitB = 0.921; public static float[] Silhouettes(byte[] mip, int frames) { int num = (int)Math.Round(Math.Sqrt(mip.Length)); int num2 = num / frames; float[] array = new float[frames * frames * 2]; if (num2 == 0 || num * num != mip.Length) { return array; } for (int i = 0; i < frames; i++) { for (int j = 0; j < frames; j++) { double num3 = 0.0; double num4 = 0.0; for (int k = 0; k < num2; k++) { double num5 = ((double)k + 0.5) / (double)num2 * 2.0 - 1.0; int num6 = (i * num2 + k) * num + j * num2; for (int l = 0; l < num2; l++) { byte b = mip[num6 + l]; num3 += (double)(int)b; num4 += (double)(int)b * num5; } } int num7 = (i * frames + j) * 2; array[num7] = (float)(num3 / (255.0 * (double)num2 * (double)num2)); array[num7 + 1] = ((num3 > 0.0) ? ((float)(num4 / num3)) : 0f); } } return array; } public static bool ForSun(float[] silhouettes, int frames, float centreY, float radius, float lx, float ly, float lz, out float area, out float offset, out float length) { area = (offset = (length = 0f)); if (silhouettes == null || silhouettes.Length < frames * frames * 2 || radius <= 0f) { return false; } float num = Math.Max(ly, 0.087f); float num2 = (float)Math.Sqrt(lx * lx + lz * lz); float num3 = ((num2 > 1E-06f) ? (lx / num2) : 0f); float num4 = ((num2 > 1E-06f) ? (lz / num2) : 0f); float num5 = (float)Math.Sqrt(1.0 - (double)(num * num)); ImpostorMath.Encode(num3 * num5, num, num4 * num5, out var u, out var v); float num6 = Clamp((u * 0.5f + 0.5f) * (float)frames - 0.5f, frames); float num7 = Clamp((v * 0.5f + 0.5f) * (float)frames - 0.5f, frames); int num8 = Math.Min(frames - 2, (int)num6); int num9 = Math.Min(frames - 2, (int)num7); float num10 = num6 - (float)num8; float num11 = num7 - (float)num9; float num12 = 0f; float num13 = 0f; for (int i = 0; i < 4; i++) { int num14 = num8 + (i & 1); int num15 = num9 + (i >> 1); float num16 = (((i & 1) != 0) ? num10 : (1f - num10)) * ((i >> 1 != 0) ? num11 : (1f - num11)); float num17 = silhouettes[(num15 * frames + num14) * 2]; num12 += num16 * num17; num13 += num16 * num17 * silhouettes[(num15 * frames + num14) * 2 + 1]; } if (num12 <= 0f) { return false; } float num18 = num13 / num12 * radius; float num19 = num12 * 4f * radius * radius; area = num19 / num; offset = (centreY * num5 + num18) / num; length = (float)Math.Sqrt(num19) / num; return true; } public static float SkyThrough(float density) { if (density <= 0f) { return 1f; } return (float)(Math.Exp(-0.874 * (double)density) / (1.0 + 0.921 * (double)density)); } private static float Clamp(float g, int frames) { return Math.Max(0f, Math.Min(frames - 1, g)); } } public sealed class CoverageSet<T> where T : class { private Dictionary<TileKey, T> _shown = new Dictionary<TileKey, T>(); private readonly Dictionary<TileKey, T> _arrived = new Dictionary<TileKey, T>(); private HashSet<TileKey> _wanted; public IReadOnlyDictionary<TileKey, T> Shown => _shown; public bool HasWanted => _wanted != null; public IEnumerable<T> Views => _shown.Values.Concat(_arrived.Values); public bool Ready { get { if (_wanted == null) { return false; } foreach (TileKey item in _wanted) { if (!_shown.ContainsKey(item) && !_arrived.ContainsKey(item)) { return false; } } return true; } } public IEnumerable<KeyValuePair<TileKey, T>> Coming { get { T value; if (_wanted != null) { return from k in _wanted where _arrived.ContainsKey(k) || _shown.ContainsKey(k) select new KeyValuePair<TileKey, T>(k, _arrived.TryGetValue(k, out value) ? value : _shown[k]); } return Enumerable.Empty<KeyValuePair<TileKey, T>>(); } } public int Missing { get { if (_wanted == null) { return 0; } int num = 0; foreach (TileKey item in _wanted) { if (!_shown.ContainsKey(item) && !_arrived.ContainsKey(item)) { num++; } } return num; } } public List<T> Want(IEnumerable<TileKey> keys) { _wanted = new HashSet<TileKey>(keys); List<T> list = new List<T>(); foreach (TileKey item in _arrived.Keys.Where((TileKey k) => !_wanted.Contains(k)).ToList()) { list.Add(_arrived[item]); _arrived.Remove(item); } return list; } public bool IsWanted(TileKey key) { if (_wanted != null) { return _wanted.Contains(key); } return false; } public bool TryGetComing(TileKey key, out T view) { view = null; if (IsWanted(key)) { if (!_arrived.TryGetValue(key, out view)) { return _shown.TryGetValue(key, out view); } return true; } return false; } public List<TileKey> ToBuild() { if (_wanted != null) { return _wanted.Where((TileKey k) => !_shown.ContainsKey(k) && !_arrived.ContainsKey(k)).ToList(); } return new List<TileKey>(); } public T Arrive(TileKey key, T view) { if (!IsWanted(key)) { return view; } _arrived.TryGetValue(key, out var value); _arrived[key] = view; return value; } public T Replace(TileKey key, T view) { if (IsWanted(key)) { return Arrive(key, view); } if (!_shown.TryGetValue(key, out var value)) { return view; } _shown[key] = view; return value; } public bool TrySwap(out List<T> hidden, out List<T> shownNow) { hidden = null; shownNow = null; if (!Ready) { return false; } Dictionary<TileKey, T> dictionary = new Dictionary<TileKey, T>(); shownNow = new List<T>(); hidden = (from p in _shown where !_wanted.Contains(p.Key) select p.Value).ToList(); foreach (TileKey item in _wanted) { if (_arrived.TryGetValue(item, out var value)) { dictionary[item] = value; shownNow.Add(value); if (_shown.TryGetValue(item, out var value2)) { hidden.Add(value2); } } else { dictionary[item] = _shown[item]; } } _shown = dictionary; _arrived.Clear(); _wanted = null; return true; } } public sealed class DetailGovernor { public const float Raise = 0.6f; public const float Max = 2.5f; public const double RoomBelow = 0.6; public const double BusyAbove = 0.85; public const double WindowSeconds = 10.0; private const int WindowsToChange = 2; private readonly List<double> _shares = new List<double>(); private double _windowTime; private int _room; private int _busy; public bool Raised { get; private set; } public double LastShare { get; private set; } = double.NaN; public bool Frame(double gpuMs, double frameMs) { if (frameMs <= 0.0 || double.IsNaN(frameMs)) { return false; } if (!double.IsNaN(gpuMs) && gpuMs > 0.0) { _shares.Add(gpuMs / frameMs); } _windowTime += frameMs / 1000.0; if (_windowTime < 10.0) { return false; } _windowTime = 0.0; if (_shares.Count < 30) { _shares.Clear(); _room = (_busy = 0); return false; } _shares.Sort(); LastShare = _shares[_shares.Count / 2]; _shares.Clear(); _room = ((LastShare < 0.6) ? (_room + 1) : 0); _busy = ((LastShare > 0.85) ? (_busy + 1) : 0); if (!Raised && _room >= 2) { Raised = true; _room = 0; return true; } if (Raised && _busy >= 2) { Raised = false; _busy = 0; return true; } return false; } public void Skip() { _shares.Clear(); _windowTime = 0.0; _room = (_busy = 0); } public float Detail(float set) { if (!Raised) { return set; } return Math.Min(set + 0.6f, 2.5f); } public float Near(float set) { if (!Raised) { return set; } return Math.Max(2f, set * 0.5f); } } public sealed class EditMap { private sealed class Snapshot { public readonly Dictionary<(int X, int Z), ZoneEdit> Zones; public readonly HashSet<TileKey> Cells; public Snapshot() { Zones = new Dictionary<(int, int), ZoneEdit>(); Cells = new HashSet<TileKey>(); } public Snapshot(Snapshot from) { Zones = new Dictionary<(int, int), ZoneEdit>(from.Zones); Cells = new HashSet<TileKey>(from.Cells); } } private readonly object _write = new object(); private volatile Snapshot _snap = new Snapshot(); private readonly List<(int X, int Z)> _changed = new List<(int, int)>(); private readonly HashSet<(int X, int Z)> _changedSet = new HashSet<(int, int)>(); public static TileKey CellOf(int zoneX, int zoneZ) { return new TileKey(0, FloorDiv(zoneX), FloorDiv(zoneZ)); } private static int FloorDiv(int zone) { return (int)Math.Floor((double)zone / 8.0); } public void SetCell(TileKey cell, IEnumerable<ZoneEdit> edits) { SetCells(new(TileKey, IEnumerable<ZoneEdit>)[1] { (cell, edits) }); } public void SetCells(IEnumerable<(TileKey Cell, IEnumerable<ZoneEdit> Edits)> cells) { List<(TileKey, Dictionary<(int, int), ZoneEdit>)> list = new List<(TileKey, Dictionary<(int, int), ZoneEdit>)>(); foreach (var cell in cells) { TileKey item = cell.Cell; IEnumerable<ZoneEdit> item2 = cell.Edits; Dictionary<(int, int), ZoneEdit> dictionary = new Dictionary<(int, int), ZoneEdit>(); foreach (ZoneEdit item5 in item2) { if (!CellOf(item5.ZoneX, item5.ZoneZ).Equals(item)) { throw new ArgumentException($"zone ({item5.ZoneX}, {item5.ZoneZ}) is not in cell {item}"); } if (!item5.IsEmpty) { dictionary[(item5.ZoneX, item5.ZoneZ)] = item5; } } list.Add((item, dictionary)); } if (list.Count == 0) { return; } lock (_write) { Snapshot snapshot = new Snapshot(_snap); foreach (var item6 in list) { TileKey item3 = item6.Item1; Dictionary<(int, int), ZoneEdit> item4 = item6.Item2; bool flag = !snapshot.Cells.Add(item3); for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { (int, int) tuple = (item3.X * 8 + j, item3.Z * 8 + i); snapshot.Zones.TryGetValue(tuple, out var value); item4.TryGetValue(tuple, out var value2); if (!flag || (value?.Print ?? 0) != (value2?.Print ?? 0)) { Changed(tuple); } if (value2 != null) { snapshot.Zones[tuple] = value2; } else if (value != null) { snapshot.Zones.Remove(tuple); } } } } _snap = snapshot; } } private void Changed((int X, int Z) zone) { if (_changedSet.Add(zone)) { _changed.Add(zone); } } public void ForgetCell(TileKey cell) { lock (_write) { if (!_snap.Cells.Contains(cell)) { return; } Snapshot snapshot = new Snapshot(_snap); snapshot.Cells.Remove(cell); for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { (int, int) tuple = (cell.X * 8 + j, cell.Z * 8 + i); if (snapshot.Zones.Remove(tuple)) { Changed(tuple); } } } _snap = snapshot; } } public bool Known(TileKey cell) { return _snap.Cells.Contains(cell); } public bool HasZone(int zoneX, int zoneZ) { return _snap.Zones.ContainsKey((zoneX, zoneZ)); } public float Delta(float x, float z) { if (!_snap.Zones.TryGetValue((ForestCell.ZoneOf(x), ForestCell.ZoneOf(z)), out var value)) { return 0f; } return value.DeltaAt(x, z); } public bool Paint(float x, float z, out uint paint) { if (_snap.Zones.TryGetValue((ForestCell.ZoneOf(x), ForestCell.ZoneOf(z)), out var value)) { return value.PaintAt(x, z, out paint); } paint = 0u; return false; } public uint PrintOver(float minX, float minZ, float maxX, float maxZ) { Snapshot snap = _snap; uint num = 2166136261u; bool flag = false; int num2 = ForestCell.ZoneOf(minZ); int num3 = ForestCell.ZoneOf(maxZ); int num4 = ForestCell.ZoneOf(minX); int num5 = ForestCell.ZoneOf(maxX); for (int i = num2; i <= num3; i++) { for (int j = num4; j <= num5; j++) { if (snap.Zones.TryGetValue((j, i), out var value)) { flag = true; num = (num ^ (uint)j) * 16777619; num = (num ^ (uint)i) * 16777619; num = (num ^ value.Print) * 16777619; } } } if (flag) { if (num != 0) { return num; } return 1u; } return 0u; } public List<(int X, int Z)> TakeChangedZones() { lock (_write) { List<(int, int)> result = new List<(int, int)>(_changed); _changed.Clear(); _changedSet.Clear(); return result; } } } public static class FarLandsLand { private sealed class Change { public string Owner; public Func<float, float, float, float> Delta; public Func<float, float, float, uint, uint> Paint; public Func<float, float, float, float, float, uint> Print; public Func<float, float, bool> Cleared; } internal sealed class ZoneFeed { private readonly Queue<(int X, int Z)> _queue = new Queue<(int, int)>(); private readonly HashSet<(int X, int Z)> _set = new HashSet<(int, int)>(); internal void Add((int X, int Z) zone) { if (_set.Add(zone)) { _queue.Enqueue(zone); } } public void Take(List<(int X, int Z)> into, int most) { lock (Gate) { for (int i = 0; i < most; i++) { if (_queue.Count <= 0) { break; } (int, int) item = _queue.Dequeue(); _set.Remove(item); into.Add(item); } } } } public const int ApiVersion = 1; internal const int ChangedPerFrame = 256; private static readonly object Gate = new object(); private static volatile Change[] s_all = new Change[0]; private static volatile int s_version; private static ZoneFeed[] s_feeds = new ZoneFeed[0]; internal static volatile EditMap Edits; internal static Action<string> Warn; internal static int Version => s_version; public static object Add(string owner, Func<float, float, float, float> delta, Func<float, float, float, uint, uint> paint, Func<float, float, float, float, float, uint> print, Func<float, float, bool> cleared) { if (print == null) { throw new ArgumentNullException("print"); } Change change = new Change { Owner = (owner ?? "?"), Delta = delta, Paint = paint, Print = print, Cleared = cleared }; lock (Gate) { Change[] array = new Change[s_all.Length + 1]; Array.Copy(s_all, array, s_all.Length); array[s_all.Length] = change; s_all = array; s_version++; return change; } } public static void Remove(object handle) { lock (Gate) { int num = Array.IndexOf(s_all, handle as Change); if (num >= 0) { Change[] array = new Change[s_all.Length - 1]; Array.Copy(s_all, 0, array, 0, num); Array.Copy(s_all, num + 1, array, num, array.Length - num); s_all = array; s_version++; } } } public static void ZoneChanged(int zoneX, int zoneZ) { lock (Gate) { ZoneFeed[] array = s_feeds; for (int i = 0; i < array.Length; i++) { array[i].Add((X: zoneX, Z: zoneZ)); } } } public static bool PlayerEdited(int zoneX, int zoneZ) { return Edits?.HasZone(zoneX, zoneZ) ?? false; } internal static ZoneFeed Follow() { ZoneFeed zoneFeed = new ZoneFeed(); lock (Gate) { ZoneFeed[] array = new ZoneFeed[s_feeds.Length + 1]; Array.Copy(s_feeds, array, s_feeds.Length); array[s_feeds.Length] = zoneFeed; s_feeds = array; return zoneFeed; } } internal static void Unfollow(ZoneFeed feed) { lock (Gate) { int num = Array.IndexOf(s_feeds, feed); if (num >= 0) { ZoneFeed[] array = new ZoneFeed[s_feeds.Length - 1]; Array.Copy(s_feeds, 0, array, 0, num); Array.Copy(s_feeds, num + 1, array, num, array.Length - num); s_feeds = array; } } } internal static float Delta(float x, float z, float spacing) { Change[] array = s_all; float num = 0f; Change[] array2 = array; foreach (Change change in array2) { if (change.Delta != null) { try { num += change.Delta(x, z, spacing); } catch (Exception e) { Fail(change, e); } } } return num; } internal static uint Paint(float x, float z, float spacing, uint mask) { Change[] array = s_all; foreach (Change change in array) { if (change.Paint != null) { try { mask = change.Paint(x, z, spacing, mask); } catch (Exception e) { Fail(change, e); } } } return mask; } internal static uint Print(uint edits, float minX, float minZ, float maxX, float maxZ, float spacing) { Change[] array = s_all; uint num = edits; bool flag = false; Change[] array2 = array; foreach (Change change in array2) { uint num2; try { num2 = change.Print(minX, minZ, maxX, maxZ, spacing); } catch (Exception e) { Fail(change, e); continue; } if (num2 != 0) { if (!flag) { num = (0x811C9DC5u ^ edits) * 16777619; flag = true; } num = (num ^ num2) * 16777619; } } if (!flag || num != 0) { return num; } return 1u; } internal static bool Cleared(float x, float z) { Change[] array = s_all; foreach (Change change in array) { if (change.Cleared == null) { continue; } try { if (change.Cleared(x, z)) { return true; } } catch (Exception e) { Fail(change, e); } } return false; } internal static uint ClearedPrint(float minX, float minZ, float maxX, float maxZ) { Change[] array = s_all; uint num = 2166136261u; bool flag = false; Change[] array2 = array; foreach (Change change in array2) { if (change.Cleared != null) { uint num2; try { num2 = change.Print(minX, minZ, maxX, maxZ, 0f); } catch (Exception e) { Fail(change, e); continue; } if (num2 != 0) { flag = true; num = (num ^ num2) * 16777619; } } } if (flag) { if (num != 0) { return num; } return 1u; } return 0u; } private static void Fail(Change c, Exception e) { lock (Gate) { if (Array.IndexOf(s_all, c) < 0) { return; } Remove(c); } Warn?.Invoke($"{c.Owner}'s change of the far land failed and is left out: {e}"); } } public static class FarSink { public const float NoHiding = 30f; public static float UnderLoaded(float distance, float radius, float depth) { if (distance >= radius) { return 0f; } float num = radius - 30f; if (!(distance <= num)) { return depth * (radius - distance) / 30f; } return depth; } public static float Depth(float distance, float hide, float height) { float num = 1f - distance / (hide - 30f); float num2 = 80f * (float.IsNaN(num) ? 0f : Clamp01(num)); float num3 = Clamp01((distance - 100f) * 0.005f); float num4 = 2f * num3 * num3 * (3f - 2f * num3); if (!(30f < height)) { return num2 + num4; } return num2 - num4; } private static float Clamp01(float v) { return Math.Max(0f, Math.Min(1f, v)); } } public readonly struct FarSurface : IEquatable<FarSurface> { public readonly float Hide; public static FarSurface Tiles => new FarSurface(30f); public FarSurface(float hide) { Hide = hide; } public static FarSurface Game(float hide) { return new FarSurface(hide); } public float Depth(float distance, float height) { return FarSink.Depth(distance, Hide, height); } public bool Equals(FarSurface o) { return Hide == o.Hide; } public override bool Equals(object o) { if (o is FarSurface o2) { return Equals(o2); } return false; } public override int GetHashCode() { return Hide.GetHashCode(); } } public sealed class Footprint { public struct Shape { public float X; public float Z; public float AxisX; public float AxisZ; public float HalfX; public float HalfZ; public bool Box; } public readonly Shape[] Shapes; public Footprint(Shape[] shapes) { Shapes = shapes; } public bool Covers(float dx, float dz, float yaw, float scale) { float num = (float)Math.Cos(yaw); float num2 = (float)Math.Sin(yaw); float num3 = (dx * num - dz * num2) / scale; float num4 = (dx * num2 + dz * num) / scale; Shape[] shapes = Shapes; for (int i = 0; i < shapes.Length; i++) { Shape shape = shapes[i]; float num5 = num3 - shape.X; float num6 = num4 - shape.Z; float num7 = (num5 * shape.AxisX + num6 * shape.AxisZ) / shape.HalfX; float num8 = (num6 * shape.AxisX - num5 * shape.AxisZ) / shape.HalfZ; bool num9; if (!shape.Box) { num9 = num7 * num7 + num8 * num8 <= 1f; } else { if (!(Math.Abs(num7) <= 1f)) { continue; } num9 = Math.Abs(num8) <= 1f; } if (num9) { return true; } } return false; } public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); Shape[] shapes = Shapes; for (int i = 0; i < shapes.Length; i++) { Shape shape = shapes[i]; stringBuilder.Append(string.Join(",", F(shape.X), F(shape.Z), F(shape.AxisX), F(shape.AxisZ), F(shape.HalfX), F(shape.HalfZ), shape.Box)).Append(';'); } return stringBuilder.ToString(); } private static string F(float v) { return v.ToString("R", CultureInfo.InvariantCulture); } } public sealed class ForestCell { public const int ZonesPerSide = 8; public readonly TileKey Key; public readonly TreeInstance[] Trees; public readonly ulong Observed; public readonly uint Clearings; public ForestCell(TileKey key, TreeInstance[] trees, ulong observed, uint clearings = 0u) { if (key.Level != 0) { throw new ArgumentException($"forest cell {key}: cells are level-0 tiles"); } Key = key; Trees = trees; Observed = observed; Clearings = clearings; } public static int ZoneOf(float coordinate) { return (int)Math.Floor((coordinate + 32f) / 64f); } public static TileKey CellOf(int zoneX, int zoneZ) { return new TileKey(0, (int)Math.Floor((double)zoneX / 8.0), (int)Math.Floor((double)zoneZ / 8.0)); } public static void Bounds(TileKey key, out float minX, out float minZ, out float maxX, out float maxZ) { minX = (float)key.X * 512f - 32f; minZ = (float)key.Z * 512f - 32f; maxX = minX + 512f; maxZ = minZ + 512f; } public ForestCell Cleared(uint clearings, Func<float, float, bool> cleared) { List<TreeInstance> list = new List<TreeInstance>(Trees.Length); TreeInstance[] trees = Trees; for (int i = 0; i < trees.Length; i++) { TreeInstance item = trees[i]; int num = ZoneOf(item.X) - Key.X * 8; int num2 = ZoneOf(item.Z) - Key.Z * 8; if ((num >= 0 && num2 >= 0 && num < 8 && num2 < 8 && (Observed & (ulong)(1L << num2 * 8 + num)) != 0) || !cleared(item.X, item.Z)) { list.Add(item); } } return new ForestCell(Key, list.ToArray(), Observed, clearings); } public int ZoneBit(int zoneX, int zoneZ) { int num = zoneX - Key.X * 8; int num2 = zoneZ - Key.Z * 8; if (num < 0 || num2 < 0 || num >= 8 || num2 >= 8) { throw new ArgumentOutOfRangeException("zoneX", $"zone ({zoneX}, {zoneZ}) is not in cell {Key}"); } return num2 * 8 + num; } public bool IsObserved(int zoneX, int zoneZ) { return (Observed & (ulong)(1L << ZoneBit(zoneX, zoneZ))) != 0; } public ForestCell WithZone(int zoneX, int zoneZ, IReadOnlyList<TreeInstance> seen) { return WithZones(new Dictionary<(int, int), List<TreeInstance>> { [(zoneX, zoneZ)] = seen.ToList() }); } public ForestCell WithZones(IReadOnlyDictionary<(int X, int Z), List<TreeInstance>> seen) { ulong num = 0uL; bool flag = true; foreach (KeyValuePair<(int, int), List<TreeInstance>> zone in seen) { num |= (ulong)(1L << ZoneBit(zone.Key.Item1, zone.Key.Item2)); flag = flag && IsObserved(zone.Key.Item1, zone.Key.Item2) && SameTrees(Trees.Where((TreeInstance t) => ZoneOf(t.X) == zone.Key.Item1 && ZoneOf(t.Z) == zone.Key.Item2), zone.Value); } if (flag) { return this; } TreeInstance[] trees = Trees.Where((TreeInstance t) => !seen.ContainsKey((ZoneOf(t.X), ZoneOf(t.Z)))).Concat(seen.Values.SelectMany((List<TreeInstance> v) => v)).ToArray(); return new ForestCell(Key, trees, Observed | num); } private static bool SameTrees(IEnumerable<TreeInstance> a, IEnumerable<TreeInstance> b) { Func<TreeInstance, (byte, float, float, float, float, float, float, float)> selector = (TreeInstance t) => (t.Type, t.X, t.Z, t.Y, t.Yaw, t.Scale, t.TiltX, t.TiltZ); return (from k in a.Select(selector) orderby k select k).SequenceEqual(from k in b.Select(selector) orderby k select k); } } public sealed class ForestCodec : ITileCodec<ForestCell> { public const int Version = 2; public static readonly ForestCodec Instance = new ForestCodec(); public TileKey KeyOf(ForestCell cell) { return cell.Key; } public void Encode(ForestCell cell, Stream to) { using DeflateStream output = new DeflateStream(to, CompressionLevel.Fastest, leaveOpen: true); using BinaryWriter binaryWriter = new BinaryWriter(output); binaryWriter.Write(2); binaryWriter.Write(cell.Key.Level); binaryWriter.Write(cell.Key.X); binaryWriter.Write(cell.Key.Z); binaryWriter.Write(cell.Observed); binaryWriter.Write(cell.Trees.Length); TreeInstance[] trees = cell.Trees; for (int i = 0; i < trees.Length; i++) { TreeInstance treeInstance = trees[i]; binaryWriter.Write(treeInstance.Type); binaryWriter.Write(treeInstance.X); binaryWriter.Write(treeInstance.Y); binaryWriter.Write(treeInstance.Z); binaryWriter.Write(treeInstance.Yaw); binaryWriter.Write(treeInstance.Scale); binaryWriter.Write(treeInstance.TiltX); binaryWriter.Write(treeInstance.TiltZ); } } public ForestCell Decode(byte[] data, int length) { try { using DeflateStream input = new DeflateStream(new MemoryStream(data, 0, length, writable: false), CompressionMode.Decompress); using BinaryReader binaryReader = new BinaryReader(input); int num = binaryReader.ReadInt32(); if (num != 2) { throw new InvalidDataException($"forest cell format {num}, expected {2}"); } TileKey key = new TileKey(binaryReader.ReadInt32(), binaryReader.ReadInt32(), binaryReader.ReadInt32()); ulong observed = binaryReader.ReadUInt64(); int num2 = binaryReader.ReadInt32(); if (num2 < 0 || num2 > 1048576) { throw new InvalidDataException($"forest cell of {num2} trees"); } TreeInstance[] array = new TreeInstance[num2]; for (int i = 0; i < num2; i++) { array[i] = new TreeInstance { Type = binaryReader.ReadByte(), X = binaryReader.ReadSingle(), Y = binaryReader.ReadSingle(), Z = binaryReader.ReadSingle(), Yaw = binaryReader.ReadSingle(), Scale = binaryReader.ReadSingle(), TiltX = binaryReader.ReadSingle(), TiltZ = binaryReader.ReadSingle() }; } return new ForestCell(key, array, observed); } catch (InvalidDataException) { throw; } catch (Exception ex2) when (ex2 is EndOfStreamException || ex2 is IOException || ex2 is ArgumentException) { throw new InvalidDataException("damaged forest cell: " + ex2.Message, ex2); } } } public static class ForestGenerator { public const int Version = 8; public static ForestCell Generate(TileKey cell, IReadOnlyList<VegetationRule> rules, IVegetationWorld world, int worldSeed) { List<TreeInstance> list = new List<TreeInstance>(); for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { int num = cell.X * 8 + j; int num2 = cell.Z * 8 + i; float num3 = (float)num * 64f; float num4 = (float)num2 * 64f; if (!(Math.Sqrt(num3 * num3 + num4 * num4) > (double)TileGrid.WorldRadius)) { VegetationRules.PlaceTrees(num, num2, rules, world, worldSeed, list); } } } return new ForestCell(cell, list.ToArray(), 0uL); } } public static class ForestSelection { public static List<TileKey> Cells(float cx, float cz, float distance) { List<TileKey> list = new List<TileKey>(); if (distance <= 0f) { return list; } float num = TileGrid.Size(0); int num2 = (int)Math.Floor((cx - distance) / num); int num3 = (int)Math.Floor((cx + distance) / num); int num4 = (int)Math.Floor((cz - distance) / num); int num5 = (int)Math.Floor((cz + distance) / num); for (int i = num2; i <= num3; i++) { for (int j = num4; j <= num5; j++) { TileKey tileKey = new TileKey(0, i, j); if (TileGrid.Distance(tileKey, cx, cz) <= distance && TileGrid.Distance(tileKey, 0f, 0f) <= TileGrid.WorldRadius) { list.Add(tileKey); } } } return list.OrderBy((TileKey k) => TileGrid.Distance(k, cx, cz)).ToList(); } } public sealed class FrameBudget { public static readonly FrameBudget Shared = new FrameBudget(2.0); public const double SharedMs = 2.0; private readonly double _ms; private int _frame = int.MinValue; private double _spent; private long _start; public FrameBudget(double ms) { _ms = ms; } public double Grant(int frame, double capMs) { if (frame != _frame) { _frame = frame; _spent = 0.0; } _start = Stopwatch.GetTimestamp(); return Math.Max(0.0, Math.Min(capMs, _ms - _spent)); } public void Done() { _spent += (double)(Stopwatch.GetTimestamp() - _start) * 1000.0 / (double)Stopwatch.Frequency; } } public enum Haze { Clear, Light, Standard, Thick, Custom } public readonly struct HazeValues : IEquatable<HazeValues> { public readonly float NearHaze; public readonly float NearHazeDistance; public readonly float FarLandsHaze; public HazeValues(float nearHaze, float nearHazeDistance, float farLandsHaze) { NearHaze = nearHaze; NearHazeDistance = nearHazeDistance; FarLandsHaze = farLandsHaze; } public bool Equals(HazeValues o) { if (Math.Abs(NearHaze - o.NearHaze) < 0.005f && Math.Abs(NearHazeDistance - o.NearHazeDistance) < 0.5f) { return Math.Abs(FarLandsHaze - o.FarLandsHaze) < 0.005f; } return false; } public override bool Equals(object o) { if (o is HazeValues o2) { return Equals(o2); } return false; } public override int GetHashCode() { return (int)NearHazeDistance; } } public static class HazePresets { public static HazeValues Get(Haze h) { return h switch { Haze.Clear => new HazeValues(0.4f, 290f, 0.15f), Haze.Light => new HazeValues(0.55f, 290f, 0.35f), Haze.Thick => new HazeValues(0.8f, 250f, 0.85f), _ => new HazeValues(0.6f, 180f, 0.6f), }; } public static Haze Match(HazeValues v) { Haze[] array = new Haze[4] { Haze.Clear, Haze.Light, Haze.Standard, Haze.Thick }; foreach (Haze haze in array) { if (Get(haze).Equals(v)) { return haze; } } return Haze.Custom; } public static Haze OnLoad(Haze saved, bool inFile, HazeValues current, out bool apply) { Haze haze = Match(current); apply = inFile && saved != Haze.Custom && haze != Haze.Custom && haze != saved; if (!apply) { return haze; } return saved; } } public sealed class HeightField { public const float None = -10000f; public readonly float OriginX; public readonly float OriginZ; public readonly float Texel; public readonly int Size; public readonly float[] Heights; public HeightField(float originX, float originZ, float texel, int size, float[] heights = null) { if (heights != null && heights.Length != size * size) { throw new ArgumentException($"{heights.Length} heights for a {size}x{size} field"); } OriginX = originX; OriginZ = originZ; Texel = texel; Size = size; Heights = heights ?? new float[size * size]; } public static HeightField Build(IEnumerable<TerrainTile> tiles, float cx, float cz, float reach, int size, float[] into = null) { float texel = 2f * reach / (float)size; HeightField heightField = new HeightField(cx - reach, cz - reach, texel, size, into); for (int i = 0; i < heightField.Heights.Length; i++) { heightField.Heights[i] = -10000f; } foreach (TerrainTile tile in tiles) { heightField.Paint(tile); } return heightField; } private void Paint(TerrainTile tile) { TileKey key = tile.Key; float num = TileGrid.Size(key.Level); float num2 = TileGrid.MinX(key); float num3 = TileGrid.MinZ(key); int num4 = Math.Max(0, (int)Math.Ceiling((num2 - OriginX) / Texel - 0.5f)); int num5 = Math.Min(Size - 1, (int)Math.Floor((num2 + num - OriginX) / Texel - 0.5f)); int num6 = Math.Max(0, (int)Math.Ceiling((num3 - OriginZ) / Texel - 0.5f)); int num7 = Math.Min(Size - 1, (int)Math.Floor((num3 + num - OriginZ) / Texel - 0.5f)); for (int i = num6; i <= num7; i++) { float z = OriginZ + ((float)i + 0.5f) * Texel; for (int j = num4; j <= num5; j++) { Heights[i * Size + j] = TileSurface.Height(tile, OriginX + ((float)j + 0.5f) * Texel, z); } } } public float Sample(float x, float z) { float num = Math.Max(0f, Math.Min(Size - 1, (x - OriginX) / Texel - 0.5f)); float num2 = Math.Max(0f, Math.Min(Size - 1, (z - OriginZ) / Texel - 0.5f)); int num3 = Math.Min((int)num, Size - 2); int num4 = Math.Min((int)num2, Size - 2); float num5 = num - (float)num3; float num6 = num2 - (float)num4; int num7 = num4 * Size + num3; float num8 = Heights[num7] + (Heights[num7 + 1] - Heights[num7]) * num5; float num9 = Heights[num7 + Size] + (Heights[num7 + Size + 1] - Heights[num7 + Size]) * num5; return num8 + (num9 - num8) * num6; } } internal struct AirLayer { public float Density; public float Height; public float At(float aboveSea) { return Density * (float)Math.Exp((0f - Math.Max(-200f, float.IsNaN(aboveSea) ? 0f : aboveSea)) / Math.Max(Height, 1f)); } public float MeanToSea(float aboveSea) { float num = (float.IsNaN(aboveSea) ? 0f : aboveSea); float num2 = Math.Max(Height, 1f); if (num < 0.01f * num2) { return At(num); } return Density * (float)((double)num2 / (double)num * (1.0 - Math.Exp((double)(0f - num) / (double)num2))); } } internal static class HeightFog { public const float MistVisibility = 400f; public const float MistHeight = 25f; public const float NearHazeMaxGameFog = 0.006f; public const float FarRate = 0.17f; public const float MistMaxDepth = 0.8f; public const float SeaMistView = 1500f; public const float SeaMistLayerHeight = 150f; public const float SeaSampleNear = 60f; public const float SeaSampleFar = 150f; public const int SeaSamples = 16; public const float MoonGlowBelow = -0.15f; public static AirLayer SeaMist(float amount, float view = 1500f, float height = 150f) { return new AirLayer { Density = Visibility.Koschmieder(view) * Clamp01(amount), Height = height }; } public static float SeaMistAmount(float waterShare) { return Smooth(0.4f, 0.85f, waterShare); } public static double MistDepth(double raw, float cap) { if (cap > 0f && raw > 0.0) { return (double)cap * (1.0 - Math.Exp((0.0 - raw) / (double)cap)); } return Math.Max(raw, 0.0); } public static float FarRateFor(float rain, float snow, float fog, float rate = 0.17f) { float num = Math.Max(Visibility.DenseShare(fog), Math.Max(Clamp01(rain), Clamp01(snow))); rate = Clamp01(rate); return rate + (1f - rate) * num; } public static double NearOpticalDepth(float density, float reach, float distance) { if (!(density > 0f) || !(reach > 0f) || !(distance > 0f) || float.IsInfinity(density)) { return 0.0; } if (float.IsInfinity(distance)) { return (double)density * (double)reach; } return (double)density * (double)reach * (1.0 - Math.Exp((double)(0f - distance) / (double)reach)); } public static double NearEquivalentDensity(float density, float reach) { if (!(reach > 0f)) { return 0.0; } return NearOpticalDepth(density, reach, 2f * reach) / (2.0 * (double)reach); } public static AirLayer Haze(float view, float height) { return new AirLayer { Density = Visibility.Koschmieder(view), Height = height }; } public static AirLayer Mist(float amount) { return new AirLayer { Density = Visibility.Koschmieder(400f) * amount, Height = 25f }; } public static float BuiltInDensity(in AirLayer haze, in AirLayer mist, float near, float nearReach, float aboveSea, in AirLayer sea = default(AirLayer)) { return haze.At(aboveSea) + mist.At(aboveSea) + sea.At(aboveSea) + (float)NearEquivalentDensity(near, nearReach); } public static float SeaViewDensity(in AirLayer haze, in AirLayer mist, in AirLayer sea, float near, float nearReach, float aboveSea) { return haze.MeanToSea(aboveSea) + mist.MeanToSea(aboveSea) + sea.MeanToSea(aboveSea) + (float)NearEquivalentDensity(near, nearReach); } public static void GlowDirection(float sx, float sy, float sz, out float x, out float y, out float z) { float num = (float)Math.Sqrt(sx * sx + sy * sy + sz * sz); if (!(num > 1E-05f)) { x = 0f; y = 1f; z = 0f; } else { float num2 = ((sy / num < -0.15f) ? (-1f) : 1f); x = num2 * sx / num; y = num2 * sy / num; z = num2 * sz / num; } } public static float MistAmount(float dayFraction, float morningMist, float rain, float snow) { float num = Smooth(0.2f, 0.25f, dayFraction); float num2 = 1f - Smooth(0.32f, 0.42f, dayFraction); return Clamp01(morningMist) * num * num2 * (1f - Clamp01(rain)) * (1f - Clamp01(snow)); } private static float Smooth(float a, float b, float x) { float num = Clamp01((x - a) / (b - a)); return num * num * (3f - 2f * num); } private static float Clamp01(float v) { if (!float.IsNaN(v)) { if (!(v < 0f)) { if (!(v > 1f)) { return v; } return 1f; } return 0f; } return 0f; } } public interface IHeightSource { int Biome(float x, float z); float BiomeHeight(int biome, float x, float z, out uint mask); uint SectorColor(float x, float z); } public static class ImpostorMath { public const int Frames = 8; public const int CutMips = 8; public const int FirstCutMip = 2; public static void Encode(float x, float y, float z, out float u, out float v) { float num = Math.Abs(x) + Math.Abs(y) + Math.Abs(z); x /= num; z /= num; u = x + z; v = x - z; } public static void Decode(float u, float v, out float x, out float y, out float z) { float num = (u + v) * 0.5f; float num2 = (u - v) * 0.5f; x = num; z = num2; y = 1f - Math.Abs(num) - Math.Abs(num2); float num3 = (float)Math.Sqrt(x * x + y * y + z * z); x /= num3; y /= num3; z /= num3; } public static void Frame(float u, float v, int frames, out int fx, out int fy) { fx = Math.Min(frames - 1, Math.Max(0, (int)Math.Floor((u * 0.5f + 0.5f) * (float)frames))); fy = Math.Min(frames - 1, Math.Max(0, (int)Math.Floor((v * 0.5f + 0.5f) * (float)frames))); } public static void FrameDirection(int fx, int fy, int frames, out float x, out float y, out float z) { Decode(((float)fx + 0.5f) / (float)frames * 2f - 1f, ((float)fy + 0.5f) / (float)frames * 2f - 1f, out x, out y, out z); } public static float[] CoverageCuts(IReadOnlyList<byte[]> alpha) { float[] array = new float[8]; for (int i = 0; i < 8; i++) { array[i] = 0.5f; } if (alpha.Count == 0 || alpha[0].Length == 0) { return array; } double num = 0.0; byte[] array2 = alpha[0]; foreach (byte b in array2) { num += (double)(int)b; } double num2 = num / (255.0 * (double)alpha[0].Length); int[] array3 = new int[256]; for (int k = 0; k < alpha.Count && 2 + k < 8; k++) { byte[] array4 = alpha[k]; if (array4.Length == 0) { break; } Array.Clear(array3, 0, 256); array2 = array4; foreach (byte b2 in array2) { array3[b2]++; } int num3 = 0; int num4 = 127; double num5 = double.MaxValue; for (int num6 = 255; num6 >= 0; num6--) { double num7 = Math.Abs((double)num3 / (double)array4.Length - num2); if (num7 < num5) { num5 = num7; num4 = num6; } num3 += array3[num6]; } array[2 + k] = Math.Min(0.5f, ((float)num4 + 0.5f) / 255f); } for (int l = 2 + alpha.Count; l < 8; l++) { array[l] = array[Math.Max(0, l - 1)]; } return array; } } public sealed class InstancePool { private readonly SortedDictionary<int, int> _free = new SortedDictionary<int, int>(); private readonly Dictionary<int, int> _used = new Dictionary<int, int>(); public int Capacity { get; } public int Used { get; private set; } public int HighWater { get; private set; } public InstancePool(int capacity) { Capacity = capacity; _free[0] = capacity; } public int Allocate(int count) { if (count <= 0) { throw new ArgumentOutOfRangeException("count"); } int num = -1; int num2 = 0; foreach (KeyValuePair<int, int> item in _free) { if (item.Value >= count) { num = item.Key; num2 = item.Value; break; } } if (num < 0) { return -1; } _free.Remove(num); if (num2 > count) { _free[num + count] = num2 - count; } _used[num] = count; Used += count; HighWater = Math.Max(HighWater, num + count); return num; } public int SizeOf(int offset) { return _used[offset]; } public void Free(int offset) { int num = _used[offset]; _used.Remove(offset); Used -= num; int num2 = offset; int num3 = offset + num; foreach (KeyValuePair<int, int> item in _free.ToList()) { if (item.Key + item.Value == num2) { num2 = item.Key; _free.Remove(item.Key); } else if (item.Key == num3) { num3 = item.Key + item.Value; _free.Remove(item.Key); } } _free[num2] = num3 - num2; HighWater = ((_used.Count != 0) ? _used.Max((KeyValuePair<int, int> u) => u.Key + u.Value) : 0); } } public interface ITileCodec<T> { TileKey KeyOf(T value); void Encode(T value, Stream to); T Decode(byte[] data, int length); } public interface IVegetationWorld { int Biome(float x, float z); float BiomeHeight(int biome, float x, float z, out float vegetationMask); float ForestFactor(float x, float z); bool AltBiomeAllows(string vegetationName, string altBiomeParent, float x, float z); uint AltBiomes(float x, float z); } public interface IEasedTile { TileKey Key { get; } TerrainTile Tile { get; } bool Morphing { get; } float PrepareMorph(TerrainTile other, bool toOther); void ApplyMorphStart(); void StartMorph(float now, float seconds); bool StepMorph(float now); } public interface IEasingStats { void Pop(int levelA, int levelB, float metres); void Started(int folds, int unfolds, double ms); void Stepped(double ms); } public sealed class LevelEasing<T> where T : class, IEasedTile { public const float FoldSeconds = 1.2f; public const float UnfoldSeconds = 1.2f; public const double BudgetMs = 0.5; private readonly IEasingStats _stats; private readonly Stopwatch _clock = new Stopwatch(); private readonly List<T> _folding = new List<T>(); private readonly List<T> _unfolding = new List<T>(); private readonly List<(T View, TerrainTile Other)> _folds = new List<(T, TerrainTile)>(); private readonly List<(T View, TerrainTile Other)> _unfolds = new List<(T, TerrainTile)>(); private Task _preparing; private int _preloaded; private int _next; public bool Planned { get; private set; } public bool Easing => _folding.Count + _unfolding.Count > 0; public bool Unfolding => _unfolding.Count > 0; public LevelEasing(IEasingStats stats) { _stats = stats; } public void Plan(CoverageSet<T> coverage, bool ease, Func<T, bool> inView) { Planned = true; IReadOnlyDictionary<TileKey, T> old = coverage.Shown; if (old.Count == 0 || !ease) { return; } foreach (KeyValuePair<TileKey, T> item in coverage.Coming) { if (!old.ContainsKey(item.Key) && Above(item.Key, (TileKey k) => old.ContainsKey(k), out var found) && inView(item.Value)) { _unfolds.Add((item.Value, old[found].Tile)); } } foreach (KeyValuePair<TileKey, T> item2 in old) { if (!coverage.IsWanted(item2.Key) && Above(item2.Key, coverage.IsWanted, out var found2) && coverage.TryGetComing(found2, out var view) && inView(item2.Value)) { _folds.Add((item2.Value, view.Tile)); } } if (_folds.Count + _unfolds.Count == 0) { return; } (T View, TerrainTile Other)[] folds = _folds.ToArray(); (T View, TerrainTile Other)[] unfolds = _unfolds.ToArray(); IEasingStats stats = _stats; _preparing = Task.Run(delegate { (T, TerrainTile)[] array = folds; for (int i = 0; i < array.Length; i++) { (T, TerrainTile) tuple = array[i]; float metres = tuple.Item1.PrepareMorph(tuple.Item2, toOther: true); stats?.Pop(tuple.Item1.Key.Level, tuple.Item2.Key.Level, metres); } array = unfolds; for (int i = 0; i < array.Length; i++) { (T, TerrainTile) tuple2 = array[i]; float metres2 = tuple2.Item1.PrepareMorph(tuple2.Item2, toOther: false); stats?.Pop(tuple2.Item1.Key.Level, tuple2.Item2.Key.Level, metres2); } }); } public bool ReadyToSwap(float now) { if (Prepared(now) && Preloaded()) { return _folding.Count == 0; } return false; } public void Swapped(float now) { Planned = false; _clock.Restart(); foreach (var unfold in _unfolds) { T item = unfold.View; item.StartMorph(now, 1.2f); _unfolding.Add(item); } int count = _unfolds.Count; _unfolds.Clear(); _stats?.Started(0, count, _clock.Elapsed.TotalMilliseconds); } public void Step(float now) { int num = _folding.Count + _unfolding.Count; if (num == 0) { return; } _clock.Restart(); for (int i = 0; i < num; i++) { if (!(_clock.Elapsed.TotalMilliseconds < 0.5)) { break; } int num2 = _next++ % num; ((num2 < _folding.Count) ? _folding[num2] : _unfolding[num2 - _folding.Count]).StepMorph(now); } _folding.RemoveAll((T v) => !v.Morphing); _unfolding.RemoveAll((T v) => !v.Morphing); _stats?.Stepped(_clock.Elapsed.TotalMilliseconds); } private bool Prepared(float now) { if (_preparing == null) { return true; } if (!_preparing.IsCompleted) { return false; } if (_preparing.IsFaulted) { throw new Exception("working out the level easings failed", _preparing.Exception); } _preparing = null; _clock.Restart(); foreach (var fold in _folds) { T item = fold.View; item.StartMorph(now, 1.2f); _folding.Add(item); } int count = _folds.Count; _folds.Clear(); _preloaded = 0; _stats?.Started(count, 0, _clock.Elapsed.TotalMilliseconds); return false; } private bool Preloaded() { if (_preloaded >= _unfolds.Count) { return true; } _clock.Restart(); do { _unfolds[_preloaded++].View.ApplyMorphStart(); } while (_preloaded < _unfolds.Count && _clock.Elapsed.TotalMilliseconds < 0.5); _stats?.Started(0, 0, _clock.Elapsed.TotalMilliseconds); return _preloaded >= _unfolds.Count; } public static bool Above(TileKey key, Func<TileKey, bool> has, out TileKey found) { for (int i = 1; key.Level + i <= 6; i++) { found = new TileKey(key.Level + i, key.X >> i, key.Z >> i); if (has(found)) { return true; } } found = default(TileKey); return false; } } public static class ZoneBits { public const byte None = 0; public const byte Distant = 128; public const byte All = byte.MaxValue; public static bool Stands(byte zone, bool distant) { if (zone != byte.MaxValue) { if (distant) { return zone != 0; } return false; } return true; } } public static class MeshCoverage { public static void Compose(byte[] mask, bool ignoredMask, int cornerZoneX, int cornerZoneZ, int size, IEnumerable<TileKey> lateCells, byte[] coverage) { if (ignoredMask) { Array.Clear(coverage, 0, size * size); } else { Array.Copy(mask, coverage, size * size); } foreach (TileKey lateCell in lateCells) { for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { int num = lateCell.X * 8 + j - cornerZoneX; int num2 = lateCell.Z * 8 + i - cornerZoneZ; if (num >= 0 && num2 >= 0 && num < size && num2 < size) { coverage[num2 * size + num] = byte.MaxValue; } } } } } public static bool ListsDraw(bool withinReach, bool hasMeshes, byte coverage, bool distant) { if (withinReach && hasMeshes) { return !ZoneBits.Stands(coverage, distant); } return false; } public static bool ImpostorDraws(bool withinReach, bool hasMeshes, byte coverage, byte maskNow, bool distant) { if (!ListsDraw(withinReach, hasMeshes, coverage, distant)) { return !ZoneBits.Stands(maskNow, distant); } return false; } } public sealed class MergedGeometry { public static class Layout { public const int Stride = 28; public const int Position = 0; public const int Normal = 12; public const int Tangent = 16; public const int Color = 20; public const int TexCoord0 = 24; } public int VertexCount; public int[] Indices; public byte[] Vertices; public float MinX; public float MinY; public float MinZ; public float MaxX; public float MaxY; public float MaxZ; } public static class MeshMerge { public static MergedGeometry Merge(IReadOnlyList<(PieceGeometry Part, float[] Matrix, float Mark)> parts, float originX, float originY, float originZ) { int num = 0; int num2 = 0; foreach (var part in parts) { PieceGeometry item = part.Part; num += item.VertexCount; num2 += item.Indices.Length; } MergedGeometry mergedGeometry = new MergedGeometry { VertexCount = num, Indices = new int[num2], Vertices = new byte[num * 28] }; float num3 = float.MaxValue; float num4 = float.MaxValue; float num5 = float.MaxValue; float num6 = float.MinValue; float num7 = float.MinValue; float num8 = float.MinValue; int num9 = 0; int num10 = 0; foreach (var part2 in parts) { PieceGeometry item2 = part2.Part; float[] item3 = part2.Matrix; float item4 = part2.Mark; for (int i = 0; i < item2.VertexCount; i++) { int num11 = (num9 + i) * 28; float num12 = item2.Positions[i * 3]; float num13 = item2.Positions[i * 3 + 1]; float num14 = item2.Positions[i * 3 + 2]; float num15 = item3[0] * num12 + item3[4] * num13 + item3[8] * num14 + item3[12] - originX; float num16 = item3[1] * num12 + item3[5] * num13 + item3[9] * num14 + item3[13] - originY; float num17 = item3[2] * num12 + item3[6] * num13 + item3[10] * num14 + item3[14] - originZ; if (num15 < num3) { num3 = num15; } if (num15 > num6) { num6 = num15; } if (num16 < num4) { num4 = num16; } if (num16 > num7) { num7 = num16; } if (num17 < num5) { num5 = num17; } if (num17 > num8) { num8 = num17; } PutFloat(mergedGeometry.Vertices, num11, num15); PutFloat(mergedGeometry.Vertices, num11 + 4, num16); PutFloat(mergedGeometry.Vertices, num11 + 8, num17); PutTurned(mergedGeometry.Vertices, num11 + 12, item3, item2.Normals[i * 3], item2.Normals[i * 3 + 1], item2.Normals[i * 3 + 2], item4); PutTurned(mergedGeometry.Vertices, num11 + 16, item3, item2.Tangents[i * 4], item2.Tangents[i * 4 + 1], item2.Tangents[i * 4 + 2], item2.Tangents[i * 4 + 3]); uint num18 = ((item2.Colors == null) ? uint.MaxValue : item2.Colors[i]); for (int j = 0; j < 4; j++) { mergedGeometry.Vertices[num11 + 20 + j] = (byte)(num18 >> 8 * j); } PutHalf(mergedGeometry.Vertices, num11 + 24, item2.Uvs[i * 2]); PutHalf(mergedGeometry.Vertices, num11 + 24 + 2, item2.Uvs[i * 2 + 1]); } int[] indices = item2.Indices; foreach (int num19 in indices) { mergedGeometry.Indices[num10++] = num9 + num19; } num9 += item2.VertexCount; } if (num > 0) { mergedGeometry.MinX = num3; mergedGeometry.MinY = num4; mergedGeometry.MinZ = num5; mergedGeometry.MaxX = num6; mergedGeometry.MaxY = num7; mergedGeometry.MaxZ = num8; } return mergedGeometry; } public static float[] Place(PlacedPiece piece, float[] local) { float qx = piece.Qx; float qy = piece.Qy; float qz = piece.Qz; float qw = piece.Qw; float num = (float)Math.Sqrt(qx * qx + qy * qy + qz * qz + qw * qw); if (num > 1E-12f) { qx /= num; qy /= num; qz /= num; qw /= num; } else { qx = (qy = (qz = 0f)); qw = 1f; } float num2 = qx * qx * 2f; float num3 = qy * qy * 2f; float num4 = qz * qz * 2f; float num5 = qx * qy * 2f; float num6 = qx * qz * 2f; float num7 = qy * qz * 2f; float num8 = qw * qx * 2f; float num9 = qw * qy * 2f; float num10 = qw * qz * 2f; float num11 = 1f - (num3 + num4); float num12 = num5 - num10; float num13 = num6 + num9; float num14 = num5 + num10; float num15 = 1f - (num2 + num4); float num16 = num7 - num8; float num17 = num6 - num9; float num18 = num7 + num8; float num19 = 1f - (num2 + num3); float[] array = new float[16]; for (int i = 0; i < 4; i++) { float num20 = local[i * 4]; float num21 = local[i * 4 + 1]; float num22 = local[i * 4 + 2]; float num23 = local[i * 4 + 3]; array[i * 4] = num11 * num20 + num12 * num21 + num13 * num22 + piece.X * num23; array[i * 4 + 1] = num14 * num20 + num15 * num21 + num16 * num22 + piece.Y * num23; array[i * 4 + 2] = num17 * num20 + num18 * num21 + num19 * num22 + piece.Z * num23; array[i * 4 + 3] = num23; } return array; } private static void PutFloat(byte[] b, int at, float v) { int num = BitConverter.SingleToInt32Bits(v); for (int i = 0; i < 4; i++) { b[at + i] = (byte)(num >> 8 * i); } } private static sbyte SNorm(float v) { return (sbyte)Math.Max(-127, Math.Min(127, (int)Math.Round(v * 127f))); } private static void PutTurned(byte[] b, int at, float[] m, float x, float y, float z, float w) { float num = m[0] * x + m[4] * y + m[8] * z; float num2 = m[1] * x + m[5] * y + m[9] * z; float num3 = m[2] * x + m[6] * y + m[10] * z; float num4 = (float)Math.Sqrt(num * num + num2 * num2 + num3 * num3); if (num4 > 1E-12f) { num /= num4; num2 /= num4; num3 /= num4; } b[at] = (byte)SNorm(num); b[at + 1] = (byte)SNorm(num2); b[at + 2] = (byte)SNorm(num3); b[at + 3] = (byte)SNorm(w); } private static void PutHalf(byte[] b, int at, float v) { ushort num = ToHalf(v); b[at] = (byte)num; b[at + 1] = (byte)(num >> 8); } private static ushort ToHalf(float v) { int num = BitConverter.SingleToInt32Bits(v); int num2 = (num >> 16) & 0x8000; int num3 = ((num >> 23) & 0xFF) - 127 + 15; int num4 = num & 0x7FFFFF; if (((num >> 23) & 0xFF) == 255 && num4 != 0) { return 0; } if (num3 >= 31) { return (ushort)(num2 | 0x7BFF); } if (num3 <= 0) { return (ushort)num2; } int num5 = (num3 << 10) | (num4 >> 13); int num6 = num4 & 0x1FFF; if (num6 > 4096 || (num6 == 4096 && (num5 & 1) != 0)) { num5++; } if (num5 > 31743) { num5 = 31743; } return (ushort)(num2 | num5); } } internal static class MorningMist { public static float For(int worldSeed, int day, string environment) { uint h = 2166136261u; h = Mix(h, worldSeed); h = Mix(h, day); if (environment != null) { foreach (char c in environment) { h ^= c; h *= 16777619; } } uint s = h; for (int j = 0; j < 4; j++) { Next(ref s); } if (!(Next(ref s) < 0.4f)) { return 0f; } return 1f; } private static uint Mix(uint h, int v) { for (int i = 0; i < 4; i++) { h ^= (uint)((v >> 8 * i) & 0xFF); h *= 16777619; } return h; } private static float Next(ref uint s) { s += 2654435769u; uint num = s; int num2 = (int)(num ^ (num >> 16)) * -2048144789; int num3 = (num2 ^ (num2 >>> 13)) * -1028477387; return (float)((uint)(num3 ^ (num3 >>> 16)) >> 8) / 16777216f; } } public sealed class PieceGeometry { public readonly float[] Positions; public readonly float[] Normals; public readonly float[] Tangents; public readonly float[] Uvs; public readonly uint[] Colors; public readonly int[] Indices; public readonly float Size; public int VertexCount => Positions.Length / 3; public PieceGeometry(float[] positions, float[] normals, float[] tangents, float[] uvs, uint[] colors, int[] indices) { Positions = positions; Normals = normals; Tangents = tangents; Uvs = uvs; Colors = colors; Indices = indices; int num = positions.Length / 3; if (positions.Length != num * 3 || normals.Length != num * 3 || tangents.Length != num * 4 || uvs.Length != num * 2 || (colors != null && colors.Length != num)) { throw new ArgumentException("the vertex arrays disagree in length"); } if (indices.Length % 3 != 0) { throw new ArgumentException("the index count is not a multiple of 3"); } foreach (int num2 in indices) { if (num2 < 0 || num2 >= num) { throw new ArgumentException("an index lies outside the vertices"); } } if (num == 0) { return; } float num3 = float.MaxValue; float num4 = float.MaxValue; float num5 = float.MaxValue; float num6 = float.MinValue; float num7 = float.MinValue; float num8 = float.MinValue; for (int j = 0; j < num; j++) { float num9 = positions[j * 3]; float num10 = positions[j * 3 + 1]; float num11 = positions[j * 3 + 2]; if (num9 < num3) { num3 = num9; } if (num9 > num6) { num6 = num9; } if (num10 < num4) { num4 = num10; } if (num10 > num7) { num7 = num10; } if (num11 < num5) { num5 = num11; } if (num11 > num8) { num8 = num11; } } Size = Math.Max(num6 - num3, Math.Max(num7 - num4, num8 - num5)); } } internal interface IFogSwitch { bool FogEnabled { get; set; } object Key { get; } } internal sealed class PostFogKeeper { private IFogSwitch _current; private bool _was; private bool _engaged; public bool Off { get { if (_engaged && _current != null) { return !_current.FogEnabled; } return false; } } public void Engage(IFogSwitch profile) { if (profile != _current) { bool flag = _engaged && _current != null && PostFogClaims.Found(_current.Key, _was); Release(); _current = profile; if (profile == null) { return; } _was = PostFogClaims.Claim(profile.Key, profile.FogEnabled || flag); _engaged = true; } if (_current != null && _current.FogEnabled) { _current.FogEnabled = false; } } public void Release() { if (_engaged && _current != null && PostFogClaims.Release(_current.Key, _was, out var found)) { _current.FogEnabled = found; } _engaged = false; _current = null; } } internal static class PostFogClaims { private const string Slot = "ValheimPostFog.Claims.v1"; private static Hashtable Table { get { if (AppDomain.CurrentDomain.GetData("ValheimPostFog.Claims.v1") is Hashtable result) { return result; } Hashtable hashtable = new Hashtable(); AppDomain.CurrentDomain.SetData("ValheimPostFog.Claims.v1", hashtable); return hashtable; } } private static object[] Entry(Hashtable table, object key, out int holders, out bool found) { holders = 0; found = false; if (!(table[key] is object[] array) || array.Length != 2 || !(array[0] is int num) || !(array[1] is bool flag)) { return null; } holders = num; found = flag; return array; } public static bool Claim(object key, bool found) { Hashtable table = Table; int holders; bool found2; object[] array = Entry(table, key, out holders, out found2); if (array != null) { array[0] = holders + 1; return found2; } table[key] = new object[2] { 1, found }; return found; } public static bool Found(object key, bool fallback) { if (Entry(Table, key, out var _, out var found) == null) { return fallback; } return found; } public static bool Release(object key, bool fallback, out bool found) { Hashtable table = Table; int holders; object[] array = Entry(table, key, out holders, out found); if (array == null) { found = fallback; return true; } if (holders > 1) { array[0] = holders - 1; return false; } table.Remove(key); return true; } } public enum Quality { Low, Medium, High, Custom } public readonly struct QualityValues : IEquatable<QualityValues> { public readonly float TerrainDistance; public readonly float ForestDistance; public readonly float TerrainDetail; public readonly float TreeModelDistance; public readonly int ImpostorResolution; public readonly int NearDetail; public readonly bool SmoothImpostors; public readonly float BuildingDistance; public QualityValues(float terrainDistance, float forestDistance, float terrainDetail, int impostorResolution, bool smoothImpostors, float treeModelDistance, int nearDetail = 4, float buildingDistance = 2500f) { TerrainDistance = terrainDistance; ForestDistance = forestDistance; TerrainDetail = terrainDetail; ImpostorResolution = impostorResolution; SmoothImpostors = smoothImpostors; TreeModelDistance = treeModelDistance; NearDetail = nearDetail; BuildingDistance = buildingDistance; } public QualityValues WithModels(float treeModelDistance) { return new QualityValues(TerrainDistance, ForestDistance, TerrainDetail, ImpostorResolution, SmoothImpostors, treeModelDistance, NearDetail, BuildingDistance); } public QualityValues WithNear(int nearDetail) { return new QualityValues(TerrainDistance, ForestDistance, TerrainDetail, ImpostorResolution, SmoothImpostors, TreeModelDistance, nearDetail, BuildingDistance); } public QualityValues WithBuildings(float buildingDistance) { return new QualityValues(TerrainDistance, ForestDistance, TerrainDetail, ImpostorResolution, SmoothImpostors, TreeModelDistance, NearDetail, buildingDistance); } public bool Equals(QualityValues o) { if (Math.Abs(TerrainDistance - o.TerrainDistance) < 0.5f && Math.Abs(ForestDistance - o.ForestDistance) < 0.5f && Math.Abs(TerrainDetail - o.TerrainDetail) < 0.005f && ImpostorResolution == o.ImpostorResolution && SmoothImpostors == o.SmoothImpostors && Math.Abs(TreeModelDistance - o.TreeModelDistance) < 0.5f && NearDetail == o.NearDetail) { return Math.Abs(BuildingDistance - o.BuildingDistance) < 0.5f; } return false; } public override bool Equals(object o) { if (o is QualityValues o2) { return Equals(o2); } return false; } public override int GetHashCode() { return ImpostorResolution; } } public static class QualityPresets { public const float LowModels = 800f; public const float MediumModels = 1200f; public const float HighModels = 1200f; public const int LowNear = 8; public const int MediumNear = 4; public const int HighNear = 4; public const float LowBuildings = 1500f; public const float MediumBuildings = 2500f; public const float HighBuildings = 3500f; private static readonly (Quality Quality, QualityValues Values)[] Retired = new(Quality, QualityValues)[1] { (Quality.High, new QualityValues(6000f, 3500f, 2f, 128, smoothImpostors: true, 1500f, 2, 3500f)) }; public static QualityValues Get(Quality q) { return q switch { Quality.Low => new QualityValues(2500f, 1500f, 1f, 64, smoothImpostors: false, 800f, 8, 1500f), Quality.High => new QualityValues(6000f, 3500f, 2f, 128, smoothImpostors: true, 1200f, 4, 3500f), _ => new QualityValues(4000f, 2500f, 1.4f, 128, smoothImpostors: true, 1200f), }; } public static Quality Match(QualityValues s) { Quality[] array = new Quality[3] { Quality.Low, Quality.Medium, Quality.High }; foreach (Quality quality in array) { if (Get(quality).Equals(s)) { return quality; } } return Quality.Custom; } public static float ModelsForOldFile(Quality saved, float terrainDistance, float forestDistance, float terrainDetail, int impostorResolution, bool smoothImpostors) { return ForOldFile(saved, (QualityValues p) => new QualityValues(terrainDistance, forestDistance, terrainDetail, impostorResolution, smoothImpostors, p.TreeModelDistance, p.NearDetail, p.BuildingDistance)).TreeModelDistance; } public static QualityValues ForOldFile(Quality saved, Func<QualityValues, QualityValues> withMissingFrom) { Quality[] array = new Quality[3] { Quality.Low, Quality.Medium, Quality.High }; for (int i = 0; i < array.Length; i++) { QualityValues qualityValues = Get(array[i]); if (qualityValues.Equals(withMissingFrom(qualityValues))) { return qualityValues; } } return Get(saved); } public static Quality OnLoad(Quality saved, QualityValues current, out bool apply) { Quality quality = Match(current); if (quality == Quality.Custom && saved != Quality.Custom) { (Quality, QualityValues)[] retired = Retired; for (int i = 0; i < retired.Length; i++) { (Quality, QualityValues) tuple = retired[i]; if (tuple.Item2.Equals(current)) { apply = true; return saved; } } } apply = saved != Quality.Custom && quality != Quality.Custom && quality != saved; if (!apply) { return quality; } return saved; } } public struct SeenTree { public TreeInstance Tree; public float Ground; } public static class SeenLand { public static List<TreeInstance> OnFarLand(IVegetationWorld world, int zoneX, int zoneZ, List<SeenTree> seen, EditMap edits) { List<TreeInstance> list = new List<TreeInstance>(seen.Count); ZoneLand zoneLand = null; foreach (SeenTree item in seen) { TreeInstance tree = item.Tree; if (!float.IsNaN(item.Ground)) { tree.Y = tree.Y - item.Ground + (zoneLand ?? (zoneLand = new ZoneLand(world, zoneX, zoneZ))).Height(tree.X, tree.Z) + (edits?.Delta(tree.X, tree.Z) ?? 0f) + FarLandsLand.Delta(tree.X, tree.Z, 0f); } list.Add(tree); } return list; } public static Dictionary<(int X, int Z), List<TreeInstance>> OnFarLand(IVegetationWorld world, Dictionary<(int X, int Z), List<SeenTree>> zones, EditMap edits) { Dictionary<(int, int), List<TreeInstance>> dictionary = new Dictionary<(int, int), List<TreeInstance>>(zones.Count); foreach (KeyValuePair<(int, int), List<SeenTree>> zone in zones) { dictionary[zone.Key] = OnFarLand(world, zone.Key.Item1, zone.Key.Item2, zone.Value, edits); } return dictionary; } } public sealed class SettledZones { private readonly float _seconds; private readonly Dictionary<(int X, int Z), float> _since = new Dictionary<(int, int), float>(); private readonly List<(int X, int Z)> _gone = new List<(int, int)>(); public int Count => _since.Count; public SettledZones(float seconds) { _seconds = seconds; } public void Update(HashSet<(int X, int Z)> present, float now) { _gone.Clear(); foreach (var key in _since.Keys) { if (!present.Contains(key)) { _gone.Add(key); } } foreach (var item in _gone) { _since.Remove(item); } foreach (var item2 in present) { if (!_since.ContainsKey(item2)) { _since[item2] = now; } } } public bool Settled((int X, int Z) zone, float now) { if (_since.TryGetValue(zone, out var value)) { return now - value >= _seconds; } return false; } public void Clear() { _since.Clear(); } } public static class ShadowMath { public const float FirstStep = 1.5f; public const float Growth = 1.08f; public const float MaxDistance = 4000f; public const float SunSpread = 0.0093f; public const float MinSoftness = 2f; public const float Bias = 1f; public const float BiasPerMetre = 0.001f; public static float ShadowHeight(HeightField field, float x, float z, float sunX, float sunY, float sunZ, out float occluder) { occluder = 0f; float num = (float)Math.Sqrt(sunX * sunX + sunZ * sunZ); if (num < 0.0001f) { return -10000f; } float num2 = sunY / num; float num3 = sunX / num; float num4 = sunZ / num; float num5 = -10000f; float originX = field.OriginX; float num6 = field.OriginX + (float)field.Size * field.Texel; float originZ = field.OriginZ; float num7 = field.OriginZ + (float)field.Size * field.Texel; for (float num8 = 1.5f * field.Texel; num8 <= 4000f; num8 = num8 * 1.08f + 0.5f * field.Texel) { float num9 = x + num3 * num8; float num10 = z + num4 * num8; if (num9 < originX || num9 > num6 || num10 < originZ || num10 > num7) { break; } float num11 = field.Sample(num9, num10); if (!(num11 <= -9999f)) { float num12 = num11 - num8 * num2; if (num12 > num5) { num5 = num12; occluder = num8; } } } return num5; } public static float Lit(float y, float shadow, float occluder, float eyeDistance) { float num = Math.Max(2f, occluder * 0.0093f); float val = (y - shadow + 1f + 0.001f * eyeDistance + num) / num; val = Math.Max(0f, Math.Min(1f, val)); return val * val * (3f - 2f * val); } } public static class StableHash { public static int Of(string s) { int num = 5381; int num2 = num; for (int i = 0; i < s.Length && s[i] != 0; i += 2) { num = ((num << 5) + num) ^ s[i]; if (i == s.Length - 1 || s[i + 1] == '\0') { break; } num2 = ((num2 << 5) + num2) ^ s[i + 1]; } return num + num2 * 1566083941; } } public static class TerrainCompData { private const int Vertices = 4225; private const int OldPaintWidth = 64; public static ZoneEdit Parse(int zoneX, int zoneZ, byte[] compressed) { try { using GZipStream gZipStream = new GZipStream(new MemoryStream(compressed, writable: false), CompressionMode.Decompress); using BinaryReader binaryReader = new BinaryReader(gZipStream); binaryReader.ReadInt32(); int operations = binaryReader.ReadInt32(); binaryReader.ReadSingle(); binaryReader.ReadSingle(); binaryReader.ReadSingle(); binaryReader.ReadSingle(); int num = binaryReader.ReadInt32(); if (num != 4225) { throw new InvalidDataException($"terrain data holds {num} heights, expected {4225}"); } float[] array = new float[4225]; for (int i = 0; i < num; i++) { if (binaryReader.ReadBoolean()) { float num2 = binaryReader.ReadSingle(); float num3 = binaryReader.ReadSingle(); float num4 = num2 + num3; array[i] = (float.IsNaN(num4) ? 0f : Math.Max(-8f, Math.Min(8f, num4))); } } int num5 = binaryReader.ReadInt32(); if (num5 < 0 || num5 > 4225) { throw new InvalidDataException($"terrain data holds {num5} paint entries"); } uint[] array2 = new uint[num5]; bool[] array3 = new bool[num5]; for (int j = 0; j < num5; j++) { if (binaryReader.ReadBoolean()) { array3[j] = true; uint num6 = Byte(binaryReader.ReadSingle()); uint num7 = Byte(binaryReader.ReadSingle()); uint num8 = Byte(binaryReader.ReadSingle()); uint num9 = Byte(binaryReader.ReadSingle()); array2[j] = num6 | (num7 << 8) | (num8 << 16) | (num9 << 24); } } gZipStream.CopyTo(Stream.Null); switch (num5) { case 4096: Respread(ref array2, ref array3); break; default: Array.Resize(ref array2, 4225); Array.Resize(ref array3, 4225); break; case 4225: break; } return new ZoneEdit(zoneX, zoneZ, operations, array, array2, array3); } catch (InvalidDataException) { throw; } catch (Exception ex2) when (ex2 is IOException || ex2 is ArgumentException || ex2 is OverflowException) { throw new InvalidDataException("terrain data is damaged: " + ex2.Message, ex2); } } public static ZoneEdit Pick(IEnumerable<ZoneEdit> compilers) { ZoneEdit zoneEdit = null; foreach (ZoneEdit compiler in compilers) { if (zoneEdit == null || compiler.Operations > zoneEdit.Operations) { zoneEdit = compiler; } } return zoneEdit; } private static uint Byte(float c) { if (!float.IsNaN(c)) { return (uint)Math.Max(0, Math.Min(255, (int)Math.Round(Math.Max(-1f, Math.Min(2f, c)) * 255f))); } return 0u; } private static void Respread(ref uint[] paint, ref bool[] painted) { uint[] array = new uint[4225]; bool[] array2 = new bool[4225]; int num = 65; for (int i = 0; i < 4225; i++) { int num2 = i / num; int num3 = (i + 1) / num; int num4 = i - num2; if (num2 == 64) { num4 -= 64; } if (i > 0 && (i - num2) % 64 == 0 && (i + 1 - num3) % 64 == 0) { num4--; } if (num4 >= 0 && num4 < paint.Length) { array[i] = paint[num4]; array2[i] = painted[num4]; } } paint = array; painted = array2; } } public sealed class TerrainTile { public readonly TileKey Key; public readonly float[] Heights; public readonly float[] Normals; public readonly uint[] Colors; public readonly uint[] Mask; public readonly uint Edits; public int ColourEpoch { get; private set; } public TerrainTile(TileKey key, float[] heights, float[] normals, uint[] colors, uint[] mask, uint edits = 0u) { int num = 4225; if (heights == null || heights.Length != num || normals?.Length != num * 3 || colors == null || colors.Length != num || mask == null || mask.Length != num) { throw new ArgumentException($"tile {key}: arrays do not match a {65}x{65} grid"); } Key = key; Heights = heights; Normals = normals; Colors = colors; Mask = mask; Edits = edits; } public TerrainTile WithColours(uint[] colors, int epoch) { return new TerrainTile(Key, Heights, Normals, colors, Mask, Edits) { ColourEpoch = epoch }; } } public sealed class TileBuilder<T> : IDisposable where T : class { private readonly Func<TileStore<T>> _openStore; private TileStore<T> _store; private readonly Func<TileKey, T> _generate; private readonly Func<T, bool> _fresh; private readonly Func<T, T> _deliver; private Dictionary<TileKey, T> _updated; private bool _readOnly; private readonly object _lock = new object(); private List<TileKey> _pending = new List<TileKey>(); private readonly ConcurrentQueue<T> _ready = new ConcurrentQueue<T>(); private readonly ConcurrentQueue<(TileKey Key, Func<T, T> Change)> _updates = new ConcurrentQueue<(TileKey, Func<T, T>)>(); private readonly ConcurrentQueue<T> _redeliveries = new ConcurrentQueue<T>(); private readonly Thread _thread; private volatile bool _stop; private volatile Exception _failure; private volatile bool _cacheReady; private volatile bool _cacheMissing; private volatile string _cacheNote; private volatile bool _cacheNoteWarns; private int _cacheNews; private int _cacheNewsTaken; public int Generated; public int Loaded; public const int OpenRetryMs = 3000; public Exception Failure => _failure; public bool CacheReady => _cacheReady; public bool CacheMissing => _cacheMissing; public string CacheNote => _cacheNote; public bool CacheNoteWarns => _cacheNoteWarns; public TileBuilder(Func<TileStore<T>> openStore, Func<TileKey, T> generate, Func<T, bool> fresh = null, bool generateMakesUpdates = false, Func<T, T> deliver = null) { _openStore = openStore; _generate = generate; _fresh = fresh; _deliver = deliver; if (!generateMakesUpdates) { _updated = new Dictionary<TileKey, T>(); } _thread = new Thread(Run) { IsBackground = true, Priority = ThreadPriority.BelowNormal, Name = "FarLands builder" }; _thread.Start(); } public void Want(IEnumerable<TileKey> keys, float cx, float cz) { List<TileKey> pending = keys.OrderBy((TileKey k) => TileGrid.Distance(k, cx, cz)).ToList(); lock (_lock) { _pending = pending; Monitor.PulseAll(_lock); } } public void Update(TileKey key, Func<T, T> change) { _updates.Enqueue((key, change)); lock (_lock) { Monitor.PulseAll(_lock); } } public void Redeliver(T value) { _redeliveries.Enqueue(value); lock (_lock) { Monitor.PulseAll(_lock); } } public bool TryTake(out T value) { return _ready.TryDequeue(out value); } private void Deliver(T value) { _ready.Enqueue((_deliver != null) ? _deliver(value) : value); } public bool TakeCacheNews(out string note, out bool missing, out bool warns) { note = _cacheNote; missing = _cacheMissing; warns = _cacheNoteWarns | missing; int num = Volatile.Read(in _cacheNews); if (num == _cacheNewsTaken) { return false; } _cacheNewsTaken = num; return true; } private void Run() { try { OpenStore(); while (!_stop) { if (_