Decompiled source of Earthlike Worlds v1.3.1

plugins/ValynWorldTerrain.dll

Decompiled 2 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Valyn Earthlike Worlds")]
[assembly: AssemblyDescription("Seed-based Earth-inspired world generation for Valheim")]
[assembly: AssemblyFileVersion("1.3.1.0")]
[assembly: AssemblyVersion("1.3.1.0")]
namespace Valyn.WorldTerrain;

[HarmonyPatch(typeof(WorldGenerator), "GetBiomeHeight")]
internal static class ClimateHeightBlend
{
	[ThreadStatic]
	private static int depth;

	private static readonly ClimateBiome[] ContinuousTypes = new ClimateBiome[9]
	{
		ClimateBiome.Meadows,
		ClimateBiome.BlackForest,
		ClimateBiome.Swamp,
		ClimateBiome.Mountain,
		ClimateBiome.Plains,
		ClimateBiome.Mistlands,
		ClimateBiome.Ocean,
		ClimateBiome.AshLands,
		ClimateBiome.DeepNorth
	};

	internal static bool Busy => depth > 0;

	private static bool Prefix(WorldGenerator __instance, ref Biome __0, float __1, float __2, ref Color __3, bool __4, bool __5, ref float __result)
	{
		//IL_007f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0085: Expected I4, but got Unknown
		if (depth > 0)
		{
			return true;
		}
		ClimateWorld climateWorld = ClimateTerrain.For(__instance);
		if (climateWorld == null)
		{
			return true;
		}
		ClimateSample sample = climateWorld.Sample(__1, __2);
		if (climateWorld.Revision == 17 && sample.Blend.Count == 1 && (sample.Blend.Ocean == 1f || sample.Blend.Weight(sample.LandBiome) == 1f))
		{
			__0 = (Biome)(int)ClimateTerrain.Native((sample.Blend.Ocean != 1f) ? sample.LandBiome : ClimateBiome.Ocean);
			return true;
		}
		__result = Resolve(__instance, sample, __1, __2, __4, __5, out __3);
		return false;
	}

	internal static float Resolve(WorldGenerator generator, ClimateSample sample, float x, float z, bool pre, bool river, out Color mask)
	{
		//IL_0016: 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_0098: Unknown result type (might be due to invalid IL or missing references)
		double num = 0.0;
		double num2 = 0.0;
		mask = default(Color);
		bool flag = false;
		depth++;
		try
		{
			ClimateBiome[] continuousTypes = ContinuousTypes;
			foreach (ClimateBiome climateBiome in continuousTypes)
			{
				float num3 = sample.Blend.Weight(climateBiome);
				if (!(num3 <= 0f))
				{
					Color mask2;
					float num4 = NativeMountainInput.Height(generator, climateBiome, x, z, out mask2, pre, river);
					num += (double)num4 * (double)num3;
					num2 += (double)num3;
					if (climateBiome == sample.LandBiome || sample.Blend.Ocean == 1f)
					{
						mask = mask2;
						flag = true;
					}
				}
			}
			if (!flag)
			{
				NativeMountainInput.Height(generator, sample.LandBiome, x, z, out mask, pre, river);
			}
		}
		finally
		{
			depth--;
		}
		if (num2 <= 0.0)
		{
			throw new InvalidOperationException("Aligned terrain has no active native height profile");
		}
		float num5 = (float)(num / num2);
		if (float.IsNaN(num5) || float.IsInfinity(num5))
		{
			throw new InvalidOperationException("Aligned terrain produced a non-finite native height");
		}
		ClimateWorld climateWorld = ClimateTerrain.For(generator);
		if (climateWorld != null && climateWorld.Revision == 22)
		{
			return InlandHeightTest.ApplyIncludingMountain(num5, sample.Blend);
		}
		if (climateWorld == null || (climateWorld.Revision != 18 && climateWorld.Revision != 21))
		{
			return num5;
		}
		return InlandHeightTest.Apply(num5, sample.Blend);
	}

	private static void Postfix(WorldGenerator __instance, Biome __0, float __1, float __2, bool __4, bool __5, ref float __result)
	{
		if (depth > 0 || ClimateTerrain.For(__instance) == null || (!float.IsNaN(__result) && !float.IsInfinity(__result)))
		{
			return;
		}
		throw new InvalidOperationException("Aligned terrain produced a non-finite native height");
	}
}
internal static class ClimateTerrain
{
	internal static ClimateWorld For(WorldGenerator generator)
	{
		TerrainPlugin.GenerationContext current = TerrainPlugin.Current;
		if (!TerrainPlugin.Ready || current == null || current.Generator != generator || current.World.m_menu)
		{
			return null;
		}
		return current.Climate;
	}

	internal static Biome Native(ClimateBiome biome)
	{
		return (Biome)(biome switch
		{
			ClimateBiome.Meadows => 1, 
			ClimateBiome.BlackForest => 8, 
			ClimateBiome.Swamp => 2, 
			ClimateBiome.Mountain => 4, 
			ClimateBiome.Plains => 16, 
			ClimateBiome.Mistlands => 512, 
			ClimateBiome.AshLands => 32, 
			ClimateBiome.DeepNorth => 64, 
			_ => 256, 
		});
	}
}
[HarmonyPatch(typeof(WorldGenerator), "GetBaseHeight")]
internal static class ClimateBaseHeight
{
	[ThreadStatic]
	private static int nativeDepth;

	private static readonly MethodInfo BaseHeight = AccessTools.Method(typeof(WorldGenerator), "GetBaseHeight", (Type[])null, (Type[])null);

	internal static float SampleRecipeBase(WorldGenerator generator, float x, float z)
	{
		return (float)BaseHeight.Invoke(generator, new object[3] { x, z, false });
	}

	internal static Func<float, float, float> NativeSampler(WorldGenerator generator)
	{
		Func<float, float, bool, float> sample = (Func<float, float, bool, float>)BaseHeight.CreateDelegate(typeof(Func<float, float, bool, float>), generator);
		return delegate(float x, float z)
		{
			nativeDepth++;
			try
			{
				return sample(x, z, arg3: false);
			}
			finally
			{
				nativeDepth--;
			}
		};
	}

	private static bool Prefix(WorldGenerator __instance, float __0, float __1, bool __2, ref float __result)
	{
		if (nativeDepth > 0)
		{
			return true;
		}
		ClimateWorld climateWorld = ClimateTerrain.For(__instance);
		if (__2 || climateWorld == null)
		{
			return true;
		}
		__result = NativeMountainInput.Base(__instance, climateWorld.Sample(__0, __1));
		return false;
	}
}
[HarmonyPatch(typeof(WorldGenerator), "GetBiome", new Type[]
{
	typeof(float),
	typeof(float),
	typeof(float),
	typeof(bool)
})]
internal static class ClimateBiomeSelection
{
	private static bool Prefix(WorldGenerator __instance, float __0, float __1, float __2, bool __3, ref Biome __result)
	{
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_0097: Unknown result type (might be due to invalid IL or missing references)
		//IL_009d: Expected I4, but got Unknown
		//IL_0078: Unknown result type (might be due to invalid IL or missing references)
		ClimateWorld climateWorld = ClimateTerrain.For(__instance);
		if (climateWorld == null)
		{
			return true;
		}
		ClimateSample sample = climateWorld.Sample(__0, __1);
		__result = (Biome)((sample.BaseHeight <= __2) ? 256 : ((int)ClimateTerrain.Native(sample.LandBiome)));
		if (__3 || (__2 <= 0.02f && sample.BaseHeight <= __2 && sample.Blend.Ocean < 1f))
		{
			Color mask;
			float num = ClimateHeightBlend.Resolve(__instance, sample, __0, __1, pre: false, river: true, out mask);
			if (__3)
			{
				__result = (Biome)((num <= __2) ? 256 : ((int)ClimateTerrain.Native(sample.LandBiome)));
			}
			else if (num > 30f)
			{
				__result = (Biome)(int)ClimateTerrain.Native(sample.LandBiome);
			}
		}
		return false;
	}
}
internal enum ClimateBiome
{
	Ocean,
	Meadows,
	BlackForest,
	Swamp,
	Mountain,
	Plains,
	Mistlands,
	AshLands,
	DeepNorth
}
internal struct ClimateSample
{
	internal float NativeBaseHeight;

	internal float BaseHeight;

	internal float CoastDistance;

	internal float Temperature;

	internal float Moisture;

	internal float Ridge;

	internal float MountainHeight;

	internal float InlandLake;

	internal ClimateBiome Biome;

	internal ClimateBiome LandBiome;

	internal TerrainBlendWeights Blend;
}
internal sealed class ClimateWorld
{
	private struct Landmass
	{
		internal double X;

		internal double Z;

		internal double Width;

		internal double Length;

		internal double Cos;

		internal double Sin;

		internal double Phase;

		internal Landmass(double x, double z, double width, double length, double angle)
		{
			X = x;
			Z = z;
			Width = width;
			Length = length;
			Cos = Math.Cos(angle);
			Sin = Math.Sin(angle);
			Phase = x / 1300.0 + z / 1700.0 + angle * 7.0;
		}

		internal double Distance(double x, double z, out double across, out double along)
		{
			double num = x - X;
			double num2 = z - Z;
			across = num * Cos - num2 * Sin;
			along = num * Sin + num2 * Cos;
			double num3 = across / Width;
			double num4 = along / Length;
			double num5 = Math.Atan2(num4, num3);
			double num6 = 1.0 + 0.19 * Math.Sin(num5 * 3.0 + Phase) + 0.11 * Math.Sin(num5 * 5.0 - Phase);
			return (1.0 - Math.Sqrt(num3 * num3 + num4 * num4) / num6) * Math.Min(Width, Length);
		}
	}

	private readonly uint seed;

	private readonly CoastalTerrain coastal;

	private readonly object cacheIdentity = new object();

	private readonly Landmass[] land;

	private readonly Landmass[] seas;

	private readonly Landmass[] islands;

	private readonly double phase;

	internal readonly int Revision;

	[ThreadStatic]
	private static object previousIdentity;

	[ThreadStatic]
	private static TerrainSampleCache fieldCache;

	[ThreadStatic]
	private static bool previousValid;

	[ThreadStatic]
	private static float previousX;

	[ThreadStatic]
	private static float previousZ;

	[ThreadStatic]
	private static ClimateSample previousSample;

	internal int IslandCount
	{
		get
		{
			if (coastal == null)
			{
				if (islands != null)
				{
					return islands.Length;
				}
				return 0;
			}
			return coastal.IslandCount;
		}
	}

	internal string LayoutDescription
	{
		get
		{
			if (coastal != null)
			{
				return coastal.LayoutDescription;
			}
			return "3 main continents + polar regions";
		}
	}

	internal ClimateWorld(int worldSeed, int revision = 17, Func<float, float, float> nativeBase = null)
	{
		if (revision != 9 && revision != 17 && revision != 18 && revision != 21 && revision != 22)
		{
			throw new ArgumentOutOfRangeException("revision", "Only v17/v18/v21/v22 and their frozen internal v9 edge field are available");
		}
		Revision = revision;
		if (revision >= 17)
		{
			coastal = new CoastalTerrain(worldSeed, revision, nativeBase);
			return;
		}
		seed = (uint)worldSeed;
		phase = Unit(100u) * Math.PI * 2.0;
		land = new Landmass[5]
		{
			new Landmass((Unit(1u) - 0.5) * 400.0, (Unit(2u) - 0.5) * 500.0, 2850.0 + Unit(3u) * 400.0, 3650.0 + Unit(4u) * 600.0, (Unit(5u) - 0.5) * 0.65),
			new Landmass(-6000.0 + (Unit(6u) - 0.5) * 500.0, -1500.0 + (Unit(7u) - 0.5) * 1800.0, 2350.0 + Unit(8u) * 350.0, 3750.0 + Unit(9u) * 650.0, -0.35 + (Unit(10u) - 0.5) * 0.6),
			new Landmass(5900.0 + (Unit(11u) - 0.5) * 600.0, 1800.0 + (Unit(12u) - 0.5) * 1800.0, 2350.0 + Unit(13u) * 350.0, 3750.0 + Unit(14u) * 650.0, -0.4 + (Unit(15u) - 0.5) * 0.65),
			new Landmass((Unit(16u) - 0.5) * 1000.0, 9050.0, 4644.0, 1595.0, (Unit(17u) - 0.5) * 0.1),
			new Landmass((Unit(18u) - 0.5) * 1100.0, -9100.0, 4590.0, 1375.0, (Unit(19u) - 0.5) * 0.1)
		};
		seas = new Landmass[2]
		{
			new Landmass(-3200.0 + (Unit(70u) - 0.5) * 400.0, -1200.0, 900.0 + Unit(71u) * 250.0, 5100.0, -0.12),
			new Landmass(3300.0 + (Unit(72u) - 0.5) * 400.0, 1400.0, 900.0 + Unit(73u) * 250.0, 5100.0, -0.12)
		};
		islands = CreateIslands();
	}

	private Landmass[] CreateIslands()
	{
		List<Landmass> list = new List<Landmass>();
		List<double[]> list2 = new List<double[]>();
		for (int i = 0; i < 5; i++)
		{
			int num = ((i == 0) ? 6 : ((i >= 3) ? 1 : 5));
			for (uint num2 = 0u; num2 < 512; num2++)
			{
				uint num3 = (uint)(400 + i * 10000) + num2 * 12;
				double num4 = (Unit(num3) - 0.5) * 17200.0;
				double num5 = (Unit(num3 + 1) - 0.5) * 17200.0;
				double num6 = Math.Sqrt(num4 * num4 + num5 * num5);
				if (num6 < 2500.0 || num6 > 8400.0 || North((float)num4, (float)num5) || South((float)num4, (float)num5))
				{
					continue;
				}
				bool flag = true;
				foreach (double[] item in list2)
				{
					if (Math.Pow(num4 - item[0], 2.0) + Math.Pow(num5 - item[1], 2.0) < 6250000.0)
					{
						flag = false;
						break;
					}
				}
				if (!flag || Calculate((float)num4, (float)num5).CoastDistance > -1100f)
				{
					continue;
				}
				double num7 = Unit(num3 + 2) * Math.PI * 2.0;
				double num8 = Math.Cos(num7);
				double num9 = Math.Sin(num7);
				List<Landmass> list3 = new List<Landmass>();
				for (int j = 0; j < num; j++)
				{
					double num10 = 0.0;
					double num11 = 0.0;
					double num12;
					double num13;
					switch (i)
					{
					case 0:
						num10 = ((double)j - 2.5) * 390.0;
						num11 = 75.0 * Math.Sin((double)j * 0.9);
						num12 = 90.0 + Unit((uint)((int)num3 + j + 3)) * 35.0;
						num13 = 160.0 + Unit((uint)((int)num3 + j + 4)) * 45.0;
						break;
					case 1:
					{
						double num15 = -0.9 + (double)j * 0.45;
						num10 = 1100.0 * Math.Sin(num15);
						num11 = 550.0 * Math.Cos(num15);
						num12 = 160.0 + Unit((uint)((int)num3 + j + 3)) * 45.0;
						num13 = 235.0 + Unit((uint)((int)num3 + j + 4)) * 55.0;
						break;
					}
					case 2:
						if (j > 0)
						{
							double num14 = (double)(j - 1) * Math.PI / 2.0;
							num10 = 780.0 * Math.Cos(num14);
							num11 = 780.0 * Math.Sin(num14);
						}
						num12 = ((j == 0) ? 300.0 : (160.0 + Unit((uint)((int)num3 + j + 3)) * 45.0));
						num13 = ((j == 0) ? 390.0 : (225.0 + Unit((uint)((int)num3 + j + 4)) * 55.0));
						break;
					default:
						num12 = 210.0 + Unit(num3 + 3) * 70.0;
						num13 = 290.0 + Unit(num3 + 4) * 75.0;
						break;
					}
					double num16 = num4 + num10 * num8 - num11 * num9;
					double num17 = num5 + num10 * num9 + num11 * num8;
					ClimateSample climateSample = Calculate((float)num16, (float)num17);
					double num18 = 1.6 * Math.Max(num12, num13) + 160.0;
					if ((double)climateSample.CoastDistance > 0.0 - (Math.Max(num12, num13) + 450.0) || Math.Sqrt(num16 * num16 + num17 * num17) > 9000.0 || Math.Sqrt(num16 * num16 + (num17 + 4000.0) * (num17 + 4000.0)) > 11900.0 - num18 || Math.Sqrt(num16 * num16 + (num17 - 4000.0) * (num17 - 4000.0)) > 11900.0 - num18)
					{
						flag = false;
						break;
					}
					double x = num16 + 440.0 * Noise(num16 / 2600.0, num17 / 2600.0, 21u);
					double z = num17 + 440.0 * Noise(num16 / 2600.0, num17 / 2600.0, 22u);
					list3.Add(new Landmass(x, z, num12, num13, num7 + 0.35 * Math.Sin(j * 2)));
				}
				if (flag)
				{
					list.AddRange(list3);
					list2.Add(new double[2] { num4, num5 });
					break;
				}
			}
		}
		return list.ToArray();
	}

	private static uint Mix(uint n)
	{
		n ^= n >> 16;
		n *= 2146121005;
		n ^= n >> 15;
		n *= 2221713035u;
		return n ^ (n >> 16);
	}

	private double Unit(uint salt)
	{
		return (double)Mix(seed ^ Mix(salt)) / 4294967295.0;
	}

	private double Value(int x, int z, uint salt)
	{
		return (double)Mix(seed ^ (uint)(x * -1640531527) ^ (uint)(z * -2048144789) ^ Mix(salt)) / 2147483647.5 - 1.0;
	}

	private static double Fade(double t)
	{
		return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
	}

	private static double Lerp(double a, double b, double t)
	{
		return a + (b - a) * t;
	}

	internal static double Smooth(double lo, double hi, double v)
	{
		double num = Math.Max(0.0, Math.Min(1.0, (v - lo) / (hi - lo)));
		return num * num * (3.0 - 2.0 * num);
	}

	private double Noise(double x, double z, uint salt)
	{
		int num = (int)Math.Floor(x);
		int num2 = (int)Math.Floor(z);
		double t = Fade(x - (double)num);
		double t2 = Fade(z - (double)num2);
		return Lerp(Lerp(Value(num, num2, salt), Value(num + 1, num2, salt), t), Lerp(Value(num, num2 + 1, salt), Value(num + 1, num2 + 1, salt), t), t2);
	}

	internal static float Angle(float x, float z)
	{
		return (float)Math.Sin((float)((double)(float)Math.Atan2(x, z) * 20.0));
	}

	internal static bool North(float x, float z)
	{
		return (float)Math.Sqrt((double)x * (double)x + (double)(z + 4000f) * (double)(z + 4000f)) > (float)(12000.0 + (double)Angle(x, z) * 100.0);
	}

	internal static bool South(float x, float z)
	{
		return (double)(float)Math.Sqrt((double)x * (double)x + (double)(z - 4000f) * (double)(z - 4000f)) > 12000.0 + (double)Angle(x, z) * 100.0;
	}

	internal ClimateSample Sample(float x, float z)
	{
		if (previousValid && previousIdentity == cacheIdentity && x == previousX && z == previousZ)
		{
			return previousSample;
		}
		TerrainSampleCache terrainSampleCache = ((Revision >= 11) ? (fieldCache ?? (fieldCache = new TerrainSampleCache())) : null);
		if (terrainSampleCache == null || !terrainSampleCache.TryGet(cacheIdentity, x, z, out var sample))
		{
			sample = Calculate(x, z);
			terrainSampleCache?.Store(cacheIdentity, x, z, sample);
		}
		previousIdentity = cacheIdentity;
		previousValid = true;
		previousX = x;
		previousZ = z;
		previousSample = sample;
		return sample;
	}

	private ClimateSample Calculate(float x, float z)
	{
		if (coastal != null)
		{
			return coastal.Sample(x, z);
		}
		double num = Math.Sqrt((double)x * (double)x + (double)z * (double)z);
		if (num >= 10500.0)
		{
			return new ClimateSample
			{
				BaseHeight = -2f,
				CoastDistance = -2000f,
				Biome = ClimateBiome.Ocean,
				Blend = TerrainBlendWeights.Only(ClimateBiome.Ocean)
			};
		}
		double num2 = (double)x + 440.0 * Noise((double)x / 2600.0, (double)z / 2600.0, 21u);
		double num3 = (double)z + 440.0 * Noise((double)x / 2600.0, (double)z / 2600.0, 22u);
		double val = double.NegativeInfinity;
		double num4 = 0.0;
		for (int i = 0; i < land.Length; i++)
		{
			double num5 = land[i].Distance(num2, num3, out var across, out var along);
			num5 += 220.0 * Noise(num2 / 1200.0, num3 / 1200.0, (uint)(30 + i));
			val = Math.Max(val, num5);
			if (i < 3)
			{
				double num6 = 420.0 * Noise(along / 1800.0, (double)i + phase, 40u);
				double num7 = (across - num6 - 550.0) / 650.0;
				double val2 = Math.Exp((0.0 - num7) * num7) * (1.0 - Smooth(2100.0, 3300.0, Math.Abs(along))) * Smooth(250.0, 850.0, num5);
				num4 = Math.Max(num4, val2);
			}
		}
		Landmass[] array = seas;
		foreach (Landmass landmass in array)
		{
			val = Math.Min(val, 0.0 - landmass.Distance(num2, num3, out var _, out var _));
		}
		double num8 = 1250.0 + 200.0 * Noise((double)z / 2800.0, phase, 50u);
		double num9 = 1400.0 + 250.0 * Math.Sin(phase);
		double val3 = (Math.Sqrt(Math.Pow((num2 - num8) / 1400.0, 2.0) + Math.Pow((num3 - num9) / 1050.0, 2.0)) - 1.0) * 1050.0;
		double val4 = Math.Max(Math.Max(Math.Abs(num3 - num9 - 160.0 * Noise(num2 / 1700.0, phase, 51u)) - 480.0, 1200.0 - num2), num2 - 3800.0);
		val = Math.Min(val, Math.Min(val3, val4));
		val = Math.Max(val, 1250.0 - num);
		val = Math.Min(val, (9600.0 - num) * 4.0);
		num4 *= Smooth(1400.0, 2200.0, num) * Smooth(80.0, 700.0, val);
		double num10 = double.NegativeInfinity;
		if (islands != null)
		{
			for (int k = 0; k < islands.Length; k++)
			{
				double num11 = Math.Max(val, num10);
				double num12 = Math.Min(islands[k].Width, islands[k].Length);
				double num13 = (num12 + 25.0 - num11) * Math.Max(islands[k].Width, islands[k].Length) * 1.3 / num12;
				if (!(num13 <= 0.0) && !((num2 - islands[k].X) * (num2 - islands[k].X) + (num3 - islands[k].Z) * (num3 - islands[k].Z) > num13 * num13))
				{
					double across3;
					double along3;
					double val5 = islands[k].Distance(num2, num3, out across3, out along3) + 25.0 * Noise(num2 / 220.0, num3 / 220.0, (uint)(200 + k));
					num10 = Math.Max(num10, val5);
				}
			}
		}
		val = Math.Max(val, num10);
		double num14 = 0.15 + 0.275 * Math.Tanh(val / 2200.0) + 0.52 * num4;
		if (num10 > 0.0)
		{
			num14 += 0.1 * Smooth(0.0, 200.0, num10);
		}
		if (val < 0.0)
		{
			num14 -= (100.0 + 35.0 * Noise(num2 / 3000.0, num3 / 3000.0, 90u)) / 200.0 * Smooth(80.0, 1600.0, 0.0 - val);
		}
		if (num > 10000.0)
		{
			num14 = Lerp(num14, -0.2, Smooth(10000.0, 10490.0, num));
		}
		if (num > 10490.0)
		{
			num14 = Lerp(num14, -2.0, Smooth(10490.0, 10500.0, num));
		}
		double num15 = 0.68 - 0.32 * (double)z / 10000.0 + 0.1 * Noise((double)x / 4200.0, (double)z / 4200.0, 60u) - 0.2 * num4;
		double num16 = 0.5 + 0.3 * Noise(num2 / 2400.0, num3 / 2400.0, 61u) + 0.1 * Noise(num2 / 5000.0, num3 / 5000.0, 62u) + 0.08 * Math.Exp((0.0 - Math.Max(0.0, val)) / 1200.0) - 0.08 * num4;
		double num17 = Angle(x, z) * 100f;
		ClimateBiome biome = ((!(val <= 0.0)) ? (South(x, z) ? ClimateBiome.AshLands : (North(x, z) ? ClimateBiome.DeepNorth : ((num < 1050.0) ? ClimateBiome.Meadows : ((num14 > 0.4 && num > 1700.0) ? ClimateBiome.Mountain : ((num > 6000.0 + num17 && num < 9700.0 && num16 > 0.43) ? ClimateBiome.Mistlands : ((num > 2200.0 && num < 6000.0 && num14 < 0.24 && num4 < 0.1 && num16 > 0.55) ? ClimateBiome.Swamp : ((num > 3200.0 + num17 && num < 8000.0 && num15 > 0.45 && num16 < 0.54) ? ClimateBiome.Plains : ((num < 4200.0 + num17 && num15 > 0.48 && num16 < 0.49) ? ClimateBiome.Meadows : ClimateBiome.BlackForest)))))))) : ClimateBiome.Ocean);
		ClimateSample result = new ClimateSample
		{
			BaseHeight = (float)num14,
			CoastDistance = (float)val,
			Temperature = (float)num15,
			Moisture = (float)num16,
			Ridge = (float)num4,
			Biome = biome
		};
		TerrainBlendWeights blend = TerrainBlendWeights.FromFields(num, num17, num15, num16, num14, num4, 0.04);
		double v = Math.Sqrt((double)x * (double)x + (double)(z + 4000f) * (double)(z + 4000f)) - (12000.0 + num17);
		double v2 = Math.Sqrt((double)x * (double)x + (double)(z - 4000f) * (double)(z - 4000f)) - (12000.0 + num17);
		blend.Layer(ClimateBiome.DeepNorth, Smooth(-60.0, 60.0, v));
		blend.Layer(ClimateBiome.AshLands, Smooth(-60.0, 60.0, v2));
		blend.Layer(ClimateBiome.Ocean, 1.0 - Smooth(0.0, 120.0, val));
		result.Blend = blend;
		if (blend.Mountain > 0f)
		{
			double num18 = Smooth(0.5, 0.92, num4) * (0.55 + 0.45 * Smooth(-0.15, 0.6, Noise(num2 / 1250.0, num3 / 1500.0, 81u)));
			double num19 = Smooth(-0.05, 0.6, Noise(num2 / 800.0, num3 / 1000.0, 82u)) * Smooth(0.44, 0.62, num14);
			double num20 = num14 + 0.03 + 0.04 * Noise(num2 / 500.0, num3 / 600.0, 80u) - 0.06 * num19 + 0.26 * num18;
			num20 += 0.004 * Noise((double)x / 24.0, (double)z / 24.0, 83u) + 0.002 * Noise((double)x / 9.0, (double)z / 9.0, 84u);
			num20 += 0.1 * Noise((double)x / 60.0, (double)z / 60.0, 85u) * num18;
			result.MountainHeight = (float)num20;
		}
		return result;
	}
}
internal sealed class CoastalTerrain
{
	private sealed class LandBalance
	{
		internal double Strength;

		internal double Before;

		internal double After;
	}

	private struct BalancePoint
	{
		internal double Native;

		internal double Mainland;
	}

	private struct Gates
	{
		internal double Start;

		internal double MeadowEnd;

		internal double PlainStart;

		internal double PlainEnd;

		internal double SwampStart;

		internal double SwampEnd;

		internal double MistStart;

		internal double MistEnd;

		internal double MountainStart;
	}

	private readonly uint seed;

	private readonly DriftLayout drift;

	private readonly Func<float, float, float> nativeBase;

	private readonly Lazy<LandBalance> landBalance;

	internal int IslandCount => drift.IslandCount;

	internal string LayoutDescription
	{
		get
		{
			string text = "seeded " + drift.Style + " arrangement; plates=" + drift.PlateCount + "; sea basins=" + drift.SeaCount;
			if (!landBalance.IsValueCreated)
			{
				return text + "; land calibration=pending";
			}
			LandBalance value = landBalance.Value;
			return text + string.Format(CultureInfo.InvariantCulture, "; calibrated lift={0:0.0}m; footprint extension={1:0} field units; sampled base land={2:0.0}%->{3:0.0}%; target=34%; cap={4}", (0.2 + 0.25 * value.Strength) * 200.0, value.Strength * 1400.0, value.Before * 100.0, value.After * 100.0, (value.Strength >= 1.0) ? "reached" : "clear");
		}
	}

	internal CoastalTerrain(int worldSeed, int revision = 17, Func<float, float, float> nativeBase = null)
	{
		if (revision != 17 && revision != 18 && revision != 21 && revision != 22)
		{
			throw new ArgumentOutOfRangeException("revision", "Only v17/v18/v21/v22 saved terrain is supported");
		}
		seed = (uint)worldSeed;
		this.nativeBase = nativeBase ?? throw new ArgumentNullException("nativeBase");
		drift = new DriftLayout(worldSeed);
		landBalance = new Lazy<LandBalance>(CalibrateLand, isThreadSafe: true);
	}

	private static uint Mix(uint n)
	{
		n ^= n >> 16;
		n *= 2146121005;
		n ^= n >> 15;
		n *= 2221713035u;
		return n ^ (n >> 16);
	}

	private double Value(int x, int z, uint salt)
	{
		return (double)Mix(seed ^ (uint)(x * -1640531527) ^ (uint)(z * -2048144789) ^ Mix(salt)) / 2147483647.5 - 1.0;
	}

	private static double Fade(double t)
	{
		return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
	}

	private static double Lerp(double a, double b, double t)
	{
		return a + (b - a) * t;
	}

	private static double S(double a, double b, double t)
	{
		return ClimateWorld.Smooth(a, b, t);
	}

	private double Noise(double x, double z, uint salt)
	{
		int num = (int)Math.Floor(x);
		int num2 = (int)Math.Floor(z);
		double t = Fade(x - (double)num);
		double t2 = Fade(z - (double)num2);
		return Lerp(Lerp(Value(num, num2, salt), Value(num + 1, num2, salt), t), Lerp(Value(num, num2 + 1, salt), Value(num + 1, num2 + 1, salt), t), t2);
	}

	private ClimateSample NativeMacro(float x, float z, double influence, out double mainland)
	{
		double num = (double)x + influence * (130.0 * Noise((double)x / 820.0, (double)z / 820.0, 1001u) + 38.0 * Noise((double)x / 250.0, (double)z / 250.0, 1002u) + 9.0 * Noise((double)x / 80.0, (double)z / 80.0, 1003u));
		double num2 = (double)z + influence * (130.0 * Noise((double)x / 820.0, (double)z / 820.0, 1004u) + 38.0 * Noise((double)x / 250.0, (double)z / 250.0, 1005u) + 9.0 * Noise((double)x / 80.0, (double)z / 80.0, 1006u));
		return drift.Sample((float)num, (float)num2, out mainland);
	}

	private LandBalance CalibrateLand()
	{
		List<BalancePoint> list = new List<BalancePoint>();
		for (int i = -8400; i <= 8400; i += 300)
		{
			for (int j = -8400; j <= 8400; j += 300)
			{
				double num = Math.Sqrt((double)j * (double)j + (double)i * (double)i);
				if (!(num > 8500.0) && !ClimateWorld.North(j, i) && !ClimateWorld.South(j, i))
				{
					NativeMacro(j, i, S(1000.0, 1600.0, num) * (1.0 - S(8800.0, 9200.0, num)), out var mainland);
					double num2 = nativeBase(j, i);
					if (double.IsNaN(num2) || double.IsInfinity(num2))
					{
						throw new InvalidOperationException("Native land calibration produced a non-finite sample");
					}
					list.Add(new BalancePoint
					{
						Native = num2,
						Mainland = mainland
					});
				}
			}
		}
		int num3 = CountLand(list, 0.0, 0.155);
		int num4 = (int)Math.Ceiling(0.34 * (double)list.Count);
		int num5 = 0;
		int num6 = 10000;
		while (num5 < num6)
		{
			int num7 = (num5 + num6) / 2;
			if (CountLand(list, (double)num7 / 10000.0, 0.155) >= num4)
			{
				num6 = num7;
			}
			else
			{
				num5 = num7 + 1;
			}
		}
		double strength = (double)num5 / 10000.0;
		int num8 = CountLand(list, strength, 0.155);
		return new LandBalance
		{
			Strength = strength,
			Before = (double)num3 / (double)list.Count,
			After = (double)num8 / (double)list.Count
		};
	}

	private static double MainlandLift(double mainland, double strength)
	{
		return (0.2 + 0.25 * strength) * S(-400.0 - 1400.0 * strength, 1700.0 - 500.0 * strength, mainland);
	}

	private static int CountLand(List<BalancePoint> points, double strength, double threshold)
	{
		int num = 0;
		foreach (BalancePoint point in points)
		{
			double num2 = 0.3 * (1.0 - S(-2500.0, 500.0, point.Mainland));
			if ((double)(float)(point.Native + MainlandLift(point.Mainland, strength) - num2) >= threshold)
			{
				num++;
			}
		}
		return num;
	}

	private ClimateSample AlignedSample(float x, float z, double r, double actual, double temp, double moist, double ridge, double coast, float native, Gates g, double north, double northInset, double south, double southInset)
	{
		if (actual > 0.35)
		{
			double num = actual - 0.35;
			actual = Lerp(0.35 + num / (1.0 + num / 0.04), actual, S(1050.0, 1700.0, r));
		}
		double value = 1.0 - S(g.Start - 60.0, g.Start + 60.0, r);
		double value2 = (1.0 - S(g.MeadowEnd - 100.0, g.MeadowEnd + 100.0, r)) * S(0.46, 0.5, temp) * (1.0 - S(0.47, 0.51, moist));
		double value3 = S(g.PlainStart - 100.0, g.PlainStart + 100.0, r) * (1.0 - S(g.PlainEnd - 100.0, g.PlainEnd + 100.0, r)) * S(0.43, 0.47, temp) * (1.0 - S(0.52, 0.56, moist));
		double value4 = S(g.SwampStart - 160.0, g.SwampStart + 160.0, r) * (1.0 - S(g.SwampEnd - 160.0, g.SwampEnd + 160.0, r)) * (1.0 - S(0.15, 0.25, actual)) * S(0.52, 0.58, moist);
		double value5 = S(g.MistStart - 100.0, g.MistStart + 100.0, r) * (1.0 - S(g.MistEnd - 100.0, g.MistEnd + 100.0, r)) * S(0.41, 0.45, moist);
		double value6 = S(0.4, 0.45, actual) * S(g.MountainStart - 100.0, g.MountainStart + 100.0, r);
		TerrainBlendWeights blend = new TerrainBlendWeights
		{
			Forest = 1f
		};
		blend.Layer(ClimateBiome.Meadows, value2);
		blend.Layer(ClimateBiome.Plains, value3);
		blend.Layer(ClimateBiome.Swamp, value4);
		blend.Layer(ClimateBiome.Mistlands, value5);
		blend.Layer(ClimateBiome.Mountain, value6);
		blend.Layer(ClimateBiome.Meadows, value);
		blend.Layer(ClimateBiome.DeepNorth, S(northInset - 60.0, northInset + 60.0, north));
		blend.Layer(ClimateBiome.AshLands, S(southInset - 60.0, southInset + 60.0, south));
		ClimateBiome climateBiome = ((south > southInset) ? ClimateBiome.AshLands : ((north > northInset) ? ClimateBiome.DeepNorth : ((r < g.Start) ? ClimateBiome.Meadows : ((actual >= 0.45 && r > g.MountainStart) ? ClimateBiome.Mountain : ((r > g.MistStart && r < g.MistEnd && moist > 0.43) ? ClimateBiome.Mistlands : ((r > g.SwampStart && r < g.SwampEnd && actual < 0.2 && moist > 0.55) ? ClimateBiome.Swamp : ((r > g.PlainStart && r < g.PlainEnd && temp > 0.45 && moist < 0.54) ? ClimateBiome.Plains : ((r < g.MeadowEnd && temp > 0.48 && moist < 0.49) ? ClimateBiome.Meadows : ClimateBiome.BlackForest))))))));
		blend.Layer(ClimateBiome.Ocean, 1.0 - S(0.0, 0.02, actual));
		return new ClimateSample
		{
			NativeBaseHeight = native,
			BaseHeight = (float)actual,
			CoastDistance = (float)coast,
			Temperature = (float)temp,
			Moisture = (float)moist,
			Ridge = (float)ridge,
			Biome = ((!(actual <= 0.02)) ? climateBiome : ClimateBiome.Ocean),
			LandBiome = climateBiome,
			Blend = blend
		};
	}

	internal ClimateSample Sample(float x, float z)
	{
		double num = Math.Sqrt((double)x * (double)x + (double)z * (double)z);
		double num2 = S(1000.0, 1600.0, num) * (1.0 - S(8800.0, 9200.0, num));
		double mainland;
		ClimateSample climateSample = NativeMacro(x, z, num2, out mainland);
		double num3 = climateSample.CoastDistance;
		num3 += num2 * (56.0 * Noise((double)x / 460.0, (double)z / 480.0, 1007u) + 17.0 * Noise((double)x / 145.0, (double)z / 160.0, 1008u)) * (1.0 - S(700.0, 1300.0, Math.Abs(num3)));
		num3 += num2 * (62.0 * Noise((double)x / 510.0, (double)z / 460.0, 1065u) + 19.0 * Noise((double)x / 110.0, (double)z / 135.0, 1066u)) * (1.0 - S(180.0, 550.0, Math.Abs(num3)));
		double num4 = (double)climateSample.Temperature + num2 * (0.03 * Noise((double)x / 850.0, (double)z / 900.0, 1030u) + 0.008 * Noise((double)x / 260.0, (double)z / 290.0, 1031u));
		double num5 = (double)climateSample.Moisture + num2 * (0.038 * Noise((double)x / 760.0, (double)z / 820.0, 1032u) + 0.01 * Noise((double)x / 240.0, (double)z / 270.0, 1033u));
		num4 += num2 * 0.009 * Noise((double)x / 410.0, (double)z / 460.0, 1067u);
		num5 += num2 * 0.013 * Noise((double)x / 420.0, (double)z / 390.0, 1068u);
		double num6 = num2 * (48.0 + 35.0 * Noise((double)x / 740.0, (double)z / 830.0, 1040u) + 12.0 * Noise((double)x / 240.0, (double)z / 270.0, 1041u));
		double num7 = num2 * (48.0 + 35.0 * Noise((double)x / 790.0, (double)z / 710.0, 1042u) + 12.0 * Noise((double)x / 260.0, (double)z / 230.0, 1043u));
		double num8 = ClimateWorld.Angle(x, z) * 100f;
		Gates g = new Gates
		{
			Start = 1050.0 + num7,
			MeadowEnd = 4200.0 + num8 - num7,
			PlainStart = 3200.0 + num8 + num6,
			PlainEnd = 8000.0 - num7,
			SwampStart = 2200.0 + num6,
			SwampEnd = 6000.0 - num7,
			MistStart = 6000.0 + num8 + num6,
			MistEnd = 9700.0 - num7,
			MountainStart = 1700.0 + num6
		};
		double north = Math.Sqrt((double)x * (double)x + (double)(z + 4000f) * (double)(z + 4000f)) - (12000.0 + num8);
		double south = Math.Sqrt((double)x * (double)x + (double)(z - 4000f) * (double)(z - 4000f)) - (12000.0 + num8);
		double northInset = num6 * 0.65;
		double southInset = num7 * 0.65;
		double num9 = MainlandLift(mainland, landBalance.Value.Strength);
		double num10 = 0.3 * (1.0 - S(-2500.0, 500.0, mainland));
		float num11 = nativeBase(x, z);
		double actual = (double)num11 + (num9 - num10) * (1.0 - S(8800.0, 9800.0, num));
		return AlignedSample(x, z, num, actual, num4, num5, climateSample.Ridge, num3, num11, g, north, northInset, south, southInset);
	}
}
internal static class CoverageMetrics
{
	internal static int LargestComponent(bool[] grid, int width)
	{
		if (grid == null || width <= 0 || grid.Length % width != 0)
		{
			throw new ArgumentException("Invalid coverage grid");
		}
		bool[] array = new bool[grid.Length];
		Queue<int> queue = new Queue<int>();
		int num = 0;
		for (int i = 0; i < grid.Length; i++)
		{
			if (!grid[i] || array[i])
			{
				continue;
			}
			int num2 = 0;
			array[i] = true;
			queue.Enqueue(i);
			while (queue.Count > 0)
			{
				int num3 = queue.Dequeue();
				num2++;
				for (int j = 0; j < 4; j++)
				{
					if ((j != 0 || num3 % width != 0) && (j != 1 || num3 % width != width - 1))
					{
						int num4 = num3 + j switch
						{
							2 => -width, 
							1 => 1, 
							0 => -1, 
							_ => width, 
						};
						if (num4 >= 0 && num4 < grid.Length && !array[num4] && grid[num4])
						{
							array[num4] = true;
							queue.Enqueue(num4);
						}
					}
				}
			}
			num = Math.Max(num, num2);
		}
		return num;
	}
}
internal sealed class DriftLayout
{
	private struct Lobe
	{
		internal double X;

		internal double Z;

		internal double W;

		internal double L;

		internal double C;

		internal double S;

		internal double Phase;

		internal Lobe(double x, double z, double w, double l, double angle, double phase)
		{
			X = x;
			Z = z;
			W = w;
			L = l;
			C = Math.Cos(angle);
			S = Math.Sin(angle);
			Phase = phase;
		}

		internal double Distance(double x, double z)
		{
			double num = x - X;
			double num2 = z - Z;
			double num3 = (num * C - num2 * S) / W;
			double num4 = (num * S + num2 * C) / L;
			double num5 = Math.Atan2(num4, num3);
			double num6 = 1.0 + 0.13 * Math.Sin(3.0 * num5 + Phase) + 0.07 * Math.Sin(5.0 * num5 - Phase);
			return (1.0 - Math.Sqrt(num3 * num3 + num4 * num4) / num6) * Math.Min(W, L);
		}
	}

	private struct Range
	{
		internal double X;

		internal double Z;

		internal double C;

		internal double S;

		internal double W;

		internal double L;

		internal double Phase;

		internal Range(double x, double z, double angle, double w, double l, double phase)
		{
			X = x;
			Z = z;
			C = Math.Cos(angle);
			S = Math.Sin(angle);
			W = w;
			L = l;
			Phase = phase;
		}
	}

	private readonly uint seed;

	private readonly ClimateWorld edge;

	private readonly Lobe[] land;

	private readonly Lobe[] seas;

	private readonly Lobe[] lakes;

	private readonly Lobe[] islands;

	private readonly Lobe? chainSaddle;

	private readonly Range[] ranges;

	internal readonly string Style;

	internal readonly int PlateCount;

	internal int IslandCount
	{
		get
		{
			if (islands != null)
			{
				return islands.Length;
			}
			return 0;
		}
	}

	internal int LakeCount => lakes.Length;

	internal int SeaCount => seas.Length;

	private static uint Mix(uint n)
	{
		n ^= n >> 16;
		n *= 2146121005;
		n ^= n >> 15;
		n *= 2221713035u;
		return n ^ (n >> 16);
	}

	private double Unit(uint n)
	{
		return (double)Mix(seed ^ Mix(n)) / 4294967295.0;
	}

	private double Value(int x, int z, uint salt)
	{
		return (double)Mix(seed ^ (uint)(x * -1640531527) ^ (uint)(z * -2048144789) ^ Mix(salt)) / 2147483647.5 - 1.0;
	}

	private static double Fade(double t)
	{
		return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
	}

	private static double Lerp(double a, double b, double t)
	{
		return a + (b - a) * t;
	}

	private static double Smooth(double a, double b, double t)
	{
		return ClimateWorld.Smooth(a, b, t);
	}

	private double Noise(double x, double z, uint salt)
	{
		int num = (int)Math.Floor(x);
		int num2 = (int)Math.Floor(z);
		double t = Fade(x - (double)num);
		double t2 = Fade(z - (double)num2);
		return Lerp(Lerp(Value(num, num2, salt), Value(num + 1, num2, salt), t), Lerp(Value(num, num2 + 1, salt), Value(num + 1, num2 + 1, salt), t), t2);
	}

	private static double Join(double a, double b, double width)
	{
		double num = Math.Max(0.0, width - Math.Abs(a - b)) / width;
		return Math.Max(a, b) + num * num * width * 0.25;
	}

	internal DriftLayout(int worldSeed, int revision = 13)
	{
		if (revision != 13)
		{
			throw new ArgumentOutOfRangeException("revision", "Frozen v13 layout is required by v17");
		}
		seed = (uint)worldSeed;
		edge = new ClimateWorld(worldSeed, 9);
		double num = Unit(6000u);
		double num2 = Unit(6001u) * Math.PI * 2.0;
		Style = ((num < 0.34) ? "connected" : ((num < 0.67) ? "mixed" : "separated"));
		PlateCount = 4 + ((Unit(6002u) > 0.55) ? 1 : 0);
		List<Lobe> list = new List<Lobe>();
		List<Range> list2 = new List<Range>();
		List<double[]> list3 = new List<double[]>();
		for (int i = 0; i < PlateCount; i++)
		{
			uint num3 = (uint)(6100 + i * 100);
			double num4 = num2 + (double)(i - 1) * Math.PI * 2.0 / (double)(PlateCount - 1) + (Unit(num3) - 0.5) * 0.55;
			double num5 = ((Style == "connected") ? (2800.0 + Unit(num3 + 1) * 900.0) : ((Style == "mixed") ? (3900.0 + Unit(num3 + 1) * 1300.0) : (5600.0 + Unit(num3 + 1) * 800.0)));
			double num6 = ((i == 0) ? ((Unit(num3 + 2) - 0.5) * 500.0) : (Math.Cos(num4) * num5));
			double num7 = ((i == 0) ? ((Unit(num3 + 3) - 0.5) * 500.0) : (Math.Sin(num4) * num5 * 0.88));
			double num8 = Unit(num3 + 4) * Math.PI * 2.0;
			double num9 = ((Style == "separated") ? 0.84 : 1.0);
			num9 *= 1.07 + 0.04 * Unit(num3 + 46);
			double num10 = (2200.0 + Unit(num3 + 5) * 550.0) * num9;
			double num11 = (2500.0 + Unit(num3 + 6) * 800.0) * num9;
			if (i == 0 && Style == "connected")
			{
				num10 *= 1.15;
				num11 *= 1.1;
			}
			list.Add(new Lobe(num6, num7, num10, num11, num8, Unit(num3 + 7) * Math.PI * 2.0));
			list3.Add(new double[2] { num6, num7 });
			int num12 = 2 + ((Unit(num3 + 8) > 0.6) ? 1 : 0);
			for (int j = 0; j < num12; j++)
			{
				uint num13 = num3 + (uint)(10 + j * 5);
				double num14 = num8 + (double)j * Math.PI * 2.0 / (double)num12 + (Unit(num13) - 0.5) * 0.9;
				double num15 = Math.Min(num10, num11) * (0.55 + 0.3 * Unit(num13 + 1));
				list.Add(new Lobe(num6 + Math.Cos(num14) * num15, num7 + Math.Sin(num14) * num15, num10 * (0.47 + 0.25 * Unit(num13 + 2)), num11 * (0.45 + 0.28 * Unit(num13 + 3)), num14 + (Unit(num13 + 4) - 0.5), Unit(num13) * Math.PI * 2.0));
			}
			list2.Add(new Range(num6 + (Unit(num3 + 40) - 0.5) * num10 * 0.55, num7 + (Unit(num3 + 41) - 0.5) * num11 * 0.55, num8 + (Unit(num3 + 42) - 0.5) * 0.65, (450.0 + Unit(num3 + 43) * 350.0) * 1.15, num11 * (0.75 + 0.25 * Unit(num3 + 44)), Unit(num3 + 45) * 8.0));
		}
		list.Add(new Lobe((Unit(16u) - 0.5) * 1000.0, 9050.0, 4644.0, 1595.0, (Unit(17u) - 0.5) * 0.1, Unit(6010u) * 6.0));
		list.Add(new Lobe((Unit(18u) - 0.5) * 1100.0, -9100.0, 4590.0, 1375.0, (Unit(19u) - 0.5) * 0.1, Unit(6011u) * 6.0));
		land = list.ToArray();
		ranges = list2.ToArray();
		List<Lobe> list4 = new List<Lobe>();
		int num16 = ((Style == "connected") ? 2 : ((!(Style == "mixed")) ? 1 : (1 + ((Unit(6500u) > 0.5) ? 1 : 0))));
		for (int k = 0; k < num16; k++)
		{
			uint num17 = (uint)(6510 + k * 20);
			int val = 1 + (int)(Unit(num17) * (double)(PlateCount - 1));
			double[] array = list3[Math.Min(val, PlateCount - 1)];
			double num18 = 0.38 + 0.24 * Unit(num17 + 1);
			double num19 = array[0] * num18;
			double num20 = array[1] * num18;
			if (Math.Sqrt(num19 * num19 + num20 * num20) < 1900.0)
			{
				double num21 = num2 + (double)k * 2.4;
				num19 = Math.Cos(num21) * 2200.0;
				num20 = Math.Sin(num21) * 2200.0;
			}
			list4.Add(new Lobe(num19, num20, 850.0 + Unit(num17 + 2) * 650.0, 1200.0 + Unit(num17 + 3) * 950.0, Unit(num17 + 4) * Math.PI, Unit(num17 + 5) * 6.0));
		}
		seas = list4.ToArray();
		lakes = CreateLakes();
		islands = CreateIslands(out chainSaddle);
	}

	private double Coast(double x, double z, bool includeLakes)
	{
		double num = -1000000000.0;
		Lobe[] array = land;
		foreach (Lobe lobe in array)
		{
			num = Join(num, lobe.Distance(x, z), 360.0);
		}
		array = seas;
		foreach (Lobe lobe2 in array)
		{
			num = Math.Min(num, 0.0 - lobe2.Distance(x, z));
		}
		if (includeLakes && lakes != null)
		{
			array = lakes;
			foreach (Lobe lobe3 in array)
			{
				num = Math.Min(num, 0.0 - lobe3.Distance(x, z));
			}
		}
		double num2 = Math.Sqrt(x * x + z * z);
		num = Math.Max(num, 1250.0 - num2);
		return Math.Min(num, (9600.0 - num2) * 4.0);
	}

	private Lobe[] CreateLakes()
	{
		List<Lobe> list = new List<Lobe>();
		int num = 2 + (int)(Unit(6700u) * 3.0);
		for (int i = 0; i < num; i++)
		{
			for (uint num2 = 0u; num2 < 512; num2++)
			{
				uint num3 = (uint)(6800 + i * 2000) + num2 * 7;
				double num4 = (Unit(num3) - 0.5) * 13500.0;
				double num5 = (Unit(num3 + 1) - 0.5) * 13500.0;
				double num6 = Math.Sqrt(num4 * num4 + num5 * num5);
				if (num6 < 2300.0 || num6 > 7400.0 || Coast(num4, num5, includeLakes: false) < (double)((num2 < 128) ? 1200 : 900) || ClimateWorld.North((float)num4, (float)num5) || ClimateWorld.South((float)num4, (float)num5))
				{
					continue;
				}
				bool flag = true;
				foreach (Lobe item in list)
				{
					if (Math.Pow(num4 - item.X, 2.0) + Math.Pow(num5 - item.Z, 2.0) < 2890000.0)
					{
						flag = false;
						break;
					}
				}
				if (flag)
				{
					list.Add(new Lobe(num4, num5, 260.0 + Unit(num3 + 2) * 160.0, 380.0 + Unit(num3 + 3) * 250.0, Unit(num3 + 4) * Math.PI, Unit(num3 + 5) * 6.0));
					break;
				}
			}
		}
		return list.ToArray();
	}

	private Lobe[] CreateIslands(out Lobe? saddle)
	{
		saddle = null;
		List<Lobe> list = new List<Lobe>();
		List<double[]> list2 = new List<double[]>();
		for (int i = 0; i < 5; i++)
		{
			for (uint num = 0u; num < 512; num++)
			{
				uint num2 = (uint)(20000 + i * 10000) + num * 12;
				double num3 = (Unit(num2) - 0.5) * 17200.0;
				double num4 = (Unit(num2 + 1) - 0.5) * 17200.0;
				double num5 = Math.Sqrt(num3 * num3 + num4 * num4);
				if (num5 < 2500.0 || num5 > 8400.0 || Coast(num3, num4, includeLakes: true) > -1250.0 || ClimateWorld.North((float)num3, (float)num4) || ClimateWorld.South((float)num3, (float)num4))
				{
					continue;
				}
				bool flag = true;
				foreach (double[] item in list2)
				{
					if (Math.Pow(num3 - item[0], 2.0) + Math.Pow(num4 - item[1], 2.0) < 5760000.0)
					{
						flag = false;
						break;
					}
				}
				if (!flag)
				{
					continue;
				}
				double num6 = Unit(num2 + 2) * Math.PI * 2.0;
				double num7 = Math.Cos(num6);
				double num8 = Math.Sin(num6);
				int num9 = ((i == 0) ? 6 : ((i >= 3) ? 1 : 5));
				List<Lobe> list3 = new List<Lobe>();
				for (int j = 0; j < num9; j++)
				{
					double num10 = 0.0;
					double num11 = 0.0;
					double num12 = 180.0;
					double num13 = 240.0;
					switch (i)
					{
					case 0:
						num10 = ((double)j - 2.5) * 390.0;
						num11 = 75.0 * Math.Sin((double)j * 0.9);
						num12 = 100.0;
						num13 = 180.0;
						break;
					case 1:
					{
						double num15 = -0.9 + (double)j * 0.45;
						num10 = 1100.0 * Math.Sin(num15);
						num11 = 550.0 * Math.Cos(num15);
						break;
					}
					case 2:
						if (j > 0)
						{
							double num14 = (double)(j - 1) * Math.PI / 2.0;
							num10 = 780.0 * Math.Cos(num14);
							num11 = 780.0 * Math.Sin(num14);
						}
						else
						{
							num12 = 300.0;
							num13 = 390.0;
						}
						break;
					default:
						num12 = 250.0;
						num13 = 330.0;
						break;
					}
					if (i == 1)
					{
						num10 *= 1.2;
						num11 *= 1.2;
						if (j == 1 || j == 3)
						{
							num12 *= 1.48;
							num13 *= 1.38;
						}
					}
					if (i == 2 && j == 0)
					{
						num12 *= 1.65;
						num13 *= 1.55;
					}
					if (i >= 3)
					{
						num12 *= 1.7;
						num13 *= 1.6;
					}
					double num16 = num3 + num10 * num7 - num11 * num8;
					double num17 = num4 + num10 * num8 + num11 * num7;
					if (Coast(num16, num17, includeLakes: true) > 0.0 - (num13 + 450.0) || Math.Sqrt(num16 * num16 + num17 * num17) > 9000.0 || Math.Sqrt(num16 * num16 + (num17 + 4000.0) * (num17 + 4000.0)) > 11900.0 - num13 * 1.6 - 160.0 || Math.Sqrt(num16 * num16 + (num17 - 4000.0) * (num17 - 4000.0)) > 11900.0 - num13 * 1.6 - 160.0)
					{
						flag = false;
						break;
					}
					list3.Add(new Lobe(num16, num17, num12, num13, num6 + 0.35 * Math.Sin(j * 2), Unit((uint)((int)num2 + j + 3)) * 6.0));
				}
				if (flag)
				{
					if (i == 0 && Unit(num2 + 20) > 0.45)
					{
						Lobe lobe = list3[2];
						Lobe lobe2 = list3[3];
						saddle = new Lobe((lobe.X + lobe2.X) * 0.5, (lobe.Z + lobe2.Z) * 0.5, 190.0, 130.0, 0.0 - num6, Unit(num2 + 21) * 6.0);
					}
					list.AddRange(list3);
					list2.Add(new double[2] { num3, num4 });
					break;
				}
			}
		}
		return list.ToArray();
	}

	internal ClimateSample Sample(float x, float z)
	{
		double mainland;
		return Sample(x, z, out mainland);
	}

	internal ClimateSample Sample(float x, float z, out double mainland)
	{
		double num = Math.Sqrt((double)x * (double)x + (double)z * (double)z);
		if (num >= 9900.0)
		{
			mainland = 0.0;
			return edge.Sample(x, z);
		}
		double num2 = (double)x + 300.0 * Noise((double)x / 2600.0, (double)z / 2600.0, 7000u);
		double num3 = (double)z + 300.0 * Noise((double)x / 2600.0, (double)z / 2600.0, 7001u);
		double num4 = Coast(num2, num3, includeLakes: false);
		double num5 = 1000000000.0;
		mainland = num4;
		Lobe[] array = lakes;
		foreach (Lobe lobe in array)
		{
			num5 = Math.Min(num5, 0.0 - lobe.Distance(num2, num3));
		}
		bool flag = num5 < num4;
		num4 = Math.Min(num4, num5);
		num4 = Math.Max(num4, 1250.0 - num);
		num4 = Math.Min(num4, (9600.0 - num) * 4.0);
		double num6 = -1000000000.0;
		array = islands;
		foreach (Lobe lobe2 in array)
		{
			num6 = Math.Max(num6, lobe2.Distance(num2, num3));
		}
		num4 = Math.Max(num4, num6);
		if (chainSaddle.HasValue)
		{
			num4 = Math.Max(num4, chainSaddle.Value.Distance(num2, num3) - 158.0);
		}
		double num7 = 0.0;
		Range[] array2 = ranges;
		for (int i = 0; i < array2.Length; i++)
		{
			Range range = array2[i];
			double num8 = num2 - range.X;
			double num9 = num3 - range.Z;
			double num10 = num8 * range.C - num9 * range.S;
			double num11 = num8 * range.S + num9 * range.C;
			double num12 = 280.0 * Noise(num11 / 1500.0, range.Phase, 7010u);
			double num13 = (num10 - num12) / range.W;
			double val = Math.Exp((0.0 - num13) * num13) * (1.0 - Smooth(range.L * 0.55, range.L, Math.Abs(num11)));
			num7 = Math.Max(num7, val);
		}
		num7 *= Smooth(1400.0, 2200.0, num) * Smooth(130.0, 970.0, num4);
		double num14 = 0.15 + 0.275 * Math.Tanh(num4 / 2200.0) + 0.52 * num7;
		if (num6 > 0.0)
		{
			num14 += 0.08 * Smooth(0.0, 320.0, num6);
		}
		double num15 = 0.68 - 0.32 * (double)z / 10000.0 + 0.1 * Noise((double)x / 4200.0, (double)z / 4200.0, 60u) - 0.2 * num7;
		double num16 = 0.5 + 0.3 * Noise(num2 / 2400.0, num3 / 2400.0, 61u) + 0.1 * Noise(num2 / 5000.0, num3 / 5000.0, 62u) + 0.08 * Math.Exp((0.0 - Math.Max(0.0, num4)) / 1200.0) - 0.08 * num7;
		if (num > 9400.0)
		{
			ClimateSample climateSample = edge.Sample(x, z);
			double t = Smooth(9400.0, 9900.0, num);
			num14 = Lerp(num14, climateSample.BaseHeight, t);
			num4 = Lerp(num4, climateSample.CoastDistance, t);
		}
		return new ClimateSample
		{
			BaseHeight = (float)num14,
			CoastDistance = (float)num4,
			Ridge = (float)num7,
			Temperature = (float)num15,
			Moisture = (float)num16,
			InlandLake = (flag ? 1 : 0),
			Biome = ((!(num4 <= 0.0)) ? ClimateBiome.Meadows : ClimateBiome.Ocean),
			Blend = TerrainBlendWeights.Only((!(num4 <= 0.0)) ? ClimateBiome.Meadows : ClimateBiome.Ocean)
		};
	}
}
internal static class InlandHeightTest
{
	internal const float MaximumLowering = 16f;

	internal const float UnchangedBelow = 45f;

	internal const float FullLoweringAt = 85f;

	internal static float ApplyIncludingMountain(float height, TerrainBlendWeights weights)
	{
		if (weights.Mountain == 0f)
		{
			return Apply(height, weights);
		}
		float num = Math.Max(0f, Math.Min(1f, weights.Meadows + weights.Forest + weights.Plains + weights.Mountain));
		if (height <= 45f || num == 0f)
		{
			return height;
		}
		double num2 = Math.Max(0f, Math.Min(1f, (height - 45f) / 40f));
		return (float)((double)height - (double)(16f * num) * num2 * num2 * (3.0 - 2.0 * num2));
	}

	internal static float Apply(float height, TerrainBlendWeights weights)
	{
		float num = Math.Max(0f, Math.Min(1f, weights.Meadows + weights.Forest + weights.Plains));
		if (height <= 45f || num == 0f)
		{
			return height;
		}
		double num2 = Math.Max(0f, Math.Min(1f, (height - 45f) / 40f));
		return (float)((double)height - (double)(16f * num) * num2 * num2 * (3.0 - 2.0 * num2));
	}
}
internal static class NativeMountainInput
{
	[ThreadStatic]
	private static WorldGenerator active;

	internal static bool Busy => active != null;

	internal static float Adjust(float continental, float native)
	{
		float num = Math.Max(0f, continental - Math.Max(native, 0.4f));
		return continental - num * 0.5f;
	}

	internal static float Base(WorldGenerator generator, ClimateSample sample)
	{
		if (active != generator)
		{
			return sample.BaseHeight;
		}
		return Adjust(sample.BaseHeight, sample.NativeBaseHeight);
	}

	internal static float Height(WorldGenerator generator, ClimateBiome biome, float x, float z, out Color mask, bool pre, bool river)
	{
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		ClimateWorld climateWorld = ClimateTerrain.For(generator);
		if (biome != ClimateBiome.Mountain || climateWorld == null || (climateWorld.Revision != 21 && climateWorld.Revision != 22))
		{
			return generator.GetBiomeHeight(ClimateTerrain.Native(biome), x, z, ref mask, pre, river);
		}
		WorldGenerator val = active;
		active = generator;
		try
		{
			return generator.GetBiomeHeight((Biome)4, x, z, ref mask, pre, river);
		}
		finally
		{
			active = val;
		}
	}
}
internal struct TerrainBlendWeights
{
	internal float Meadows;

	internal float Forest;

	internal float Swamp;

	internal float Mountain;

	internal float Plains;

	internal float Mistlands;

	internal float Ocean;

	internal float Ashlands;

	internal float North;

	internal int Count => ((Meadows > 0f) ? 1 : 0) + ((Forest > 0f) ? 1 : 0) + ((Swamp > 0f) ? 1 : 0) + ((Mountain > 0f) ? 1 : 0) + ((Plains > 0f) ? 1 : 0) + ((Mistlands > 0f) ? 1 : 0) + ((Ocean > 0f) ? 1 : 0) + ((Ashlands > 0f) ? 1 : 0) + ((North > 0f) ? 1 : 0);

	internal float Sum => Meadows + Forest + Swamp + Mountain + Plains + Mistlands + Ocean + Ashlands + North;

	internal static TerrainBlendWeights Only(ClimateBiome biome)
	{
		TerrainBlendWeights result = default(TerrainBlendWeights);
		result.Layer(biome, 1.0);
		return result;
	}

	internal float Weight(ClimateBiome biome)
	{
		return biome switch
		{
			ClimateBiome.Meadows => Meadows, 
			ClimateBiome.BlackForest => Forest, 
			ClimateBiome.Swamp => Swamp, 
			ClimateBiome.Mountain => Mountain, 
			ClimateBiome.Plains => Plains, 
			ClimateBiome.Mistlands => Mistlands, 
			ClimateBiome.Ocean => Ocean, 
			ClimateBiome.AshLands => Ashlands, 
			ClimateBiome.DeepNorth => North, 
			_ => 0f, 
		};
	}

	internal void Layer(ClimateBiome biome, double value)
	{
		float num = (float)Math.Max(0.0, Math.Min(1.0, value));
		float num2 = 1f - num;
		Meadows *= num2;
		Forest *= num2;
		Swamp *= num2;
		Mountain *= num2;
		Plains *= num2;
		Mistlands *= num2;
		Ocean *= num2;
		Ashlands *= num2;
		North *= num2;
		switch (biome)
		{
		case ClimateBiome.Meadows:
			Meadows += num;
			break;
		case ClimateBiome.Plains:
			Plains += num;
			break;
		case ClimateBiome.Swamp:
			Swamp += num;
			break;
		case ClimateBiome.Mountain:
			Mountain += num;
			break;
		case ClimateBiome.Mistlands:
			Mistlands += num;
			break;
		case ClimateBiome.Ocean:
			Ocean += num;
			break;
		case ClimateBiome.AshLands:
			Ashlands += num;
			break;
		case ClimateBiome.DeepNorth:
			North += num;
			break;
		case ClimateBiome.BlackForest:
			break;
		}
	}

	private static double Above(double value, double threshold, double halfWidth)
	{
		return ClimateWorld.Smooth(threshold - halfWidth, threshold + halfWidth, value);
	}

	private static double Below(double value, double threshold, double halfWidth)
	{
		return 1.0 - Above(value, threshold, halfWidth);
	}

	internal static TerrainBlendWeights FromFields(double radius, double angle, double temperature, double moisture, double elevation, double ridge, double mountainHalfWidth = 0.008)
	{
		TerrainBlendWeights result = new TerrainBlendWeights
		{
			Forest = 1f
		};
		result.Layer(ClimateBiome.Meadows, Below(radius, 4200.0 + angle, 60.0) * Above(temperature, 0.48, 0.004) * Below(moisture, 0.49, 0.004));
		result.Layer(ClimateBiome.Plains, Above(radius, 3200.0 + angle, 60.0) * Below(radius, 8000.0, 60.0) * Above(temperature, 0.45, 0.004) * Below(moisture, 0.54, 0.004));
		result.Layer(ClimateBiome.Swamp, Above(radius, 2200.0, 60.0) * Below(radius, 6000.0, 60.0) * Below(elevation, 0.24, 0.008) * Below(ridge, 0.1, 0.015) * Above(moisture, 0.55, 0.004));
		result.Layer(ClimateBiome.Mistlands, Above(radius, 6000.0 + angle, 60.0) * Below(radius, 9700.0, 60.0) * Above(moisture, 0.43, 0.004));
		result.Layer(ClimateBiome.Mountain, Above(elevation, 0.4, mountainHalfWidth) * Above(radius, 1700.0, 60.0));
		result.Layer(ClimateBiome.Meadows, Below(radius, 1050.0, 60.0));
		return result;
	}
}
internal static class TerrainConfig
{
	internal const float DefaultDig = 16f;

	internal const float DefaultRaise = 16f;

	internal const string Defaults = "# Valyn Earthlike Worlds 1.3.1 — new-world settings.\n# Change while Valheim is closed. Settings apply to newly created worlds.\n# New worlds use v22; existing v17/v18/v21 worlds retain their heights and saved limits.\n\n[Climate continents]\nEnableForNewWorlds = true\nDigDepthMeters = 16\nRaiseHeightMeters = 16\n";

	internal static bool IsPrevious(string text)
	{
		if (text == null)
		{
			return false;
		}
		string[] array = text.Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
		for (int i = 0; i < array.Length; i++)
		{
			string text2 = array[i].Trim();
			if (text2.StartsWith("#", StringComparison.Ordinal) || text2.StartsWith(";", StringComparison.Ordinal))
			{
				continue;
			}
			if (text2 == "[New worlds]")
			{
				return true;
			}
			int num = text2.IndexOf('=');
			if (num >= 0)
			{
				switch (text2.Substring(0, num).Trim())
				{
				case "GenerationMode":
				case "ContinentScale":
				case "BiomeRegionScale":
				case "BiomeCleanupRadiusMeters":
				case "BaseLandLiftMeters":
				case "LowlandLiftMeters":
					return true;
				}
			}
		}
		return false;
	}
}
internal sealed class TerrainFrameProbe
{
	private struct Row
	{
		internal double At;

		internal double Ms;

		internal int G0;

		internal int G1;

		internal int G2;

		internal bool Scan;
	}

	private Row[] rows;

	private long previous;

	private int count;

	private int slow;

	private int rowCount;

	private int dropped;

	private int g0;

	private int g1;

	private int g2;

	private int start0;

	private int start1;

	private int start2;

	private double elapsed;

	private double maximum;

	private string path;

	private string identity;

	internal bool Active { get; private set; }

	internal string Start()
	{
		if (Active)
		{
			return Finish();
		}
		TerrainPlugin.GenerationContext current = TerrainPlugin.Current;
		if (current == null || current.World.m_menu)
		{
			return "Load a gameplay world first.";
		}
		string path = Path.Combine(Paths.ConfigPath, "ValynWorldTerrain", "reports");
		Directory.CreateDirectory(path);
		this.path = Path.Combine(path, "frames-" + current.World.m_uid + "-" + DateTime.UtcNow.ToString("yyyyMMdd-HHmmss-fff", CultureInfo.InvariantCulture) + ".csv");
		identity = "Valyn Earthlike Worlds 1.3.1; uid=" + current.World.m_uid + "; " + current.Profile.Encode();
		rows = new Row[1024];
		count = (slow = (rowCount = (dropped = 0)));
		elapsed = (maximum = 0.0);
		start0 = (g0 = GC.CollectionCount(0));
		start1 = (g1 = GC.CollectionCount(1));
		start2 = (g2 = GC.CollectionCount(2));
		previous = Stopwatch.GetTimestamp();
		Active = true;
		return "Frame-interval capture started for 30 seconds. Stand still with no terrain scan running. Run vt_perf again to finish early. This is a timing capture, not a CPU/GPU profiler.";
	}

	internal void Observe(double ms, int current0, int current1, int current2, bool scanning)
	{
		if (!Active || ms < 0.0 || double.IsNaN(ms) || double.IsInfinity(ms))
		{
			return;
		}
		int num = Math.Max(0, current0 - g0);
		int num2 = Math.Max(0, current1 - g1);
		int num3 = Math.Max(0, current2 - g2);
		g0 = current0;
		g1 = current1;
		g2 = current2;
		elapsed += ms;
		count++;
		maximum = Math.Max(maximum, ms);
		if (ms >= 40.0)
		{
			slow++;
		}
		if (ms >= 40.0 || num + num2 + num3 > 0)
		{
			if (rowCount < rows.Length)
			{
				rows[rowCount++] = new Row
				{
					At = elapsed,
					Ms = ms,
					G0 = num,
					G1 = num2,
					G2 = num3,
					Scan = scanning
				};
			}
			else
			{
				dropped++;
			}
		}
	}

	internal void Tick(bool scanning)
	{
		if (Active)
		{
			long timestamp = Stopwatch.GetTimestamp();
			double ms = (double)(timestamp - previous) * 1000.0 / (double)Stopwatch.Frequency;
			previous = timestamp;
			Observe(ms, GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2), scanning);
			if (elapsed >= 30000.0)
			{
				TerrainPlugin.Instance.Log(Finish());
			}
		}
	}

	internal void Cancel()
	{
		Active = false;
		rows = null;
	}

	internal string Finish()
	{
		if (!Active)
		{
			return "No frame capture is running.";
		}
		Active = false;
		string text = "Frame capture complete; intervals=" + count + "; mean_ms=" + F(elapsed / (double)Math.Max(1, count)) + "; max_ms=" + F(maximum) + "; intervals>=40ms=" + slow + "; GC collections=" + (g0 - start0) + "/" + (g1 - start1) + "/" + (g2 - start2) + "; omitted_rows=" + dropped;
		try
		{
			using (StreamWriter streamWriter = new StreamWriter(path))
			{
				streamWriter.WriteLine("# " + identity);
				streamWriter.WriteLine("# " + text);
				streamWriter.WriteLine("# Update-to-Update wall-clock intervals; slow/GC intervals only. GC overlap is not proof of cause. Not CPU/GPU timings. Writing this file occurs after capture.");
				streamWriter.WriteLine("elapsed_ms,interval_ms,gc_gen0,gc_gen1,gc_gen2,terrain_scan_active");
				for (int i = 0; i < rowCount; i++)
				{
					Row row = rows[i];
					streamWriter.WriteLine(F(row.At) + "," + F(row.Ms) + "," + row.G0 + "," + row.G1 + "," + row.G2 + "," + (row.Scan ? "true" : "false"));
				}
			}
			return text + " | report=" + path;
		}
		catch (Exception ex)
		{
			return "Frame capture could not be saved: " + ex.Message;
		}
		finally
		{
			rows = null;
		}
	}

	private static string F(double v)
	{
		return v.ToString("0.###", CultureInfo.InvariantCulture);
	}
}
internal static class TerrainFrost
{
	internal const float Start = 75f;

	internal const float TreeSnow = 100f;

	internal const float PeakSnow = 160f;

	[ThreadStatic]
	private static List<Vector3> vertices;

	private static float Smooth(float low, float high, float y)
	{
		float num = Math.Max(0f, Math.Min(1f, (y - low) / (high - low)));
		return num * num * (3f - 2f * num);
	}

	internal static float Coverage(float height)
	{
		if (float.IsNaN(height) || float.IsInfinity(height))
		{
			return 0f;
		}
		if (!(height <= 100f))
		{
			return 0.75f + 0.25f * Smooth(100f, 160f, height);
		}
		return 0.75f * Smooth(75f, 100f, height);
	}

	internal static Color32 Blend(Color32 original, float amount)
	{
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: 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_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_004e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		if (float.IsNaN(amount) || float.IsInfinity(amount))
		{
			return original;
		}
		float num = Math.Max(0f, Math.Min(1f, amount));
		float num2 = Math.Max(0, 255 - Math.Max(original.r, original.a) - original.g);
		return new Color32(original.r, (byte)Math.Round((float)(int)original.g + num2 * num), (byte)Math.Round((float)(int)original.b - Math.Min((int)original.b, num2) * num), original.a);
	}

	internal static float GrassRemoval(float height)
	{
		if (!float.IsNaN(height) && !float.IsInfinity(height))
		{
			return Smooth(85f, 100f, height);
		}
		return 0f;
	}

	internal static float PositionFraction(float x, float z)
	{
		int num = ((int)Math.Floor(x * 16f) * -1640531527) ^ ((int)Math.Floor(z * 16f) * -2048144789);
		int num2 = (num ^ (num >>> 16)) * 2146121005;
		int num3 = (num2 ^ (num2 >>> 15)) * -2073254261;
		return (float)(uint)((num3 ^ (num3 >>> 16)) & 0xFFFFFF) / 16777216f;
	}

	internal static bool KeepClutter(float x, float y, float z)
	{
		float num = GrassRemoval(y);
		if (!(num <= 0f))
		{
			if (num < 1f)
			{
				return PositionFraction(x, z) >= num;
			}
			return false;
		}
		return true;
	}

	internal static ClimateWorld CurrentWorld()
	{
		ClimateWorld climateWorld = ClimateTerrain.For(WorldGenerator.instance);
		if (climateWorld == null || climateWorld.Revision < 17)
		{
			return null;
		}
		return climateWorld;
	}

	internal static void Apply(List<Vector3> points, List<Color32> colors, Vector3 origin)
	{
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		if (points.Count != colors.Count)
		{
			return;
		}
		for (int i = 0; i < points.Count; i++)
		{
			float num = Coverage(points[i].y + origin.y);
			if (!(num <= 0f))
			{
				colors[i] = Blend(colors[i], num);
			}
		}
	}

	internal static void SetColors(Mesh mesh, List<Color32> colors, Heightmap heightmap)
	{
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		if (CurrentWorld() != null)
		{
			if (vertices == null)
			{
				vertices = new List<Vector3>();
			}
			mesh.GetVertices(vertices);
			Apply(vertices, colors, ((Component)heightmap).transform.position);
			vertices.Clear();
		}
		mesh.SetColors(colors);
	}
}
[HarmonyPatch(typeof(Heightmap), "RebuildRenderMesh")]
internal static class FrostedGround
{
	private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> input)
	{
		MethodInfo set = typeof(Mesh).GetMethod("SetColors", new Type[1] { typeof(List<Color32>) });
		MethodInfo replacement = typeof(TerrainFrost).GetMethod("SetColors", BindingFlags.Static | BindingFlags.NonPublic);
		int count = 0;
		foreach (CodeInstruction item in input)
		{
			if ((item.opcode == OpCodes.Call || item.opcode == OpCodes.Callvirt) && object.Equals(item.operand, set))
			{
				item.opcode = OpCodes.Ldarg_0;
				item.operand = null;
				yield return item;
				yield return new CodeInstruction(OpCodes.Call, (object)replacement);
				count++;
			}
			else
			{
				yield return item;
			}
		}
		if (set == null || replacement == null || count != 1)
		{
			throw new InvalidOperationException("Terrain frost requires one native Color32 mesh upload; found " + count);
		}
	}
}
[HarmonyPatch(typeof(ClutterSystem), "GetGroundInfo")]
internal static class FrostedClutter
{
	private static void Postfix(Vector3 __1, Biome __4, ref bool __result)
	{
		//IL_0004: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Invalid comparison between Unknown and I4
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_000a: Invalid comparison between Unknown and I4
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		if (__result && ((int)__4 == 1 || (int)__4 == 8) && !(TerrainFrost.GrassRemoval(__1.y) <= 0f) && TerrainFrost.CurrentWorld() != null && !TerrainFrost.KeepClutter(__1.x, __1.y, __1.z))
		{
			__result = false;
		}
	}
}
internal static class TerrainNetwork
{
	private const string OfferRpc = "ValynTerrain_Offer_v1";

	private const string AckRpc = "ValynTerrain_Ack_v1";

	private static readonly Dictionary<ZRpc, string> offers = new Dictionary<ZRpc, string>();

	private static readonly Dictionary<ZRpc, string> acks = new Dictionary<ZRpc, string>();

	private static TerrainWire incoming;

	private static ZRpc pendingServer;

	internal static void Reset()
	{
		offers.Clear();
		acks.Clear();
		incoming = null;
		pendingServer = null;
	}

	internal static void Register(ZNetPeer peer)
	{
		if (!ZNet.instance.IsServer())
		{
			incoming = null;
			pendingServer = (peer.m_server ? peer.m_rpc : null);
		}
		peer.m_rpc.Register<string>("ValynTerrain_Offer_v1", (Action<ZRpc, string>)ReceiveOffer);
		peer.m_rpc.Register<string>("ValynTerrain_Ack_v1", (Action<ZRpc, string>)ReceiveAck);
	}

	internal static void Offer(ZRpc rpc)
	{
		World world = ZNet.instance.GetWorld();
		TerrainProfile terrainProfile = TerrainPlugin.Instance.Recipe(world);
		if (!terrainProfile.IsVanilla)
		{
			string text = new TerrainWire(world.m_uid, world.m_seed, world.m_worldGenVersion, terrainProfile).Encode();
			offers[rpc] = text;
			acks.Remove(rpc);
			rpc.Invoke("ValynTerrain_Offer_v1", new object[1] { text });
		}
	}

	private static void ReceiveOffer(ZRpc rpc, string text)
	{
		if (!Object.op_Implicit((Object)(object)ZNet.instance) || ZNet.instance.IsServer())
		{
			return;
		}
		if (pendingServer != rpc)
		{
			Reject(rpc, "Terrain recipe did not come from the connecting server");
			return;
		}
		try
		{
			incoming = TerrainWire.Decode(text);
			rpc.Invoke("ValynTerrain_Ack_v1", new object[1] { text });
			TerrainPlugin.Instance.Log("Received host terrain recipe before native generation: " + incoming.Profile.Encode());
		}
		catch (Exception ex)
		{
			incoming = null;
			Reject(rpc, "Terrain recipe rejected: " + ex.Message);
		}
	}

	private static void ReceiveAck(ZRpc rpc, string text)
	{
		if (Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer() && offers.TryGetValue(rpc, out var value) && value == text)
		{
			acks[rpc] = text;
		}
	}

	internal static bool PermitPeerInfo(ZRpc rpc)
	{
		if (!ZNet.instance.IsServer())
		{
			return true;
		}
		if (TerrainPlugin.Instance.Recipe(ZNet.instance.GetWorld()).IsVanilla)
		{
			return true;
		}
		if (offers.TryGetValue(rpc, out var value) && acks.TryGetValue(rpc, out var value2) && value == value2)
		{
			return true;
		}
		Reject(rpc, "This custom terrain world requires Valyn Earthlike Worlds 1.3.1 on every player. No matching pre-generation acknowledgement received.");
		return false;
	}

	internal static TerrainProfile IncomingRecipe(World world)
	{
		if (incoming == null)
		{
			return null;
		}
		if (world.m_uid != incoming.WorldId || world.m_seed != incoming.Seed || world.m_worldGenVersion != incoming.Generation)
		{
			throw new InvalidOperationException("Native server world data does not match its offered terrain recipe");
		}
		return incoming.Profile;
	}

	internal static void Reject(ZRpc rpc, string reason)
	{
		TerrainPlugin.Instance.Error(reason);
		if (Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer())
		{
			rpc.Invoke("Error", new object[1] { 3 });
		}
		rpc.GetSocket().Close();
	}
}
[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
internal static class TerrainConnectionSetup
{
	private static void Prefix(ZNetPeer __0)
	{
		if (TerrainPlugin.Ready)
		{
			TerrainNetwork.Register(__0);
		}
	}
}
[HarmonyPatch(typeof(ZNet), "RPC_ServerHandshake")]
internal static class TerrainPreHandshake
{
	private static bool Prefix(ZRpc __0)
	{
		if (!TerrainPlugin.Ready)
		{
			return true;
		}
		try
		{
			TerrainNetwork.Offer(__0);
			return true;
		}
		catch (Exception ex)
		{
			TerrainNetwork.Reject(__0, "Cannot prepare terrain handshake: " + ex.Message);
			return false;
		}
	}
}
[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
internal static class TerrainPeerAdmission
{
	private static bool Prefix(ZRpc __0)
	{
		if (TerrainPlugin.Ready)
		{
			return TerrainNetwork.PermitPeerInfo(__0);
		}
		return true;
	}
}
[HarmonyPatch(typeof(ZNet), "Shutdown", new Type[] { typeof(bool) })]
internal static class TerrainNetworkClosing
{
	private static void Prefix()
	{
		TerrainNetwork.Reset();
	}
}
[HarmonyPatch]
internal static class NewWorldCreationScope
{
	private static IEnumerable<MethodBase> TargetMethods()
	{
		yield return AccessTools.Method(AccessTools.TypeByName("FejdStartup"), "OnNewWorldDone", (Type[])null, (Type[])null);
		yield return AccessTools.Method(typeof(World), "GetCreateWorld", (Type[])null, (Type[])null);
		yield return AccessTools.Method(typeof(World), "GetDevWorld", (Type[])null, (Type[])null);
	}

	private static void Prefix()
	{
		TerrainPlugin.CreationScope++;
	}

	private static Exception Finalizer(Exception __exception)
	{
		TerrainPlugin.CreationScope--;
		return __exception;
	}
}
[HarmonyPatch(/*Could not decode attribute arguments.*/)]
internal static class MarkNewTerrainWorld
{
	private static void Postfix(World __instance)
	{
		if (Object.op_Implicit((Object)(object)TerrainPlugin.Instance))
		{
			TerrainPlugin.Instance.MarkNewWorld(__instance);
		}
	}
}
[HarmonyPatch(typeof(World), "LoadWorld")]
internal static class RememberTerrainWorld
{
	private static void Postfix(World __result)
	{
		if (Object.op_Implicit((Object)(object)TerrainPlugin.Instance))
		{
			TerrainPlugin.Instance.RememberLoaded(__result);
		}
	}
}
[HarmonyPatch]
internal static class PreserveTerrainWorld
{
	private static IEnumerable<MethodBase> TargetMethods()
	{
		return from m in typeof(World).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
			where m.Name == "SaveWorldFWLData"
			select m;
	}

	private static void Prefix(World __instance)
	{
		if (Object.op_Implicit((Object)(object)TerrainPlugin.Instance))
		{
			TerrainPlugin.Instance.PreserveBeforeSave(__instance);
		}
	}
}
[HarmonyPatch(/*Could not decode attribute arguments.*/)]
internal static class BeforeNativeWorldGeneration
{
	private static void Prefix(WorldGenerator __instance, World __0)
	{
		if (TerrainPlugin.Ready && Object.op_Implicit((Object)(object)TerrainPlugin.Instance))
		{
			TerrainPlugin.Instance.BeginGeneration(__instance, __0);
		}
	}
}
[HarmonyPatch]
internal static class TerrainEditLimits
{
	private static IEnumerable<MethodBase> TargetMethods()
	{
		yield return AccessTools.Method(typeof(TerrainComp), "LevelTerrain", (Type[])null, (Type[])null);
		yield return AccessTools.Method(typeof(TerrainComp), "RaiseTerrain", (Type[])null, (Type[])null);
		yield return AccessTools.Method(typeof(TerrainComp), "ApplyToHeightmap", (Type[])null, (Type[])null);
		yield return AccessTools.Method(typeof(Heightmap), "LevelTerrain", (Type[])null, (Type[])null);
		yield return AccessTools.Method(typeof(Heightmap), "AtMaxWorldLevelDepth", (Type[])null, (Type[])null);
	}

	private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> input, MethodBase __originalMethod)
	{
		List<CodeInstruction> list = new List<CodeInstruction>(input);
		bool flag = __originalMethod.DeclaringType == typeof(Heightmap) && __originalMethod.Name == "LevelTerrain";
		bool flag2 = __originalMethod.Name == "AtMaxWorldLevelDepth";
		int num = 0;
		foreach (CodeInstruction item in list)
		{
			string text = null;
			if (flag2 && item.opcode == OpCodes.Ldc_R4 && Math.Abs((float)item.operand - 7.95f) < 0.0001f)
			{
				text = "DigThreshold";
			}
			else if (flag && item.opcode == OpCodes.Ldc_R8 && (double)item.operand == 8.0)
			{
				text = ((num == 0) ? "LowerDistance" : "UpperDistance");
			}
			else if (!flag && !flag2 && item.opcode == OpCodes.Ldc_R4)
			{
				float num2 = (float)item.operand;
				if (__originalMethod.Name == "ApplyToHeightmap" && num2 == 8f)
				{
					text = ((num == 0) ? "LowerDistanceFloat" : "Upper");
				}
				else if (num2 == -8f)
				{
					text = "Lower";
				}
				else if (num2 == 8f)
				{
					text = "Upper";
				}
			}
			if (text != null)
			{
				item.opcode = OpCodes.Call;
				item.operand = ((text == "LowerDistanceFloat") ? AccessTools.Method(typeof(TerrainEditLimits), "LowerDistanceFloat", (Type[])null, (Type[])null) : AccessTools.Method(typeof(TerrainPlugin), text, (Type[])null, (Type[])null));
				num++;
			}
		}
		if (num != (flag2 ? 1 : 2))
		{
			throw new InvalidOperationException("Native terrain limit sites changed in " + __originalMethod?.ToString() + ": " + num);
		}
		return list;
	}

	private static float LowerDistanceFloat()
	{
		return TerrainPlugin.Active.Dig;
	}
}
[HarmonyPatch(typeof(Terminal), "InitTerminal")]
internal static class TerrainTestCommands
{
	[Serializable]
	[CompilerGenerated]
	private sealed class <>c
	{
		public static readonly <>c <>9 = new <>c();

		public static ConsoleEvent <>9__0_0;

		public static ConsoleEvent <>9__0_1;

		public static ConsoleEvent <>9__0_2;

		public static ConsoleEvent <>9__0_3;

		internal void <Postfix>b__0_0(ConsoleEventArgs args)
		{
			if (Object.op_Implicit((Object)(object)TerrainPlugin.Instance))
			{
				TerrainPlugin.Instance.Status(args.Context);
			}
		}

		internal void <Postfix>b__0_1(ConsoleEventArgs args)
		{
			if (Object.op_Implicit((Object)(object)TerrainPlugin.Instance))
			{
				TerrainPlugin.Instance.Scan(args.Context);
			}
		}

		internal void <Postfix>b__0_2(ConsoleEventArgs args)
		{
			if (Object.op_Implicit((Object)(object)TerrainPlugin.Instance))
			{
				TerrainPlugin.Instance.StopScan(args.Context);
			}
		}

		internal void <Postfix>b__0_3(ConsoleEventArgs args)
		{
			if (Object.op_Implicit((Object)(object)TerrainPlugin.Instance))
			{
				TerrainPlugin.Instance.CaptureFrames(args.Context);
			}
		}
	}

	private static void Postfix()
	{
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: 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_0029: Expected O, but got Unknown
		//IL_006c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0057: 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_0062: Expected O, but got Unknown
		//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_0090: Unknown result type (might be due to invalid IL or missing references)
		//IL_0095: Unknown result type (might be due to invalid IL or missing references)
		//IL_009b: Expected O, but got Unknown
		//IL_00de: 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_00ce: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d4: Expected O, but got Unknown
		object obj = <>c.<>9__0_0;
		if (obj == null)
		{
			ConsoleEvent val = delegate(ConsoleEventArgs args)
			{
				if (Object.op_Implicit((Object)(object)TerrainPlugin.Instance))
				{
					TerrainPlugin.Instance.Status(args.Context);
				}
			};
			<>c.<>9__0_0 = val;
			obj = (object)val;
		}
		new ConsoleCommand("vt_status", "Show saved world terrain settings and test status.", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		object obj2 = <>c.<>9__0_1;
		if (obj2 == null)
		{
			ConsoleEvent val2 = delegate(ConsoleEventArgs args)
			{
				if (Object.op_Implicit((Object)(object)TerrainPlugin.Instance))
				{
					TerrainPlugin.Instance.Scan(args.Context);
				}
			};
			<>c.<>9__0_1 = val2;
			obj2 = (object)val2;
		}
		new ConsoleCommand("vt_scan", "Read-only coarse terrain/biome/land-water scan; writes a CSV report without spawning a world of objects.", (ConsoleEvent)obj2, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		object obj3 = <>c.<>9__0_2;
		if (obj3 == null)
		{
			ConsoleEvent val3 = delegate(ConsoleEventArgs args)
			{
				if (Object.op_Implicit((Object)(object)TerrainPlugin.Instance))
				{
					TerrainPlugin.Instance.StopScan(args.Context);
				}
			};
			<>c.<>9__0_2 = val3;
			obj3 = (object)val3;
		}
		new ConsoleCommand("vt_scan_stop", "Stop the active terrain scan and discard its incomplete report.", (ConsoleEvent)obj3, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		object obj4 = <>c.<>9__0_3;
		if (obj4 == null)
		{
			ConsoleEvent val4 = delegate(ConsoleEventArgs args)
			{
				if (Object.op_Implicit((Object)(object)TerrainPlugin.Instance))
				{
					TerrainPlugin.Instance.CaptureFrames(args.Context);
				}
			};
			<>c.<>9__0_3 = val4;
			obj4 = (object)val4;
		}
		new ConsoleCommand("vt_perf", "Capture 30 seconds of frame intervals/GC counts to a small CSV; run again to finish early.", (ConsoleEvent)obj4, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
	}
}
[BepInPlugin("valyn.valheim.worldterrain", "Valyn Earthlike Worlds", "1.3.1")]
public sealed class TerrainPlugin : BaseUnityPlugin
{
	internal sealed class SavedRecipe
	{
		internal int Seed;

		internal string Recipe;
	}

	internal sealed class GenerationContext
	{
		internal readonly WorldGenerator Generator;

		internal readonly World World;

		internal readonly TerrainProfile Profile;

		internal readonly ClimateWorld Climate;

		internal GenerationContext(WorldGenerator generator, World world, TerrainProfile profile)
		{
			Generator = generator;
			World = world;
			Profile = profile;
			if (!profile.IsVanilla)
			{
				Climate = new ClimateWorld(world.m_seed, profile.RecipeVersion, ClimateBaseHeight.NativeSampler(generator));
			}
		}
	}

	public const string Guid = "valyn.valheim.worldterrain";

	public const string DisplayName = "Valyn Earthlike Worlds";

	public const string ModVersion = "1.3.1";

	internal static TerrainPlugin Instance;

	internal static bool Ready;

	internal static volatile GenerationContext Current;

	[ThreadStatic]
	internal static int CreationScope;

	private ConfigEntry<bool> newWorlds;

	private ConfigEntry<float> dig;

	private ConfigEntry<float> raise;

	private Harmony harmony;

	private Harmony guard;

	private readonly Dictionary<long, SavedRecipe> known = new Dictionary<long, SavedRecipe>();

	private readonly TerrainSurvey survey = new TerrainSurvey();

	private readonly TerrainFrameProbe frameProbe = new TerrainFrameProbe();

	internal static TerrainProfile Active
	{
		get
		{
			GenerationContext current = Current;
			if (!Ready || current == null || current.World == null || current.World.m_menu)
			{
				return TerrainProfile.Vanilla;
			}
			return current.Profile;
		}
	}

	internal static TerrainProfile For(WorldGenerator generator)
	{
		GenerationContext current = Current;
		if (!Ready || current == null || current.Generator != generator || current.World.m_menu)
		{
			return TerrainProfile.Vanilla;
		}
		return current.Profile;
	}

	private void Awake()
	{
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Expected O, but got Unknown
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Expected O, but got Unknown
		//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c4: Expected O, but got Unknown
		//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
		//IL_0102: Expected O, but got Unknown
		//IL_0112: Unknown result type (might be due to invalid IL or missing references)
		//IL_011c: Expected O, but got Unknown
		Instance = this;
		try
		{
			guard = new Harmony("valyn.valheim.worldterrain.loading-guard");
			guard.Patch((MethodBase)AccessTools.Constructor(typeof(WorldGenerator), new Type[1] { typeof(World) }, false), new HarmonyMethod(AccessTools.Method(typeof(TerrainPlugin), "GuardGeneration", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			ResetPreviousConfig();
			newWorlds = ((BaseUnityPlugin)this).Config.Bind<bool>("Climate continents", "EnableForNewWorlds", true, "Generate new worlds with Earth-inspired continental structure and climate. Saved worlds retain their own terrain recipe; unmarked existing worlds stay native.");
			dig = ((BaseUnityPlugin)this).Config.Bind<float>("Climate continents", "DigDepthMeters", 16f, new ConfigDescription("Maximum digging depth below generated ground. Saved per new world.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(8f, 64f), Array.Empty<object>()));
			raise = ((BaseUnityPlugin)this).Config.Bind<float>("Climate continents", "RaiseHeightMeters", 16f, new ConfigDescription("Maximum terrain raising above generated ground. Saved per new world.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(8f, 32f), Array.Empty<object>()));
			ValidateApi();
			harmony = new Harmony("valyn.valheim.worldterrain");
			harmony.PatchAll(typeof(TerrainPlugin).Assembly);
			Ready = true;
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Valyn Earthlike Worlds 1.3.1 loaded; reference=Valheim 1.0.20; new-world generator=ClimateContinents v22; seed-calibrated continental uplift over native elevation, native waterways; exact-field cache; native sea level; dig=" + dig.Value + "m; raise=" + raise.Value + "m."));
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Climate controls region shapes; native terrain detail, lowland Swamps, resources and content routines retained. Use vt_status for the saved world recipe and edit limits.");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"New v22 worlds use lower inland elevation and balanced native Mountain inputs. The inland reduction continues through Mountain transitions; digging/raising limits are independent of generation height adjustments.");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Existing v17/v18/v21/v22 worlds keep their saved terrain recipe and limits. Create a new world to use the latest generation. Original frost thresholds remain unchanged.");
			((BaseUnityPlugin)this).Logger.LogWarning((object)"All players need the same plugin version. Use one world-generation mod and one terrain-edit-limit provider at a time. Keep this plugin installed for worlds generated with it.");
		}
		catch (Exception ex)
		{
			Ready = false;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			((BaseUnityPlugin)this).Logger.LogError((object)("Valyn Earthlike Worlds disabled because required patch checks failed: " + ex));
		}
	}

	private void ResetPreviousConfig()
	{
		string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath;
		if (File.Exists(configFilePath) && TerrainConfig.IsPrevious(File.ReadAllText(configFilePath)))
		{
			string text = configFilePath + ".pre-climate-050.bak";
			if (!File.Exists(text))
			{
				File.Copy(configFilePath, text);
			}
			File.WriteAllText(configFilePath, "# Valyn Earthlike Worlds 1.3.1 — new-world settings.\n# Change while Valheim is closed. Settings apply to newly created worlds.\n# New worlds use v22; existing v17/v18/v21 worlds retain their heights and saved limits.\n\n[Climate continents]\nEnableForNewWorlds = true\nDigDepthMeters = 16\nRaiseHeightMeters = 16\n");
			((BaseUnityPlugin)this).Config.Reload();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Previous world-terrain config replaced with the climate-continent defaults. A recovery copy was retained beside the config.");
		}
	}

	private void OnDestroy()
	{
		Ready = false;
		Current = null;
		survey.Cancel();
		frameProbe.Cancel();
		TerrainNetwork.Reset();
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
		if (guard != null)
		{
			guard.UnpatchSelf();
		}
		Instance = null;
	}

	private void Update()
	{
		if (Ready)
		{
			survey.Tick();
			if (frameProbe.Active)
			{
				frameProbe.Tick(survey.Active);
			}
		}
	}

	private static void GuardGeneration(World __0)
	{
		if (Ready || __0 == null || __0.m_menu || __0.m_startingGlobalKeys == null || !__0.m_startingGlobalKeys.Any((string k) => k?.StartsWith("valynterrain_", StringComparison.Ordinal) ?? false))
		{
			return;
		}
		throw new InvalidOperationException("This world requires a working Valyn Earthlike Worlds plugin. Patch validation failed; generation was blocked to preserve its terrain.");
	}

	internal void Log(string text)
	{
		((BaseUnityPlugin)this).Logger.LogInfo((object)text);
	}

	internal void Error(string text)
	{
		((BaseUnityPlugin)this).Logger.LogError((object)text);
	}

	internal void MarkNewWorld(World world)
	{
		if (!Ready || CreationScope <= 0 || world == null || world.m_menu || !newWorlds.Value)
		{
			return;
		}
		TerrainProfile terrainProfile = TerrainProfile.ClimateConsistentUplands(dig.Value, raise.Value);
		if (!terrainProfile.IsVanilla)
		{
			if (world.m_startingGlobalKeys == null)
			{
				world.m_startingGlobalKeys = new List<string>();
			}
			if (TerrainProfile.Read(world.m_startingGlobalKeys) != null)
			{
				throw new InvalidOperationException("New world already contains a terrain recipe");
			}
			world.m_startingGlobalKeys.Add(terrainProfile.Encode());
			Remember(world, terrainProfile);
			Log("New terrain world marked: " + world.m_name + "; UID=" + world.m_uid + "; " + terrainProfile.Encode());
		}
	}

	private void Remember(World world, TerrainProfile p)
	{
		if (world != null && !world.m_menu && p != null)
		{
			if (known.TryGetValue(world.m_uid, out var value) && (value.Seed != world.m_seed || value.Recipe != p.Encode()))
			{
				throw new InvalidOperationException("Terrain recipe changed for an existing world UID. Refusing to reshape the world.");
			}
			known[world.m_uid] = new SavedRecipe
			{
				Seed = world.m_seed,
				Recipe = p.Encode()
			};
		}
	}

	internal void RememberLoaded(World world)
	{
		if (world == null || world.m_menu)
		{
			return;
		}
		try
		{
			Remember(world, TerrainProfile.Read(world.m_startingGlobalKeys));
		}
		catch (FormatException ex)
		{
			Error("Terrain recipe not loaded for '" + world.m_name + "': " + ex.Message);
		}
	}

	internal void PreserveBeforeSave(World world)
	{
		if (world == null || world.m_menu)
		{
			return;
		}
		TerrainProfile terrainProfile = TerrainProfile.Read(world.m_startingGlobalKeys);
		SavedRecipe value;
		if (terrainProfile != null)
		{
			Remember(world, terrainProfile);
		}
		else if (known.TryGetValue(world.m_uid, out value))
		{
			if (value.Seed != world.m_seed)
			{
				throw new InvalidOperationException("World UID/seed changed before terrain save");
			}
			if (world.m_startingGlobalKeys == null)
			{
				world.m_startingGlobalKeys = new List<string>();
			}
			world.m_startingGlobalKeys.Add(value.Recipe);
			Log("Preserved saved terrain recipe during world-settings save: " + world.m_uid);
		}
	}

	internal TerrainProfile Recipe(World world)
	{
		if (world == null || world.m_menu)
		{
			return TerrainProfile.Vanilla;
		}
		TerrainProfile terrainProfile = TerrainProfile.Read(world.m_startingGlobalKeys);
		if (terrainProfile == null && known.TryGetValue(world.m_uid, out var value))
		{
			if (value.Seed != world.m_seed)
			{
				throw new InvalidOperationException("World UID reused with another terrain seed");
			}
			terrainProfile = TerrainProfile.Decode(value.Recipe);
		}
		if (terrainProfile != null)
		{
			Remember(world, terrainProfile);
		}
		return terrainProfile ?? TerrainProfile.Vanilla;
	}

	internal void BeginGeneration(WorldGenerator generator, World world)
	{
		survey.Cancel();
		frameProbe.Cancel();
		TerrainProfile terrainProfile = ((Object.op_Implicit((Object)(object)ZNet.instance) && !ZNet.instance.IsServer() && !world.m_menu) ? (TerrainNetwork.IncomingRecipe(world) ?? TerrainProfile.Vanilla) : Recipe(world));
		Current = new GenerationContext(generator, world, terrainProfile);
		if (!world.m_menu)
		{
			Log("World terrain recipe loaded BEFORE generation: " + world.m_name + "; UID=" + world.m_uid + "; seed=" + world.m_seed + "; " + (terrainProfile.IsVanilla ? "native" : terrainProfile.Encode()) + "; Swamp remains native lowland.");
		}
	}

	internal static float Lower()
	{
		return 0f - Active.Dig;
	}

	internal static float Upper()
	{
		return Active.Raise;
	}

	internal static double LowerDistance()
	{
		return Active.Dig;
	}

	internal static double UpperDistance()
	{
		return Active.Raise;
	}

	internal static float DigThreshold()
	{
		return Active.Dig - 0.05f;
	}

	internal void Status(Terminal terminal)
	{
		GenerationContext current = Current;
		string text = ((current == null || current.World.m_menu) ? "No gameplay world loaded." : ("Valyn Earthlike Worlds 1.3.1 | world=" + current.World.m_name + " | recipe=" + current.Profile.Encode() + " | dig=" + current.Profile.Dig + "m | raise=" + current.Profile.Raise + "m" + ((current.Climate == null) ? " | native terrain" : (" | land=" + current.Climate.LayoutDescription + " | height/material aligned | terrain profile=" + ((current.Profile.RecipeVersion == 22) ? "v22 consistent ordinary+Mountain reduction" : ((current.Profile.RecipeVersion == 21) ? "v21 native Mountain input + v18 inland reduction" : ((current.Profile.RecipeVersion == 18) ? "v18 inland reduction" : "original v17"))) + " | original frost thresholds")) + " | sea level=30m | " + TerrainSurvey.Waterways(current.Generator) + " | " + survey.Status));
		terminal.AddString(text);
		Log(text);
	}

	internal void Scan(Terminal terminal)
	{
		string text = survey.Start();
		terminal.AddString(text);
		Log(text);
	}

	internal void StopScan(Terminal terminal)
	{
		survey.Cancel();
		terminal.AddString("Terrain scan stopped; scan idle.");
		Log("Terrain scan stopped; scan idle.");
	}

	internal void CaptureFrames(Terminal terminal)
	{
		try
		{
			string text = frameProbe.Start();
			terminal.AddString(text);
			Log(text);
		}
		catch (Exception ex)
		{
			frameProbe.Cancel();
			terminal.AddString("Frame capture could not start: " + ex.Message);
		}
	}

	private static void ValidateApi()
	{
		MethodInfo methodInfo = AccessTools.Method(typeof(WorldGenerator), "GetBiomeHeight", (Type[])null, (Type[])null);
		ParameterInfo[] array = ((methodInfo == null) ? null : methodInfo.GetParameters());
		Type[] second = new Type[6]
		{
			typeof(Biome),
			typeof(float),
			typeof(float),
			typeof(Color).MakeByRefType(),
			typeof(bool),
			typeof(bool)
		};
		if (methodInfo == null || methodInfo.ReturnType != typeof(float) || array.Length != 6 || !array.Select((ParameterInfo p) => p.ParameterType).SequenceEqual(second) || !array[3].IsOut)
		{
			throw new MissingMethodException("Native biome-height blend signature changed");
		}
		MethodInfo methodInfo2 = AccessTools.Method(typeof(ClutterSystem), "GetGroundInfo", (Type[])null, (Type[])null);
		Type[] second2 = new Type[5]
		{
			typeof(Vector3),
			typeof(Vector3).MakeByRefType(),
			typeof(Vector3).MakeByRefType(),
			typeof(Heightmap).MakeByRefType(),
			typeof(Biome).MakeByRefType()
		};
		if (methodInfo2 == null || methodInfo2.ReturnType != typeof(bool) || !(from p in methodInfo2.GetParameters()
			select p.ParameterType).SequenceEqual(second2) || !methodInfo2.GetParameters().Skip(1).All((ParameterInfo p) => p.IsOut))
		{
			throw new MissingMethodException("Native ground clutter signature changed");
		}
		if (AccessTools.Method(typeof(Heightmap), "RebuildRenderMesh", (Type[])null, (Type[])null) == null || typeof(Mesh).GetMethod("SetColors", new Type[1] { typeof(List<Color32>) }) == null || typeof(Mesh).GetMethod("GetVertices", new Type[1] { typeof(List<Vector3>) }) == null)
		{
			throw new MissingMethodException("Native terrain frost mesh API changed");
		}
		string[] array2 = new string[2] { "GetBaseHeight", "GetBiome" };
		foreach (string name in array2)
		{
			if (!typeof(WorldGenerator).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Any((MethodInfo m) => m.Name == name))
			{
				throw new MissingMethodException(name);
			}
		}
		array2 = new string[4] { "m_startingGlobalKeys", "m_uid", "m_seed", "m_worldGenVersion" };
		foreach (string text in array2)
		{
			if (AccessTools.Field(typeof(World), text) == null)
			{
				throw new MissingFieldException("World", text);
			}
		}
	}
}
internal static class TerrainVersion
{
	internal const string Current = "1.3.1";
}
internal sealed class TerrainWire
{
	internal readonly long WorldId;

	internal readonly int Seed;

	internal readonly int Generation;

	internal readonly TerrainProfile Profile;

	internal TerrainWire(long id, int seed, int generation, TerrainProfile profile)
	{
		WorldId = id;
		Seed = seed;
		Generation = generation;
		Profile = profile;
	}

	internal string Encode()
	{
		return "v1|1.3.1|" + WorldId.ToString(CultureInfo.InvariantCulture) + "|" + Seed.ToString(CultureInfo.InvariantCulture) + "|" + Generation.ToString(CultureInfo.InvariantCulture) + "|" + Profile.Encode();
	}

	internal static TerrainWire Decode(string value)
	{
		if (value == null || value.Length > 300)
		{
			throw new FormatException("Invalid terrain handshake length");
		}
		string[] array = value.Split('|');
		if (array.Length != 6 || array[0] != "v1" || array[1] != "1.3.1" || !long.TryParse(array[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || !int.TryParse(array[3], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2) || !int.TryParse(array[4], NumberStyles.None, CultureInfo.InvariantCulture, out var result3))
		{
			throw new FormatException("Terrain plugin/protocol version or world data mismatch");
		}
		return new TerrainWire(result, result2, result3, TerrainProfile.Decode(array[5]));
	}
}
internal sealed class TerrainProfile
{
	internal const string Family = "valynterrain_";

	internal const string AlignedContinentsPrefix = "valynterrain_v17_";

	internal const string LowlandContinentsPrefix = "valynterrain_v18_";

	internal const string NativeMountainPrefix = "valynterrain_v21_";

	internal const string ConsistentUplandsPrefix = "valynterrain_v22_";

	internal static readonly TerrainProfile Vanilla = new TerrainProfile(8f, 8f, 0);

	internal readonly float Dig;

	internal readonly float Raise;

	internal readonly int RecipeVersion;

	internal bool IsVanilla => RecipeVersion == 0;

	private TerrainProfile(float dig, float raise, int revision)
	{
		if (!Valid(dig, 8f, 64f) || !Valid(raise, 8f, 32f))
		{
			throw new ArgumentOutOfRangeException("Terrain edit limits outside supported bounds");
		}
		Dig = Quantize(dig);
		Raise = Quantize(raise);
		RecipeVersion = revision;
	}

	internal static TerrainProfile ClimateAlignedContinents(float dig, float raise)
	{
		return new TerrainProfile(dig, raise, 17);
	}

	internal static TerrainProfile ClimateLowlandContinents(float dig, float raise)
	{
		return new TerrainProfile(dig, raise, 18);
	}

	internal static TerrainProfile ClimateNativeMountain(float dig, float raise)
	{
		return new TerrainProfile(dig, raise, 21);
	}

	internal static TerrainProfile ClimateConsistentUplands(float dig, float raise)
	{
		return new TerrainProfile(dig, raise, 22);
	}

	private static bool Valid(float v, float min, float max)
	{
		if (!float.IsNaN(v) && !float.IsInfinity(v) && v >= min)
		{
			return v <= max;
		}
		return false;
	}

	private static float Quantize(float v)
	{
		return (float)Math.Round(v * 1000f, MidpointRounding.AwayFromZero) / 1000f;
	}

	private static string Milli(float v)
	{
		return ((int)Math.Round(v * 1000f)).ToString(CultureInfo.InvariantCulture);
	}

	internal string Encode()
	{
		if (!IsVanilla)
		{
			return ((RecipeVersion == 22) ? "valynterrain_v22_" : ((RecipeVersion == 21) ? "valynterrain_v21_" : ((RecipeVersion == 18) ? "valynterrain_v18_" : "valynterrain_v17_"))) + Milli(Dig) + "_" + Milli(Raise);
		}
		return "native";
	}

	internal static TerrainProfile Decode(string key)
	{
		int num = ((key != null && key.StartsWith("valynterrain_v17_", StringComparison.Ordinal)) ? 17 : ((key != null && key.StartsWith("valynterrain_v18_", StringComparison.Ordinal)) ? 18 : ((key != null && key.StartsWith("valynterrain_v21_", StringComparison.Ordinal)) ? 21 : ((key != null && key.StartsWith("valynterrain_v22_", StringComparison.Ordinal)) ? 22 : 0))));
		if (num == 0)
		{
			throw new FormatException("Unsupported terrain recipe. Valyn Earthlike Worlds 1.3.1 supports saved v17/v18/v21/v22 worlds only (retired v19/v20 development recipes are unsupported). No migration was performed. Recipe: " + key);
		}
		string[] array = key.Substring("valynterrain_v17_".Length).Split('_');
		if (array.Length != 2 || !int.TryParse(array[0], NumberStyles.None, CultureInfo.InvariantCulture, out var result) || !int.TryParse(array[1], NumberStyles.None, CultureInfo.InvariantCulture, out var result2))
		{
			throw new FormatException("Malformed terrain recipe");
		}
		try
		{
			return new TerrainProfile((float)result / 1000f, (float)result2 / 1000f, num);
		}
		catch (ArgumentOutOfRangeException innerException)
		{
			throw new FormatException("Terrain limits outside supported bounds", innerException);
		}
	}

	internal static TerrainProfile Read(IEnumerable<string> keys)
	{
		TerrainProfile terrainProfile = null;
		if (keys != null)
		{
			foreach (string key in keys)
			{
				if (key != null && key.StartsWith("valynterrain_", StringComparison.Ordinal))
				{
					TerrainProfile terrainProfile2 = Decode(key);
					if (terrainProfile != null && terrainProfile.Encode() != terrainProfile2.Encode())
					{
						throw new FormatException("Conflicting terrain recipes in world metadata");
					}
					terrainProfile = terrainProfile2;
				}
			}
		}
		return terrainProfile;
	}
}
internal static class TerrainPolicy
{
	internal const float NativeHeightScale = 200f;

	internal const float SeaLevel = 30f;
}
internal sealed class TerrainSampleCache
{
	private struct Entry
	{
		internal bool Valid;

		internal object Owner;

		internal float X;

		internal float Z;

		internal ClimateSample Value;
	}

	private const int Size = 2048;

	private readonly Entry[] entries = new Entry[2048];

	private static int Slot(float x, float z)
	{
		int hashCode = x.GetHashCode();
		uint hashCode2 = (uint)z.GetHashCode();
		int num = hashCode ^ (int)((hashCode2 << 16) | (hashCode2 >> 16));
		int num2 = (num ^ (num >>> 16)) * 2146121005;
		int num3 = (num2 ^ (num2 >>> 15)) * -2073254261;
		return (num3 ^ (num3 >>> 16)) & 0x7FF;
	}

	internal bool TryGet(float x, float z, out ClimateSample sample)
	{
		return TryGet(null, x, z, out sample);
	}

	internal bool TryGet(object owner, float x, float z, out ClimateSample sample)
	{
		Entry entry = entries[Slot(x, z)];
		sample = entry.Value;
		if (entry.Valid && entry.Owner == owner && entry.X == x)
		{
			return entry.Z == z;
		}
		return false;
	}

	internal void Store(float x, float z, ClimateSample sample)
	{
		Store(null, x, z, sample);
	}

	internal void Store(object owner, float x, float z, ClimateSample sample)
	{
		entries[Slot(x, z)] = new Entry
		{
			Valid = true,
			Owner = owner,
			X = x,
			Z = z,
			Value = sample
		};
	}
}
internal sealed class TerrainSurvey
{
	private const int Width = 101;

	private const int Step = 200;

	private const int Half = 10000;

	private StreamWriter writer;

	private WorldGenerator generator;

	private TerrainPlugin.GenerationContext context;

	private int index;

	private int samples;

	private int dry;

	private int wet;

	private int shore;

	private int dryOcean;

	private int swamp;

	private int swampCore;

	private int oceanSamples;

	private int deepOcean;

	private int mountainSamples;

	private int dryPointOcean;

	private int biomeDisagreements;

	private int mountainDry;

	private int mountainHigh;

	private float oceanDepthMin = float.MaxValue;

	private float oceanDepthMax;

	private float mountainMin = float.MaxValue;

	private float mountainMax = float.MinValue;

	private float swampMin = float.MaxValue;

	private float swampMax = float.MinValue;

	private readonly bool[] land = new bool[10201];

	private readonly Dictionary<string, int> biomes = new Dictionary<string, int>();

	private string path;

	private string partial;

	private string last = "scan idle";

	internal string Status
	{
		get
		{
			if (writer != null)
			{
				return "scanning " + index + "/" + land.Length;
			}
			return last;
		}
	}

	internal bool Active => writer != null;

	private static string F(double x)
	{