Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Earthlike Worlds v1.3.1
plugins/ValynWorldTerrain.dll
Decompiled 2 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.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) {