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 Riverheim v1.0.0
BepInEx/plugins/Riverheim.dll
Decompiled 2 days 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.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using DelaunatorSharp; using HarmonyLib; using Microsoft.CodeAnalysis; using Riverheim.Configuration; using Riverheim.Configuration.Extensions; using Riverheim.Pipeline; using Riverheim.Pipeline.Extensions; using Riverheim.Plugin.Rendering; using Riverheim.Rendering; using Riverheim.Rendering.Biomes; using Riverheim.Rendering.Rivers; using Riverheim.Rendering.Splats; using Riverheim.Util; using Riverheim.Util.Debug; using Riverheim.Util.Geometry; using Riverheim.Util.Grids; using Riverheim.Util.Interpolation; using Riverheim.Util.Mathematics; using Riverheim.Util.Random; using Riverheim.Util.Random.Noise; using Riverheim.Util.Random.Noise.Primitives; using Riverheim.Util.Random.Primitives; using Riverheim.Util.Splines; using Riverheim.Util.Tilings; using Riverheim.Util.Tilings.Containers; using Riverheim.Util.Tilings.Internal; using Riverheim.Util.Tilings.Processing; using Riverheim.World; using Riverheim.World.Biomes; using Riverheim.World.Biomes.Competitive; using Riverheim.World.Height; using Riverheim.World.Height.Mountains; using Riverheim.World.Landmass; using Riverheim.World.Rivers; using Riverheim.World.Splats; using Riverheim.World.Tilings; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_utils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Riverheim")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Comprehensive terrain generator for Valheim")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+979decd56006774960bb6ef32917a9b261131be4")] [assembly: AssemblyProduct("Riverheim")] [assembly: AssemblyTitle("Riverheim")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class ExtensionMarkerAttribute : Attribute { public ExtensionMarkerAttribute(string name) { } } } internal static class PluginInfo { public const string PLUGIN_GUID = "Riverheim"; public const string PLUGIN_NAME = "Riverheim"; public const string PLUGIN_VERSION = "1.0.0"; } namespace Riverheim.Plugin { public static class WorldExtensions { private class ExtensionData { public WorldSettings settings; } [SpecialName] public sealed class <G>$7B5663A8641255449155D6EF66167945 { [SpecialName] public static class <M>$45CD4584ACAF47103DDEBE68327735F9 { } [ExtensionMarker("<M>$45CD4584ACAF47103DDEBE68327735F9")] private ExtensionData GetData() { throw null; } [ExtensionMarker("<M>$45CD4584ACAF47103DDEBE68327735F9")] public WorldSettings GetSettings() { throw null; } [ExtensionMarker("<M>$45CD4584ACAF47103DDEBE68327735F9")] public void SetSettings(WorldSettings settings) { throw null; } } private static readonly ConditionalWeakTable<World, ExtensionData> Storage = new ConditionalWeakTable<World, ExtensionData>(); private static WorldSettings GetDefaultSettings(World world) { if (world.m_name.StartsWith("RHEXP")) { return new WorldSettings("builtin.experimental", 0); } return ConfigManager.DefaultWorldSettings(); } private static ExtensionData GetData(this World world) { return Storage.GetValue(world, (World wld) => new ExtensionData { settings = GetDefaultSettings(wld) }); } public static WorldSettings GetSettings(this World world) { return world.GetData().settings; } public static void SetSettings(this World world, WorldSettings settings) { world.GetData().settings = settings; } } [BepInPlugin("dev.gurebu.riverheim.stable", "Riverheim", "1.0.0")] [BepInIncompatibility("dev.gurebu.riverheim.unstable")] [BepInIncompatibility("BetterContinents")] public class RiverheimPlugin : BaseUnityPlugin { [HarmonyPatch(typeof(WorldGenerator))] private static class WorldGeneratorPatch { private static GeneratorPipeline BuildPipeline() { string[] resultResourceIds = new string[1] { "Rendering/WorldRenderer" }; GeneratorPipeline pipeline = new GeneratorPipeline.Builder(resultResourceIds).SetParallel(value: true).SetClearUnneededCache(value: true).Build(); pipeline.PipelineCompleted += delegate(GeneratorPipeline.PipelineCompletionEvent evt) { Logger.LogInfo($"World generation completed in {evt.duration.TotalMilliseconds:0.}ms"); }; pipeline.TaskCompleted += delegate(GeneratorPipeline.TaskCompletionEvent evt) { string text = ""; string text2 = string.Join(", ", evt.resources); string text3 = evt.duration.TotalMilliseconds.ToString("0."); Logger.LogInfo(evt.cached ? $"[{evt.progress} / {pipeline.Length}] {text2} retrieved from cache" : $"[{evt.progress} / {pipeline.Length}] {text2} computed in {text3}ms {text}"); }; return pipeline; } [HarmonyPrefix] [HarmonyPatch("Pregenerate")] private static bool Pregenerate(WorldGenerator __instance) { World world = __instance.m_world; if (world.m_menu) { return true; } if (world.m_seed == 0) { Logger.LogWarning("Seed is 0 for world '" + world.m_name + "', but not a menu world, must be an error"); Logger.LogWarning("Skipping world generation, treating as a vanilla world"); return true; } GeneratorPipeline generatorPipeline = BuildPipeline(); WorldSettings settings = world.GetSettings(); Logger.LogInfo($"Generating world '{world.m_name}'/{world.m_seed} with preset: '{settings.Preset}' v.{settings.PresetVersion}..."); generatorPipeline.Compute(world.m_seed, ConfigManager.GetConfig(settings)); __instance.SetRenderer(new ValheimWorldRenderer(generatorPipeline.GetResult<WorldRenderer>("Rendering/WorldRenderer"), 30f)); return false; } [HarmonyPrefix] [HarmonyPatch("GetBiomeHeight")] private static bool GetBiomeHeight(WorldGenerator __instance, Biome biome, float wx, float wy, out Color mask, ref float __result) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) mask = Color.black; ValheimWorldRenderer renderer = __instance.GetRenderer(); if (renderer == null) { return true; } __result = renderer.GetHeight(wx, wy, out mask); return false; } [HarmonyPrefix] [HarmonyPatch("GetHeight")] [HarmonyPatch(new Type[] { typeof(float), typeof(float) })] private static bool GetHeight(WorldGenerator __instance, float wx, float wy, ref float __result) { ValheimWorldRenderer renderer = __instance.GetRenderer(); if (renderer == null) { return true; } __result = renderer.GetHeight(wx, wy, out var _); return false; } [HarmonyPrefix] [HarmonyPatch("GetHeight")] [HarmonyPatch(/*Could not decode attribute arguments.*/)] private static bool GetHeightWithMask(WorldGenerator __instance, float wx, float wy, out Color mask, ref float __result) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) mask = Color.black; ValheimWorldRenderer renderer = __instance.GetRenderer(); if (renderer == null) { return true; } __result = renderer.GetHeight(wx, wy, out mask); return false; } [HarmonyPrefix] [HarmonyPatch("GetBiome", new Type[] { typeof(float), typeof(float), typeof(float), typeof(bool) })] private static bool GetBiome(WorldGenerator __instance, float wx, float wy, ref Biome __result) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected I4, but got Unknown ValheimWorldRenderer renderer = __instance.GetRenderer(); if (renderer == null) { return true; } __result = (Biome)(int)renderer.GetBiome(wx, wy); return false; } [HarmonyPrefix] [HarmonyPatch("GetForestFactor")] private static bool GetForestFactor(ref Vector3 pos, ref float __result) { ValheimWorldRenderer renderer = WorldGenerator.instance.GetRenderer(); if (renderer == null) { return true; } __result = renderer.GetForestFactor(pos.x, pos.z); return false; } [HarmonyPrefix] [HarmonyPatch("GetAshlandsHeight")] private static bool GetAshlandsHeight(WorldGenerator __instance, float wx, float wy, out Color mask, ref float __result) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) mask = Color.black; ValheimWorldRenderer renderer = __instance.GetRenderer(); if (renderer == null) { return true; } __result = renderer.GetHeight(wx, wy, out mask); return false; } } [HarmonyPatch(typeof(World))] private static class WorldSaveLoadPatch { private const int CUSTOM_WORLDGEN_MARKER = -1; private const string RIVERHEIM_WORLDGEN = "dev.gurebu.riverheim.stable"; private static void Log(string message) { Logger.LogInfo(message); } private static void WriteHeader(ZPackage package) { package.Write(-1); package.Write("dev.gurebu.riverheim.stable"); } private static bool ReadHeader(ZPackage package) { int num = package.ReadInt(); if (num != -1) { Log($"World doesn't have custom worldgen marker {-1}, instead got: {num}, skipping"); return false; } string text = package.ReadString(); if (text != "dev.gurebu.riverheim.stable") { Log("Custom worldgen '" + text + "' does not match 'dev.gurebu.riverheim.stable', skipping"); return false; } return true; } private static void WriteWorldSettings(ZPackage package, World world) { package.Write(world.GetSettings()); } private static bool ReadWorldSettings(ZPackage package, World world) { try { world.SetSettings(package.ReadWorldSettings()); } catch (WorldSettingSerialization.Error error) { Log("World settings malformed or unsupported: " + error.Message + ", skipping"); return false; } return true; } public static IEnumerable<CodeInstruction> TranspileWorldSave(IEnumerable<CodeInstruction> instructions) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown CodeInstruction ld_ZPackage; CodeInstruction ld_World; return CodeMatcherExtensions.AdvanceToAfterZPackageConstructor(new CodeMatcher(instructions, (ILGenerator)null), out ld_ZPackage).InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { ld_ZPackage }).InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { CodeInstruction.Call(typeof(WorldSaveLoadPatch), "WriteHeader", (Type[])null, (Type[])null) }) .AdvanceToAfterWorldVersionWrite(out ld_World) .InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { ld_ZPackage }) .InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { ld_World }) .InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { CodeInstruction.Call(typeof(WorldSaveLoadPatch), "WriteWorldSettings", (Type[])null, (Type[])null) }) .DebugLog() .InstructionEnumeration(); } [HarmonyTranspiler] [HarmonyPatch("LoadWorld")] private static IEnumerable<CodeInstruction> TranspileWorldLoad(IEnumerable<CodeInstruction> instructions, ILGenerator generator) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Expected O, but got Unknown Label label; CodeInstruction ld_ZPackage; CodeInstruction ld_World; return CodeMatcherExtensions.MatchBadVersionWorldConstructor(new CodeMatcher(instructions, generator), out label).Start().AdvanceToAfterZPackageConstructor(out ld_ZPackage) .InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { ld_ZPackage }) .InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { CodeInstruction.Call(typeof(WorldSaveLoadPatch), "ReadHeader", (Type[])null, (Type[])null) }) .InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { new CodeInstruction(OpCodes.Brfalse_S, (object)label) }) .AdvanceToAfterDefaultWorldConstructor(out ld_World) .InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { ld_ZPackage }) .InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { ld_World }) .InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { CodeInstruction.Call(typeof(WorldSaveLoadPatch), "ReadWorldSettings", (Type[])null, (Type[])null) }) .InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { new CodeInstruction(OpCodes.Brfalse_S, (object)label) }) .DebugLog() .InstructionEnumeration(); } } [HarmonyPatch(typeof(World))] private static class WorldServerLoadPatch { [HarmonyPrefix] [HarmonyPatch("GetCreateWorld")] private static void GetCreateWorld(ref World __result, string name, FileSource source) { //IL_001e: 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) SaveWithBackups val = default(SaveWithBackups); if (SaveSystem.TryGetSaveByName(name, (SaveDataType)0, ref val) && !val.IsDeleted) { __result = World.LoadWorld(val); if ((int)__result.m_dataError != 0) { throw new Exception($"Failed to load world with name \"{name}\", data error {__result.m_dataError}."); } } } } [HarmonyPatch(typeof(ZNet))] private static class ZNetPatch { private static readonly Version LocalVersion = new Version("1.0.0"); private static readonly ConditionalWeakTable<ZRpc, Version> PeerVersions = new ConditionalWeakTable<ZRpc, Version>(); private static bool VersionIsCompatible(Version remoteVersion) { if (LocalVersion.Major == remoteVersion.Major) { return LocalVersion.Minor == remoteVersion.Minor; } return false; } private static void RPC_AssertModVersion(ZRpc sender, string versionString) { Version version = new Version(versionString); PeerVersions.Add(sender, version); if (!VersionIsCompatible(version)) { Logger.LogWarning($"Riverheim plugin version mismatch! Local version: {LocalVersion}, remote: {version}"); sender.Invoke("Error", new object[1] { (object)(ConnectionStatus)3 }); } } [HarmonyPriority(700)] [HarmonyPrefix] [HarmonyPatch("OnNewConnection")] private static void OnNewConnection(ZNet __instance, ZNetPeer peer) { peer.m_rpc.Register<string>("AssertRiverheimVersion", (Action<ZRpc, string>)RPC_AssertModVersion); } [HarmonyPriority(700)] [HarmonyPrefix] [HarmonyPatch("RPC_ClientHandshake")] private static void RPC_ClientHandshake(ZNet __instance, ZRpc rpc) { rpc.Invoke("AssertRiverheimVersion", new object[1] { "1.0.0" }); } [HarmonyPriority(700)] [HarmonyPrefix] [HarmonyPatch("RPC_ServerHandshake")] private static void RPC_ServerHandshake(ZNet __instance, ZRpc rpc) { rpc.Invoke("AssertRiverheimVersion", new object[1] { "1.0.0" }); } [HarmonyPriority(700)] [HarmonyPrefix] [HarmonyPatch("SendPeerInfo")] private static bool SendPeerInfo(ZNet __instance, ZRpc rpc) { if (ZNet.instance.IsServer()) { return true; } if (PeerVersions.TryGetValue(rpc, out var value) && VersionIsCompatible(value)) { return true; } Logger.LogWarning($"Riverheim plugin version mismatch! Local version: {LocalVersion}, remote: {value}"); rpc.Invoke("Disconnect", Array.Empty<object>()); return false; } [HarmonyPriority(700)] [HarmonyPrefix] [HarmonyPatch("RPC_PeerInfo")] private static bool RPC_PeerInfo(ZNet __instance, ZRpc rpc) { if (!ZNet.instance.IsServer()) { return true; } if (PeerVersions.TryGetValue(rpc, out var value) && VersionIsCompatible(value)) { return true; } Logger.LogWarning($"Riverheim plugin version mismatch! Local version: {LocalVersion}, remote: {value}"); rpc.Invoke("Error", new object[1] { (object)(ConnectionStatus)3 }); return false; } } private const string GUID_STABLE = "dev.gurebu.riverheim.stable"; private const string GUID_UNSTABLE = "dev.gurebu.riverheim.unstable"; private const string GUID = "dev.gurebu.riverheim.stable"; private const string GUID_INCOMPATIBLE = "dev.gurebu.riverheim.unstable"; private const string PLUGIN_NAME = "Riverheim"; public void Awake() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown Harmony val = new Harmony("dev.gurebu.riverheim.stable"); val.PatchAll(); val.Patch((MethodBase)FindWorldSaveMethod(), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(ReplaceWorldSaveMethod()), (HarmonyMethod)null, (HarmonyMethod)null); Logger.SetLogger(delegate(Logger.LogLevel level, object message) { switch (level) { case Logger.LogLevel.Info: ((BaseUnityPlugin)this).Logger.LogInfo(message); break; case Logger.LogLevel.Warning: ((BaseUnityPlugin)this).Logger.LogWarning(message); break; case Logger.LogLevel.Error: ((BaseUnityPlugin)this).Logger.LogError(message); break; default: throw new ArgumentOutOfRangeException("level", level, null); } }); } private MethodInfo FindWorldSaveMethod() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) GameVersion currentVersion = Version.CurrentVersion; string text; Type[] array; if (currentVersion >= new GameVersion(0, 221, 13)) { text = "SaveWorldFWLData"; array = new Type[2] { typeof(DateTime), typeof(FileWriter).MakeByRefType() }; } else { text = "SaveWorldMetaData"; array = new Type[4] { typeof(DateTime), typeof(bool), typeof(bool).MakeByRefType(), typeof(FileWriter).MakeByRefType() }; } ((BaseUnityPlugin)this).Logger.LogDebug((object)$"Patching world load method: '{text}' for game version {currentVersion}"); return AccessTools.Method(typeof(World), text, array, (Type[])null); } private static MethodInfo ReplaceWorldSaveMethod() { return AccessTools.Method(typeof(WorldSaveLoadPatch), "TranspileWorldSave", (Type[])null, (Type[])null); } } public static class WorldGeneratorExtensions { [SpecialName] public sealed class <G>$6FCB1B340A09A2B299219C5F5C371FD8 { [SpecialName] public static class <M>$6F6DF1476D6644A49741E96742C809A0 { } [ExtensionMarker("<M>$6F6DF1476D6644A49741E96742C809A0")] public void SetRenderer(ValheimWorldRenderer renderer) { throw null; } [ExtensionMarker("<M>$6F6DF1476D6644A49741E96742C809A0")] public ValheimWorldRenderer GetRenderer() { throw null; } } private static readonly ConditionalWeakTable<WorldGenerator, ValheimWorldRenderer> Renderers = new ConditionalWeakTable<WorldGenerator, ValheimWorldRenderer>(); public static void SetRenderer(this WorldGenerator worldGenerator, ValheimWorldRenderer renderer) { Renderers.AddOrUpdate(worldGenerator, renderer); } public static ValheimWorldRenderer GetRenderer(this WorldGenerator worldGenerator) { if (worldGenerator == null) { return null; } Renderers.TryGetValue(worldGenerator, out var value); return value; } } internal static class CodeMatcherExtensions { [SpecialName] public sealed class <G>$6C7DE3593F499BD1D1FE1E0A0EDB9649 { [SpecialName] public static class <M>$F04B44C5FDA9C4CCFD20224FF826B3CB { } [ExtensionMarker("<M>$F04B44C5FDA9C4CCFD20224FF826B3CB")] public CodeMatcher DebugLog() { throw null; } [ExtensionMarker("<M>$F04B44C5FDA9C4CCFD20224FF826B3CB")] public CodeMatcher AdvanceToAfterZPackageConstructor(out CodeInstruction ld_ZPackage) { throw null; } [ExtensionMarker("<M>$F04B44C5FDA9C4CCFD20224FF826B3CB")] public CodeMatcher AdvanceToAfterWorldVersionWrite(out CodeInstruction ld_World) { throw null; } [ExtensionMarker("<M>$F04B44C5FDA9C4CCFD20224FF826B3CB")] public CodeMatcher MatchBadVersionWorldConstructor(out Label label) { throw null; } [ExtensionMarker("<M>$F04B44C5FDA9C4CCFD20224FF826B3CB")] public CodeMatcher AdvanceToAfterDefaultWorldConstructor(out CodeInstruction ld_World) { throw null; } } private static CodeInstruction MatchStoreWithLoad(CodeInstruction instruction) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Expected O, but got Unknown if (instruction.opcode == OpCodes.Stloc_0) { return new CodeInstruction(OpCodes.Ldloc_0, (object)null); } if (instruction.opcode == OpCodes.Stloc_1) { return new CodeInstruction(OpCodes.Ldloc_1, (object)null); } if (instruction.opcode == OpCodes.Stloc_2) { return new CodeInstruction(OpCodes.Ldloc_2, (object)null); } if (instruction.opcode == OpCodes.Stloc_3) { return new CodeInstruction(OpCodes.Ldloc_3, (object)null); } if (instruction.opcode == OpCodes.Stloc_S) { return new CodeInstruction(OpCodes.Ldloc_S, instruction.operand); } if (instruction.opcode == OpCodes.Stloc) { return new CodeInstruction(OpCodes.Ldloc, instruction.operand); } throw new InvalidOperationException($"Not a store instruction: {instruction.opcode}"); } public static CodeMatcher DebugLog(this CodeMatcher matcher) { return matcher; } public static CodeMatcher AdvanceToAfterZPackageConstructor(this CodeMatcher matcher, out CodeInstruction ld_ZPackage) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown matcher.MatchForward(true, (CodeMatch[])(object)new CodeMatch[2] { new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction instruction) => !(instruction.opcode != OpCodes.Newobj) && ((ConstructorInfo)instruction.operand).DeclaringType == typeof(ZPackage)), (string)null), new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction instruction) => CodeInstructionExtensions.IsStloc(instruction, (LocalBuilder)null)), (string)null) }); if (matcher.IsInvalid) { throw new InvalidOperationException("Error patching, unable to find zPackage instancing"); } ld_ZPackage = MatchStoreWithLoad(matcher.Instruction); return matcher.Advance(1); } public static CodeMatcher AdvanceToAfterWorldVersionWrite(this CodeMatcher matcher, out CodeInstruction ld_World) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown ld_World = new CodeInstruction(OpCodes.Ldarg_0, (object)null); matcher.MatchForward(true, (CodeMatch[])(object)new CodeMatch[1] { new CodeMatch((Func<CodeInstruction, bool>)delegate(CodeInstruction instruction) { if (instruction.opcode != OpCodes.Callvirt) { return false; } return ((MethodInfo)instruction.operand).DeclaringType == typeof(ZPackage) && ((MethodInfo)instruction.operand).Name == "Write" && ((MethodInfo)instruction.operand).GetParameters()[0].ParameterType == typeof(int); }, (string)null) }); return matcher.Advance(1); } public static CodeMatcher MatchBadVersionWorldConstructor(this CodeMatcher matcher, out Label label) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Expected O, but got Unknown matcher.MatchForward(false, (CodeMatch[])(object)new CodeMatch[3] { new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction instruction) => CodeInstructionExtensions.IsLdarg(instruction, (int?)null)), (string)null), new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction instruction) => instruction.opcode == OpCodes.Ldc_I4_1 || CodeInstructionExtensions.Is(instruction, OpCodes.Ldc_I4, (object)1) || CodeInstructionExtensions.Is(instruction, OpCodes.Ldc_I4_S, (object)1)), (string)null), new CodeMatch((Func<CodeInstruction, bool>)delegate(CodeInstruction instruction) { if (instruction.opcode != OpCodes.Newobj) { return false; } ConstructorInfo constructorInfo = (ConstructorInfo)instruction.operand; if (constructorInfo.DeclaringType != typeof(World)) { return false; } ParameterInfo[] parameters = constructorInfo.GetParameters(); if (parameters.Length != 2) { return false; } return parameters[0].ParameterType == typeof(SaveWithBackups) && parameters[1].ParameterType == typeof(SaveDataError); }, (string)null) }); if (matcher.IsInvalid) { throw new InvalidOperationException("Error patching, unable to find World instancing"); } matcher.CreateLabel(ref label); return matcher; } public static CodeMatcher AdvanceToAfterDefaultWorldConstructor(this CodeMatcher matcher, out CodeInstruction ld_World) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown matcher.MatchForward(true, (CodeMatch[])(object)new CodeMatch[2] { new CodeMatch((Func<CodeInstruction, bool>)delegate(CodeInstruction instruction) { if (instruction.opcode != OpCodes.Newobj) { return false; } ConstructorInfo constructorInfo = (ConstructorInfo)instruction.operand; return !(constructorInfo.DeclaringType != typeof(World)) && constructorInfo.GetParameters().Length == 0; }, (string)null), new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction instruction) => CodeInstructionExtensions.IsStloc(instruction, (LocalBuilder)null)), (string)null) }); ld_World = MatchStoreWithLoad(matcher.Instruction); return matcher.Advance(1); } } internal static class ZPackageExtensions { [SpecialName] public sealed class <G>$3129DFD62690FF8C352496DD2A72ABBA { [SpecialName] public static class <M>$26CCCE991BE6EACF57A91F03469C03EA { } [ExtensionMarker("<M>$26CCCE991BE6EACF57A91F03469C03EA")] public void Write(WorldSettings settingsObject) { throw null; } [ExtensionMarker("<M>$26CCCE991BE6EACF57A91F03469C03EA")] public WorldSettings ReadWorldSettings() { throw null; } } public static void Write(this ZPackage package, WorldSettings settingsObject) { WorldSettingSerialization.Setting[] array = WorldSettingSerialization.Serialize(settingsObject); package.WriteNumItems(array.Length); WorldSettingSerialization.Setting[] array2 = array; for (int i = 0; i < array2.Length; i++) { WorldSettingSerialization.Setting setting = array2[i]; package.Write(setting.name); switch (WorldSettingSerialization.GetSettingType(setting.name)) { case WorldSettings.SettingType.String: package.Write((string)setting.value); break; case WorldSettings.SettingType.Int: package.Write((int)setting.value); break; case WorldSettings.SettingType.Bool: package.Write((bool)setting.value); break; case WorldSettings.SettingType.Float: package.Write((float)setting.value); break; case WorldSettings.SettingType.Double: package.Write((double)setting.value); break; default: throw new ArgumentOutOfRangeException(); } } } public static WorldSettings ReadWorldSettings(this ZPackage package) { WorldSettingSerialization.Setting[] array = new WorldSettingSerialization.Setting[package.ReadNumItems()]; for (int i = 0; i < array.Length; i++) { string text = package.ReadString(); object value = WorldSettingSerialization.GetSettingType(text) switch { WorldSettings.SettingType.String => package.ReadString(), WorldSettings.SettingType.Int => package.ReadInt(), WorldSettings.SettingType.Bool => package.ReadBool(), WorldSettings.SettingType.Float => package.ReadSingle(), WorldSettings.SettingType.Double => package.ReadDouble(), _ => throw new ArgumentOutOfRangeException(), }; array[i] = new WorldSettingSerialization.Setting { name = text, value = value }; } return WorldSettingSerialization.Deserialize(array); } } } namespace Riverheim.Plugin.Rendering { public class ValheimWorldRenderer(WorldRenderer renderer, float heightOffset) { private const double FOREST_FACTOR_MULTIPLIER = 2.15; private readonly ThreadLocal<(Rfloat2, IWorldGen.PointData)> cache = new ThreadLocal<(Rfloat2, IWorldGen.PointData)>(); private static Biome ToValheim(Biome biome) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) switch (biome) { case Biome.Meadows: case Biome.Marsh: return (Biome)1; case Biome.Swamp: return (Biome)2; case Biome.Mountain: return (Biome)4; case Biome.Forest: return (Biome)8; case Biome.Plains: return (Biome)16; case Biome.Ashlands: case Biome.Lavaplain: return (Biome)32; case Biome.DeepNorth: return (Biome)64; case Biome.Ocean: return (Biome)256; case Biome.Mistlands: case Biome.Mistglade: return (Biome)512; default: return (Biome)0; } } public float GetHeight(float wx, float wy, out Color mask) { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) Rfloat2 rfloat = new Rfloat2(wx, wy); if (!cache.IsValueCreated || cache.Value.Item1 != rfloat) { cache.Value = (rfloat, renderer.RenderPoint(rfloat)); } IWorldGen.PointData item = cache.Value.Item2; mask = new Color((float)item.dirt, 0f, (float)item.pave, (float)Math.Max(item.lava, item.fertility)); return heightOffset + (float)item.height; } public Biome GetBiome(float wx, float wy) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) return ToValheim(renderer.GetBiome(new Rfloat2(wx, wy))); } public float GetForestFactor(float wx, float wy) { return (float)(2.15 * (1.0 - renderer.GetForestation(new Rfloat2(wx, wy)))); } } } namespace Riverheim.Plugin.Compat { public static class ModCompat { public const string GUID_BETTER_CONTINENTS = "BetterContinents"; public const string GUID_EWS = "expand_world_size"; public const string GUID_EWD = "expand_world_data"; } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace Riverheim { [Serializable] public struct CommonConfig { public double worldSize; public double tileSpacing; public double mountainHeight; public double oceanDepth; public double startingAreaRadius; } internal static class DeterministicMath { private const short INLINE = 256; public const double PI = Math.PI; public const double RELATIVE_ERROR = 1E-10; private const double TWO_PI = Math.PI * 2.0; private const double HALF_PI = Math.PI / 2.0; private const double EIGHTH_OF_PI = Math.PI / 8.0; private const double TAN_PI_OVER_8 = 0.4142135623730951; private const double SQRT2 = 1.4142135623730951; private const double LN2 = 0.6931471805599453; private const double LN2_INV = 1.4426950408889634; [MethodImpl(MethodImplOptions.AggressiveInlining)] private static double GuardNaN(double x) { throw new ArgumentOutOfRangeException($"Function input outside range: {x}"); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Abs(double x) { return BitConverter.Int64BitsToDouble(BitConverter.DoubleToInt64Bits(x) & 0x7FFFFFFFFFFFFFFFL); } public static double Sqrt(double x) { if ((x < 0.0 || double.IsNaN(x)) ? true : false) { return GuardNaN(x); } if (x == 0.0 || double.IsPositiveInfinity(x)) { return x; } return x * InvSqrt(x); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static double InvSqrt(double x) { double num = 0.5 * x; long num2 = BitConverter.DoubleToInt64Bits(x); num2 = 6910469410427058089L - (num2 >> 1); double num3 = BitConverter.Int64BitsToDouble(num2); num3 *= 1.5 - num * num3 * num3; num3 *= 1.5 - num * num3 * num3; return num3 * (1.5 - num * num3 * num3); } public static double Exp(double x) { if (double.IsNaN(x)) { return GuardNaN(x); } if (double.IsNegativeInfinity(x) || x < -745.133) { return 0.0; } if (double.IsPositiveInfinity(x) || x > 709.782) { return double.PositiveInfinity; } double num = x * 1.4426950408889634; long num2 = (long)(num + ((num >= 0.0) ? 0.5 : (-0.5))); double num3 = x - (double)num2 * 0.6931471805599453; return (1.0 + num3 * (1.0 + num3 * (0.5 + num3 * (1.0 / 6.0 + num3 * (1.0 / 24.0 + num3 * (1.0 / 120.0 + num3 * (1.0 / 720.0 + num3 * (0.0001984126984126984 + num3 * (2.48015873015873E-05 + num3 * 2.7557319223985893E-06))))))))) * BitConverter.Int64BitsToDouble(num2 + 1023 << 52); } public static double Log(double x) { if (double.IsNaN(x) || x < 0.0) { return GuardNaN(x); } if (x == 0.0) { return double.NegativeInfinity; } int num = 0; if (x < 2.2250738585072014E-308) { x *= 4503599627370496.0; num = -52; } long num2 = BitConverter.DoubleToInt64Bits(x); int num3 = (int)((num2 >> 52) & 0x7FF) - 1023 + num; double num4 = BitConverter.Int64BitsToDouble((num2 & 0xFFFFFFFFFFFFFL) | 0x3FF0000000000000L); if (num4 > 1.4142135623730951) { num4 *= 0.5; num3++; } double num5 = (num4 - 1.0) / (num4 + 1.0); double num6 = num5 * num5; return 2.0 * num5 * (1.0 + num6 * (1.0 / 3.0 + num6 * (0.2 + num6 * (1.0 / 7.0 + num6 * (1.0 / 9.0 + num6 * (1.0 / 11.0)))))) + (double)num3 * 0.6931471805599453; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static double Pow(double x, long p) { bool flag = p < 0; if (flag) { p = -p; } double num = 1.0; double num2 = x; while (p > 0) { if ((p & 1) != 0L) { num *= num2; } num2 *= num2; p >>= 1; } if (!flag) { return num; } return 1.0 / num; } public static double Pow(double x, int p) { if (p == 0) { return 1.0; } if (x == 0.0) { if (p <= 0) { return double.PositiveInfinity; } return 0.0; } return Pow(x, (long)p); } public static double Pow(double x, double p) { if (x < 0.0) { return GuardNaN(x); } if (p == 0.0) { return 1.0; } if (x == 0.0) { if (!(p > 0.0)) { return double.PositiveInfinity; } return 0.0; } long num = (long)p; if (p == (double)num) { return Pow(x, num); } if (p == 0.5) { return Sqrt(x); } return Exp(p * Log(x)); } public static double Sin(double x) { if (!double.IsFinite(x)) { return GuardNaN(x); } x %= Math.PI * 2.0; if (x > Math.PI) { x -= Math.PI * 2.0; } else if (x < -Math.PI) { x += Math.PI * 2.0; } if (x > Math.PI / 2.0) { x = Math.PI - x; } else if (x < -Math.PI / 2.0) { x = -Math.PI - x; } double num = x * x; return x * (1.0 - num * (1.0 / 6.0 - num * (1.0 / 120.0 - num * (0.0001984126984126984 - num * (2.7557319223985893E-06 - num * (2.505210838544172E-08 - num * (1.6059043836821613E-10 - num * 7.647163731819816E-13))))))); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Cos(double x) { return Sin(x + Math.PI / 2.0); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Tan(double x) { return Sin(x) / Cos(x); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Asin(double x) { if (x < -1.0 || x > 1.0) { return GuardNaN(x); } return Atan2(x, Sqrt(1.0 - x * x)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Acos(double x) { if (x < -1.0 || x > 1.0) { return GuardNaN(x); } return Atan2(Sqrt(1.0 - x * x), x); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Atan(double x) { if (!double.IsFinite(x)) { return GuardNaN(x); } bool num = x < 0.0; x = Abs(x); bool num2 = x > 1.0; if (num2) { x = 1.0 / x; } bool num3 = x > 0.4142135623730951; if (num3) { x = (x - 0.4142135623730951) / (1.0 + x * 0.4142135623730951); } double num4 = x * x; double num5 = x * (1.0 - num4 * (1.0 / 3.0 - num4 * (0.2 - num4 * (1.0 / 7.0 - num4 * (1.0 / 9.0 - num4 * (1.0 / 11.0 - num4 * (1.0 / 13.0 - num4 * (1.0 / 15.0 - num4 * (1.0 / 17.0 - num4 * (1.0 / 19.0 - num4 * (1.0 / 21.0 - num4 * (1.0 / 23.0)))))))))))); if (num3) { num5 += Math.PI / 8.0; } if (num2) { num5 = Math.PI / 2.0 - num5; } if (num) { num5 = 0.0 - num5; } return num5; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Atan2(double y, double x) { if (x > 0.0) { return Atan(y / x); } if (x < 0.0) { if (!(y >= 0.0)) { return Atan(y / x) - Math.PI; } return Atan(y / x) + Math.PI; } if (y > 0.0) { return Math.PI / 2.0; } if (y < 0.0) { return -Math.PI / 2.0; } return 0.0; } } public readonly struct InertFloat { private readonly float value; private InertFloat(float value) { this.value = value; } public static implicit operator double(InertFloat f) { return f.value; } public static implicit operator InertFloat(double f) { return new InertFloat((float)f); } public override string ToString() { return $"{value}"; } } public readonly struct InertFloat2 { private readonly float x; private readonly float y; private InertFloat2(float x, float y) { this.x = x; this.y = y; } public static implicit operator Rfloat2(InertFloat2 f2) { return new Rfloat2(f2.x, f2.y); } public static implicit operator InertFloat2(Rfloat2 f2) { return new InertFloat2((float)f2.x, (float)f2.y); } public override string ToString() { return $"({x}, {y})"; } } public readonly struct Rfloat2 : IEquatable<Rfloat2> { private const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; private const double TOLERANCE = 1E-06; public readonly double x; public readonly double y; public double Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return Rmath.Sqrt(x * x + y * y); } } public double SqrLength { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return x * x + y * y; } } public bool IsNegligible { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { if (Rmath.Abs(x) < 1E-06) { return Rmath.Abs(y) < 1E-06; } return false; } } public static Rfloat2 Zero => new Rfloat2(0.0, 0.0); public static Rfloat2 One => new Rfloat2(1.0, 1.0); public Rfloat2 Normalized { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { double length = Length; if (!(length > 1E-06)) { return Zero; } return new Rfloat2(x / length, y / length); } } public Rfloat2(double x, double y) { this.x = x; this.y = y; } public Rfloat2(double val) : this(val, val) { } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator Rfloat2(Rint2 ivec) { return new Rfloat2(ivec.x, ivec.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Distance(Rfloat2 lhs, Rfloat2 rhs) { return (lhs - rhs).Length; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Dot(Rfloat2 lhs, Rfloat2 rhs) { return lhs.x * rhs.x + lhs.y * rhs.y; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Cross(Rfloat2 lhs, Rfloat2 rhs) { return lhs.x * rhs.y - lhs.y * rhs.x; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Angle(Rfloat2 a, Rfloat2 b) { double num = Rmath.Sqrt(a.SqrLength * b.SqrLength); if (num < 1E-06) { return 0.0; } return Rmath.Acos(Rmath.Clamp(Dot(a, b) / num, -1.0, 1.0)) * (double)Rmath.Sign(Cross(a, b)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 UnclampedLerp(double t, Rfloat2 a, Rfloat2 b) { return new Rfloat2(Rmath.UnclampedLerp(t, a.x, b.x), Rmath.UnclampedLerp(t, a.y, b.y)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 Lerp(double t, Rfloat2 a, Rfloat2 b) { return new Rfloat2(Rmath.Lerp(t, a.x, b.x), Rmath.Lerp(t, a.y, b.y)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public double DistanceTo(Rfloat2 other) { return (this - other).Length; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Rint2 FloorToInt() { return new Rint2(Rmath.FloorToInt(x), Rmath.FloorToInt(y)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Rint2 CeilToInt() { return new Rint2(Rmath.CeilToInt(x), Rmath.CeilToInt(y)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Rint2 RoundToInt() { return new Rint2(Rmath.RoundToInt(x), Rmath.RoundToInt(y)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Rfloat2 other) { if (x.Equals(other.x)) { return y.Equals(other.y); } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object obj) { if (obj is Rfloat2 other) { return Equals(other); } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return HashCode.Combine(x, y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override string ToString() { return $"({x}, {y})"; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Rfloat2 lhs, Rfloat2 rhs) { if (Rmath.Abs(lhs.x - rhs.x) < 1E-06) { return Rmath.Abs(lhs.y - rhs.y) < 1E-06; } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Rfloat2 lhs, Rfloat2 rhs) { if (!(Rmath.Abs(lhs.x - rhs.x) > 1E-06)) { return Rmath.Abs(lhs.y - rhs.y) > 1E-06; } return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator +(Rfloat2 lhs, Rfloat2 rhs) { return new Rfloat2(lhs.x + rhs.x, lhs.y + rhs.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator -(Rfloat2 lhs, Rfloat2 rhs) { return new Rfloat2(lhs.x - rhs.x, lhs.y - rhs.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator -(Rfloat2 vec) { return new Rfloat2(0.0 - vec.x, 0.0 - vec.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator +(Rfloat2 vec, double scalar) { return new Rfloat2(vec.x + scalar, vec.y + scalar); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator +(double scalar, Rfloat2 vec) { return new Rfloat2(scalar * vec.x, scalar * vec.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator -(Rfloat2 vec, double scalar) { return new Rfloat2(vec.x - scalar, vec.y - scalar); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator -(double scalar, Rfloat2 vec) { return new Rfloat2(scalar - vec.x, scalar - vec.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator *(Rfloat2 lhs, Rfloat2 rhs) { return new Rfloat2(lhs.x * rhs.x, lhs.y * rhs.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator *(Rfloat2 vec, double scalar) { return new Rfloat2(vec.x * scalar, vec.y * scalar); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator *(double scalar, Rfloat2 vec) { return new Rfloat2(scalar * vec.x, scalar * vec.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator /(Rfloat2 lhs, Rfloat2 rhs) { return new Rfloat2(lhs.x / rhs.x, lhs.y / rhs.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator /(Rfloat2 vec, double scalar) { return new Rfloat2(vec.x / scalar, vec.y / scalar); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator /(double scalar, Rfloat2 vec) { return new Rfloat2(scalar / vec.x, scalar / vec.y); } } [Serializable] public struct Rint2 : IEquatable<Rint2> { private const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; public readonly int x; public readonly int y; public static Rint2 Zero => new Rint2(0, 0); public static Rint2 One => new Rint2(1, 1); public Rint2(int x, int y) { this.x = x; this.y = y; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Rint2 other) { if (x == other.x) { return y == other.y; } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object obj) { if (obj is Rint2 other) { return Equals(other); } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return HashCode.Combine(x, y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Rint2 left, Rint2 right) { return left.Equals(right); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Rint2 left, Rint2 right) { return !left.Equals(right); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override string ToString() { return $"({x}, {y})"; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rint2 operator +(Rint2 lhs, Rint2 rhs) { return new Rint2(lhs.x + rhs.x, lhs.y + rhs.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rint2 operator -(Rint2 lhs, Rint2 rhs) { return new Rint2(lhs.x - rhs.x, lhs.y - rhs.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rint2 operator -(Rint2 vec) { return new Rint2(-vec.x, -vec.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rint2 operator +(Rint2 vec, int scalar) { return new Rint2(vec.x + scalar, vec.y + scalar); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rint2 operator +(int scalar, Rint2 vec) { return new Rint2(scalar * vec.x, scalar * vec.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rint2 operator -(Rint2 vec, int scalar) { return new Rint2(vec.x - scalar, vec.y - scalar); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rint2 operator -(int scalar, Rint2 vec) { return new Rint2(scalar - vec.x, scalar - vec.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rint2 operator *(Rint2 lhs, Rint2 rhs) { return new Rint2(lhs.x * rhs.x, lhs.y * rhs.x); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rint2 operator *(Rint2 vec, int scalar) { return new Rint2(vec.x * scalar, vec.y * scalar); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rint2 operator *(int scalar, Rint2 vec) { return new Rint2(scalar * vec.x, scalar * vec.y); } } public static class Rmath { [Serializable] public struct Range { public double min; public double max; public bool IsEmpty => min.Equals(max); public bool IsPositive => max > min; public static Range Unit => new Range { min = -1.0, max = 1.0 }; public static Range From(double a, double b) { if (!(a <= b)) { return new Range { min = b, max = a }; } return new Range { min = a, max = b }; } } [Serializable] public struct RemapConfig { public double fromA; public double fromB; public double toA; public double toB; public Range From { get { return new Range { min = fromA, max = fromB }; } set { double min = value.min; double max = value.max; fromA = min; fromB = max; } } public Range To { get { return new Range { min = toA, max = toB }; } set { double min = value.min; double max = value.max; toA = min; toB = max; } } public bool IsEmpty { get { if (!From.IsEmpty) { return To.IsEmpty; } return true; } } } private const short INLINE = 256; public const double PI = Math.PI; public const double RAD_TO_DEG = 180.0 / Math.PI; public const double DEG_TO_RAD = Math.PI / 180.0; public const double EPSILON = 1E-06; public const double RELATIVE_TOLERANCE = 1E-10; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int FloorToInt(double f) { if (f < 0.0) { return (int)f - 1; } return (int)f; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int CeilToInt(double f) { if (f > 0.0) { return (int)f + 1; } return (int)f; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int RoundToInt(double f) { return FloorToInt(f + 0.5); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Min(int a, int b) { if (a >= b) { return b; } return a; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Max(int a, int b) { if (a <= b) { return b; } return a; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Min(double a, double b) { if (!double.IsNaN(a) && !(a < b)) { return b; } return a; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Max(double a, double b) { if (!double.IsNaN(a) && !(a > b)) { return b; } return a; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Abs(double value) { return DeterministicMath.Abs(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Sign(double value) { if (!(value < 0.0)) { return 1; } return -1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Sqrt(double value) { return DeterministicMath.Sqrt(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Pow(double value, int power) { return DeterministicMath.Pow(value, power); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Pow(double value, double power) { return DeterministicMath.Pow(value, power); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Exp(double value) { return DeterministicMath.Exp(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Log(double value) { return DeterministicMath.Log(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Sin(double value) { return DeterministicMath.Sin(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Cos(double value) { return DeterministicMath.Cos(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Tan(double value) { return DeterministicMath.Tan(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Asin(double value) { return DeterministicMath.Asin(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Acos(double value) { return DeterministicMath.Acos(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Atan(double value) { return DeterministicMath.Atan(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Atan2(double y, double x) { return DeterministicMath.Atan2(y, x); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Smooth3(double t) { return t * t * (3.0 - 2.0 * t); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Smooth5(double t) { return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double UnclampedLerp(double t, double a, double b) { return a + t * (b - a); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double UnclampedLerp(double t, Range range) { return UnclampedLerp(t, range.min, range.max); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Lerp(double t, double a, double b) { return UnclampedLerp(Clamp01(t), a, b); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Lerp(double t, Range range) { return Lerp(t, range.min, range.max); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double SmoothStep(double t, double a, double b) { if (!(t <= 0.0)) { if (t >= 1.0) { return b; } return UnclampedLerp(Smooth3(t), a, b); } return a; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double SmoothStep(double t, Range range) { return SmoothStep(t, range.min, range.max); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double SmootherStep(double t, double a, double b) { if (!(t <= 0.0)) { if (t >= 1.0) { return b; } return UnclampedLerp(Smooth5(t), a, b); } return a; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double SmootherStep(double t, Range range) { return SmootherStep(t, range.min, range.max); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double InverseLerp(double value, double a, double b) { if (a.Equals(b)) { return 0.5; } return Clamp01((value - a) / (b - a)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double InverseLerp(double value, Range range) { return InverseLerp(value, range.min, range.max); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Clamp(double value, double min, double max) { if (value < min) { return min; } if (!(value > max)) { return value; } return max; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Clamp(double value, Range range) { return Clamp(value, range.min, range.max); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Clamp01(double value) { if (!(value < 0.0)) { if (value > 1.0) { return 1.0; } return value; } return 0.0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Remap(double value, RemapConfig config) { return Lerp(InverseLerp(value, config.fromA, config.fromB), config.toA, config.toB); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Within(double value, Range range) { if (value >= range.min) { return value <= range.max; } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Rectifier(double value) { if (!(value > 0.0)) { return 0.0; } return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Curvature(Rfloat2 d, Rfloat2 d2) { double num = 1.0 + d.SqrLength; return d2.Length / Sqrt(num * num * num); } } public interface IWorldGen { public struct PointData { public Biome biome; public double height; public double fertility; public double lava; public double dirt; public double pave; } Biome GetBiome(Rfloat2 pos); double GetForestation(Rfloat2 pos); PointData RenderPoint(Rfloat2 pos); } [Serializable] [CompositeConfig] public struct WorldGenerationConfig { public CommonConfig common; public WorldConfig world; public RenderingConfig rendering; } } namespace Riverheim.World { public readonly struct ExtendedTileFlags { public bool IsCenter { get; } public bool IsLand => !IsWater; public bool IsWater { get { if (!IsOcean) { return IsLake; } return true; } } public bool IsOcean { get; } public bool IsLake { get; } public bool IsCoast { get; } public bool IsOceanCoast { get; } public bool IsLakeCoast { get; } public ExtendedTileFlags(bool isCenter, bool isOcean, bool isLake, bool isOceanCoast, bool isLakeCoast) { IsCenter = isCenter; IsOcean = isOcean; IsLake = isLake; IsCoast = isOceanCoast || isLakeCoast; IsOceanCoast = isOceanCoast; IsLakeCoast = isLakeCoast; } } [Producer(new string[] { "Flags/Extended" })] [Fork("Region")] public class ExtendedTileFlagStage : IGeneratorStage<TileData<ExtendedTileFlags>> { public const string OUTPUT = "Flags/Extended"; [Resource("Regions/Tiling")] public Tiling tiling; [Resource("Flags/Base")] public TileData<TileFlags> baseFlags; [Resource("Heights/Lakes")] public TileSet lakes; public TileData<ExtendedTileFlags> Compute() { return tiling.CreateData((Tile tile) => new ExtendedTileFlags(baseFlags[tile].IsCenter, baseFlags[tile].IsOcean, lakes.Contains(tile), baseFlags[tile].IsCoast, IsLakeCoast(tile, lakes))); } private bool IsLakeCoast(Tile tile, TileSet lakeTiles) { if (!baseFlags[tile].IsLand) { return false; } bool isLake = lakeTiles.Contains(tile); return tile.GetNeighbors().Any((Tile neighbor) => lakeTiles.Contains(neighbor) != isLake); } } [Producer(new string[] { "Forestation" })] [Fork("Region")] public class ForestationStage : IGeneratorStage<TileData<InertFloat>> { [Serializable] public struct Config { public double noiseScale; public NoiseField.FractalConfig noiseFractal; public double marshBonus; public double plainsRiverBonus; public Rmath.Range plainsRiverBonusProximityRange; public Rmath.Range plainsRiverBonusHeightRange; public double meadowsRiverBonus; public Rmath.Range meadowsRiverBonusProximityRange; } public const string OUTPUT = "Forestation"; [Random("Forestation")] public RandomDirectory random; [LaneConfig] public Config config; [Resource("Regions/Tiling")] public Tiling tiling; [Resource("Biomes")] public TileData<Biome> biomes; [Resource("Rivers/Proximity")] public TileData<InertFloat> riverProximity; [Resource("Heights/SettledHeightMap")] public TileData<InertFloat> heights; public TileData<InertFloat> Compute() { NoiseField noise = random.BiasedNoise2D("Forestation", config.noiseScale).Fractal(config.noiseFractal); return tiling.CreateData((Func<Tile, InertFloat>)delegate(Tile tile) { double result = 1.0 - noise.Sample(tile.Center); return Rmath.Clamp01(AdjustResult(biomes[tile], tile, result)); }); } private double AdjustResult(Biome biome, Tile tile, double result) { switch (biome) { case Biome.Meadows: { double meadowsRiverBonus = config.meadowsRiverBonus; meadowsRiverBonus *= 1.0 - Rmath.InverseLerp(riverProximity[tile], config.meadowsRiverBonusProximityRange); return result + meadowsRiverBonus; } case Biome.Marsh: return result + config.marshBonus; case Biome.Plains: { double plainsRiverBonus = config.plainsRiverBonus; plainsRiverBonus *= 1.0 - Rmath.InverseLerp(riverProximity[tile], config.plainsRiverBonusProximityRange); plainsRiverBonus *= 1.0 - Rmath.InverseLerp(heights[tile], config.plainsRiverBonusHeightRange); return result + plainsRiverBonus; } case Biome.Mistglade: return 0.0; default: return result; } } } public class LandmassInfo { public int border; public double area; public double coastline; public double Compactness => Math.PI * 4.0 * area / (coastline * coastline); } public struct LandmassMetrics { public int count; public double totalArea; public double maxArea; public double avgArea; public double avgCompactness; } public struct RiverMetrics { public int count; public double maxLength; public double avgLength; } public class BiomeMetrics { public double totalArea; public double innerArea; } public struct WorldMetrics { public LandmassMetrics landmassMetrics; public RiverMetrics riverMetrics; public Dictionary<Biome, BiomeMetrics> biomeMetrics; public double highestPoint; public double centerCoastalProximity; public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("World Metrics:"); stringBuilder.AppendLine(" Total Land: " + ToPercentage(landmassMetrics.totalArea)); stringBuilder.AppendLine($" Landmass Count: {landmassMetrics.count}"); stringBuilder.AppendLine(" Largest Landmass: " + ToPercentage(landmassMetrics.maxArea)); stringBuilder.AppendLine(" Average Landmass: " + ToPercentage(landmassMetrics.avgArea)); stringBuilder.AppendLine($" Average Compactness: {landmassMetrics.avgCompactness:0.0}"); stringBuilder.AppendLine($" Highest point: {highestPoint:0.0}m"); stringBuilder.AppendLine(); stringBuilder.AppendLine($" Total Rivers: {riverMetrics.count}"); stringBuilder.AppendLine($" Longest River: {riverMetrics.maxLength:0.0}m"); stringBuilder.AppendLine($" Average River: {riverMetrics.avgLength:0.0}m"); stringBuilder.AppendLine(); foreach (Biome key in this.biomeMetrics.Keys) { BiomeMetrics biomeMetrics = this.biomeMetrics[key]; stringBuilder.AppendLine($" {key} Total Area (Inner): {ToPercentage(biomeMetrics.totalArea)} ({ToPercentage(biomeMetrics.innerArea)})"); } return stringBuilder.ToString(); } private static string ToPercentage(double value) { return $"{value * 100.0:0.00}%"; } } [Producer(new string[] { "WorldMetrics" })] public class WorldMetricCalculator : IGeneratorStage<WorldMetrics> { public const string OUTPUT = "WorldMetrics"; [Resource("MainTiling")] public Tiling tiling; [Resource("Regions/Tiling")] public Tiling[] regionTilings; [Resource("Regions/Mapping")] public SubtilingMapping[] regionMappings; [Resource("LandmassIds")] public TileData<int>[] landmassIds; [Resource("Rivers/Splines")] public River[][] rivers; [Resource("Biomes")] public TileData<Biome>[] biomes; [Resource("Heights/JoinedHeights")] public TileData<InertFloat> heights; [Resource("CoastalProximity")] public TileData<InertFloat>[] coastalProximity; public WorldMetrics Compute() { return new WorldMetrics { landmassMetrics = ComputeLandmassMetrics(), riverMetrics = ComputeRiverMetrics(RegionStage.JoinRegionData(rivers)), biomeMetrics = ComputeBiomeMetrics(tiling, RegionStage.JoinRegionData(tiling, regionTilings, regionMappings, biomes)), highestPoint = tiling.Tiles.Max((Func<Tile, double>)((Tile tile) => heights[tile])), centerCoastalProximity = coastalProximity[0][tiling.GetTile(Rfloat2.Zero).Value] }; } private static List<LandmassInfo> ComputeLandmassInfos(Tiling tiling, TileData<int> landmassIds) { Dictionary<int, LandmassInfo> dictionary = new Dictionary<int, LandmassInfo>(); foreach (Tile tile in tiling.Tiles) { int landmassId = tile.Data(landmassIds); if (landmassId != -1) { if (!dictionary.ContainsKey(landmassId)) { dictionary[landmassId] = new LandmassInfo(); } dictionary[landmassId].area += tile.Area; if (tile.GetNeighbors().Any((Tile neighbor) => neighbor.Data(landmassIds) != landmassId)) { dictionary[landmassId].border++; } } } foreach (LandmassInfo value in dictionary.Values) { value.coastline = (double)value.border * tiling.Spacing; } return dictionary.Values.ToList(); } private LandmassMetrics ComputeLandmassMetrics() { List<LandmassInfo> list = new List<LandmassInfo>(); foreach (int value in Enum.GetValues(typeof(Region))) { list.AddRange(ComputeLandmassInfos(regionTilings[value], landmassIds[value])); } return new LandmassMetrics { count = list.Count, totalArea = list.Sum((LandmassInfo x) => x.area) / tiling.TotalArea, maxArea = list.Max((LandmassInfo x) => x.area) / tiling.TotalArea, avgArea = list.Average((LandmassInfo x) => x.area) / tiling.TotalArea, avgCompactness = list.Average((LandmassInfo x) => x.Compactness) }; } private static RiverMetrics ComputeRiverMetrics(River[] rivers) { List<double> source = rivers.Select(RiverLength).ToList(); return new RiverMetrics { count = rivers.Length, maxLength = ((rivers.Length != 0) ? source.Max() : 0.0), avgLength = ((rivers.Length != 0) ? source.Average() : 0.0) }; } private static double RiverLength(River river) { return river.spline.Length; } private static Dictionary<Biome, BiomeMetrics> ComputeBiomeMetrics(Tiling tiling, TileData<Biome> biomes) { Dictionary<Biome, BiomeMetrics> dictionary = new Dictionary<Biome, BiomeMetrics>(); foreach (Tile tile in tiling.Tiles) { if (!dictionary.ContainsKey(tile.Data(biomes))) { dictionary[tile.Data(biomes)] = new BiomeMetrics(); } double num = tile.Area / tiling.TotalArea; dictionary[tile.Data(biomes)].totalArea += num; if (tile.GetNeighbors().All((Tile neighbor) => neighbor.Data(biomes) == tile.Data(biomes))) { dictionary[tile.Data(biomes)].innerArea += num; } } return dictionary; } } public enum PolarProximityType { Any, North, South } public delegate double PolarProximityMap(Rfloat2 pos, PolarProximityType type = PolarProximityType.Any); [Serializable] public struct PolarBeltConfig { public double offset; public double radius; public double noiseScale; public double noiseAmplitude; } [Producer(new string[] { "PolarProximity" })] public class PolarProximityStage : IGeneratorStage<PolarProximityMap> { public const string OUTPUT = "PolarProximity"; [Random("PolarProximityMap")] public RandomDirectory random; [Config] public PolarBeltConfig config; public PolarProximityMap Compute() { NoiseField noise = random.Noise2D("PolarProximity", config.noiseScale); return delegate(Rfloat2 pos, PolarProximityType type) { double num = Rmath.Atan2(pos.x, (pos.y < 0.0) ? (pos.y - config.offset) : pos.y); double num2 = config.radius + Rmath.Sin(num * 20.0) * 100.0; double num3 = (pos + new Rfloat2(0.0, config.offset)).Length - num2; double num4 = (pos - new Rfloat2(0.0, config.offset)).Length - num2; double num5 = type switch { PolarProximityType.North => num3, PolarProximityType.South => num4, PolarProximityType.Any => Rmath.Max(num3, num4), _ => throw new ArgumentOutOfRangeException(), }; double num6 = num5 / config.noiseAmplitude; return num5 + Rmath.Clamp01(num6 * num6) * config.noiseAmplitude * noise.Sample(pos); }; } } public enum Region { Main, SouthPole, NorthPole } [Serializable] public struct RegionalConfig<T> : ILaneConfig<T>, ILaneConfig { public T main; public T southPole; public T northPole; readonly object ILaneConfig.Get(int lane) { return Get((Region)lane); } public readonly T Get(int lane) { return Get((Region)lane); } public readonly T Get(Region region) { return region switch { Region.Main => main, Region.SouthPole => southPole, Region.NorthPole => northPole, _ => default(T), }; } public IEnumerable<(Region, T)> All() { yield return (Region.Main, main); yield return (Region.SouthPole, southPole); yield return (Region.NorthPole, northPole); } } [ForkingDimension("Region", typeof(Region))] [Producer(new string[] { "Regions" })] public class RegionStage : IGeneratorStage<TileData<Region>[]> { public const string OUTPUT = "Regions"; public const string FORK_DIMENSION = "Region"; [Config] public CommonConfig commonConfig; [Config] public PolarBeltConfig polarBeltConfig; [Resource("MainTiling")] public Tiling tiling; [Resource("TilingLods")] public Tiling[] tilingLods; [Resource("PolarProximity")] public PolarProximityMap polarProximity; public TileData<Region>[] Compute() { TileData<Region>[] array = new TileData<Region>[tilingLods.Length + 1]; for (int i = 0; i < array.Length; i++) { array[i] = ComputeLod(i); } return array; } private TileData<Region> ComputeLod(int lod) { if (lod == 0) { MutableTileData<Region> mutableTileData = tiling.CreateData((Tile tile) => GetRegion(tile, polarProximity)); FixOrphanRegions(mutableTileData); return mutableTileData; } return tilingLods[lod - 1].CreateData((Tile tile) => GetRegion(tile, polarProximity)); } private static Region GetRegion(Tile tile, PolarProximityMap polarProximity) { if (polarProximity(tile.Center, PolarProximityType.North) > 0.0) { return Region.NorthPole; } if (polarProximity(tile.Center, PolarProximityType.South) > 0.0) { return Region.SouthPole; } return Region.Main; } private Rfloat2 RegionOrigin(Region region) { double num = Rmath.Lerp(0.5, commonConfig.worldSize, polarBeltConfig.radius - polarBeltConfig.offset); return region switch { Region.Main => Rfloat2.Zero, Region.NorthPole => new Rfloat2(0.0, num), Region.SouthPole => new Rfloat2(0.0, 0.0 - num), _ => throw new ArgumentOutOfRangeException(), }; } private void FixOrphanRegions(MutableTileData<Region> regions) { MutableTileData<int> mutableTileData = MarkRegionComponents(regions); Dictionary<Region, int> dictionary = new Dictionary<Region, int>(); foreach (Region value in Enum.GetValues(typeof(Region))) { Tile? tile = tiling.GetTile(RegionOrigin(value)); if (tile.HasValue) { Tile valueOrDefault = tile.GetValueOrDefault(); dictionary[value] = mutableTileData[valueOrDefault]; } } TileQueue tileQueue = new TileQueue(tiling); foreach (Tile tile2 in tiling.Tiles) { Region region2 = regions[tile2]; if (mutableTileData[tile2] != dictionary[region2]) { continue; } tileQueue.Enqueue(tile2); while (tileQueue.Count > 0) { foreach (Tile neighbor in tileQueue.Dequeue().GetNeighbors()) { if (mutableTileData[neighbor] != dictionary[regions[neighbor]]) { regions[neighbor] = region2; mutableTileData[neighbor] = dictionary[region2]; tileQueue.Enqueue(neighbor); } } } } } private MutableTileData<int> MarkRegionComponents(TileData<Region> regions) { MutableTileData<int> mutableTileData = tiling.CreateData(-1); int num = 0; TileQueue tileQueue = new TileQueue(tiling); foreach (Tile tile in tiling.Tiles) { if (mutableTileData[tile] != -1) { continue; } mutableTileData[tile] = num; tileQueue.Enqueue(tile); while (tileQueue.Count > 0) { foreach (Tile neighbor in tileQueue.Dequeue().GetNeighbors()) { if (regions[neighbor] == regions[tile] && mutableTileData[neighbor] == -1) { mutableTileData[neighbor] = num; tileQueue.Enqueue(neighbor); } } } num++; } return mutableTileData; } public static TileData<T> JoinRegionData<T>(Tiling tiling, Tiling[] regionTilings, SubtilingMapping[] regionMappings, TileData<T>[] regionData) { MutableTileData<T> mutableTileData = tiling.CreateData<T>(); foreach (int value in Enum.GetValues(typeof(Region))) { SubtilingMapping subtilingMapping = regionMappings[value]; foreach (Tile tile in regionTilings[value].Tiles) { mutableTileData[subtilingMapping.GetParentTile(tile)] = regionData[value][tile]; } } return mutableTileData; } public static T[] JoinRegionData<T>(T[][] regionData) { List<T> list = new List<T>(); foreach (int value in Enum.GetValues(typeof(Region))) { list.AddRange(regionData[value]); } return list.ToArray(); } } public readonly struct TileFlags { public bool IsCenter { get; } public bool IsLand { get; } public bool IsOcean => !IsLand; public bool IsCoast { get; } public TileFlags(bool isLand, bool isCenter, bool isCoast) { IsLand = isLand; IsCenter = isCenter; IsCoast = isCoast; } } [Producer(new string[] { "Flags/Base" })] [Fork("Region")] public class TileFlagStage : IGeneratorStage<TileData<TileFlags>> { public const string OUTPUT = "Flags/Base"; [Resource("Regions/Tiling")] public Tiling tiling; [Resource("LandmassIds")] public TileData<int> landmassIds; public TileData<TileFlags> Compute() { return tiling.CreateData((Tile tile) => new TileFlags(IsLandTile(tile), tile.Id == tiling.GetTile(Rfloat2.Zero)?.Id, IsCoastalTile(tile))); } private bool IsLandTile(Tile tile) { return tile.Data(landmassIds) != -1; } private bool IsCoastalTile(Tile tile) { foreach (Tile neighbor in tile.GetNeighbors()) { if (IsLandTile(tile) != IsLandTile(neighbor)) { return true; } } return false; } } [Producer(new string[] { "TravelDistance" })] [Fork("Region")] public class TravelDistanceStage : IGeneratorStage<TileData<InertFloat>> { [Serializable] public struct Config { public bool normalize; public double landCost; public double riverCost; public double oceanCost; public double embarkCost; public FrontierRaymarchingConfig frontier; } [Serializable] public struct FrontierRaymarchingConfig { public int rays; public int maxSteps; public double stepRate; public double noiseScale; public double noiseMagnitude; } public const string OUTPUT = "TravelDistance"; [Lane] public Region region; [Random("TravelDistance")] public RandomDirectory random; [LaneConfig] public Config config; [Resource("MainTiling")] public Tiling mainTiling; [Resource("Regions")] public TileData<Region>[] regions; [Resource("Regions/Tiling")] public Tiling tiling; [Resource("Flags/Base")] public TileData<TileFlags> flags; [Resource("PrunedRivers")] public TileData<RiverTile> rivers; public TileData<InertFloat> Compute() { TileSet frontier = Frontier(); if (frontier.Count == 0) { return tiling.CreateData((Func<Tile, InertFloat>)((Tile tile) => tile.Center.Length)); } NoiseField noise = config.frontier.noiseMagnitude * random.Noise2D("FrontierNoise", config.frontier.noiseScale); TileData<InertFloat> tileData = tiling.DistanceTransform((Tile tile) => (!frontier.Contains(tile)) ? ((double?)null) : new double?(tile.Center.Length * (1.0 + noise.Sample(tile.Center))), (Tile _) => true, (Tile t1, Tile t2) => Rfloat2.Distance(t1.Center, t2.Center) * EdgeCostMultiplier(t1, t2)); if (!config.normalize) { return tileData; } double correction = GetAverageRealDistanceRate(tileData); return tileData.Transform((Func<InertFloat, InertFloat>)((InertFloat distance) => correction * (double)distance)); } private double GetAverageRealDistanceRate(TileData<InertFloat> distances) { double num = 0.0; int num2 = 0; foreach (Tile tile in tiling.Tiles) { if (tile.HopsToBounds <= 0) { num += tile.Center.SqrLength / ((double)distances[tile] * (double)distances[tile]); num2++; } } return num / (double)num2; } private TileSet Frontier() { Tile? tile = tiling.GetTile(Rfloat2.Zero); if (tile.HasValue) { Tile valueOrDefault = tile.GetValueOrDefault(); return TileSet.Of(valueOrDefault); } TileSet tileSet = new TileSet(tiling); if (config.frontier.rays == 0) { return tileSet; } TileData<InertFloat> tileData = mainTiling.DistanceTransform((Tile tile2) => (regions[0][tile2] != region) ? ((double?)null) : new double?(0.0), (Tile _) => true, (Tile t1, Tile t2) => Rfloat2.Distance(t1.Center, t2.Center)); double spacing = tiling.Spacing; for (int num = 0; num < config.frontier.rays; num++) { Direction direction = new Direction(Math.PI * 2.0 * (double)num / (double)config.frontier.rays); Rfloat2 zero = Rfloat2.Zero; for (int num2 = 0; num2 < config.frontier.maxSteps; num2++) { tile = tiling.GetTile(zero); if (tile.HasValue) { Tile valueOrDefault2 = tile.GetValueOrDefault(); tileSet.Add(valueOrDefault2); break; } tile = mainTiling.GetTile(zero); double num3; if (tile.HasValue) { Tile valueOrDefault3 = tile.GetValueOrDefault(); num3 = tileData[valueOrDefault3]; } else { num3 = spacing; } double num4 = num3; zero += Rmath.Max(spacing, num4 * config.frontier.stepRate) * direction.ToVector(); } } return tileSet; } private double EdgeCostMultiplier(Tile t1, Tile t2) { if (flags[t1].IsLand != flags[t2].IsLand) { return config.embarkCost; } if (flags[t1].IsOcean || flags[t2].IsOcean) { return config.oceanCost; } if (rivers[t1].IsRiver() || rivers[t2].IsRiver()) { return config.riverCost; } return config.landCost; } } [Serializable] [CompositeConfig] public record WorldConfig { public WorldVerticesCalculator.Config tiling; public PolarBeltConfig polar; public RegionalConfig<TravelDistanceStage.Config> travel; public LandmassConfig landmass; public RiverConfig rivers; public HeightConfig height; public BiomeConfig biomes; public Riverheim.World.Splats.SplatConfig splats; public RegionalConfig<ForestationStage.Config> forestation; } public class WorldState { public readonly Tiling tiling; public readonly TriangleMesh interpolationMesh; public readonly TileData<InertFloat> heights; public readonly TileData<Biome> biomes; public readonly TileData<InertFloat> forestation; public readonly River[] rivers; public readonly Splat[] splats; public WorldState(Tiling tiling, TriangleMesh interpolationMesh, TileData<InertFloat> heights, TileData<Biome> biomes, TileData<InertFloat> forestation, River[] rivers, Splat[] splats) { this.tiling = tiling; this.interpolationMesh = interpolationMesh; this.heights = heights; this.biomes = biomes; this.forestation = forestation; this.rivers = rivers; this.splats = splats; } } [Producer(new string[] { "WorldState" })] public class WorldStateCompiler : IGeneratorStage<WorldState> { public const string OUTPUT = "WorldState"; [Resource("MainTiling")] public Tiling tiling; [Resource("MainInterpolationMesh")] public TriangleMesh interpolationMesh; [Resource("Regions/Tiling")] public Tiling[] regionTilings; [Resource("Regions/Mapping")] public SubtilingMapping[] regionMappings; [Resource("Heights/JoinedHeights")] public TileData<InertFloat> heights; [Resource("Biomes")] public TileData<Biome>[] biomes; [Resource("Rivers/Splines")] public River[][] rivers; [Resource("Splats")] public Splat[] splats; [Resource("Forestation")] public TileData<InertFloat>[] forestation; public WorldState Compute() { return new WorldState(tiling, interpolationMesh, heights, RegionStage.JoinRegionData(tiling, regionTilings, regionMappings, biomes), RegionStage.JoinRegionData(tiling, regionTilings, regionMappings, forestation), RegionStage.JoinRegionData(rivers), splats); } } } namespace Riverheim.World.Tilings { [Producer(new string[] { "MainTiling", "MainInterpolationMesh" })] public class MainTilingStage : IGeneratorStage<(Tiling, TriangleMesh)> { public const string TILING = "MainTiling"; public const string MESH = "MainInterpolationMesh"; [Resource("WorldVertices")] public WorldVertices[] vertexLayers; public (Tiling, TriangleMesh) Compute() { WorldVertices worldVertices = vertexLayers[0]; BarycentricDualTilingBuilder barycentricDualTilingBuilder = new BarycentricDualTilingBuilder(worldVertices.vertices, worldVertices.spacing); return (barycentricDualTilingBuilder.Build(), barycentricDualTilingBuilder.BuildMesh()); } } [Producer(new string[] { "Regions/Tiling", "Regions/Mapping" })] [Fork("Region")] public class RegionTilingStage : IGeneratorStage<(Tiling, SubtilingMapping)> { public const string TILING = "Regions/Tiling"; public const string MAPPING = "Regions/Mapping"; [Lane] public Region region; [Resource("MainTiling")] public Tiling tiling; [Resource("Regions")] public TileData<Region>[] regions; public (Tiling, SubtilingMapping) Compute() { SubtilingMapping mapping; return (tiling.CreateSubTiling((Tile tile) => regions[0][tile] == region, out mapping), mapping); } } [Producer(new string[] { "TilingLods" })] public class TilingLodStage : IGeneratorStage<Tiling[]> { public const string OUTPUT = "TilingLods"; [Resource("WorldVertices")] public WorldVertices[] worldVertices; public Tiling[] Compute() { Tiling[] array = new Tiling[this.worldVertices.Length - 1]; for (int i = 0; i < array.Length; i++) { WorldVertices worldVertices = this.worldVertices[i + 1]; VoronoiTilingBuilder voronoiTilingBuilder = new VoronoiTilingBuilder(worldVertices.vertices, worldVertices.spacing, worldVertices.boundingRadius); array[i] = voronoiTilingBuilder.Build(); } return array; } } public struct WorldVertices { public double spacing; public double boundingRadius; public InertFloat2[] vertices; } [Producer(new string[] { "WorldVertices" })] public class WorldVerticesCalculator : IGeneratorStage<WorldVertices[]> { [Serializable] public struct Config { public int lodLevels; public double lodScale; } public const string OUTPUT = "WorldVertices"; [Random("WorldVertices")] public RandomDirectory random; [Config] public CommonConfig commonConfig; [Config] public Config config; public WorldVertices[] Compute() { WorldVertices[] array = new WorldVertices[1 + config.lodLevels]; for (int i = 0; i < array.Length; i++) { double num = commonConfig.tileSpacing * Rmath.Pow(config.lodScale, i); double boundingRadius = commonConfig.worldSize + Rmath.Sqrt(3.0) * num; InertFloat2[] vertices = (from point in new PoissonDiscSampler(random.Sequence(i.ToString()), boundingRadius, num / Rmath.Sqrt(2.0)).GetPoints() where point.SqrLength < boundingRadius * boundingRadius select point).Select((Func<Rfloat2, InertFloat2>)((Rfloat2 point) => point)).ToArray(); array[i] = new WorldVertices { spacing = num, boundingRadius = boundingRadius, vertices = vertices }; } return array; } } } namespace Riverheim.World.Splats { public record Crag : Splat { public InertFloat radius; public InertFloat terrace; public InertFloat cliffiness; } public record Pond : Splat { public InertFloat radius; } [Producer(new string[] { "Splats/RiverSources" })] [Fork("Region")] public class RiverSourceSplatStage : IGeneratorStage<Splat[]> { [Serializable] public struct Config { public AllBiomeConfig<bool> allowedBiomes; public CragConfig crags; public PondConfig ponds; } [Serializable] public struct SpawnFeatureAffinityConfig { public enum AffinityType { None, LowerBound, UpperBound, Both } public AffinityType type; public Rmath.Range lowerBound; public Rmath.Range upperBound; } [Serializable] public struct CragConfig { public double frequency; public SpawnFeatureAffinityConfig height; public SpawnFeatureAffinityConfig riverLength; public CragRandomizer.Config random; } [Serializable] public struct PondConfig { public double frequency; public SpawnFeatureAffinityConfig height; public SpawnFeatureAffinityConfig riverLength; public double stepback; public PondRandomizer.Config random; } public const string OUTPUT = "Splats/RiverSources"; [Random("Splats/RiverSources")] public RandomDirectory random; [LaneConfig] public Config config; [Resource("Regions/Tiling")] public Tiling tiling; [Resource("Regions/Mapping")] public SubtilingMapping mapping; [Resource("MainTiling")] public Tiling mainTiling; [Resource("MainInterpolationMesh")] public TriangleMesh mesh; [Resource("Rivers/Splines")] public River[] rivers; [Resource("Biomes/PostprocessedHeightMap")] public TileData<InertFloat> heights; [Resource("Biomes")] public TileData<Biome> biomes; public Splat[] Compute() { List<Crag> list = new List<Crag>(); List<Pond> list2 = new List<Pond>(); RandomIndexer randomIndexer = random.Indexer("Crags/Spawn"); CragRandomizer cragRandomizer = new CragRandomizer(random.Sub("Crags"), config.crags.random); RandomIndexer randomIndexer2 = random.Indexer("Ponds/Spawn"); PondRandomizer pondRandomizer = new PondRandomizer(random.Sub("Ponds"), config.ponds.random); BarycentricInterpolator barycentricInterpolator = new BarycentricInterpolator(mainTiling, mesh); River[] array = rivers; for (int i = 0; i < array.Length; i++) { River river = array[i]; Rfloat2 pos = river.spline.EvaluatePosition(1.0); Tile? tile = tiling.GetTile(pos); if (!tile.HasValue) { continue; } Tile valueOrDefault = tile.GetValueOrDefault(); if (config.allowedBiomes.Get(biomes[valueOrDefault])) { double num = barycentricInterpolator.InterpolateOnSubtilingData(pos, heights, mapping); double length = river.spline.Length; double frequency = config.crags.frequency; frequency *= SpawnProb(num, config.crags.height); frequency *= SpawnProb(length, config.crags.riverLength); if (randomIndexer.Single(valueOrDefault.Id).Value < frequency) { list.Add(cragRandomizer.Generate(valueOrDefault.Id, pos, num)); } double frequency2 = config.ponds.frequency; frequency2 *= SpawnProb(num, config.ponds.height); frequency2 *= SpawnProb(length, config.ponds.riverLength); if (randomIndexer2.Single(valueOrDefault.Id).Value < frequency2) { Rfloat2 pos2 = river.spline.EvaluatePosition(1.0 - config.ponds.stepback / length); list2.Add(pondRandomizer.Generate(valueOrDefault.Id, pos2)); } } } List<Splat> list3 = new List<Splat>(); list3.AddRange(list); list3.AddRange(list2); return list3.ToArray(); } private static double SpawnProb(double value, SpawnFeatureAffinityConfig config) { return config.type switch { SpawnFeatureAffinityConfig.AffinityType.LowerBound => Rmath.Smooth3(Rmath.InverseLerp(value, config.lowerBound)), SpawnFeatureAffinityConfig.AffinityType.UpperBound => 1.0 - Rmath.Smooth3(Rmath.InverseLerp(value, config.upperBound)), SpawnFeatureAffinityConfig.AffinityType.Both => Rmath.Smooth3(Rmath.InverseLerp(value, config.lowerBound)) - Rmath.Smooth3(Rmath.InverseLerp(value, config.upperBound)), SpawnFeatureAffinityConfig.AffinityType.None => 1.0, _ => 0.0, }; } } public class CragRandomizer { [Serializable] public struct Config { public Rmath.Range height; public Rmath.Range radius; public Rmath.Range terrace; } private readonly Config config; private readonly RandomIndexer height; private readonly RandomIndexer radius; private readonly RandomIndexer terrace; public CragRandomizer(RandomDirectory random, Config config) { this.config = config; height = random.Indexer("Height"); radius = random.Indexer("Radius"); terrace = random.Indexer("Terrace"); } public Crag Generate(int id, Rfloat2 pos, double baseHeight) { double num = height.Single(id).Range(config.height); return new Crag { pos = pos, height = baseHeight + num, radius = radius.Single(id).Range(config.radius), terrace = num * terrace.Single(id).Range(config.terrace), cliffiness = 0.0 }; } } public class PondRandomizer { [Serializable] public struct Config { public Rmath.Range height; public Rmath.Range radius; } private readonly Config config; private readonly RandomIndexer height; private readonly RandomIndexer radius; public PondRandomizer(RandomDirectory random, Config config) { this.config = config; height = random.Indexer("Height"); radius = random.Indexer("Radius"); } public Pond Generate(int id, Rfloat2 pos) { return new Pond { pos = pos, height = height.Single(id).Range(config.height), radius = radius.Single(id).Range(config.radius) }; } } public record Plateau : Splat { public InertFloat2 p1; public InertFloat2 p2; public InertFloat radius; public InertFloat2 incline; public Direction bulgeDirection; public InertFloat2 bulgeMagnitude; public double maxNeighborHeight; public bool inverted; } [Serializable] public struct PlateauConfig { public double spawnNoiseScale; public PlateauMountainPeakConfig mountainPeaks; public PlateauSeasideCliffConfig coastalCliffs; public PlateauHillConfig hills; } [Serializable] public struct PlateauMountainPeakConfig { public double spawnRate; public double minHeightDiff; public Rmath.Range size; public ClampingConfig gradientClamping; public Rpoint2 bulge; public double cragThreshold; public Rmath.Range cragHeight; public double cragRadius; public double cragTerrace; public AllBiomeConfig<bool> allowedBiomes; } [Serializable] public struct PlateauSeasideCliffConfig { public double spawnRate; public double neighborHeight; public double minHeight; public double maxHeight; public Rmath.Range size; public ClampingConfig gradientClamping; public Rmath.Range bulge; } [Serializable] public struct PlateauHillConfig { public double spawnRate; public double minHeightDiff; public Rmath.Range size; public double maxLength; public ClampingConfig gradientClamping; public Rpoint2 bulge; public AllBiomeConfig<bool> allowedBiomes; } [Serializable] public struct ClampingConfig { public enum Mode { Linear, Asymptotic } public Mode mode; public double min; public double max; public double midpoint; public double pow; public double rate; } [Producer(new string[] { "Splats/Plateaus" })] [Fork("Region")] public class PlateauStage : IGeneratorStage<Splat[]> { public const string OUTPUT = "Splats/Plateaus"; [Random("Plateaus")] public RandomDirectory random; [LaneConfig] public PlateauConfig config; [Resource("Regions/Tiling")] public Tiling tiling; [Resource("Biomes/PostprocessedHeightMap")] public TileData<InertFloat> heights; [Resource("Biomes")] public TileData<Biome> biomes; public Splat[] Compute() { List<Splat> list = new List<Splat>(); TileSet occupied = new TileSet(tiling); AddMountainPeaks(list, occupied); AddCoastalCliffs(list, occupied); AddHills(list, occupied); return list.ToArray(); } private void AddMountainPeaks(List<Splat> result, TileSet occupied) { PlateauMountainPeakConfig cfg = config.mountainPeaks; NoiseField noiseField = random.BiasedNoise2D("SpawnRate/MountainPeaks", config.spawnNoiseScale); RandomIndexer randomIndexer = random.Indexer("Size"); foreach (Tile tile in tiling.Tiles) { if (cfg.allowedBiomes.Get(biomes[tile]) && !(noiseField.Sample(tile.Center) > cfg.spawnRate) && !tile.GetNeighbors().Any((Tile neighbor) => !cfg.allowedBiomes.Get(biomes[neighbor])) && !tile.GetNeighbors().Any((Tile neighbor) => (double)neighbor.Data(heights) > (double)tile.Data(heights) - cfg.minHeightDiff) && occupied.Add(tile)) { Rfloat2 gradient = HeightUtility.ComputeRoughness(tile, heights).Gradient; if (gradient.Length < cfg.cragThreshold) { result.Add(new Crag { pos = tile.Center, height = (double)tile.Data(heights) + randomIndexer.Single(tile.Id).Range(cfg.cragHeight), radius = cfg.cragRadius, terrace = cfg.cragTerrace, cliffiness = 1.0 }); } else { result.Add(new Plateau { pos = tile.Center, height = tile.Data(heights), radius = Rmath.Lerp(randomIndexer.Single(tile.Id).Value, cfg.size), incline = Clamp(gradient, cfg.gradientClamping), bulgeDirection = new Direction(gradient), bulgeMagnitude = cfg.bulge }); } } } } private void AddCoastalCliffs(List<Splat> result, TileSet occupied) { NoiseField noiseField = random.BiasedNoise2D("SpawnRate/CoastalCliffs", config.spawnNoiseScale); RandomIndexer randomIndexer = random.Indexer("Size"); RandomIndexer randomIndexer2 = random.Indexer("PlateauBulge"); TileSet tileSet = new TileSet(tiling); List<(Tile, Tile)> list = new List<(Tile, Tile)>(); foreach (Tile tile2 in tiling.Tiles) { if ((double)tile2.Data(heights) < config.coastalCliffs.minHeight || (double)tile2.Data(heights) > config.coastalCliffs.maxHeight || noiseField.Sample(tile2.Center) > config.coastalCliffs.spawnRate || tile2.GetNeighbors().All((Tile neighbor) => (double)neighbor.Data(heights) >= config.coastalCliffs.neighborHeight) || !occupied.Add(tile2)) { continue; } tileSet.Add(tile2); foreach (Tile neighbor in tile2.GetNeighbors()) { if (tileSet.Contains(neighbor)) { list.Add((tile2, neighbor)); } } } foreach (var item3 in list) { Tile item = item3.Item1; Tile item2 = item3.Item2; Rfloat2 v = 0.5 * (item.Center + item2.Center); double num = 0.5 * ((double)item.Data(heights) + (double)item2.Data(heights)); double num2 = Rmath.Lerp(randomIndexer.Single(item.Id, item2.Id).Value, config.coastalCliffs.size); Rfloat2 rfloat = heights.InclineVector(item, item2); List<Tile> list2 = item.GetNeighbors().Intersect(item2.GetNeighbors()).ToList(); if (list2.Count != 2) { continue; } int num3 = list2.Count((Tile neighbor) => (double)neighbor.Data(heights) >= config.coastalCliffs.neighborHeight); if (num3 != 0) { if (num3 != 1) { continue; } Tile tile = list2.First((Tile neighbor) => (double)neighbor.Data(heights) >= config.coastalCliffs.neighborHeight); rfloat += Clamp(HeightUtility.InclineVector(v, num, tile.Center, tile.Data(heights)), config.coastalCliffs.gradientClamping); } else { int num4 = Rmath.Sign(Rfloat2.Dot(Normal(item.Center, item2.Center), list2[1].Center - list2[0].Center)); rfloat += num4 * heights.InclineVector(list2[0], list2[1]); } Rfloat2 rfloat2 = 0.5 * (item.Center + item2.Center); result.Add(new Plateau { pos = rfloat2, p1 = item.Center - rfloat2, p2 = item2.Center - rfloat2, height = num, radius = num2, incline = rfloat, bulgeDirection = new Direction(rfloat), bulgeMagnitude = randomIndexer2.Double(item.Id, item2.Id).Range(config.coastalCliffs.bulge) }); } } private void AddHills(List<Splat> result, TileSet occupied) { NoiseField noiseField = random.BiasedNoise2D("SpawnRate/Hills", config.spawnNoiseScale); RandomIndexer randomIndexer = random.Indexer("Size"); RandomIndexer randomIndexer2 = random.Indexer("Length"); foreach (Tile tile in tiling.Tiles) { if (config.hills.allowedBiomes.Get(tile.Data(biomes)) && !(noiseField.Sample(tile.Center) > config.hills.spawnRate) && !tile.GetNeighbors().Any((Tile neighbor) => (double)neighbor.Data(heights) > (double)tile.Data(heights) - config.hills.minHeightDiff) && occupied.Add(tile)) { Rfloat2 rfloat = Clamp(HeightUtility.ComputeRoughness(tile, heights).Gradient, config.hills.gradientClamping); Rfloat2 rfloat2 = rfloat.Normalized * randomIndexer2.Single(tile.Id).Value * 0.5 * config.hills.