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 SeparateSpawns v0.1.1
SeparateSpawns.dll
Decompiled a week 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.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using MushroomMods; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("SeparateSpawns")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+0f34766723a6669ef7b79dbcc093f8b56065e786")] [assembly: AssemblyProduct("SeparateSpawns")] [assembly: AssemblyTitle("SeparateSpawns")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [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 MushroomMods { internal static class PatchIsolation { internal static int PatchAllIsolated(Harmony harmony, Assembly assembly, ManualLogSource log) { int num = 0; Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(assembly); foreach (Type type in typesFromAssembly) { try { harmony.CreateClassProcessor(type).Patch(); } catch (Exception ex) { num++; log.LogError((object)("Harmony patch class " + type.FullName + " was skipped and the rest of " + harmony.Id + " is still active. " + ex)); } } return num; } } } namespace SeparateSpawns { internal sealed class BiomeMapBuilder { private sealed class UnionFind { private readonly int[] _parent; public UnionFind(int size) { _parent = new int[size]; for (int i = 0; i < size; i++) { _parent[i] = i; } } public int Find(int value) { if (_parent[value] != value) { _parent[value] = Find(_parent[value]); } return _parent[value]; } public void Union(int a, int b) { _parent[Find(a)] = Find(b); } } private readonly float _biomeStep; private readonly float _gridStep; private readonly float _innerRadius; private readonly int _width; private readonly int _height; private readonly Vector2 _origin; private readonly Biome[] _biomes; private readonly bool[] _land; private readonly int[] _biomePatchIds; private readonly int[] _islandIds; private readonly Dictionary<int, BiomePatchInfo> _patchesById = new Dictionary<int, BiomePatchInfo>(); public IReadOnlyDictionary<int, HashSet<int>> MeadowsPatchNeighbors { get; private set; } public IReadOnlyDictionary<int, HashSet<int>> ForestPatchNeighbors { get; private set; } public IReadOnlyCollection<int> MeadowsPatchesTouchingCoast { get; private set; } = (IReadOnlyCollection<int>)(object)Array.Empty<int>(); public IReadOnlyList<BiomePatchInfo> PatchStatistics { get; private set; } = Array.Empty<BiomePatchInfo>(); private BiomeMapBuilder(float biomeStep, float gridStep, float innerRadius, int width, int height, Vector2 origin, Biome[] biomes, bool[] land, int[] biomePatchIds, int[] islandIds, Dictionary<int, HashSet<int>> meadowsNeighbors, Dictionary<int, HashSet<int>> forestNeighbors, HashSet<int> meadowsTouchingCoast) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) _biomeStep = biomeStep; _gridStep = gridStep; _innerRadius = innerRadius; _width = width; _height = height; _origin = origin; _biomes = biomes; _land = land; _biomePatchIds = biomePatchIds; _islandIds = islandIds; MeadowsPatchNeighbors = meadowsNeighbors; ForestPatchNeighbors = forestNeighbors; MeadowsPatchesTouchingCoast = meadowsTouchingCoast; } public static BiomeMapBuilder Build(float biomeStep, float gridStep, float innerRadius, float biomeSplitGapDistance, float islandSplitGapDistance, float minPatchArea) { //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0056: 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_0076: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected I4, but got Unknown WorldGenerator instance = WorldGenerator.instance; int num = Mathf.FloorToInt(innerRadius * 2f / biomeStep) + 1; int num2 = num; Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(0f - innerRadius, 0f - innerRadius); Biome[] array = (Biome[])(object)new Biome[num * num2]; bool[] array2 = new bool[num * num2]; for (int i = 0; i < num2; i++) { for (int j = 0; j < num; j++) { float num3 = val.x + (float)j * biomeStep; float num4 = val.y + (float)i * biomeStep; int num5 = i * num + j; Vector2 val2 = new Vector2(num3, num4); if (((Vector2)(ref val2)).magnitude > innerRadius) { array[num5] = (Biome)0; array2[num5] = false; } else { array[num5] = (Biome)(int)instance.GetBiome(num3, num4, 0.02f, false); float height = instance.GetHeight(num3, num4); array2[num5] = (int)array[num5] != 256 && height > 30f; } } } int[] array3 = FloodFillLandPatches(array, array2, num, num2); MergeLandPatchesAcrossNarrowGaps(array3, array, array2, num, num2, biomeStep, biomeSplitGapDistance); AbsorbSmallPatches(array3, array, array2, num, num2, biomeStep, minPatchArea); int[] islandIds = FloodFillIslands(array2, num, num2); MergeIslandsAcrossNarrowGaps(islandIds, array2, num, num2, biomeStep, islandSplitGapDistance); BuildAdjacency(array3, array, array2, num, num2, out var meadowsNeighbors, out var forestNeighbors); HashSet<int> meadowsTouchingCoast = FindMeadowsTouchingCoast(array3, array, array2, num, num2); BiomeMapBuilder biomeMapBuilder = new BiomeMapBuilder(biomeStep, gridStep, innerRadius, num, num2, val, array, array2, array3, islandIds, meadowsNeighbors, forestNeighbors, meadowsTouchingCoast); biomeMapBuilder.BuildPatchStatistics(); return biomeMapBuilder; } public void CountBurialChambers(LocationCatalog locations) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) foreach (Vector3 burialChamber in locations.BurialChambers) { if (TryGetCell(burialChamber, out var _, out var _, out var biomePatchId, out var islandId, out var _) && biomePatchId >= 0 && _patchesById.TryGetValue(biomePatchId, out var value)) { islandId = value.BurialChamberCount++; } } } public string GetPatchName(int patchId) { if (!_patchesById.TryGetValue(patchId, out var value)) { return $"patch_{patchId}"; } return value.Name; } public static void AppendPatchStatistics(StringBuilder summary, IReadOnlyList<BiomePatchInfo> patches) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) summary.AppendLine("Biome patch statistics:"); summary.AppendLine(" (land patches of the same biome; patches under MinPatchArea absorbed into their largest neighbor; merged across water gaps up to BiomeSplitGapDistance; height <= 30 treated as water)"); foreach (IGrouping<Biome, BiomePatchInfo> item in from p in patches group p by p.Biome into g orderby ((object)g.Key/*cast due to .constrained prefix*/).ToString() select g) { summary.AppendLine($" {item.Key}: {item.Count()} patches"); } summary.AppendLine(" name | biome | land_area_m2 | burial_chambers | center_x | center_z"); foreach (BiomePatchInfo item2 in patches.OrderBy<BiomePatchInfo, string>((BiomePatchInfo p) => p.Name, StringComparer.OrdinalIgnoreCase)) { summary.AppendLine($" {item2.Name} | {item2.Biome} | {item2.ApproximateAreaSquareMeters:F0} | {item2.BurialChamberCount} | {item2.Center.x:F0} | {item2.Center.y:F0}"); } summary.AppendLine(); } public void LogPatchStatistics() { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) foreach (BiomePatchInfo patchStatistic in PatchStatistics) { ModLog.Info($"Biome {patchStatistic.Name}: ~{patchStatistic.ApproximateAreaSquareMeters:F0}m², burial_chambers={patchStatistic.BurialChamberCount}, center=({patchStatistic.Center.x:F0}, {patchStatistic.Center.y:F0})"); } } private void BuildPatchStatistics() { //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) Dictionary<int, (Biome, int, float, float)> dictionary = new Dictionary<int, (Biome, int, float, float)>(); for (int i = 0; i < _height; i++) { for (int j = 0; j < _width; j++) { int num = i * _width + j; int num2 = _biomePatchIds[num]; if (num2 >= 0 && IsBiomePatchCell(_biomes[num]) && _land[num]) { float num3 = _origin.x + (float)j * _biomeStep; float num4 = _origin.y + (float)i * _biomeStep; if (!dictionary.TryGetValue(num2, out var value)) { value = (_biomes[num], 0, 0f, 0f); } value.Item2++; value.Item3 += num3; value.Item4 += num4; dictionary[num2] = value; } } } Dictionary<Biome, int> dictionary2 = new Dictionary<Biome, int>(); List<BiomePatchInfo> list = new List<BiomePatchInfo>(); float num5 = _biomeStep * _biomeStep; foreach (KeyValuePair<int, (Biome, int, float, float)> item in dictionary.OrderBy<KeyValuePair<int, (Biome, int, float, float)>, int>((KeyValuePair<int, (Biome biome, int landCellCount, float sumX, float sumZ)> entry) => entry.Key)) { int key = item.Key; (Biome, int, float, float) value2 = item.Value; if (value2.Item2 > 0) { if (!dictionary2.TryGetValue(value2.Item1, out var value3)) { value3 = 0; } value3++; dictionary2[value2.Item1] = value3; BiomePatchInfo biomePatchInfo = new BiomePatchInfo { PatchId = key, Name = FormatPatchName(value2.Item1, value3), Biome = value2.Item1, CellCount = value2.Item2, ApproximateAreaSquareMeters = (float)value2.Item2 * num5, Center = new Vector2(value2.Item3 / (float)value2.Item2, value2.Item4 / (float)value2.Item2) }; list.Add(biomePatchInfo); _patchesById[key] = biomePatchInfo; } } PatchStatistics = list; } private static bool IsBiomePatchCell(Biome biome) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Invalid comparison between Unknown and I4 if ((int)biome != 0) { return (int)biome != 256; } return false; } private unsafe static string FormatPatchName(Biome biome, int index) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Invalid comparison between Unknown and I4 //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected I4, but got Unknown //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Invalid comparison between Unknown and I4 //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Invalid comparison between Unknown and I4 //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Invalid comparison between Unknown and I4 //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Invalid comparison between Unknown and I4 //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Invalid comparison between Unknown and I4 if ((int)biome <= 16) { switch (biome - 1) { default: if ((int)biome != 8) { if ((int)biome != 16) { break; } return $"plains_{index}"; } return $"blackforest_{index}"; case 0: return $"meadows_{index}"; case 1: return $"swamp_{index}"; case 3: return $"mountain_{index}"; case 2: break; } } else if ((int)biome <= 64) { if ((int)biome == 32) { return $"ashlands_{index}"; } if ((int)biome == 64) { return $"deepnorth_{index}"; } } else { if ((int)biome == 256) { return $"ocean_{index}"; } if ((int)biome == 512) { return $"mistlands_{index}"; } } return $"{((object)(*(Biome*)(&biome))/*cast due to .constrained prefix*/).ToString().ToLowerInvariant()}_{index}"; } public bool TryGetCell(Vector3 worldPosition, out int index, out Biome biome, out int biomePatchId, out int islandId, out bool isLand) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) index = -1; biome = (Biome)0; biomePatchId = -1; islandId = -1; isLand = false; int num = Mathf.RoundToInt((worldPosition.x - _origin.x) / _biomeStep); int num2 = Mathf.RoundToInt((worldPosition.z - _origin.y) / _biomeStep); if (num < 0 || num2 < 0 || num >= _width || num2 >= _height) { return false; } index = num2 * _width + num; biome = _biomes[index]; biomePatchId = _biomePatchIds[index]; islandId = _islandIds[index]; isLand = _land[index]; return true; } public bool HasAdjacentBlackForest(int meadowsPatchId) { if (MeadowsPatchNeighbors.TryGetValue(meadowsPatchId, out var value)) { return value.Count > 0; } return false; } public bool HasBiomeWithin(Vector3 worldPosition, float radiusMeters, Biome targetBiome) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Invalid comparison between I4 and Unknown //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) if (radiusMeters <= 0f || (int)targetBiome == 0 || (int)targetBiome == 256) { return false; } int num = Mathf.RoundToInt((worldPosition.x - _origin.x) / _biomeStep); int num2 = Mathf.RoundToInt((worldPosition.z - _origin.y) / _biomeStep); int num3 = Mathf.CeilToInt(radiusMeters / _biomeStep); float num4 = radiusMeters * radiusMeters; for (int i = -num3; i <= num3; i++) { for (int j = -num3; j <= num3; j++) { int num5 = num + j; int num6 = num2 + i; if (num5 < 0 || num6 < 0 || num5 >= _width || num6 >= _height) { continue; } int num7 = num6 * _width + num5; if (_land[num7] && (int)_biomes[num7] == (int)targetBiome) { float num8 = _origin.x + (float)num5 * _biomeStep; float num9 = _origin.y + (float)num6 * _biomeStep; float num10 = num8 - worldPosition.x; float num11 = num9 - worldPosition.z; if (num10 * num10 + num11 * num11 <= num4) { return true; } } } } return false; } public bool HasBlackForestWithin(Vector3 worldPosition, float radiusMeters) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return HasBiomeWithin(worldPosition, radiusMeters, (Biome)8); } public int FindNearestBlackForestPatchId(Vector3 worldPosition, float radiusMeters) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) if (radiusMeters <= 0f) { return -1; } int num = Mathf.RoundToInt((worldPosition.x - _origin.x) / _biomeStep); int num2 = Mathf.RoundToInt((worldPosition.z - _origin.y) / _biomeStep); int num3 = Mathf.CeilToInt(radiusMeters / _biomeStep); float num4 = radiusMeters * radiusMeters; int result = -1; float num5 = float.MaxValue; for (int i = -num3; i <= num3; i++) { for (int j = -num3; j <= num3; j++) { int num6 = num + j; int num7 = num2 + i; if (num6 < 0 || num7 < 0 || num6 >= _width || num7 >= _height) { continue; } int num8 = num7 * _width + num6; if (_land[num8] && (int)_biomes[num8] == 8 && _biomePatchIds[num8] >= 0) { float num9 = _origin.x + (float)num6 * _biomeStep; float num10 = _origin.y + (float)num7 * _biomeStep; float num11 = num9 - worldPosition.x; float num12 = num10 - worldPosition.z; float num13 = num11 * num11 + num12 * num12; if (num13 <= num4 && num13 < num5) { num5 = num13; result = _biomePatchIds[num8]; } } } } return result; } public bool HasAdjacentCoast(int meadowsPatchId) { return MeadowsPatchesTouchingCoast.Contains(meadowsPatchId); } public float GetPatchAreaSquareMeters(int patchId) { if (!_patchesById.TryGetValue(patchId, out var value)) { return 0f; } return value.ApproximateAreaSquareMeters; } public bool IsOnCandidateGrid(Vector3 position) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Round((position.x - _origin.x) / _gridStep) * _gridStep + _origin.x; float num2 = Mathf.Round((position.z - _origin.y) / _gridStep) * _gridStep + _origin.y; if (Mathf.Abs(position.x - num) < 0.01f) { return Mathf.Abs(position.z - num2) < 0.01f; } return false; } public IEnumerable<Vector3> EnumerateGridPoints() { int width = Mathf.FloorToInt(_innerRadius * 2f / _gridStep) + 1; int height = width; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { float num = _origin.x + (float)x * _gridStep; float num2 = _origin.y + (float)y * _gridStep; Vector2 val = new Vector2(num, num2); if (!(((Vector2)(ref val)).magnitude > _innerRadius)) { yield return new Vector3(num, 0f, num2); } } } } public int GetIslandId(Vector3 position) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) if (!TryGetCell(position, out var _, out var _, out var _, out var islandId, out var _)) { return -1; } return islandId; } public int GetMeadowsPatchId(Vector3 position) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 if (!TryGetCell(position, out var _, out var biome, out var biomePatchId, out var _, out var _)) { return -1; } if ((int)biome != 1) { return -1; } return biomePatchId; } private static int[] FloodFillLandPatches(Biome[] biomes, bool[] land, int width, int height) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) int[] array = new int[width * height]; for (int i = 0; i < array.Length; i++) { array[i] = -1; } int num = 0; Queue<int> queue = new Queue<int>(); for (int j = 0; j < height; j++) { for (int k = 0; k < width; k++) { int num2 = j * width + k; if (array[num2] == -1 && land[num2] && IsBiomePatchCell(biomes[num2])) { Biome biome = biomes[num2]; array[num2] = num; queue.Enqueue(num2); while (queue.Count > 0) { int num3 = queue.Dequeue(); int num4 = num3 % width; int num5 = num3 / width; TryEnqueueLandPatchNeighbor(num4 - 1, num5, biome, biomes, land, array, width, height, num, queue); TryEnqueueLandPatchNeighbor(num4 + 1, num5, biome, biomes, land, array, width, height, num, queue); TryEnqueueLandPatchNeighbor(num4, num5 - 1, biome, biomes, land, array, width, height, num, queue); TryEnqueueLandPatchNeighbor(num4, num5 + 1, biome, biomes, land, array, width, height, num, queue); } num++; } } } return array; } private static void TryEnqueueLandPatchNeighbor(int x, int y, Biome biome, Biome[] biomes, bool[] land, int[] patchIds, int width, int height, int patchId, Queue<int> queue) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between I4 and Unknown if (x >= 0 && y >= 0 && x < width && y < height) { int num = y * width + x; if (land[num] && patchIds[num] == -1 && (int)biomes[num] == (int)biome) { patchIds[num] = patchId; queue.Enqueue(num); } } } private static void AbsorbSmallPatches(int[] patchIds, Biome[] biomes, bool[] land, int width, int height, float biomeStep, float minAreaSquareMeters) { //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Expected I4, but got Unknown if (minAreaSquareMeters <= 0f) { return; } float num = biomeStep * biomeStep; int minCells = Mathf.CeilToInt(minAreaSquareMeters / num); Dictionary<int, List<int>> dictionary = new Dictionary<int, List<int>>(); for (int i = 0; i < patchIds.Length; i++) { if (land[i] && patchIds[i] >= 0) { if (!dictionary.TryGetValue(patchIds[i], out var value)) { value = new List<int>(); dictionary[patchIds[i]] = value; } value.Add(i); } } foreach (int item in (from pair in dictionary where pair.Value.Count < minCells orderby pair.Value.Count select pair.Key).ToList()) { List<int> list = dictionary[item]; if (list.Count == 0 || list.Count >= minCells) { continue; } Dictionary<int, int> dictionary2 = new Dictionary<int, int>(); foreach (int item2 in list) { int num2 = item2 % width; int num3 = item2 / width; CountBorderNeighbor(num2 - 1, num3, item, patchIds, land, width, height, dictionary2); CountBorderNeighbor(num2 + 1, num3, item, patchIds, land, width, height, dictionary2); CountBorderNeighbor(num2, num3 - 1, item, patchIds, land, width, height, dictionary2); CountBorderNeighbor(num2, num3 + 1, item, patchIds, land, width, height, dictionary2); } if (dictionary2.Count == 0) { continue; } int num4 = -1; int num5 = -1; foreach (KeyValuePair<int, int> item3 in dictionary2) { if (item3.Value > num5) { num5 = item3.Value; num4 = item3.Key; } } Biome val = biomes[dictionary[num4][0]]; foreach (int item4 in list) { patchIds[item4] = num4; biomes[item4] = (Biome)(int)val; } dictionary[num4].AddRange(list); list.Clear(); } } private static void CountBorderNeighbor(int x, int y, int patchId, int[] patchIds, bool[] land, int width, int height, Dictionary<int, int> borderCounts) { if (x >= 0 && y >= 0 && x < width && y < height) { int num = y * width + x; if (land[num] && patchIds[num] >= 0 && patchIds[num] != patchId) { borderCounts.TryGetValue(patchIds[num], out var value); borderCounts[patchIds[num]] = value + 1; } } } private static void MergeLandPatchesAcrossNarrowGaps(int[] patchIds, Biome[] biomes, bool[] land, int width, int height, float biomeStep, float maxGapMeters) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) if (maxGapMeters <= 0f) { return; } Dictionary<int, Biome> dictionary = new Dictionary<int, Biome>(); int num = -1; for (int i = 0; i < patchIds.Length; i++) { if (patchIds[i] >= 0 && land[i]) { dictionary[patchIds[i]] = biomes[i]; num = Math.Max(num, patchIds[i]); } } if (num < 0) { return; } UnionFind unionFind = new UnionFind(num + 1); foreach (int key2 in dictionary.Keys) { foreach (int item in FindSameBiomePatchesWithinGap(patchIds, biomes, land, width, height, biomeStep, maxGapMeters, key2, dictionary[key2])) { unionFind.Union(key2, item); } } Dictionary<int, int> dictionary2 = new Dictionary<int, int>(); int num2 = 0; for (int j = 0; j < patchIds.Length; j++) { if (patchIds[j] >= 0) { int key = unionFind.Find(patchIds[j]); if (!dictionary2.TryGetValue(key, out var value)) { value = (dictionary2[key] = num2++); } patchIds[j] = value; } } } private static HashSet<int> FindSameBiomePatchesWithinGap(int[] patchIds, Biome[] biomes, bool[] land, int width, int height, float biomeStep, float maxGapMeters, int sourcePatchId, Biome targetBiome) { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) HashSet<int> hashSet = new HashSet<int>(); bool[] array = new bool[width * height]; Queue<(int, float)> queue = new Queue<(int, float)>(); for (int i = 0; i < patchIds.Length; i++) { if (patchIds[i] == sourcePatchId && land[i]) { array[i] = true; queue.Enqueue((i, 0f)); } } while (queue.Count > 0) { (int, float) tuple = queue.Dequeue(); int item = tuple.Item1; float item2 = tuple.Item2; int num = item % width; int num2 = item / width; EnqueueGapSearchNeighbor(num - 1, num2, item2, sourcePatchId, targetBiome, patchIds, biomes, land, width, height, biomeStep, maxGapMeters, array, queue, hashSet); EnqueueGapSearchNeighbor(num + 1, num2, item2, sourcePatchId, targetBiome, patchIds, biomes, land, width, height, biomeStep, maxGapMeters, array, queue, hashSet); EnqueueGapSearchNeighbor(num, num2 - 1, item2, sourcePatchId, targetBiome, patchIds, biomes, land, width, height, biomeStep, maxGapMeters, array, queue, hashSet); EnqueueGapSearchNeighbor(num, num2 + 1, item2, sourcePatchId, targetBiome, patchIds, biomes, land, width, height, biomeStep, maxGapMeters, array, queue, hashSet); } return hashSet; } private static void EnqueueGapSearchNeighbor(int x, int y, float distance, int sourcePatchId, Biome targetBiome, int[] patchIds, Biome[] biomes, bool[] land, int width, int height, float biomeStep, float maxGapMeters, bool[] visited, Queue<(int index, float distance)> queue, HashSet<int> reachable) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Invalid comparison between I4 and Unknown if (x < 0 || y < 0 || x >= width || y >= height) { return; } int num = y * width + x; if (visited[num]) { return; } if (land[num]) { if ((int)biomes[num] != (int)targetBiome) { return; } int num2 = patchIds[num]; if (num2 >= 0 && num2 != sourcePatchId) { if (distance <= maxGapMeters) { reachable.Add(num2); } return; } } float num3 = distance + biomeStep; if (!(num3 > maxGapMeters)) { visited[num] = true; queue.Enqueue((num, num3)); } } private static int[] FloodFillIslands(bool[] land, int width, int height) { int[] array = new int[width * height]; for (int i = 0; i < array.Length; i++) { array[i] = -1; } int num = 0; Queue<int> queue = new Queue<int>(); for (int j = 0; j < height; j++) { for (int k = 0; k < width; k++) { int num2 = j * width + k; if (land[num2] && array[num2] == -1) { array[num2] = num; queue.Enqueue(num2); while (queue.Count > 0) { int num3 = queue.Dequeue(); int num4 = num3 % width; int num5 = num3 / width; TryEnqueueLand(num4 - 1, num5, land, array, width, height, num, queue); TryEnqueueLand(num4 + 1, num5, land, array, width, height, num, queue); TryEnqueueLand(num4, num5 - 1, land, array, width, height, num, queue); TryEnqueueLand(num4, num5 + 1, land, array, width, height, num, queue); } num++; } } } return array; } private static void MergeIslandsAcrossNarrowGaps(int[] islandIds, bool[] land, int width, int height, float biomeStep, float maxGapMeters) { if (maxGapMeters <= 0f) { return; } HashSet<int> hashSet = new HashSet<int>(); int num = -1; for (int i = 0; i < islandIds.Length; i++) { if (land[i] && islandIds[i] >= 0) { hashSet.Add(islandIds[i]); num = Math.Max(num, islandIds[i]); } } if (num < 0) { return; } UnionFind unionFind = new UnionFind(num + 1); foreach (int item in hashSet) { foreach (int item2 in FindIslandsWithinGap(islandIds, land, width, height, biomeStep, maxGapMeters, item)) { unionFind.Union(item, item2); } } Dictionary<int, int> dictionary = new Dictionary<int, int>(); int num2 = 0; for (int j = 0; j < islandIds.Length; j++) { if (islandIds[j] >= 0) { int key = unionFind.Find(islandIds[j]); if (!dictionary.TryGetValue(key, out var value)) { value = (dictionary[key] = num2++); } islandIds[j] = value; } } } private static HashSet<int> FindIslandsWithinGap(int[] islandIds, bool[] land, int width, int height, float biomeStep, float maxGapMeters, int sourceIslandId) { HashSet<int> hashSet = new HashSet<int>(); bool[] array = new bool[width * height]; Queue<(int, float)> queue = new Queue<(int, float)>(); for (int i = 0; i < islandIds.Length; i++) { if (islandIds[i] == sourceIslandId && land[i]) { array[i] = true; queue.Enqueue((i, 0f)); } } while (queue.Count > 0) { (int, float) tuple = queue.Dequeue(); int item = tuple.Item1; float item2 = tuple.Item2; int num = item % width; int num2 = item / width; EnqueueIslandGapSearchNeighbor(num - 1, num2, item2, sourceIslandId, islandIds, land, width, height, biomeStep, maxGapMeters, array, queue, hashSet); EnqueueIslandGapSearchNeighbor(num + 1, num2, item2, sourceIslandId, islandIds, land, width, height, biomeStep, maxGapMeters, array, queue, hashSet); EnqueueIslandGapSearchNeighbor(num, num2 - 1, item2, sourceIslandId, islandIds, land, width, height, biomeStep, maxGapMeters, array, queue, hashSet); EnqueueIslandGapSearchNeighbor(num, num2 + 1, item2, sourceIslandId, islandIds, land, width, height, biomeStep, maxGapMeters, array, queue, hashSet); } return hashSet; } private static void EnqueueIslandGapSearchNeighbor(int x, int y, float distance, int sourceIslandId, int[] islandIds, bool[] land, int width, int height, float biomeStep, float maxGapMeters, bool[] visited, Queue<(int index, float distance)> queue, HashSet<int> reachable) { if (x < 0 || y < 0 || x >= width || y >= height) { return; } int num = y * width + x; if (visited[num]) { return; } if (land[num]) { int num2 = islandIds[num]; if (num2 >= 0 && num2 != sourceIslandId) { if (distance <= maxGapMeters) { reachable.Add(num2); } return; } } float num3 = distance + biomeStep; if (!(num3 > maxGapMeters)) { visited[num] = true; queue.Enqueue((num, num3)); } } private static void TryEnqueueLand(int x, int y, bool[] land, int[] islandIds, int width, int height, int islandId, Queue<int> queue) { if (x >= 0 && y >= 0 && x < width && y < height) { int num = y * width + x; if (land[num] && islandIds[num] == -1) { islandIds[num] = islandId; queue.Enqueue(num); } } } private static void BuildAdjacency(int[] patchIds, Biome[] biomes, bool[] land, int width, int height, out Dictionary<int, HashSet<int>> meadowsNeighbors, out Dictionary<int, HashSet<int>> forestNeighbors) { meadowsNeighbors = new Dictionary<int, HashSet<int>>(); forestNeighbors = new Dictionary<int, HashSet<int>>(); for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int num = i * width + j; if (land[num]) { int num2 = patchIds[num]; if (num2 >= 0) { CheckEdge(num, j - 1, i, num2, biomes[num], patchIds, biomes, land, width, height, meadowsNeighbors, forestNeighbors); CheckEdge(num, j + 1, i, num2, biomes[num], patchIds, biomes, land, width, height, meadowsNeighbors, forestNeighbors); CheckEdge(num, j, i - 1, num2, biomes[num], patchIds, biomes, land, width, height, meadowsNeighbors, forestNeighbors); CheckEdge(num, j, i + 1, num2, biomes[num], patchIds, biomes, land, width, height, meadowsNeighbors, forestNeighbors); } } } } } private static void CheckEdge(int index, int nx, int ny, int patchId, Biome biome, int[] patchIds, Biome[] biomes, bool[] land, int width, int height, Dictionary<int, HashSet<int>> meadowsNeighbors, Dictionary<int, HashSet<int>> forestNeighbors) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Invalid comparison between Unknown and I4 //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Invalid comparison between Unknown and I4 if (nx < 0 || ny < 0 || nx >= width || ny >= height) { return; } int num = ny * width + nx; if (!land[num]) { return; } int num2 = patchIds[num]; if (num2 >= 0 && num2 != patchId) { if ((int)biome == 1 && (int)biomes[num] == 8) { AddNeighbor(meadowsNeighbors, patchId, num2); AddNeighbor(forestNeighbors, num2, patchId); } else if ((int)biome == 8 && (int)biomes[num] == 1) { AddNeighbor(forestNeighbors, patchId, num2); AddNeighbor(meadowsNeighbors, num2, patchId); } } } private static HashSet<int> FindMeadowsTouchingCoast(int[] patchIds, Biome[] biomes, bool[] land, int width, int height) { HashSet<int> hashSet = new HashSet<int>(); for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int num = i * width + j; if (land[num] && (int)biomes[num] == 1 && patchIds[num] >= 0 && (IsWaterNeighbor(j - 1, i, land, width, height) || IsWaterNeighbor(j + 1, i, land, width, height) || IsWaterNeighbor(j, i - 1, land, width, height) || IsWaterNeighbor(j, i + 1, land, width, height))) { hashSet.Add(patchIds[num]); } } } return hashSet; } private static bool IsWaterNeighbor(int x, int y, bool[] land, int width, int height) { if (x < 0 || y < 0 || x >= width || y >= height) { return false; } return !land[y * width + x]; } private static void AddNeighbor(Dictionary<int, HashSet<int>> map, int from, int to) { if (!map.TryGetValue(from, out var value)) { value = (map[from] = new HashSet<int>()); } value.Add(to); } } internal sealed class BiomePatchInfo { public int PatchId { get; set; } public string Name { get; set; } public Biome Biome { get; set; } public int CellCount { get; set; } public float ApproximateAreaSquareMeters { get; set; } public Vector2 Center { get; set; } public int BurialChamberCount { get; set; } } internal sealed class CandidateSpawnFinder { public sealed class RejectionStats { public int NotMeadows; public int NoNearbyForest; public int NoAdjacentCoast; public int TooCloseToStones; public int NotEnoughChambers; public int Underwater; public int TotalChecked; public int Accepted; } public static List<CandidateSpawnPoint> Find(BiomeMapBuilder map, LocationCatalog locations, ModConfig config, out RejectionStats stats) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Invalid comparison between Unknown and I4 //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) stats = new RejectionStats(); List<CandidateSpawnPoint> list = new List<CandidateSpawnPoint>(); WorldGenerator instance = WorldGenerator.instance; float value = config.BlackForestProximity.Value; foreach (Vector3 item in map.EnumerateGridPoints()) { stats.TotalChecked++; if (!map.TryGetCell(item, out var _, out var biome, out var biomePatchId, out var islandId, out var _)) { continue; } if ((int)biome != 1) { stats.NotMeadows++; continue; } if (!map.HasBlackForestWithin(item, value)) { stats.NoNearbyForest++; continue; } if (!map.HasAdjacentCoast(biomePatchId)) { stats.NoAdjacentCoast++; continue; } if (locations.SacrificialStones != Vector3.zero && Vector3.Distance(item, locations.SacrificialStones) < config.MinStonesDistance.Value) { stats.TooCloseToStones++; continue; } float height = instance.GetHeight(item.x, item.z); if (height <= 30f) { stats.Underwater++; continue; } int num = locations.CountBurialChambersNear(item, value, map); if (num < config.MinBurialChambers.Value) { stats.NotEnoughChambers++; continue; } stats.Accepted++; list.Add(new CandidateSpawnPoint { Position = new Vector3(item.x, height, item.z), MeadowsPatchId = biomePatchId, AdjacentForestPatchId = map.FindNearestBlackForestPatchId(item, value), IslandId = islandId, NearbyBurialChambers = num, MeadowsAreaSquareMeters = map.GetPatchAreaSquareMeters(biomePatchId), ExistingEikthyr = locations.FindEikthyrNear(item, config.EikthyrReach.Value) }); } return list; } } internal static class ClientSyncHelper { private const float FirstRetrySeconds = 2f; private const float MaxRetrySeconds = 30f; public static bool CanReachServer() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Invalid comparison between Unknown and I4 if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || ZRoutedRpc.instance == null) { return false; } return (int)ZNet.GetConnectionStatus() == 2; } public static void ResetClientState() { RosterSync.ResetClientState(); } public static IEnumerator RunSyncRetry(MonoBehaviour host) { bool wasConnected = false; float retryDelay = 2f; float waitingSince = -1f; while ((Object)(object)host != (Object)null) { if (!CanReachServer()) { if (wasConnected) { ResetClientState(); ModLog.Info("Server disconnected; waiting to resync Separate Spawns data."); } wasConnected = false; retryDelay = 2f; waitingSince = -1f; yield return (object)new WaitForSeconds(1f); continue; } if (!wasConnected) { ModLog.Info("Connected to server; syncing Separate Spawns roster and layout."); wasConnected = true; } RosterSync.Register(); LayoutSync.Register(); PortalActivationSync.Register(); bool flag = !RosterSync.ClientHasRoster; bool flag2 = Plugin.LayoutCache.Current == null; if (!flag && !flag2) { retryDelay = 2f; waitingSince = -1f; yield return (object)new WaitForSeconds(5f); continue; } bool flag3 = waitingSince < 0f; if (flag3) { waitingSince = Time.realtimeSinceStartup; } else if (retryDelay >= 30f) { string arg = ((flag && flag2) ? "roster and layout" : (flag ? "roster" : "layout")); ModLog.Info($"Still waiting for the server's Separate Spawns {arg} ({Time.realtimeSinceStartup - waitingSince:F0}s)."); } DirectPeerSync.RequestFromServer(!flag3); if (flag) { RosterSync.RequestFromServer(direct: false, !flag3); } if (flag2) { LayoutSync.RequestLayoutFromServer(direct: false, !flag3); } yield return (object)new WaitForSeconds(retryDelay); retryDelay = Mathf.Min(retryDelay * 2f, 30f); } } } internal static class DirectPeerSync { private const string RequestRpc = "SeparateSpawns.RequestDirectSync"; private const string SyncRosterRpc = "SeparateSpawns.SyncRosterDirect"; private const string SyncLayoutRpc = "SeparateSpawns.SyncLayoutDirect"; private static readonly HashSet<long> RosterSentLogged = new HashSet<long>(); private static readonly HashSet<long> LayoutUnavailableWarned = new HashSet<long>(); public static void RegisterClientHandlers(ZRpc serverRpc) { if (serverRpc != null) { serverRpc.Register<string>("SeparateSpawns.SyncRosterDirect", (Action<ZRpc, string>)delegate(ZRpc _, string json) { RosterSync.ApplyPayload(json, "direct ZRpc"); }); serverRpc.Register<string>("SeparateSpawns.SyncLayoutDirect", (Action<ZRpc, string>)delegate(ZRpc _, string json) { LayoutSync.ApplyPayload(json, "direct ZRpc"); }); ModLog.Info("Registered direct Separate Spawns sync handlers on server connection."); } } public static void RegisterServerPeer(ZNetPeer peer) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown if (peer?.m_rpc != null) { peer.m_rpc.Register("SeparateSpawns.RequestDirectSync", (Method)delegate { SendToPeer(peer); }); } } public static void ResetServerLogState() { RosterSentLogged.Clear(); LayoutUnavailableWarned.Clear(); } public static void RequestFromServer(bool quiet = false) { if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || !ClientSyncHelper.CanReachServer()) { return; } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer.m_server && peer.m_rpc != null && peer.m_rpc.IsConnected()) { if (!quiet) { ModLog.Info("Requesting Separate Spawns sync via direct ZRpc..."); } peer.m_rpc.Invoke("SeparateSpawns.RequestDirectSync", Array.Empty<object>()); return; } } if (!quiet) { ModLog.Warning("Could not find connected server peer for direct Separate Spawns sync."); } } public static void SendToPeer(ZNetPeer peer) { if (peer?.m_rpc == null || !peer.m_rpc.IsConnected() || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } if (Plugin.Roster == null || Plugin.Roster.GetGroupNames().Count == 0) { RosterSync.LoadServerRosterFromDisk(); } if (Plugin.LayoutCache.Current == null) { long worldUID = ZNet.instance.GetWorldUID(); if (worldUID != 0L) { WorldLayoutData worldLayoutData = WorldLayoutStore.Load(worldUID); if (worldLayoutData != null) { Plugin.LayoutCache.Set(worldLayoutData); } } } if (Plugin.Roster != null) { peer.m_rpc.Invoke("SeparateSpawns.SyncRosterDirect", new object[1] { Plugin.Roster.ToJson() }); if (RosterSentLogged.Add(peer.m_uid)) { ModLog.Info($"Sent roster to peer {peer.m_uid} via direct ZRpc."); } } else { ModLog.Warning($"Direct roster sync skipped for peer {peer.m_uid}; server roster unavailable."); } if (Plugin.LayoutCache.Current != null) { string text = JsonConvert.SerializeObject((object)Plugin.LayoutCache.Current, JsonSettings.Compact); peer.m_rpc.Invoke("SeparateSpawns.SyncLayoutDirect", new object[1] { text }); ModLog.Info($"Sent layout to peer {peer.m_uid} via direct ZRpc ({Plugin.LayoutCache.Current.GroupSpawnPositions.Count} spawns)."); } else if (LayoutUnavailableWarned.Add(peer.m_uid)) { ModLog.Warning($"Direct layout sync skipped for peer {peer.m_uid}; server layout unavailable. " + "The peer will keep asking; this is logged once per peer."); } } } internal static class EikthyrPlacer { private const int MaxPlacementAttempts = 3; public static Vector3? EnsureAltarNearSpawn(string groupName, Vector3 spawn, ModConfig config, WorldLayoutData layoutData) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_005a: 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_004f: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) if (layoutData.SpawnedEikthyrPositions.TryGetValue(groupName, out var value)) { return value; } LocationCatalog locationCatalog = LocationCatalog.Build(config); Vector3? val = locationCatalog.FindEikthyrNear(spawn, config.EikthyrReach.Value); if (val.HasValue) { layoutData.SpawnedEikthyrPositions[groupName] = val.Value; return val.Value; } List<Vector3> list = FindPlacementCandidates(spawn, config.EikthyrReach.Value, 3); if (list.Count == 0) { ModLog.Warning("Failed to find any Meadows placement for Eikthyr altar for " + groupName + "."); return null; } for (int i = 0; i < list.Count; i++) { Vector3 val2 = list[i]; if (ZoneSystem.instance.TestSpawnLocation(config.EikthyrLocationName.Value, val2, false)) { layoutData.SpawnedEikthyrPositions[groupName] = val2; locationCatalog.EikthyrAltars.Add(val2); if (i > 0) { ModLog.Info($"Placed Eikthyr altar for {groupName} on attempt {i + 1}/{list.Count}."); } return val2; } ModLog.Warning($"Eikthyr altar placement attempt {i + 1}/{list.Count} failed for {groupName} at ({val2.x:F0}, {val2.z:F0})."); } ModLog.Warning($"Failed to place Eikthyr altar for {groupName} after {list.Count} attempts."); return null; } private static List<Vector3> FindPlacementCandidates(Vector3 spawn, float radius, int maxCandidates) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Invalid comparison between Unknown and I4 //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) WorldGenerator instance = WorldGenerator.instance; List<(Vector3, float)> list = new List<(Vector3, float)>(); for (int i = 1; i <= 4; i++) { float num = (float)i * (radius / 4f); for (int j = 0; j < 16; j++) { float num2 = (float)j * (float)Math.PI * 2f / 16f; Vector3 val = spawn + new Vector3(Mathf.Sin(num2) * num, 0f, Mathf.Cos(num2) * num); if ((int)instance.GetBiome(val) == 1) { float height = instance.GetHeight(val.x, val.z); if (!(height <= 30f)) { val.y = height; list.Add((val, Vector3.Distance(spawn, val))); } } } } list.Sort(((Vector3 position, float distance) a, (Vector3 position, float distance) b) => a.distance.CompareTo(b.distance)); List<Vector3> list2 = new List<Vector3>(); foreach (var item in list) { if (!IsTooCloseToExisting(item.Item1, list2, 10f)) { list2.Add(item.Item1); if (list2.Count >= maxCandidates) { break; } } } return list2; } private static bool IsTooCloseToExisting(Vector3 candidate, List<Vector3> existing, float minSeparation) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) foreach (Vector3 item in existing) { if (Vector3.Distance(candidate, item) < minSeparation) { return true; } } return false; } } internal sealed class GroupEntry { public List<string> Players { get; set; } = new List<string>(); public int? Difficulty { get; set; } public bool HasDifficulty { get { if (Difficulty.HasValue) { return Difficulty.Value > 0; } return false; } } public static GroupEntry FromPlayers(IEnumerable<string> players) { return new GroupEntry { Players = ((players != null) ? new List<string>(players) : new List<string>()) }; } } internal sealed class GroupPortalMarker : MonoBehaviour { internal const string ActivateRpcName = "SeparateSpawns_ActivatePortal"; public string GroupName; public bool IsSpawnEnd; public bool Activated; public const string ZdoGroupKey = "separate_spawns_group"; public const string ZdoSpawnEndKey = "separate_spawns_spawn_end"; public const string ZdoActivatedKey = "separate_spawns_activated"; private ZNetView _nview; private bool _activateRpcRegistered; private void Awake() { _nview = ((Component)this).GetComponent<ZNetView>(); EnsureActivateRpcRegistered(); LoadFromZdo(); } internal void EnsureActivateRpcRegistered() { if (!_activateRpcRegistered && !((Object)(object)_nview == (Object)null)) { _nview.Register("SeparateSpawns_ActivatePortal", (Action<long>)RPC_ActivatePortal); _activateRpcRegistered = true; } } public void RPC_ActivatePortal(long sender) { if (ZNet.instance.IsServer()) { HandleLegacyActivateRpc(sender); } } internal void HandleLegacyActivateRpc(long sender) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_nview == (Object)null) && _nview.IsValid()) { PortalActivationSync.HandleActivation(sender, _nview.GetZDO().m_uid); } } public void SyncFromZdo() { LoadFromZdo(); } public static bool TryReadFromZdo(ZDO zdo, out string groupName, out bool isSpawnEnd, out bool activated) { groupName = null; isSpawnEnd = false; activated = false; if (zdo == null) { return false; } groupName = zdo.GetString("separate_spawns_group", ""); if (string.IsNullOrEmpty(groupName)) { groupName = null; return false; } isSpawnEnd = zdo.GetBool("separate_spawns_spawn_end", false); activated = zdo.GetBool("separate_spawns_activated", false); return true; } public static GroupPortalMarker AttachFromZdoIfNeeded(GameObject go) { if ((Object)(object)go == (Object)null) { return null; } GroupPortalMarker component = go.GetComponent<GroupPortalMarker>(); if ((Object)(object)component != (Object)null) { component.SyncFromZdo(); if (string.IsNullOrEmpty(component.GroupName)) { component.LoadFromZdo(); } if (!string.IsNullOrEmpty(component.GroupName)) { return component; } return null; } ZNetView component2 = go.GetComponent<ZNetView>(); ZDO val = ((component2 != null) ? component2.GetZDO() : null); if (val == null) { return null; } if (string.IsNullOrEmpty(val.GetString("separate_spawns_group", ""))) { return null; } GroupPortalMarker groupPortalMarker = go.AddComponent<GroupPortalMarker>(); groupPortalMarker.EnsureActivateRpcRegistered(); groupPortalMarker.LoadFromZdo(); return groupPortalMarker; } public void LoadFromZdo() { if ((Object)(object)_nview == (Object)null) { _nview = ((Component)this).GetComponent<ZNetView>(); } ZNetView nview = _nview; if (((nview != null) ? nview.GetZDO() : null) != null) { ZDO zDO = _nview.GetZDO(); string text = zDO.GetString("separate_spawns_group", ""); if (text.Length != 0) { GroupName = text; IsSpawnEnd = zDO.GetBool("separate_spawns_spawn_end", false); Activated = zDO.GetBool("separate_spawns_activated", false); } } } public void Initialize(string groupName, bool isSpawnEnd, bool activated) { GroupName = groupName; IsSpawnEnd = isSpawnEnd; Activated = activated; if ((Object)(object)_nview == (Object)null) { _nview = ((Component)this).GetComponent<ZNetView>(); } ZNetView nview = _nview; if (((nview != null) ? nview.GetZDO() : null) != null && _nview.IsOwner()) { ZDO zDO = _nview.GetZDO(); zDO.Set("separate_spawns_group", GroupName); zDO.Set("separate_spawns_spawn_end", IsSpawnEnd); zDO.Set("separate_spawns_activated", Activated); PortalManager.ApplyGroupTag(zDO, GroupName); } } public void SetActivated(bool activated) { Activated = activated; ZNetView nview = _nview; if (((nview != null) ? nview.GetZDO() : null) != null && _nview.IsOwner()) { _nview.GetZDO().Set("separate_spawns_activated", activated); } } } internal sealed class GroupRoster { private static readonly JsonSerializerSettings RosterJsonSettings = new JsonSerializerSettings { Formatting = (Formatting)1, Converters = { (JsonConverter)(object)new GroupRosterJsonConverter() } }; public Dictionary<string, GroupEntry> Groups { get; set; } = new Dictionary<string, GroupEntry>(); public static string RosterPath => ModPaths.ResolveConfigPath(ModPaths.RosterFile); public static string RosterWritePath => ModPaths.GetWriteConfigPath(ModPaths.RosterFile); public static GroupRoster CreateEmpty() { return new GroupRoster { Groups = new Dictionary<string, GroupEntry>() }; } public static GroupRoster LoadFromDisk() { string text = ResolveReadPath(); if (!File.Exists(text)) { GroupRoster groupRoster = CreateSample(); groupRoster.Save(); return groupRoster; } try { ModLog.Info("Loading group roster from " + text + "."); return FromJson(File.ReadAllText(text)); } catch (Exception arg) { ModLog.Error($"Failed to load group roster: {arg}"); return CreateEmpty(); } } private static string ResolveReadPath() { string text = null; int num = -1; int num2 = -1; foreach (string configRoot in ModPaths.GetConfigRoots()) { string text2 = Path.Combine(configRoot, ModPaths.RosterFile); if (!File.Exists(text2)) { continue; } try { GroupRoster groupRoster = FromJson(File.ReadAllText(text2)); int num3 = groupRoster.Groups.Values.Sum((GroupEntry entry) => (entry?.Players?.Count).GetValueOrDefault()); int count = groupRoster.Groups.Count; if (num3 > num || (num3 == num && count > num2)) { text = text2; num = num3; num2 = count; } } catch (Exception ex) { ModLog.Warning("Ignoring unreadable roster at " + text2 + ": " + ex.Message); } } return text ?? ModPaths.GetWriteConfigPath(ModPaths.RosterFile); } public static GroupRoster FromJson(string json) { GroupRoster groupRoster = JsonConvert.DeserializeObject<GroupRoster>(json, RosterJsonSettings) ?? CreateEmpty(); GroupRoster groupRoster2 = groupRoster; if (groupRoster2.Groups == null) { Dictionary<string, GroupEntry> dictionary = (groupRoster2.Groups = new Dictionary<string, GroupEntry>()); } foreach (string item in groupRoster.Groups.Keys.ToList()) { groupRoster.Groups[item] = groupRoster.Groups[item] ?? new GroupEntry(); GroupEntry groupEntry = groupRoster.Groups[item]; if (groupEntry.Players == null) { List<string> list = (groupEntry.Players = new List<string>()); } } return groupRoster; } public string ToJson() { return JsonConvert.SerializeObject((object)this, RosterJsonSettings); } public void Save() { if (!((Object)(object)ZNet.instance != (Object)null) || ZNet.instance.IsServer()) { string rosterWritePath = RosterWritePath; Directory.CreateDirectory(Path.GetDirectoryName(rosterWritePath)); File.WriteAllText(rosterWritePath, ToJson()); } } public IReadOnlyList<string> GetGroupNames() { return Groups.Keys.OrderBy<string, string>((string name) => name, StringComparer.Ordinal).ToList(); } public string GetGroupForPlayer(string platformUserId) { if (string.IsNullOrWhiteSpace(platformUserId)) { return null; } foreach (KeyValuePair<string, GroupEntry> group in Groups) { if (group.Value?.Players != null && group.Value.Players.Any((string id) => PlatformIdHelper.IdsMatch(id, platformUserId))) { return group.Key; } } return null; } public string AssignRandomGroup(string platformUserId) { IReadOnlyList<string> groupNames = GetGroupNames(); if (groupNames.Count == 0) { return null; } string normalized = PlatformIdHelper.Normalize(platformUserId); string text = groupNames[Random.Range(0, groupNames.Count)]; if (!Groups.TryGetValue(text, out var value)) { value = new GroupEntry(); Groups[text] = value; } GroupEntry groupEntry = value; if (groupEntry.Players == null) { List<string> list = (groupEntry.Players = new List<string>()); } if (!value.Players.Any((string id) => PlatformIdHelper.IdsMatch(id, normalized))) { value.Players.Add(normalized); Save(); RosterSync.Broadcast(); ModLog.Info("Assigned unlisted player " + normalized + " to " + text + "."); } return text; } public bool NeedsDifficultyAssignment(IEnumerable<string> layoutGroupNames) { if (layoutGroupNames == null) { return false; } foreach (string layoutGroupName in layoutGroupNames) { if (!string.IsNullOrEmpty(layoutGroupName) && (!Groups.TryGetValue(layoutGroupName, out var value) || value == null || !value.HasDifficulty)) { return true; } } return false; } public void ApplySpawnDifficulties(IReadOnlyDictionary<string, int> difficulties) { if (difficulties == null || difficulties.Count == 0) { return; } foreach (KeyValuePair<string, int> difficulty in difficulties) { if (!Groups.TryGetValue(difficulty.Key, out var value)) { value = new GroupEntry(); Groups[difficulty.Key] = value; } GroupEntry groupEntry = value; if (groupEntry.Players == null) { List<string> list = (groupEntry.Players = new List<string>()); } value.Difficulty = difficulty.Value; ModLog.Info($"Group {difficulty.Key} spawn difficulty: {difficulty.Value}."); } Save(); RosterSync.Broadcast(); } private static GroupRoster CreateSample() { return new GroupRoster { Groups = new Dictionary<string, GroupEntry> { ["groupA"] = new GroupEntry(), ["groupB"] = new GroupEntry() } }; } } internal sealed class GroupRosterJsonConverter : JsonConverter<GroupRoster> { public override void WriteJson(JsonWriter writer, GroupRoster value, JsonSerializer serializer) { writer.WriteStartObject(); writer.WritePropertyName("groups"); writer.WriteStartObject(); if (value?.Groups != null) { foreach (KeyValuePair<string, GroupEntry> group in value.Groups) { writer.WritePropertyName(group.Key); WriteGroupEntry(writer, group.Value); } } writer.WriteEndObject(); writer.WriteEndObject(); } public override GroupRoster ReadJson(JsonReader reader, Type objectType, GroupRoster existingValue, bool hasExistingValue, JsonSerializer serializer) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 if ((int)reader.TokenType == 11) { return GroupRoster.CreateEmpty(); } JObject val = JObject.Load(reader); JToken obj = val["groups"] ?? val["Groups"]; GroupRoster groupRoster = existingValue ?? GroupRoster.CreateEmpty(); GroupRoster groupRoster2 = groupRoster; if (groupRoster2.Groups == null) { Dictionary<string, GroupEntry> dictionary = (groupRoster2.Groups = new Dictionary<string, GroupEntry>()); } JObject val2 = (JObject)(object)((obj is JObject) ? obj : null); if (val2 != null) { foreach (JProperty item in val2.Properties()) { groupRoster.Groups[item.Name] = ReadGroupEntry(item.Value); } } return groupRoster; } private static void WriteGroupEntry(JsonWriter writer, GroupEntry entry) { writer.WriteStartObject(); writer.WritePropertyName("players"); writer.WriteStartArray(); if (entry?.Players != null) { foreach (string player in entry.Players) { writer.WriteValue(player); } } writer.WriteEndArray(); if (entry != null && entry.HasDifficulty) { writer.WritePropertyName("difficulty"); writer.WriteValue(entry.Difficulty.Value); } writer.WriteEndObject(); } private static GroupEntry ReadGroupEntry(JToken token) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Invalid comparison between Unknown and I4 //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Invalid comparison between Unknown and I4 if (token == null || (int)token.Type == 10) { return new GroupEntry(); } if ((int)token.Type == 2) { return GroupEntry.FromPlayers(ReadPlayerList(token)); } if ((int)token.Type == 8) { return GroupEntry.FromPlayers(ReadPlayerList(token)); } JObject val = (JObject)(object)((token is JObject) ? token : null); if (val != null) { JToken token2 = val["players"] ?? val["Players"]; JToken val2 = val["difficulty"] ?? val["Difficulty"]; return new GroupEntry { Players = ReadPlayerList(token2), Difficulty = ((val2 != null && (int)val2.Type == 6) ? Extensions.Value<int?>((IEnumerable<JToken>)val2) : ((int?)null)) }; } return new GroupEntry(); } private static List<string> ReadPlayerList(JToken token) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Invalid comparison between Unknown and I4 if (token == null || (int)token.Type == 10) { return new List<string>(); } if ((int)token.Type == 2) { return token.ToObject<List<string>>() ?? new List<string>(); } if ((int)token.Type == 8) { string text = Extensions.Value<string>((IEnumerable<JToken>)token); if (!string.IsNullOrWhiteSpace(text)) { return new List<string> { text.Trim() }; } return new List<string>(); } return new List<string>(); } } internal static class GroupSpawnResolver { private static bool _loggedMissingPlatformId; private static string _loggedLayoutMismatchGroup; public static Vector3? GetSpawnForLocalPlayer() { string localPlatformUserId = PlatformIdHelper.GetLocalPlatformUserId(); if (string.IsNullOrEmpty(localPlatformUserId)) { if (!_loggedMissingPlatformId) { ModLog.Warning("Cannot resolve group spawn: local platform user id was empty (Steam/platform APIs may not be ready yet)."); _loggedMissingPlatformId = true; } return null; } _loggedMissingPlatformId = false; return GetSpawnForPlatformUser(localPlatformUserId); } public static Vector3? GetSpawnForPlatformUser(string platformUserId) { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) if (!RosterIsAvailable()) { return null; } string text = PlatformIdHelper.Normalize(platformUserId); string text2 = ResolveGroupForPlayer(text); if (string.IsNullOrEmpty(text2)) { return null; } if (Plugin.LayoutCache.Current == null) { return null; } Vector3? spawnForGroup = Plugin.LayoutCache.GetSpawnForGroup(text2); if (!spawnForGroup.HasValue) { LogGroupLayoutMismatchOnce(text2, text); return null; } ModLog.Info($"Resolved {text} -> {text2} spawn ({spawnForGroup.Value.x:F0}, {spawnForGroup.Value.z:F0})."); return spawnForGroup.Value; } public static bool IsSeparateSpawnPending() { WorldLayoutData current = Plugin.LayoutCache.Current; if (current != null && current.Failed) { return false; } if (!SeparateSpawnsEnabled()) { return false; } string localPlatformUserId = PlatformIdHelper.GetLocalPlatformUserId(); if (string.IsNullOrEmpty(localPlatformUserId)) { return true; } if (!RosterIsAvailable()) { return true; } string text = ResolveGroupForPlayer(localPlatformUserId); if (string.IsNullOrEmpty(text)) { return true; } if (Plugin.LayoutCache.Current == null) { return true; } _ = Plugin.LayoutCache.GetSpawnForGroup(text).HasValue; return false; } public static bool HasGroupLayoutMismatch() { if (Plugin.LayoutCache.Current == null || Plugin.LayoutCache.Current.Failed || !RosterIsAvailable()) { return false; } string localPlatformUserId = PlatformIdHelper.GetLocalPlatformUserId(); if (string.IsNullOrEmpty(localPlatformUserId)) { return false; } string text = ResolveGroupForPlayer(localPlatformUserId); if (!string.IsNullOrEmpty(text)) { return !Plugin.LayoutCache.GetSpawnForGroup(text).HasValue; } return false; } public static string GetGroupForLocalPlayer() { string localPlatformUserId = PlatformIdHelper.GetLocalPlatformUserId(); if (string.IsNullOrEmpty(localPlatformUserId)) { return null; } return GetGroupNameForPlatformUser(localPlatformUserId); } public static string GetGroupNameForPlatformUser(string platformUserId) { if (string.IsNullOrEmpty(platformUserId) || !RosterIsAvailable()) { return null; } return ResolveGroupForPlayer(platformUserId); } public static bool IsLocalPlayerInGroup(string groupName) { string groupForLocalPlayer = GetGroupForLocalPlayer(); if (!string.IsNullOrEmpty(groupForLocalPlayer)) { return groupForLocalPlayer == groupName; } return false; } private static bool RosterIsAvailable() { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { if (Plugin.Roster != null) { return Plugin.Roster.GetGroupNames().Count > 0; } return false; } if (RosterSync.ClientHasRoster) { return Plugin.Roster != null; } return false; } private static string ResolveGroupForPlayer(string platformUserId) { string text = PlatformIdHelper.Normalize(platformUserId); string groupForPlayer = Plugin.Roster.GetGroupForPlayer(text); if (!string.IsNullOrEmpty(groupForPlayer)) { return groupForPlayer; } if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { groupForPlayer = Plugin.Roster.AssignRandomGroup(text); if (string.IsNullOrEmpty(groupForPlayer)) { ModLog.Warning("No groups available for player " + text + "."); } return groupForPlayer; } RosterSync.RequestAssignment(text); return null; } private static bool SeparateSpawnsEnabled() { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { if (Plugin.Roster != null) { return Plugin.Roster.GetGroupNames().Count > 0; } return false; } if (!RosterSync.ClientHasRoster) { return (Object)(object)ZNet.instance != (Object)null; } if (Plugin.Roster != null) { return Plugin.Roster.GetGroupNames().Count > 0; } return false; } private static void LogGroupLayoutMismatchOnce(string group, string platformUserId) { if (!(group == _loggedLayoutMismatchGroup)) { _loggedLayoutMismatchGroup = group; Dictionary<string, Vector3>.KeyCollection keyCollection = Plugin.LayoutCache.Current?.GroupSpawnPositions.Keys; string text = ((keyCollection == null) ? "(none)" : string.Join(", ", keyCollection)); ModLog.Error("Roster group '" + group + "' for player " + platformUserId + " has no spawn in this world's layout (layout groups: " + text + "). Group names must match the roster used when the world was created."); } } } internal static class JsonSettings { private sealed class Vector3Converter : JsonConverter { public override bool CanConvert(Type objectType) { if (!(objectType == typeof(Vector3))) { return objectType == typeof(Vector3?); } return true; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) if (value == null) { writer.WriteNull(); return; } Vector3 val = (Vector3)value; writer.WriteStartObject(); writer.WritePropertyName("x"); writer.WriteValue(val.x); writer.WritePropertyName("y"); writer.WriteValue(val.y); writer.WritePropertyName("z"); writer.WriteValue(val.z); writer.WriteEndObject(); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) if ((int)reader.TokenType == 11) { if (!(objectType == typeof(Vector3?))) { return Vector3.zero; } return null; } JObject val = JObject.Load(reader); Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(((JToken)val).Value<float>((object)"x"), ((JToken)val).Value<float>((object)"y"), ((JToken)val).Value<float>((object)"z")); _ = objectType == typeof(Vector3?); return val2; } } public static readonly JsonSerializerSettings Serializer = new JsonSerializerSettings { Formatting = (Formatting)1, Converters = { (JsonConverter)(object)new Vector3Converter() } }; public static readonly JsonSerializerSettings Compact = new JsonSerializerSettings { Converters = { (JsonConverter)(object)new Vector3Converter() } }; } internal static class LayoutGenerator { public static LayoutGenerationResult GenerateLayouts(IReadOnlyList<string> groupNames, List<CandidateSpawnPoint> candidates, ModConfig config) { LayoutGenerationResult layoutGenerationResult = new LayoutGenerationResult(); if (groupNames.Count == 0 || candidates.Count == 0) { return layoutGenerationResult; } int value = config.MaxLayouts.Value; float value2 = config.MinSpawnDistance.Value; Random random = new Random(1337); List<CandidateSpawnPoint> list = new List<CandidateSpawnPoint>(); for (int i = 0; i < value; i++) { layoutGenerationResult.TotalAttempts++; list.Clear(); foreach (string groupName in groupNames) { _ = groupName; CandidateSpawnPoint candidateSpawnPoint = PickRandomValidCandidate(candidates, list, value2, random); if (candidateSpawnPoint == null) { break; } list.Add(candidateSpawnPoint); } LayoutAssignment layoutAssignment = (layoutGenerationResult.LastAttempt = BuildAssignment(groupNames, list)); if (layoutAssignment.GroupsPlaced > layoutGenerationResult.BestPartialAttempt.GroupsPlaced) { layoutGenerationResult.BestPartialAttempt = layoutAssignment; } if (list.Count >= groupNames.Count) { ScoreLayout(layoutAssignment, config); layoutGenerationResult.Layouts.Add(layoutAssignment); layoutGenerationResult.ValidLayouts++; } } layoutGenerationResult.Layouts = layoutGenerationResult.Layouts.OrderByDescending((LayoutAssignment layout) => layout.Score).ToList(); return layoutGenerationResult; } private static CandidateSpawnPoint PickRandomValidCandidate(List<CandidateSpawnPoint> candidates, List<CandidateSpawnPoint> alreadyChosen, float minDistance, Random random) { for (int i = 0; i < 40; i++) { CandidateSpawnPoint candidateSpawnPoint = candidates[random.Next(candidates.Count)]; if (!alreadyChosen.Contains(candidateSpawnPoint) && IsFarEnoughFromOthers(candidateSpawnPoint, alreadyChosen, minDistance)) { return candidateSpawnPoint; } } return null; } private static LayoutAssignment BuildAssignment(IReadOnlyList<string> groupNames, List<CandidateSpawnPoint> chosen) { Dictionary<string, CandidateSpawnPoint> dictionary = new Dictionary<string, CandidateSpawnPoint>(); for (int i = 0; i < chosen.Count && i < groupNames.Count; i++) { dictionary[groupNames[i]] = chosen[i]; } return new LayoutAssignment { GroupSpawns = dictionary, GroupsPlaced = dictionary.Count, Complete = (dictionary.Count == groupNames.Count) }; } private static bool IsFarEnoughFromOthers(CandidateSpawnPoint candidate, IEnumerable<CandidateSpawnPoint> others, float minDistance) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) foreach (CandidateSpawnPoint other in others) { if (Vector3.Distance(candidate.Position, other.Position) < minDistance) { return false; } } return true; } public static void ScoreLayout(LayoutAssignment layout, ModConfig config) { //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) List<string> list = layout.GroupSpawns.Keys.ToList(); if (list.Count == 0) { layout.Score = 0f; return; } float num = 0f; foreach (CandidateSpawnPoint value in layout.GroupSpawns.Values) { num += value.MeadowsAreaSquareMeters; } layout.AverageMeadowsAreaSquareMeters = num / (float)list.Count; if (list.Count < 2) { layout.Score = 0f; return; } int num2 = 0; float num3 = 0f; float num4 = float.MaxValue; for (int i = 0; i < list.Count; i++) { for (int j = i + 1; j < list.Count; j++) { CandidateSpawnPoint candidateSpawnPoint = layout.GroupSpawns[list[i]]; CandidateSpawnPoint candidateSpawnPoint2 = layout.GroupSpawns[list[j]]; num2++; if (candidateSpawnPoint.IslandId >= 0 && candidateSpawnPoint2.IslandId >= 0 && candidateSpawnPoint.IslandId != candidateSpawnPoint2.IslandId) { num3 += 1f; } float num5 = Vector3.Distance(candidateSpawnPoint.Position, candidateSpawnPoint2.Position); if (num5 < num4) { num4 = num5; } } } layout.ClosestSpawnDistance = num4; layout.IslandScore = num3 / (float)num2 * (float)config.ScoreIslands.Value; layout.DistanceScore = 0f; layout.MeadowsSizeScore = 0f; layout.Score = layout.IslandScore; } public static void ApplyRelativeScores(IReadOnlyList<LayoutAssignment> layouts, ModConfig config) { if (layouts.Count == 0) { return; } float num = layouts.Max((LayoutAssignment layout) => layout.ClosestSpawnDistance); float num2 = layouts.Min((LayoutAssignment layout) => layout.ClosestSpawnDistance); float num3 = layouts.Max((LayoutAssignment layout) => layout.AverageMeadowsAreaSquareMeters); float num4 = num - num2; foreach (LayoutAssignment layout in layouts) { layout.DistanceScore = ((num4 > 0.01f) ? ((layout.ClosestSpawnDistance - num2) / num4 * (float)config.ScoreDistance.Value) : ((float)config.ScoreDistance.Value)); layout.MeadowsSizeScore = ((num3 > 0.01f) ? (layout.AverageMeadowsAreaSquareMeters / num3 * (float)config.ScoreMeadowsSize.Value) : 0f); layout.Score = layout.IslandScore + layout.DistanceScore + layout.MeadowsSizeScore; } } public static List<LayoutAssignment> SelectDiverseLayouts(IReadOnlyList<LayoutAssignment> sortedLayouts, int count, float diversityDistance) { List<LayoutAssignment> list = new List<LayoutAssignment>(); if (count <= 0 || sortedLayouts == null || sortedLayouts.Count == 0) { return list; } foreach (LayoutAssignment sortedLayout in sortedLayouts) { bool flag = false; foreach (LayoutAssignment item in list) { if (AreSpatiallySimilar(item, sortedLayout, diversityDistance)) { flag = true; break; } } if (!flag) { list.Add(sortedLayout); if (list.Count >= count) { break; } } } return list; } private static bool AreSpatiallySimilar(LayoutAssignment a, LayoutAssignment b, float diversityDistance) { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) List<Vector3> list = a.GroupSpawns.Values.Select((CandidateSpawnPoint spawn) => spawn.Position).ToList(); List<Vector3> list2 = b.GroupSpawns.Values.Select((CandidateSpawnPoint spawn) => spawn.Position).ToList(); if (list.Count != list2.Count) { return false; } bool[] array = new bool[list2.Count]; foreach (Vector3 item in list) { int num = -1; float num2 = float.MaxValue; for (int num3 = 0; num3 < list2.Count; num3++) { if (!array[num3]) { float num4 = Vector3.Distance(item, list2[num3]); if (num4 < num2) { num2 = num4; num = num3; } } } if (num < 0 || num2 > diversityDistance) { return false; } array[num] = true; } return true; } } internal static class LayoutReportWriter { public static void WriteReports(long worldUid, string worldName, string seedName, BiomeMapBuilder map, LocationCatalog locations, IReadOnlyList<LayoutAssignment> topLayouts, int candidateCount, CandidateSpawnFinder.RejectionStats rejections, ModConfig config) { string text = Path.Combine(Paths.PluginPath, "SeparateSpawns", "reports", worldUid.ToString()); Directory.CreateDirectory(text); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"Separate Spawns layout report for world {worldUid}"); stringBuilder.AppendLine("World name: " + worldName); stringBuilder.AppendLine("Seed: " + seedName); stringBuilder.AppendLine($"Generated: {DateTime.UtcNow:u}"); stringBuilder.AppendLine($"Biome sample step: {config.BiomeStep.Value}m"); stringBuilder.AppendLine($"Biome split gap distance: {config.BiomeSplitGapDistance.Value}m"); stringBuilder.AppendLine($"Island split gap distance: {config.IslandSplitGapDistance.Value}m"); stringBuilder.AppendLine($"Min patch area: {config.MinPatchArea.Value}m2"); stringBuilder.AppendLine($"Black forest proximity: {config.BlackForestProximity.Value}m"); stringBuilder.AppendLine($"Candidate grid step: {config.GridStep.Value}m"); stringBuilder.AppendLine($"Report radius: {config.ReportRadius.Value}m (inner search radius {config.InnerRadius.Value}m drawn as white circle)"); stringBuilder.AppendLine($"Eligible candidates: {candidateCount}"); stringBuilder.AppendLine($"Rejections: checked={rejections.TotalChecked}, accepted={rejections.Accepted}, meadows={rejections.NotMeadows}, forest={rejections.NoNearbyForest}, coast={rejections.NoAdjacentCoast}, stones={rejections.TooCloseToStones}, chambers={rejections.NotEnoughChambers}, water={rejections.Underwater}"); stringBuilder.AppendLine($"Layout diversity distance: {config.LayoutDiversityDistance.Value}m"); stringBuilder.AppendLine(); BiomeMapBuilder.AppendPatchStatistics(stringBuilder, map.PatchStatistics); for (int i = 0; i < topLayouts.Count; i++) { LayoutAssignment layoutAssignment = topLayouts[i]; stringBuilder.AppendLine($"#{i + 1} score={layoutAssignment.Score:F2} islands={layoutAssignment.IslandScore:F2} distance={layoutAssignment.DistanceScore:F2} meadowsSize={layoutAssignment.MeadowsSizeScore:F2} closest={layoutAssignment.ClosestSpawnDistance:F0}m avgMeadowsArea={layoutAssignment.AverageMeadowsAreaSquareMeters:F0}m2"); foreach (KeyValuePair<string, CandidateSpawnPoint> groupSpawn in layoutAssignment.GroupSpawns) { stringBuilder.AppendLine($" {groupSpawn.Key}: ({groupSpawn.Value.Position.x:F0}, {groupSpawn.Value.Position.z:F0})"); } Texture2D obj = RenderLayout(map, locations, layoutAssignment, config); byte[] array = TextureEncoder.EncodeToPng(obj); Object.Destroy((Object)(object)obj); string arg = ((array.Length > 2 && array[0] == 66 && array[1] == 77) ? "bmp" : "png"); File.WriteAllBytes(Path.Combine(text, $"layout_{i + 1:D2}.{arg}"), array); } File.WriteAllText(Path.Combine(text, "summary.txt"), stringBuilder.ToString()); ModLog.Info("Wrote layout report to " + text); } public static void WriteFailureReport(long worldUid, string worldName, string seedName, int seedRerollAttempt, int maxSeedRerolls, BiomeMapBuilder map, LocationCatalog locations, LayoutGenerationResult generation, int candidateCount, CandidateSpawnFinder.RejectionStats rejections, ModConfig config, string reason) { string text = Path.Combine(Paths.PluginPath, "SeparateSpawns", "reports", worldUid.ToString(), "failures"); Directory.CreateDirectory(text); LayoutAssignment layoutAssignment = ((generation.LastAttempt.GroupSpawns.Count > 0) ? generation.LastAttempt : generation.BestPartialAttempt); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"Separate Spawns FAILED layout report for world {worldUid}"); stringBuilder.AppendLine("World name: " + worldName); stringBuilder.AppendLine("Seed: " + seedName); stringBuilder.AppendLine($"Generated: {DateTime.UtcNow:u}"); stringBuilder.AppendLine($"Biome sample step: {config.BiomeStep.Value}m"); stringBuilder.AppendLine($"Biome split gap distance: {config.BiomeSplitGapDistance.Value}m"); stringBuilder.AppendLine($"Island split gap distance: {config.IslandSplitGapDistance.Value}m"); stringBuilder.AppendLine($"Min patch area: {config.MinPatchArea.Value}m2"); stringBuilder.AppendLine($"Black forest proximity: {config.BlackForestProximity.Value}m"); stringBuilder.AppendLine($"Candidate grid step: {config.GridStep.Value}m"); stringBuilder.AppendLine($"Report radius: {config.ReportRadius.Value}m (inner search radius {config.InnerRadius.Value}m drawn as white circle)"); stringBuilder.AppendLine($"Seed reroll attempt: {seedRerollAttempt}/{maxSeedRerolls}"); stringBuilder.AppendLine($"Layout attempts: {generation.TotalAttempts}"); stringBuilder.AppendLine($"Valid layouts found: {generation.ValidLayouts}"); stringBuilder.AppendLine($"Eligible candidates: {candidateCount}"); stringBuilder.AppendLine($"Last attempt groups placed: {generation.LastAttempt.GroupsPlaced} (complete={generation.LastAttempt.Complete})"); stringBuilder.AppendLine($"Best partial groups placed: {generation.BestPartialAttempt.GroupsPlaced}"); stringBuilder.AppendLine($"Rejections: checked={rejections.TotalChecked}, accepted={rejections.Accepted}, meadows={rejections.NotMeadows}, forest={rejections.NoNearbyForest}, coast={rejections.NoAdjacentCoast}, stones={rejections.TooCloseToStones}, chambers={rejections.NotEnoughChambers}, water={rejections.Underwater}"); stringBuilder.AppendLine("Reason: " + reason); stringBuilder.AppendLine(); BiomeMapBuilder.AppendPatchStatistics(stringBuilder, map.PatchStatistics); foreach (KeyValuePair<string, CandidateSpawnPoint> groupSpawn in layoutAssignment.GroupSpawns) { stringBuilder.AppendLine($" {groupSpawn.Key}: ({groupSpawn.Value.Position.x:F0}, {groupSpawn.Value.Position.z:F0})"); } Texture2D obj = RenderLayout(map, locations, layoutAssignment, config); byte[] array = TextureEncoder.EncodeToPng(obj); Object.Destroy((Object)(object)obj); string arg = ((array.Length > 2 && array[0] == 66 && array[1] == 77) ? "bmp" : "png"); string text2 = $"failure_reroll{seedRerollAttempt:D2}_last_attempt.{arg}"; File.WriteAllBytes(Path.Combine(text, text2), array); File.WriteAllText(Path.Combine(text, $"failure_reroll{seedRerollAttempt:D2}_summary.txt"), stringBuilder.ToString()); ModLog.Info("Wrote failure layout report to " + text + "\\" + text2); } private static Texture2D RenderLayout(BiomeMapBuilder map, LocationCatalog locations, LayoutAssignment layout, ModConfig config) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01f2: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Unknown result type (might be due to invalid IL or missing references) //IL_0269: Unknown result type (might be due to invalid IL or missing references) //IL_026e: Unknown result type (might be due to invalid IL or missing references) //IL_029e: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(1024, 1024, (TextureFormat)4, false); float value = config.ReportRadius.Value; Color32[] array = (Color32[])(object)new Color32[1048576]; WorldGenerator instance = WorldGenerator.instance; for (int i = 0; i < 1024; i++) { for (int j = 0; j < 1024; j++) { float wx = Mathf.Lerp(0f - value, value, (float)j / 1023f); float wz = Mathf.Lerp(0f - value, value, (float)i / 1023f); array[i * 1024 + j] = SampleBiomeColor(instance, wx, wz); } } DrawCircle(array, 1024, value, Vector3.zero, config.InnerRadius.Value, new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue), 1); DrawMarker(array, 1024, value, locations.SacrificialStones, new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue), 4); foreach (Vector3 burialChamber in locations.BurialChambers) { Vector2 val2 = new Vector2(burialChamber.x, burialChamber.z); if (!(((Vector2)(ref val2)).magnitude > value)) { DrawMarker(array, 1024, value, burialChamber, new Color32((byte)180, (byte)120, (byte)60, byte.MaxValue), 1); } } foreach (Vector3 eikthyrAltar in locations.EikthyrAltars) { DrawMarker(array, 1024, value, eikthyrAltar, new Color32((byte)120, (byte)220, byte.MaxValue, byte.MaxValue), 3); } Color32[] array2 = (Color32[])(object)new Color32[6] { new Color32(byte.MaxValue, (byte)64, (byte)64, byte.MaxValue), new Color32((byte)64, byte.MaxValue, (byte)64, byte.MaxValue), new Color32((byte)64, (byte)128, byte.MaxValue, byte.MaxValue), new Color32(byte.MaxValue, (byte)128, byte.MaxValue, byte.MaxValue), new Color32(byte.MaxValue, (byte)220, (byte)64, byte.MaxValue), new Color32((byte)64, byte.MaxValue, (byte)220, byte.MaxValue) }; int num = 0; foreach (CandidateSpawnPoint value2 in layout.GroupSpawns.Values) { DrawMarker(array, 1024, value, value2.Position, array2[num % array2.Length], 5); num++; } val.SetPixels32(array); val.Apply(); return val; } private static Color32 SampleBiomeColor(WorldGenerator generator, float wx, float wz) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Invalid comparison between Unknown and I4 //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Invalid comparison between Unknown and I4 //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Invalid comparison between Unknown and I4 //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected I4, but got Unknown //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Invalid comparison between Unknown and I4 //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Invalid comparison between Unknown and I4 //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Invalid comparison between Unknown and I4 //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Invalid comparison between Unknown and I4 //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) Biome biome = generator.GetBiome(wx, wz, 0.02f, false); bool flag = generator.GetHeight(wx, wz) <= 30f; if ((int)biome == 256 || flag) { return new Color32((byte)20, (byte)40, (byte)90, byte.MaxValue); } if ((int)biome <= 16) { switch (biome - 1) { default: if ((int)biome != 8) { if ((int)biome != 16) { break; } return new Color32((byte)180, (byte)160, (byte)90, byte.MaxValue); } return new Color32((byte)64, (byte)96, (byte)48, byte.MaxValue); case 0: return new Color32((byte)119, (byte)153, (byte)76, byte.MaxValue); case 1: return new Color32((byte)80, (byte)90, (byte)55, byte.MaxValue); case 3: return new Color32((byte)200, (byte)200, (byte)210, byte.MaxValue); case 2: break; } } else { if ((int)biome == 32) { return new Color32((byte)140, (byte)60, (byte)40, byte.MaxValue); } if ((int)biome == 64) { return new Color32((byte)170, (byte)190, (byte)210, byte.MaxValue); } if ((int)biome == 512) { return new Color32((byte)90, (byte)70, (byte)110, byte.MaxValue); } } return new Color32((byte)100, (byte)100, (byte)100, byte.MaxValue); } private static void DrawCircle(Color32[] pixels, int size, float reportRadius, Vector3 center, float worldRadius, Color32 color, int thickness) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) if (!(worldRadius <= 0f) && !(reportRadius <= 0f)) { int num = Mathf.Clamp(Mathf.CeilToInt(worldRadius * 2f), 128, 2048); for (int i = 0; i < num; i++) { float num2 = (float)i / (float)num * (float)Math.PI * 2f; Vector3 world = center + new Vector3(Mathf.Cos(num2) * worldRadius, 0f, Mathf.Sin(num2) * worldRadius); DrawMarker(pixels, size, reportRadius, world, color, thickness); } } } private static void DrawMarker(Color32[] pixels, int size, float radius, Vector3 world, Color32 color, int markerRadius) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) int num = Mathf.RoundToInt(Mathf.InverseLerp(0f - radius, radius, world.x) * (float)(size - 1)); int num2 = Mathf.RoundToInt(Mathf.InverseLerp(0f - radius, radius, world.z) * (float)(size - 1)); for (int i = -markerRadius; i <= markerRadius; i++) { for (int j = -markerRadius; j <= markerRadius; j++) { if (j * j + i * i <= markerRadius * markerRadius) { int num3 = num + j; int num4 = num2 + i; if (num3 >= 0 && num4 >= 0 && num3 < size && num4 < size) { pixels[num4 * size + num3] = color; } } } } } } internal static class LayoutSync { private const string RpcName = "SeparateSpawns.SyncLayout"; private static bool _registered; private static ZRoutedRpc _registeredInstance; private static readonly HashSet<long> UnavailableWarned = new HashSet<long>(); public stat