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 Unstable v0.15.0
BepInEx/plugins/Riverheim.dll
Decompiled a day 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.ComponentModel; using System.Diagnostics; using System.Linq; using System.Numerics; using System.Reflection; using System.Reflection.Emit; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using DelaunatorSharp; using FxResources.Microsoft.Bcl.HashCode; using HarmonyLib; using Microsoft.CodeAnalysis; using Riverheim.Configuration; using Riverheim.Configuration.Extensions; using Riverheim.Pipeline; 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(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("Riverheim")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Comprehensive terrain generator for Valheim")] [assembly: AssemblyFileVersion("0.15.0.0")] [assembly: AssemblyInformationalVersion("0.15.0+0679edb60f99efdae1d9deef078828ceaf44a3a8")] [assembly: AssemblyProduct("Riverheim")] [assembly: AssemblyTitle("Riverheim")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.15.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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; } } } internal static class PluginInfo { public const string PLUGIN_GUID = "Riverheim"; public const string PLUGIN_NAME = "Riverheim"; public const string PLUGIN_VERSION = "0.15.0"; } namespace Riverheim.Plugin { [BepInPlugin("dev.gurebu.riverheim.unstable", "Riverheim.Unstable", "0.15.0")] [BepInIncompatibility("dev.gurebu.riverheim")] public class RiverheimPlugin : BaseUnityPlugin { public class ValheimWorldRenderer { [CompilerGenerated] private WorldRenderer <renderer>P; [CompilerGenerated] private float <heightOffset>P; private const double FOREST_FACTOR_MULTIPLIER = 2.15; private readonly float baseHeightScale; private readonly ThreadLocal<(Rfloat2, IWorldGen.PointData)> cache; public ValheimWorldRenderer(WorldRenderer renderer, float baseHeightMultiplier, float heightOffset) { <renderer>P = renderer; <heightOffset>P = heightOffset; baseHeightScale = 1f / baseHeightMultiplier; cache = new ThreadLocal<(Rfloat2, IWorldGen.PointData)>(); base..ctor(); } private static Biome ToValheim(Biome biome) { //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_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_006e: 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.BlackForest: return (Biome)8; case Biome.Plains: return (Biome)16; case Biome.AshLands: case Biome.BoilingOcean: 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 GetBaseHeight(float wx, float wy) { return baseHeightScale * (<heightOffset>P + (float)<renderer>P.GetBaseHeight(new Rfloat2(wx, wy))); } 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>P.RenderPoint(rfloat)); } IWorldGen.PointData item = cache.Value.Item2; mask = new Color((float)item.dirt, (float)item.pave, 0f, (float)Math.Max(item.lava, item.fertility)); return <heightOffset>P + (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>P.GetBiome(new Rfloat2(wx, wy))); } public float GetForestFactor(float wx, float wy) { return (float)(2.15 * (1.0 - <renderer>P.GetForestation(new Rfloat2(wx, wy)))); } } [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) { GeneratorPipeline generatorPipeline = BuildPipeline(); string name = __instance.m_world.m_name; string text = "builtin.amur"; if (name.StartsWith("RHEXP")) { text = "builtin.experimental"; } Logger.LogInfo("Generating world '" + name + "' with preset: '" + text + "'..."); WorldSettings settings = new WorldSettings(text, 0); generatorPipeline.Compute(__instance.GetSeed(), ConfigManager.GetConfig(settings)); __instance.SetRenderer(new ValheimWorldRenderer(generatorPipeline.GetResult<WorldRenderer>("Rendering/WorldRenderer"), WorldGenerator.GetHeightMultiplier(), 30f)); return false; } [HarmonyPrefix] [HarmonyPatch("GetBaseHeight")] private static bool GetBaseHeight(WorldGenerator __instance, float wx, float wy, bool menuTerrain, ref float __result) { if (!menuTerrain) { ValheimWorldRenderer renderer = __instance.GetRenderer(); if (renderer != null) { __result = renderer.GetBaseHeight(wx, wy); return false; } } return true; } [HarmonyPrefix] [HarmonyPatch("GetBiomeHeight")] private static bool GetBiomeHeight(WorldGenerator __instance, Biome biome, float wx, float wy, out Color mask, ref float __result, World ___m_world) { //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; if (!___m_world.m_menu) { ValheimWorldRenderer renderer = __instance.GetRenderer(); if (renderer != null) { __result = renderer.GetHeight(wx, wy, out mask); return false; } } return true; } [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, World ___m_world) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected I4, but got Unknown if (!___m_world.m_menu) { ValheimWorldRenderer renderer = __instance.GetRenderer(); if (renderer != null) { __result = (Biome)(int)renderer.GetBiome(wx, wy); return false; } } return true; } [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; } [HarmonyPostfix] [HarmonyPatch("Initialize")] private static void Initialize() { } } private static class ExpressPerfTest { private const int COUNT = 1000000; [MethodImpl(MethodImplOptions.NoOptimization)] public static void Measure(WorldGenerator worldGen) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Expected I4, but got Unknown Logger.LogInfo("Express testing performance..."); float num = (float)ConfigManager.GetConfig(ConfigManager.DefaultWorldSettings()).world.common.worldSize; Vector2[] array = (Vector2[])(object)new Vector2[1000000]; for (int i = 0; i < 1000000; i++) { float num2 = Mathf.Sqrt(Random.value * num); float num3 = Random.value * (float)Math.PI * 2f; array[i] = new Vector2(num2 * Mathf.Cos(num3), num2 * Mathf.Sin(num3)); } Biome[] array2 = (Biome[])(object)new Biome[1000000]; DateTime now = DateTime.Now; for (int j = 0; j < 1000000; j++) { array2[j] = (Biome)(int)worldGen.GetBiome(array[j].x, array[j].y, 0.02f, false); } float num4 = (float)(DateTime.Now - now).TotalMilliseconds; now = DateTime.Now; Color val = default(Color); for (int k = 0; k < 1000000; k++) { worldGen.GetBiomeHeight(array2[k], array[k].x, array[k].y, ref val, false); } float num5 = (float)(DateTime.Now - now).TotalMilliseconds; Logger.LogInfo($"GetBiome(): {(double)num4 * 1000000.0 / 1000000.0:0.} ns/call"); Logger.LogInfo($"GetBiomeHeight(): {(double)num5 * 1000000.0 / 1000000.0:0.00} ns/call"); Logger.LogInfo($"Combined: {(double)(num4 + num5) * 1000000.0 / 1000000.0:0.00} ns/call"); } } [HarmonyPatch(typeof(World))] private static class WorldSaveLoadPatch { private const int CUSTOM_WORLDGEN_MARKER = -1; private const string RIVERHEIM_WORLDGEN = "dev.gurebu.riverheim"; private static void Log(string message) { Logger.LogInfo(message); } private static WorldSettings DefaultWorldSettings() { return new WorldSettings("unstable", 0, Array.Empty<WorldSettings.Feature>(), Array.Empty<WorldSettings.Parameter>()); } private static void WriteCustomMetaData(ZPackage package) { package.Write(-1); package.Write("dev.gurebu.riverheim"); package.Write(DefaultWorldSettings()); } private static bool ReadCustomMetaData(ZPackage package) { try { 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") { Log("Custom worldgen '" + text + "' does not match 'dev.gurebu.riverheim', skipping"); return false; } package.ReadWorldSettings(); } catch (WorldSettingSerialization.Error error) { Log("World settings malformed or unsupported: " + error.Message + ", skipping"); return false; } return true; } [HarmonyTranspiler] [HarmonyPatch("SaveWorldMetaData")] [HarmonyPatch(/*Could not decode attribute arguments.*/)] private 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 pushZPackage; return CodeMatcherExtensions.AdvanceToAfterZPackageConstructor(new CodeMatcher(instructions, (ILGenerator)null), out pushZPackage).InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { pushZPackage }).InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { CodeInstruction.Call(typeof(WorldSaveLoadPatch), "WriteCustomMetaData", (Type[])null, (Type[])null) }) .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 Label label; CodeInstruction pushZPackage; return CodeMatcherExtensions.MatchBadVersionWorldConstructor(new CodeMatcher(instructions, generator), out label).Start().AdvanceToAfterZPackageConstructor(out pushZPackage) .InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { pushZPackage }) .InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { CodeInstruction.Call(typeof(WorldSaveLoadPatch), "ReadCustomMetaData", (Type[])null, (Type[])null) }) .InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { new CodeInstruction(OpCodes.Brfalse_S, (object)label) }) .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("0.15.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] { "0.15.0" }); } [HarmonyPriority(700)] [HarmonyPrefix] [HarmonyPatch("RPC_ServerHandshake")] private static void RPC_ServerHandshake(ZNet __instance, ZRpc rpc) { rpc.Invoke("AssertRiverheimVersion", new object[1] { "0.15.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 class UnstablePresetHandler : IPresetHandler { public string ParentPreset() { return "builtin.amur"; } public int ParentPresetVersion(int version) { return 0; } public int MaxSupportedVersion() { return 0; } public WorldGenerationConfig GetConfig(int version, WorldGenerationConfig parentConfig) { return parentConfig; } } private const string GUID_STABLE = "dev.gurebu.riverheim"; private const string GUID_UNSTABLE = "dev.gurebu.riverheim.unstable"; private const string GUID = "dev.gurebu.riverheim.unstable"; private const string GUID_INCOMPATIBLE = "dev.gurebu.riverheim"; private const string PLUGIN_NAME = "Riverheim.Unstable"; private const string PRESET_UNSTABLE = "unstable"; public void Awake() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) new Harmony("dev.gurebu.riverheim.unstable").PatchAll(); 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); } }); ConfigManager.RegisterPresetHandler("unstable", new UnstablePresetHandler()); } } public static class WorldGeneratorExtensions { private static readonly ConditionalWeakTable<WorldGenerator, RiverheimPlugin.ValheimWorldRenderer> Renderers = new ConditionalWeakTable<WorldGenerator, RiverheimPlugin.ValheimWorldRenderer>(); public static void SetRenderer(this WorldGenerator worldGenerator, RiverheimPlugin.ValheimWorldRenderer renderer) { Renderers.Add(worldGenerator, renderer); } public static RiverheimPlugin.ValheimWorldRenderer GetRenderer(this WorldGenerator worldGenerator) { if (worldGenerator == null) { return null; } Renderers.TryGetValue(worldGenerator, out var value); return value; } } internal static class CodeMatcherExtensions { public static CodeMatcher AdvanceToAfterZPackageConstructor(this CodeMatcher matcher, out CodeInstruction pushZPackage) { //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_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0095: 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 //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Expected O, but got Unknown //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Expected O, but got Unknown //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010a: 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"); } if (matcher.Instruction.opcode == OpCodes.Stloc_0) { pushZPackage = new CodeInstruction(OpCodes.Ldloc_0, (object)null); } else if (matcher.Instruction.opcode == OpCodes.Stloc_1) { pushZPackage = new CodeInstruction(OpCodes.Ldloc_1, (object)null); } else if (matcher.Instruction.opcode == OpCodes.Stloc_2) { pushZPackage = new CodeInstruction(OpCodes.Ldloc_2, (object)null); } else if (matcher.Instruction.opcode == OpCodes.Stloc_3) { pushZPackage = new CodeInstruction(OpCodes.Ldloc_3, (object)null); } else { pushZPackage = new CodeInstruction(OpCodes.Ldloc_S, matcher.Instruction.operand); } 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; } } internal static class ZPackageExtensions { 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 System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } [CompilerGenerated] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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 { 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 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 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 Math.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 (Math.Abs(x) < 1E-06) { return Math.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 = Math.Sqrt(a.SqrLength * b.SqrLength); if (num < 1E-06) { return 0.0; } return Math.Acos(Rmath.Clamp(Dot(a, b) / num, -1.0, 1.0)) * (double)Math.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) { double num = x; if (num.Equals(other.x)) { num = y; return num.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 <cea2f7d2-7d32-438d-9027-5fd2d8593fdd>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 (Math.Abs(lhs.x - rhs.x) < 1E-06) { return Math.Abs(lhs.y - rhs.y) < 1E-06; } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Rfloat2 lhs, Rfloat2 rhs) { if (!(Math.Abs(lhs.x - rhs.x) > 1E-06)) { return Math.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 <cea2f7d2-7d32-438d-9027-5fd2d8593fdd>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 { get { Range result = default(Range); result.min = -1.0; result.max = 1.0; return result; } } public static Range From(double a, double b) { Range result; if (!(a <= b)) { result = default(Range); result.min = b; result.max = a; return result; } result = default(Range); result.min = a; result.max = b; return result; } } [Serializable] public struct RemapConfig { public double fromA; public double fromB; public double toA; public double toB; public Range From { get { Range result = default(Range); result.min = fromA; result.max = fromB; return result; } set { double min = value.min; double max = value.max; fromA = min; fromB = max; } } public Range To { get { Range result = default(Range); result.min = toA; result.max = toB; return result; } 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 RAD_TO_DEG = 180.0 / Math.PI; public const double DEG_TO_RAD = Math.PI / 180.0; public const double EPSILON = 1E-06; [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 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 Bump(double value) { if (value <= -1.0 || value >= 1.0) { return 0.0; } return Math.Exp(1.0 + 1.0 / (value * value - 1.0)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Bump(double value, double pow) { double num = Math.Abs(value); if (num >= 1.0) { return 0.0; } return Math.Exp(1.0 + 1.0 / (Math.Pow(num, pow) - 1.0)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double BumpRemap(double value, double pow, Range range) { return Bump(Remap(value, new RemapConfig { fromA = range.min, fromB = range.max, toA = -1.0, toB = 1.0 }), pow); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Rectifier(double value) { if (!(value > 0.0)) { return 0.0; } return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Gauss(double x, double nSigmas) { return Gauss2(x * x, nSigmas); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Gauss2(double x2, double nSigmas) { return Math.Exp(-0.5 * nSigmas * nSigmas * x2); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static double PolySinglet(double x) { return -8.0 * x * x * (x - 1.0) * (x - 1.0) * (2.0 * x - 1.0); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double ShapeStep(double x, double start, double end, double magnitude = 1.0) { return x + (end - start) * magnitude * PolySinglet(InverseLerp(x, start, end)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Curvature(Rfloat2 d, Rfloat2 d2) { double num = 1.0 + d.SqrLength; return d2.Length / Math.Sqrt(num * num * num); } public static double[] Softmax(double[] values, double temperature = 1.0) { double num = double.MinValue; foreach (double num2 in values) { if (num < num2) { num = num2; } } double num3 = 1.0 / temperature; double num4 = 0.0; double[] array = new double[values.Length]; for (int j = 0; j < values.Length; j++) { array[j] = Math.Exp(num3 * (values[j] - num)); num4 += array[j]; } for (int k = 0; k < values.Length; k++) { array[k] /= num4; } return array; } } 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 GetBaseHeight(Rfloat2 pos); double GetForestation(Rfloat2 pos); PointData RenderPoint(Rfloat2 pos); } [Serializable] [CompositeConfig] public struct WorldGenerationConfig { public WorldStateGenerator.Config world; public RenderingConfig rendering; } } namespace Riverheim.World { [Producer(new string[] { "CoastalProximity" })] public class CoastalProximityComponent : IGeneratorComponent<TileData<InertFloat>> { public const string OUTPUT = "CoastalProximity"; [Resource("MainTiling")] public Tiling tiling; [Resource("Flags/Base")] public TileData<TileFlags> flags; public TileData<InertFloat> Compute() { return tiling.DistanceTransform((Tile tile) => (!tile.Data(flags).IsCoast) ? null : new double?(0.5 * tiling.Spacing)); } } [Serializable] public struct CommonConfig { public double worldSize; public double tileSpacing; public double mountainHeight; public double oceanDepth; public double startingAreaRadius; } public readonly struct ExtendedTileFlags { public readonly Region region; 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 ExtendedTileFlags(bool isCenter, bool isOcean, bool isLake, bool isCoast, Region region) { IsCenter = isCenter; IsOcean = isOcean; IsLake = isLake; IsCoast = isCoast; this.region = region; } } [Producer(new string[] { "Flags/Extended" })] public class ExtendedTileFlagComponent : IGeneratorComponent<TileData<ExtendedTileFlags>> { public const string OUTPUT = "Flags/Extended"; [Resource("MainTiling")] 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 || IsLakeShore(tile), baseFlags[tile].region)); } private bool IsLakeShore(Tile tile) { if (!baseFlags[tile].IsLand) { return false; } bool isLake = lakes.Contains(tile); return tile.GetNeighbors().Any((Tile neighbor) => lakes.Contains(neighbor) != isLake); } } [Producer(new string[] { "Forestation" })] public class ForestationComponent : IGeneratorComponent<TileData<InertFloat>> { [Serializable] public struct Config { public double noiseScale; public NoiseField.FractalConfig noiseFractal; } public const string OUTPUT = "Forestation"; [Random("Forestation")] public RandomEngine random; [Config] public RegionalConfig<Config> config; [Resource("MainTiling")] public Tiling tiling; [Resource("Flags/Base")] public TileData<TileFlags> flags; [Resource("Biomes")] public TileData<Biome> biomes; public TileData<InertFloat> Compute() { Dictionary<Region, NoiseField> noise = new Dictionary<Region, NoiseField>(); foreach (var (region, config) in this.config.All()) { noise[region] = random.BiasedNoise2D($"Forestation/{region}", config.noiseScale).NormalizedFractal(config.noiseFractal); } return tiling.CreateData((Func<Tile, InertFloat>)delegate(Tile tile) { if (biomes[tile] == Biome.Mistglade) { return 0.0; } return (biomes[tile] == Biome.Marsh) ? ((InertFloat)1.0) : ((InertFloat)(1.0 - noise[flags[tile].region].Sample(tile.Center))); }); } } 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 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(); 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 : IGeneratorComponent<WorldMetrics> { public const string OUTPUT = "WorldMetrics"; [Resource("MainTiling")] public Tiling tiling; [Resource("LandmassIds")] public TileData<int> landmassIds; [Resource("Rivers/Splines")] public River[] rivers; [Resource("Biomes")] public TileData<Biome> biomes; public WorldMetrics Compute() { WorldMetrics result = default(WorldMetrics); result.landmassMetrics = ComputeLandmassMetrics(); result.riverMetrics = ComputeRiverMetrics(); result.biomeMetrics = ComputeBiomeMetrics(); return result; } private List<LandmassInfo> ComputeLandmassInfos() { 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 = ComputeLandmassInfos(); LandmassMetrics result = default(LandmassMetrics); result.count = list.Count; result.totalArea = list.Sum((LandmassInfo x) => x.area) / tiling.TotalArea; result.maxArea = list.Max((LandmassInfo x) => x.area) / tiling.TotalArea; result.avgArea = list.Average((LandmassInfo x) => x.area) / tiling.TotalArea; result.avgCompactness = list.Average((LandmassInfo x) => x.Compactness); return result; } private RiverMetrics ComputeRiverMetrics() { List<double> source = rivers.Select(RiverLength).ToList(); RiverMetrics result = default(RiverMetrics); result.count = rivers.Length; result.maxLength = ((rivers.Length != 0) ? source.Max() : 0.0); result.avgLength = ((rivers.Length != 0) ? source.Average() : 0.0); return result; } private static double RiverLength(River river) { return river.spline.Length; } private Dictionary<Biome, BiomeMetrics> ComputeBiomeMetrics() { 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 PolarProximityComponent : IGeneratorComponent<PolarProximityMap> { public const string OUTPUT = "PolarProximity"; [Random("PolarProximityMap")] public RandomEngine random; [Config] public PolarBeltConfig config; public PolarProximityMap Compute() { NoiseField noise = random.Noise2D("PolarProximity", config.noiseScale); return delegate(Rfloat2 pos, PolarProximityType type) { double num = Math.Atan2(pos.x, (pos.y < 0.0) ? (pos.y - config.offset) : pos.y); double num2 = config.radius + Math.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 => Math.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> { [CompilerGenerated] private sealed class <All>d__4 : IEnumerable<(Region, T)>, IEnumerable, IEnumerator<(Region, T)>, IDisposable, IEnumerator { private int <>1__state; private (Region, T) <>2__current; private int <>l__initialThreadId; public RegionalConfig<T> <>4__this; public RegionalConfig<T> <>3__<>4__this; (Region, T) IEnumerator<(Region, T)>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <All>d__4(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (Region.Main, <>4__this.main); <>1__state = 1; return true; case 1: <>1__state = -1; <>2__current = (Region.SouthPole, <>4__this.southPole); <>1__state = 2; return true; case 2: <>1__state = -1; <>2__current = (Region.NorthPole, <>4__this.northPole); <>1__state = 3; return true; case 3: <>1__state = -1; return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator<(Region, T)> IEnumerable<(Region, T)>.GetEnumerator() { <All>d__4 <All>d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; <All>d__ = this; } else { <All>d__ = new <All>d__4(0); } <All>d__.<>4__this = <>3__<>4__this; return <All>d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable<(Region, T)>)this).GetEnumerator(); } } public T main; public T southPole; public T northPole; public readonly T Get(Region region) { return region switch { Region.Main => main, Region.SouthPole => southPole, Region.NorthPole => northPole, _ => default(T), }; } [IteratorStateMachine(typeof(RegionalConfig<>.<All>d__4))] public IEnumerable<(Region, T)> All() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <All>d__4(-2) { <>3__<>4__this = this }; } } [Producer(new string[] { "Regions" })] public class RegionComponent : IGeneratorComponent<TileData<Region>[]> { public const string OUTPUT = "Regions"; [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 record RegionInfo { public int tileCount; public double area; } [Producer(new string[] { "RegionInfo" })] public class RegionInfoComponent : IGeneratorComponent<Dictionary<Region, RegionInfo>> { public const string OUTPUT = "RegionInfo"; [Resource("MainTiling")] public Tiling tiling; [Resource("Regions")] public TileData<Region>[] regions; public Dictionary<Region, RegionInfo> Compute() { Dictionary<Region, RegionInfo> dictionary = new Dictionary<Region, RegionInfo>(); foreach (Region value in Enum.GetValues(typeof(Region))) { dictionary[value] = new RegionInfo(); } foreach (Tile tile in tiling.Tiles) { Region key2 = regions[0][tile]; dictionary[key2].tileCount++; dictionary[key2].area += tile.Area; } return dictionary; } } public readonly struct TileFlags { public readonly Region region; public bool IsCenter { get; } public bool IsLand { get; } public bool IsOcean => !IsLand; public bool IsCoast { get; } public TileFlags(bool isLand, bool isCenter, bool isCoast, Region region) { IsLand = isLand; IsCenter = isCenter; IsCoast = isCoast; this.region = region; } } [Producer(new string[] { "Flags/Base" })] public class TileFlagComponent : IGeneratorComponent<TileData<TileFlags>> { public const string OUTPUT = "Flags/Base"; [Resource("MainTiling")] public Tiling tiling; [Resource("Regions")] public TileData<Region>[] regions; [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), regions[0][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" })] public class TravelDistanceComponent : IGeneratorComponent<TileData<InertFloat>> { [Serializable] public struct Config { public double riverCost; public double oceanCost; public double embarkCost; } public const string OUTPUT = "TravelDistance"; [Config] public Config config; [Resource("MainTiling")] public Tiling tiling; [Resource("Flags/Base")] public TileData<TileFlags> flags; [Resource("PrunedRivers")] public TileData<RiverTile> rivers; public TileData<InertFloat> Compute() { Tile? tile2 = tiling.GetTile(Rfloat2.Zero); if (tile2.HasValue) { Tile center = tile2.GetValueOrDefault(); return tiling.DistanceTransform((Tile tile) => (!(tile == center)) ? null : new double?(0.0), (Tile _) => true, (Tile t1, Tile t2) => Rfloat2.Distance(t1.Center, t2.Center) * EdgeCostMultiplier(t1, t2)); } return tiling.CreateData((InertFloat)0.0); } 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 1.0; } } 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 : IGeneratorComponent<WorldState> { public const string OUTPUT = "WorldState"; [Resource("MainTiling")] public Tiling tiling; [Resource("MainInterpolationMesh")] public TriangleMesh interpolationMesh; [Resource("Biomes/PostprocessedHeightMap")] 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, biomes, forestation, rivers, splats); } } public class WorldStateGenerator { [Serializable] [CompositeConfig] public record Config { public CommonConfig common; public WorldVerticesCalculator.Config tiling; public PolarBeltConfig polar; public TravelDistanceComponent.Config travel; public LandmassConfig landmass; public RiverConfig rivers; public HeightConfig height; public BiomeConfig biomes; public Riverheim.World.Splats.SplatConfig splats; public RegionalConfig<ForestationComponent.Config> forestation; } } } namespace Riverheim.World.Tilings { [Producer(new string[] { "MainTiling", "MainInterpolationMesh" })] public class MainTiling : IGeneratorComponent<(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[] { "TilingLods" })] public class TilingLodComponent : IGeneratorComponent<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 class TilingLodIndex { private readonly Tiling[] lods; private readonly TileData<int>[] indices; public TilingLodIndex(Tiling tiling, Tiling[] lods) { this.lods = lods; indices = new TileData<int>[lods.Length]; for (int i = 0; i < lods.Length; i++) { indices[i] = lods[i].CreateData<int>(); } foreach (Tile tile in tiling.Tiles) { _ = tile; } } public Tile GetLodTile(Tile tile, int lod) { Tile tile2 = tile; for (int i = 0; i < lod; i++) { tile2 = lods[i].GetTile(indices[i][tile2]); } return tile2; } } public struct WorldVertices { public double spacing; public double boundingRadius; public InertFloat2[] vertices; } [Producer(new string[] { "WorldVertices" })] public class WorldVerticesCalculator : IGeneratorComponent<WorldVertices[]> { [Serializable] public struct Config { public int lodLevels; public double lodScale; } public const string OUTPUT = "WorldVertices"; [Random("WorldVertices")] public RandomEngine 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 * Math.Pow(config.lodScale, i); double boundingRadius = commonConfig.worldSize + Math.Sqrt(3.0) * num; InertFloat2[] vertices = (from point in new PoissonDiscSampler(random.Sequence(i.ToString()), boundingRadius, num / Math.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 record Pond : Splat { public InertFloat radius; } [Producer(new string[] { "Splats/RiverSources" })] public class RiverSourceSplatComponent : IGeneratorComponent<Splat[]> { [Serializable] public struct Config { public AllBiomeConfig<bool> allowedBiomes; public CragConfig crags; public PondConfig ponds; } [Serializable] public struct SpawnFeatureConfig { public Rmath.Range range; public double bumpPower; } [Serializable] public struct CragConfig { public double frequency; public SpawnFeatureConfig height; public SpawnFeatureConfig riverLength; public CragRandomizer.Config random; } [Serializable] public struct PondConfig { public double frequency; public SpawnFeatureConfig height; public SpawnFeatureConfig riverLength; public double stepback; public PondRandomizer.Config random; } public const string OUTPUT = "Splats/RiverSources"; [Random("Splats/RiverSources")] public RandomEngine random; [Config] public Config config; [Resource("MainTiling")] public Tiling tiling; [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.CreateEngine("Crags"), config.crags.random); RandomIndexer randomIndexer2 = random.Indexer("Ponds/Spawn"); PondRandomizer pondRandomizer = new PondRandomizer(random.CreateEngine("Ponds"), config.ponds.random); BarycentricInterpolator barycentricInterpolator = new BarycentricInterpolator(tiling, 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.Interpolate(pos, heights); 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, SpawnFeatureConfig config) { if (config.range.IsEmpty || config.bumpPower <= 0.0) { return 1.0; } return Rmath.BumpRemap(value, config.bumpPower, config.range); } } 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(RandomEngine 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) }; } } 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(RandomEngine 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 seasideCliffs; public PlateauHillConfig hills; public double swampDitchHeight; public int swampDitchTileCount; } [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; } [Serializable] public struct PlateauSeasideCliffConfig { public double spawnRate; public double minHeight; 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; } [Serializable] public struct ClampingConfig { public enum Mode { Linear, Asymptotic } public Mode mode; public double min; public double max; public double rate; } [Producer(new string[] { "Splats/Plateaus" })] public class PlateauComponent : IGeneratorComponent<Splat[]> { public const string OUTPUT = "Splats/Plateaus"; [Random("Plateaus")] public RandomEngine random; [Config] public PlateauConfig config; [Resource("MainTiling")] 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); AddSeasideCliffs(list, occupied); AddHills(list, occupied); AddSwampDitches(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); RandomField randomField = random.Field("Size"); foreach (Tile tile in tiling.Tiles) { if (tile.Data(biomes) == Biome.Mountain && !(noiseField.Sample(tile.Center) > cfg.spawnRate) && !tile.GetNeighbors().Any((Tile neighbor) => neighbor.Data(biomes) != Biome.Mountain) && !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) + randomField.Single(tile.Center).Range(cfg.cragHeight), radius = cfg.cragRadius, terrace = cfg.cragTerrace }); } else { result.Add(new Plateau { pos = tile.Center, height = tile.Data(heights), radius = Rmath.Lerp(randomField.Single(tile.Center).Value, cfg.size), incline = Clamp(gradient, cfg.gradientClamping), bulgeDirection = new Direction(gradient), bulgeMagnitude = cfg.bulge }); } } } } private void AddSeasideCliffs(List<Splat> result, TileSet occupied) { NoiseField noiseField = random.BiasedNoise2D("SpawnRate/SeasideCliffs", config.spawnNoiseScale); RandomField randomField = random.Field("Size"); RandomField randomField2 = random.Field("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.seasideCliffs.minHeight || noiseField.Sample(tile2.Center) > config.seasideCliffs.spawnRate || tile2.GetNeighbors().All((Tile neighbor) => (double)neighbor.Data(heights) >= 0.0) || !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 rfloat = 0.5 * (item.Center + item2.Center); double num = 0.5 * ((double)item.Data(heights) + (double)item2.Data(heights)); double num2 = Rmath.Lerp(randomField.Single(rfloat).Value, config.seasideCliffs.size); Rfloat2 rfloat2 = 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) >= 0.0); if (num3 != 0) { if (num3 != 1) { continue; } Tile tile = list2.First((Tile neighbor) => (double)neighbor.Data(heights) >= 0.0); rfloat2 += Clamp(HeightUtility.InclineVector(rfloat, num, tile.Center, tile.Data(heights)), config.seasideCliffs.gradientClamping); } else { int num4 = Math.Sign(Rfloat2.Dot(Normal(item.Center, item2.Center), list2[1].Center - list2[0].Center)); rfloat2 += num4 * heights.InclineVector(list2[0], list2[1]); } Rfloat2 rfloat3 = 0.5 * (item.Center + item2.Center); result.Add(new Plateau { pos = rfloat3, p1 = item.Center - rfloat3, p2 = item2.Center - rfloat3, height = num, radius = num2, incline = rfloat2, bulgeDirection = new Direction(rfloat2), bulgeMagnitude = randomField2.Double(rfloat).Range(config.seasideCliffs.bulge) }); } } private void AddHills(List<Splat> result, TileSet occupied) { NoiseField noiseField = random.BiasedNoise2D("SpawnRate/Hills", config.spawnNoiseScale); RandomField randomField = random.Field("Size"); RandomField randomField2 = random.Field("Length"); HashSet<Biome> hashSet = new HashSet<Biome> { Biome.Meadows, Biome.BlackForest, Biome.Plains }; foreach (Tile tile in tiling.Tiles) { if (hashSet.Contains(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 * randomField2.Single(tile.Center).Value * 0.5 * config.hills.maxLength; result.Add(new Plateau { pos = tile.Center, p1 = rfloat2, p2 = -rfloat2, height = tile.Data(heights), radius = Rmath.Lerp(randomField.Single(tile.Center).Value, config.hills.size), incline = rfloat, bulgeDirection = new Direction(rfloat), bulgeMagnitude = config.hills.bulge }); } } } private void AddSwampDitches(List<Splat> result, TileSet occupied) { TileSet tileSet = new TileSet(tiling); TileQueue tileQueue = new TileQueue(tiling); foreach (Tile tile2 in tiling.Tiles) { if (tile2.Data(biomes) != Biome.Swamp || tileSet.Contains(tile2)) { continue; } TileList tileList = new TileList(tiling); tileSet.Add(tile2); tileQueue.Enqueue(tile2); while (tileQueue.Count > 0) { Tile tile = tileQueue.Dequeue(); tileList.Add(tile); foreach (Tile neighbor in tile.GetNeighbors()) { if (!tileSet.Contains(neighbor) && neighbor.Data(biomes) == Biome.Swamp) { tileSet.Add(neighbor); tileQueue.Enqueue(neighbor); } } } if (tileList.Count > config.swampDitchTileCount) { continue; } foreach (Tile item in tileList) { if (occupied.Add(item)) { result.Add(new Plateau { pos = item.Center, height = config.swampDitchHeight, radius = item.Tiling.Spacing / 2.0, maxNeighborHeight = item.Scan(item.Tiling.Spacing).Append(item).Max((Func<Tile, double>)((Tile neighbor) => neighbor.Data(heights))), inverted = true }); } } } } private static Rfloat2 Normal(Rfloat2 v1, Rfloat2 v2) { Rfloat2 rfloat = v2 - v1; return new Rfloat2(rfloat.y, 0.0 - rfloat.x); } private static Rfloat2 Clamp(Rfloat2 vector, ClampingConfig config) { if (vector.IsNegligible) { return config.min * new Rfloat2(-1.0, 0.0); } double length = vector.Length; double num = config.max - config.min; return config.mode switch { ClampingConfig.Mode.Linear => Rmath.Clamp(length, config.min, config.max), ClampingConfig.Mode.Asymptotic => config.min + num * Functions.Tanh(length * config.rate / num), _ => throw new ArgumentOutOfRangeException(), } / length * vector; } } public abstract record Splat { public InertFloat2 pos; public InertFloat height; } [Producer(new string[] { "Splats" })] public class SplatComponent : IGeneratorComponent<Splat[]> { public const string OUTPUT = "Splats"; [Resource("Splats/Plateaus")] public Splat[] plateaus; [Resource("Splats/RiverSources")] public Splat[] riverSourceSplats; public Splat[] Compute() { List<Splat> list = new List<Splat>(); list.AddRange(plateaus); list.AddRange(riverSourceSplats); return list.ToArray(); } } [Serializable] [CompositeConfig] public record SplatConfig { public PlateauConfig plateaus; public RiverSourceSplatComponent.Config riverSourceSplats; } } namespace Riverheim.World.Rivers { public struct Channel { public TileList path; public double width; } [Producer(new string[] { "Rivers/Channels" })] public class ChannelComponent : IGeneratorComponent<Channel[]> { [Serializable] public struct Config { public RegionalConfig<bool> allowedRegions; public double width; } public const string OUTPUT = "Rivers/Channels"; [Config] public Config config; [Random("Rivers/Channels")] public RandomEngine random; [Resource("MainTiling")] public Tiling tiling; [Resource("Flags/Base")] public TileData<TileFlags> flags; [Resource("Landmasses/MergedPartitions")] public TileData<int> partitions; public Channel[] Compute() { List<Channel> list = new List<Channel>(); RandomIndexer selectionRandom = random.Indexer("Selection"); foreach (TileSet faultLine in GetFaultLines()) { if (!faultLine.Unordered().Any((Tile tile) => !config.allowedRegions.Get(flags[tile].region)) && MakeChannel(faultLine, selectionRandom, out var channel)) { list.Add(channel); } } return list.ToArray(); } private List<TileSet> GetFaultLines() { Dictionary<Rint2, TileSet> dictionary = new Dictionary<Rint2, TileSet>(); foreach (Tile tile in tiling.Tiles) { if (tile.Data(flags).IsOcean) { continue; } int num = partitions[tile]; foreach (Tile neighbor in tile.GetNeighbors()) { if (neighbor.Data(flags).IsOcean) { continue; } int num2 = partitions[neighbor]; if (num != num2) { Rint2 key = ((num > num2) ? new Rint2(num2, num) : new Rint2(num, num2)); if (!dictionary.ContainsKey(key)) { dictionary[key] = new TileSet(tiling); } dictionary[key].Add(tile); } } } return new List<TileSet>(dictionary.Values); } private bool MakeChannel(TileSet faultLine, RandomIndexer selectionRandom, out Channel channel) { (TileSet, TileSet) oceanCoastalTiles = GetOceanCoastalTiles(faultLine); if (oceanCoastalTiles.Item1.Count == 0 || oceanCoastalTiles.Item2.Count == 0) { channel = default(Channel); return false; } Tile origin = Select(oceanCoastalTiles.Item1, selectionRandom); Tile target = Select(oceanCoastalTiles.Item2, selectionRandom); TileList tileList = TilingUtility.FindPathToTile(origin, target, (Tile tile) => faultLine.Contains(tile) || tile == target); if (tileList == null) { channel = default(Channel); return false; } channel = new Channel { path = tileList, width = config.width }; return true; } private Tile Select(TileSet tiles, RandomIndexer selectionRandom) { TileHeap tileHeap = new TileHeap(tiling, HeapType.Min); foreach (Tile item in tiles.Unordered()) { tileHeap.AddOrUpdate(item, selectionRandom.Single(item.Id).Value); } return tileHeap.Pop(); } private (TileSet, TileSet) GetOceanCoastalTiles(TileSet faultLine) { TileList tileList = new TileList(tiling); foreach (Tile item in faultLine.Unordered()) { if (!item.Data(flags).IsCoast) { continue; } foreach (Tile neighbor in item.GetNeighbors()) { if (neighbor.Data(flags).IsOcean) { tileList.Add(neighbor); } } } TileSet tileSet = new TileSet(tiling); TileSet tileSet2 = new TileSet(tiling); if (tileList.Count == 0) { return (tileSet, tileSet2); } TileSet tileSet3 = TileSet.Of(tileList[0]); foreach (Tile neighbor2 in tileList[0].GetNeighbors(3, (Tile neighbor) => neighbor.Data(flags).IsOcean)) { tileSet3.Add(neighbor2); } foreach (Tile item2 in tileList) { (tileSet3.Contains(item2) ? tileSet : tileSet2).Add(item2); } return (tileSet, tileSet2); } } [Serializable] public struct PrecipitationConfig { public double noiseScale; public double variability; public double spawnMinRadius; public double spawnMaxRadius; public double basinContribution; public double minimum; } [Producer(new string[] { "Precipitation" })] public class PrecipitationComponent : IGeneratorComponent<TileData<InertFloat>> { public const string OUTPUT = "Precipitation"; [Random("PrecipitationMap")] public RandomEngine random; [Config] public RegionalConfig<PrecipitationConfig> config; [Resource("MainTiling")] public Tiling tiling; [Resource("Flags/Base")] public TileData<TileFlags> flags; [Resource("Landmasses/Artifacts")] public TileData<LandmassArtifacts> artifacts; public TileData<InertFloat> Compute() { Dictionary<Region, NoiseField> noise = new Dictionary<Region, NoiseField>(); foreach (var (region, precipitationConfig) in config.All()) { noise[region] = random.Noise2D($"Precipitation/{region}", precipitationConfig.noiseScale); } return tiling.CreateData((Func<Tile, InertFloat>)delegate(Tile tile) { Region region2 = flags[tile].region; PrecipitationConfig precipitationConfig2 = config.Get(region2); double num = Rmath.Clamp(precipitationConfig2.variability * noise[region2].Sample(tile.Center), Rmath.Lerp(Rmath.InverseLerp(tile.Center.Length, precipitationConfig2.spawnMinRadius, precipitationConfig2.spawnMaxRadius), 1.0, -1.0), 1.0); if (tile.Data(artifacts).isBasin) { num += precipitationConfig2.basinContribution; } return Math.Max(precipitationConfig2.minimum, 1.0 + num); }); } } [Serializable] [CompositeConfig] public record RiverConfig { public RegionalConfig<PrecipitationConfig> precipitation; public RiverOriginFinder.Config origins; public RiverFillComponent.Config floodfill; public RiverWidthConfig width; public RiverPruneComponent.Config prune; public RiverCuriosityConfig curiosities; public ChannelComponent.Config channels; public RiverPolylineIndexBuilder.Config polylineIndex; public RiverMeanderer.Config meander; public RiverSplineBuilder.Config splines; } [Serializable] public struct RiverCuriosityConfig { public double sourceMult; public double sourceMinLength; public double sourceMaxLength; public double proximityMult; public double proximityScanRange; public double proximityDifferentBasinsMultiplier; public double proximityMinRatio; public double proximityMaxRatio; public double angleMult; public double angleMinDegrees; public double angleMaxDegrees; public double angleNormWidth; public double smoothingRange; } [Producer(new string[] { "Rivers/Curiosities" })] public class RiverCuriosityComponent : IGeneratorComponent<TileData<InertFloat>> { private class CuriosityData { public readonly Dictionary<int, double> lengthsById; public InertFloat score; public double Length { get { double num = 0.0; foreach (double value in lengthsById.Values) { num += value; } return num; } } public CuriosityData(Dictionary<int, double> lengths) { lengthsById = ((lengths == null) ? new Dictionary<int, double>() : new Dictionary<int, double>(lengths)); score = 0.0; } } public const string OUTPUT = "Rivers/Curiosities"; [Config] public CommonConfig commonConfig; [Config] public RiverCuriosityConfig config; [Resource("MainTiling")] public Tiling tiling; [Resource("PrunedRivers")] public TileData<RiverTile> rivers; [Resource("RiverTileMetrics")] public TileData<RiverBasinTile> basins; private MutableTileData<CuriosityData> data; public TileData<InertFloat> Compute() { data = tiling.CreateData<CuriosityData>(); MutableTileData<InertFloat> mutableTileData = tiling.CreateData<InertFloat>(); TileList tileList = FindMouths(); foreach (Tile item in tileList) { MarkLengths(item); } foreach (Tile item2 in tileList) { MarkScores(item2); } foreach (Tile item3 in tileList) { SmoothScores(item3, mutableTileData); } return mutableTileData; } private TileList FindMouths() { TileList tileList = new TileList(tiling); foreach (Tile tile in tiling.Tiles) { if (tile.Data(rivers) != null && tile.Data(rivers).isRiverMouth) { tileList.Add(tile); } } return tileList; } private void MarkLengths(Tile tile, Dictionary<int, double> parentLengths = null, double segmentLength = 0.0) { tile.Data(data) = new CuriosityData(parentLengths); Dictionary<int, double> lengthsById = tile.Data(data).lengthsById; int riverId = tile.Data(rivers).riverId; if (!lengthsById.ContainsKey(riverId)) { lengthsById[riverId] = segmentLength; } else { lengthsById[riverId] += segmentLength; } foreach (Tile inlet in GetInlets(tile)) { MarkLengths(inlet, lengthsById, Rfloat2.Distance(tile.Center, inlet.Center)); } } private void MarkScores(Tile tile) { double num = tile.Data(data).score; num += config.sourceMult * ComputeSourceScore(tile); num += config.proximityMult * ComputeProximityScore(tile); num += config.angleMult * ComputeAngleScore(tile); tile.Data(data).score = num; foreach (Tile inlet in GetInlets(tile)) { MarkScores(inlet); } } private double ComputeSourceScore(Tile tile) { if (!tile.Data(rivers).IsRiverTermination()) { return 0.0; } return Rmath.InverseLerp(tile.Data(data).Length, config.sourceMinLength, config.sourceMaxLength); } private double ComputeProximityScore(Tile tile) { double num = 0.0; foreach (Tile item in tile.Scan(config.proximityScanRange)) { if (item.Data(data) != null) { double num2 = Rfloat2.Distance(tile.Center, item.Center); double num3 = GraphDistance(tile, item); double num4 = ((tile.Data(basins).basinId != item.Data(basins).basinId) ? config.proximityDifferentBasinsMultiplier : 1.0); double num5 = Rmath.InverseLerp(num3 / num2 * num4, config.proximityMinRatio, config.proximityMaxRatio); num += num5 * Rmath.Gauss(num2 / config.proximityScanRange, 3.0); } } double num6 = commonConfig.tileSpacing / config.proximityScanRange; return num * num6 * num6; } private double ComputeAngleScore(Tile tile) { double num = 0.0; Direction direction = tile.DirectionTo(GetOutlet(tile)); foreach (Tile inlet in GetInlets(tile)) { double num2 = Math.Abs(tile.DirectionTo(inlet) - direction); double num3 = 1.0 - Rmath.InverseLerp(num2 * (180.0 / Math.PI), config.angleMinDegrees, config.angleMaxDegrees); num += num3 * (double)tile.Data(basins).width / config.angleNormWidth; } return num; } private void SmoothScores(Tile tile, MutableTileData<InertFloat> result) { double smoothingRange = config.smoothingRange; InertFloat score = tile.Data(data).score; if ((double)score > 0.0) { ref InertFloat reference = ref tile.Data(result); reference = (double)reference + (double)score; foreach (Tile item in tile.Scan(smoothingRange)) { Rfloat2 rfloat = (tile.Center - item.Center) / smoothingRange; ref InertFloat reference2 = ref item.Data(result); reference2 = (double)reference2 + (double)score * Rmath.Gauss2(rfloat.SqrLength, 3.0); } } foreach (Tile inlet in GetInlets(tile)) { SmoothScores(inlet, result); } } private double GraphDistance(Tile from, Tile to) { Dictionary<int, double> dictionary = new Dictionary<int, double>(from.Data(data).lengthsById); foreach (KeyValuePair<int, double> item in to.Data(data).lengthsById) { if (!dictionary.ContainsKey(item.Key)) { dictionary[item.Key] = 0.0 - item.Value; } else { dictionary[item.Key] -= item.Value; } } double num = 0.0; foreach (double value in dictionary.Values) { num += Math.Abs(value); } return num; } private Tile GetOutlet(Tile tile) { return tiling.GetTile(tile.Data(rivers).outletId); } private IEnumerable<Tile> GetInlets(Tile tile) { return tile.Data(rivers).inletIds.Select((int inletId) => tiling.GetTile(inletId)); } } public class RiverFillTile { public readonly Tile outlet; public readonly Couple<Tile> inlets; public readonly InertFloat loss; public RiverFillTile(Tile outlet, Couple<Tile> inlets, double loss) { this.outlet = outlet; this.inlets = inlets; this.loss = loss; } } [Producer(new string[] { "Rivers/FloodFill" })] public class RiverFillComponent : IGeneratorComponent<TileData<RiverFillTile>> { [Serializable] public struct Config { public double temperature; public double maxOriginRandomLoss; public RiverFillGuide.Config guide; } private class GraphNode { public readonly Tile outlet; public readonly TileList inlets; public readonly InertFloat loss; public GraphNode(Tile outlet, double loss) { this.outlet = outlet; this.loss = loss; inlets = new TileList(outlet.Tiling); } public RiverFillTile ToRiverFillTile() { return new RiverFillTile(outlet, Couple<Tile>.MakeCouple(inlets.ToArray()), loss); } } public const string OUTPUT = "Rivers/FloodFill"; [Random("Rivers/FloodFill")] public RandomEngine random; [Config] public Config config; [Resource("MainTiling")] public Tiling tiling; [Resource("Flags/Base")] public TileData<TileFlags> flags; [Resource("LandmassIds")] public TileData<int> landmassIds; [Resource("Landmasses/Artifacts")] public TileData<LandmassArtifacts> artifacts; [Resource("Precipitation")] public TileData<InertFloat> precipitationMap; [Resource("Rivers/Origins")] public List<RiverOrigin> riverOrigins; private double Loss(TileData<GraphNode> graph, Tile tile) { return (double)graph[tile].loss - (double)precipitationMap[tile]; } public TileData<RiverFillTile> Compute() { MutableTileData<GraphNode> mutableTileData = tiling.CreateData<GraphNode>(); RiverFillGuide guide = new RiverFillGuide(random.Indexer("Guide"), config.guide); Dictionary<int, TileList> dictionary = InitializeOrigins(mutableTileData); foreach (int key in dictionary.Keys) { ProcessLandmass(mutableTileData, guide, dictionary[key]); } return mutableTileData.Transform((GraphNode node) => node?.ToRiverFillTile()); } private Dictionary<int, TileList> InitializeOrigins(MutableTileData<GraphNode> g