Decompiled source of SeparateSpawns v0.1.1

SeparateSpawns.dll

Decompiled a week ago
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