diff --git a/code_mods/WorldGen_AlpineStory/.vscode/extensions.json b/code_mods/WorldGen_AlpineStory/.vscode/extensions.json new file mode 100644 index 00000000..78988773 --- /dev/null +++ b/code_mods/WorldGen_AlpineStory/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "ms-dotnettools.csdevkit", + "ms-dotnettools.csharp" + ] +} \ No newline at end of file diff --git a/code_mods/WorldGen_AlpineStory/.vscode/launch.json b/code_mods/WorldGen_AlpineStory/.vscode/launch.json new file mode 100644 index 00000000..5d9b3bc2 --- /dev/null +++ b/code_mods/WorldGen_AlpineStory/.vscode/launch.json @@ -0,0 +1,61 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Client (Debug)", + "type": "coreclr", + "request": "launch", + "program": "${env:VINTAGE_STORY}/Vintagestory.exe", + "linux": { + "program": "${env:VINTAGE_STORY}/Vintagestory" + }, + "osx": { + "program": "${env:VINTAGE_STORY}/Vintagestory" + }, + "preLaunchTask": "build", + "args": [ + // "--playStyle" , "preset-surviveandbuild", + // "--openWorld" , "modding test world", + "--tracelog", + "--addModPath", + "${workspaceFolder}/AlpineStory/bin/Debug/Mods" + ], + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": "Launch Server", + "type": "coreclr", + "request": "launch", + "program": "${env:VINTAGE_STORY}/VintagestoryServer.exe", + "linux": { + "program": "${env:VINTAGE_STORY}/VintagestoryServer" + }, + "osx": { + "program": "${env:VINTAGE_STORY}/VintagestoryServer" + }, + "preLaunchTask": "build", + "args": [ + "--tracelog", + "--addModPath", + "${workspaceFolder}/AlpineStory/bin/Debug/Mods" + ], + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": "CakeBuild", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build (Cake)", + "program": "${workspaceFolder}/CakeBuild/bin/Debug/net7.0/CakeBuild.dll", + "args": [], + "cwd": "${workspaceFolder}/CakeBuild", + "stopAtEntry": false, + "console": "internalConsole" + } + ] +} \ No newline at end of file diff --git a/code_mods/WorldGen_AlpineStory/.vscode/tasks.json b/code_mods/WorldGen_AlpineStory/.vscode/tasks.json new file mode 100644 index 00000000..01267d21 --- /dev/null +++ b/code_mods/WorldGen_AlpineStory/.vscode/tasks.json @@ -0,0 +1,40 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "-c", + "Debug", + "${workspaceFolder}/AlpineStory/AlpineStory.csproj" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "package", + "command": "dotnet", + "type": "process", + "args": [ + "run", + "--project", + "${workspaceFolder}/CakeBuild/CakeBuild.csproj" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "build (Cake)", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "-c", + "Debug", + "${workspaceFolder}/CakeBuild/CakeBuild.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/code_mods/WorldGen_AlpineStory/CakeBuild/CakeBuild.csproj b/code_mods/WorldGen_AlpineStory/CakeBuild/CakeBuild.csproj new file mode 100644 index 00000000..8e2c970c --- /dev/null +++ b/code_mods/WorldGen_AlpineStory/CakeBuild/CakeBuild.csproj @@ -0,0 +1,19 @@ + + + Exe + net7.0 + $(MSBuildProjectDirectory) + + + + + + + + + + + $(VINTAGE_STORY)/VintagestoryAPI.dll + + + \ No newline at end of file diff --git a/code_mods/WorldGen_AlpineStory/CakeBuild/Program.cs b/code_mods/WorldGen_AlpineStory/CakeBuild/Program.cs new file mode 100644 index 00000000..762d6ae3 --- /dev/null +++ b/code_mods/WorldGen_AlpineStory/CakeBuild/Program.cs @@ -0,0 +1,110 @@ +using System; +using System.IO; +using Cake.Common; +using Cake.Common.IO; +using Cake.Common.Tools.DotNet; +using Cake.Common.Tools.DotNet.Clean; +using Cake.Common.Tools.DotNet.Publish; +using Cake.Core; +using Cake.Frosting; +using Cake.Json; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Vintagestory.API.Common; + +public static class Program +{ + public static int Main(string[] args) + { + return new CakeHost() + .UseContext() + .Run(args); + } +} + +public class BuildContext : FrostingContext +{ + public const string ProjectName = "AlpineStory"; + public string BuildConfiguration { get; set; } + public string Version { get; } + public string Name { get; } + public bool SkipJsonValidation { get; set; } + + public BuildContext(ICakeContext context) + : base(context) + { + BuildConfiguration = context.Argument("configuration", "Release"); + SkipJsonValidation = context.Argument("skipJsonValidation", false); + var modInfo = context.DeserializeJsonFromFile($"../{BuildContext.ProjectName}/modinfo.json"); + Version = modInfo.Version; + Name = modInfo.ModID; + } +} + +[TaskName("ValidateJson")] +public sealed class ValidateJsonTask : FrostingTask +{ + public override void Run(BuildContext context) + { + if (context.SkipJsonValidation) + { + return; + } + var jsonFiles = context.GetFiles($"../{BuildContext.ProjectName}/assets/**/*.json"); + foreach (var file in jsonFiles) + { + try + { + var json = File.ReadAllText(file.FullPath); + JToken.Parse(json); + } + catch (JsonException ex) + { + throw new Exception($"Validation failed for JSON file: {file.FullPath}{Environment.NewLine}{ex.Message}", ex); + } + } + } +} + +[TaskName("Build")] +[IsDependentOn(typeof(ValidateJsonTask))] +public sealed class BuildTask : FrostingTask +{ + public override void Run(BuildContext context) + { + context.DotNetClean($"../{BuildContext.ProjectName}/{BuildContext.ProjectName}.csproj", + new DotNetCleanSettings + { + Configuration = context.BuildConfiguration + }); + + + context.DotNetPublish($"../{BuildContext.ProjectName}/{BuildContext.ProjectName}.csproj", + new DotNetPublishSettings + { + Configuration = context.BuildConfiguration + }); + } +} + +[TaskName("Package")] +[IsDependentOn(typeof(BuildTask))] +public sealed class PackageTask : FrostingTask +{ + public override void Run(BuildContext context) + { + context.EnsureDirectoryExists("../Releases"); + context.CleanDirectory("../Releases"); + context.EnsureDirectoryExists($"../Releases/{context.Name}"); + context.CopyFiles($"../{BuildContext.ProjectName}/bin/{context.BuildConfiguration}/Mods/mod/publish/*", $"../Releases/{context.Name}"); + context.CopyDirectory($"../{BuildContext.ProjectName}/assets", $"../Releases/{context.Name}/assets"); + context.CopyFile($"../{BuildContext.ProjectName}/modinfo.json", $"../Releases/{context.Name}/modinfo.json"); + context.Zip($"../Releases/{context.Name}", $"../Releases/{context.Name}_{context.Version}.zip"); + } +} + +[TaskName("Default")] +[IsDependentOn(typeof(PackageTask))] +public class DefaultTask : FrostingTask +{ +} \ No newline at end of file diff --git a/code_mods/WorldGen_AlpineStory/alpinestory/AlpineStoryModSystem.cs b/code_mods/WorldGen_AlpineStory/alpinestory/AlpineStoryModSystem.cs new file mode 100644 index 00000000..3faebea0 --- /dev/null +++ b/code_mods/WorldGen_AlpineStory/alpinestory/AlpineStoryModSystem.cs @@ -0,0 +1,165 @@ +using System; +using Vintagestory.API.Common; +using Vintagestory.API.Server; +using Vintagestory.Server; +using Vintagestory.ServerMods; +using SkiaSharp; +using System.Collections.Generic; +using Vintagestory.Common; + +/** + + Notes on the world generation. + + The world generation is made in several passes. The passes are defined in the enum EnumWorldGenPass. + + Within a pass, the generation is done in layers are ordered based on the index given by the function ExecuteOrder(). + The loop in StartServerSide deletes all currently registered world gen path (therefore with an index lower than this class ExecuteOrder). + + The vanilla passes are the following: + - 0 GenTerra : creates the vanilla landscape heights. + - 0.1 GenRockStrata : layers the GenTerra height in different materials. + - 0.12 GenDungeons : generates the underground dungeons. + - 0.2 GenDeposits : generates the ore deposits. + - 0.3 GenStructures : generates the world structures. + - 0.4 GenBlockLayers : generates ice, grass... + - 0.5 GenPonds : generates lakes... + - 0.5 GenVegetationAndPatches : generates forests and plants. + + The source code of the vanilla world generation can be found in the VS survival source code: + https://github.com/anegostudios/vssurvivalmod.git/Systems/WorldGen/Standard + + This mode replaces the layers GenTerra and GenRockStrata to create a map based on a .png image. + The generation is purposously split in several steps for better code readability: + - 0 AlpineTerrain : Generates the granite main layer, and a layer of dirt on top of it (replaces GenTerra). + - 1 AlpineStrata : Adds a layer of another stone below the surface using the vanilla noise generator (replaces GenRockStrata). + - 2 AlpineFloor : parametrise the data that are used later to generate plants and the block cover. + - 3 AlpineFloorCorrection : Correcting some of the vanilla world gen features that don't match the expected result (dirt layer too thick, dirt or snow on cliff side). + - 4 AlpineRiver : Remove the plants from the rivers/lakes. + +*/ +namespace AlpineStoryMod +{ + public class AlpineStoryModSystem : ModStdWorldGen + { + ICoreServerAPI api; + SKBitmap height_map; + internal float data_width_per_pixel; + internal int min_height_custom = 90; + AlpineTerrain alpineTerrain; + AlpineStrata alpineStrata; + AlpineFloor alpineFloor; + AlpineFloorCorrection alpineFloorCorrection; + AlpineRiver alpineRiver; + BiomeGrid biomeGrid; + int[] regionMap; + int[] lakeMap; + UtilTool uTool; + public override bool ShouldLoad(EnumAppSide side) + { + return side == EnumAppSide.Server; + } + + // The ExecuteOrder here is important as it has to be after all worldgen objects we want to delete (see StartServerSide function). + public override double ExecuteOrder() + { + return 0.2; + } + + public override void StartServerSide(ICoreServerAPI api) + { + this.api = api; + + // Removing all generator that was registered so far + foreach (int enumvalue in Enum.GetValues(typeof(EnumWorldGenPass))) + { + if (enumvalue < ((ServerMain)api.World).ModEventManager.WorldgenHandlers["standard"].OnChunkColumnGen.Length) + { + if (enumvalue != (int)EnumWorldGenPass.NeighbourSunLightFlood && enumvalue != (int)EnumWorldGenPass.PreDone) + { + var handlers = ((ServerMain)api.World).ModEventManager.WorldgenHandlers["standard"].OnChunkColumnGen[enumvalue] ; + List toRemove = new List(); + + if(handlers != null) + { + // Condition on which object type we want to remove + for (int i = 0; i < handlers.Count; i++){ + var type = handlers[i].Method.DeclaringType; + if (type == typeof(GenTerra) || + type == typeof(GenRockStrataNew) || + type == typeof(GenTerraPostProcess)){ + toRemove.Add(i); + } + } + for (int i = toRemove.Count - 1; i >= 0 ; i--){ + handlers.RemoveAt(toRemove[i]) ; + } + } + } + } + } + + // In this mod, the X - Z coordinates are scaled based on the map Y size + data_width_per_pixel = api.WorldManager.MapSizeY / 256; + + // Change this boolean to True to generate a climate (temperature - rain) mapping, for debug/information purpose. + bool generateBiomeGrid = false ; + + // We give a 2048 - 2048 offset of the map to not start on the borders + uTool = new UtilTool(api, 2048, 2048); + + if(generateBiomeGrid){ + biomeGrid = new BiomeGrid(api, height_map, data_width_per_pixel, min_height_custom); + api.Event.ChunkColumnGeneration(biomeGrid.OnChunkColumnGen, EnumWorldGenPass.Terrain, "standard"); + } + else{ + // Reading the height map that will be provided to all world generation passes + + IAsset asset = this.api.Assets.Get(new AssetLocation("alpinestory:worldgen/processed.png")); + BitmapExternal bmpt = new BitmapExternal(asset.Data, asset.Data.Length, api.Logger); + height_map = bmpt.bmp; + + if(height_map == null){ + uTool.print("Current directory : "+System.IO.Directory.GetCurrentDirectory()); + uTool.print("Files in current dir : "+System.IO.Directory.GetFiles(System.IO.Directory.GetCurrentDirectory()).ToString()); + throw new Exception("Height map not found"); + } + + api.Logger.Notification("Image loaded : "+height_map.Width.ToString()+", "+height_map.Height.ToString()); + + chunksize = api.WorldManager.ChunkSize; + + // The region maps correspond to macro maps of the world, (one pixel per chunk) + // - regionMap is used to set climates based on the local altitude + // - lakeMap is used to forbid forest in lakes + regionMap = uTool.build_region_map(height_map, api.WorldManager.ChunkSize, data_width_per_pixel, min_height_custom, api.WorldManager.MapSizeY, 0); + lakeMap = uTool.build_region_map(height_map, api.WorldManager.ChunkSize, data_width_per_pixel, min_height_custom, api.WorldManager.MapSizeY, 1); + + // Int random generator used as criterion to spawn halite + Random rand = new Random(); + + // Creating an instance of each generation function + alpineTerrain = new AlpineTerrain(api, height_map, data_width_per_pixel, min_height_custom, uTool); + alpineStrata = new AlpineStrata(api, height_map, data_width_per_pixel, min_height_custom, uTool); + alpineFloor = new AlpineFloor(api, height_map, data_width_per_pixel, min_height_custom, regionMap, lakeMap, uTool); + alpineFloorCorrection = new AlpineFloorCorrection(api, height_map, data_width_per_pixel, min_height_custom, regionMap, rand, uTool); + alpineRiver = new AlpineRiver(api, height_map, data_width_per_pixel, min_height_custom, regionMap, uTool); + + // Registering the generation function in the Terrain pass. It is not necessary to have them stored in different files. + api.Event.ChunkColumnGeneration(alpineTerrain.OnChunkColumnGen, EnumWorldGenPass.Terrain, "standard"); + api.Event.ChunkColumnGeneration(alpineStrata.OnChunkColumnGen, EnumWorldGenPass.Terrain, "standard"); + api.Event.ChunkColumnGeneration(alpineFloor.OnChunkColumnGen, EnumWorldGenPass.Terrain, "standard"); + api.Event.ChunkColumnGeneration(alpineFloorCorrection.OnChunkColumnGen, EnumWorldGenPass.TerrainFeatures, "standard"); + api.Event.ChunkColumnGeneration(alpineRiver.OnChunkColumnGen, EnumWorldGenPass.NeighbourSunLightFlood, "standard"); + } + + // Don't you dare removing that line, it would silently break some pass of the vanilla world gen. + api.Event.InitWorldGenerator(initWorldGen, "standard"); + } + + public void initWorldGen() + { + LoadGlobalConfig(api); + } + } +} \ No newline at end of file diff --git a/code_mods/WorldGen_AlpineStory/alpinestory/Properties/launchSettings.json b/code_mods/WorldGen_AlpineStory/alpinestory/Properties/launchSettings.json new file mode 100644 index 00000000..5038f3cc --- /dev/null +++ b/code_mods/WorldGen_AlpineStory/alpinestory/Properties/launchSettings.json @@ -0,0 +1,16 @@ +{ + "profiles": { + "Client": { + "commandName": "Executable", + "executablePath": "dotnet", + "commandLineArgs": "\"$(VINTAGE_STORY)/Vintagestory.dll\" --tracelog --addModPath \"$(ProjectDir)/bin/$(Configuration)/Mods\"", + "workingDirectory": "$(VINTAGE_STORY)" + }, + "Server": { + "commandName": "Executable", + "executablePath": "dotnet", + "commandLineArgs": "\"$(VINTAGE_STORY)/VintagestoryServer.dll\" --tracelog --addModPath \"$(ProjectDir)/bin/$(Configuration)/Mods\"", + "workingDirectory": "$(VINTAGE_STORY)" + } + } +} \ No newline at end of file diff --git a/code_mods/WorldGen_AlpineStory/alpinestory/alpinestory.csproj b/code_mods/WorldGen_AlpineStory/alpinestory/alpinestory.csproj new file mode 100644 index 00000000..e6b5fdb9 --- /dev/null +++ b/code_mods/WorldGen_AlpineStory/alpinestory/alpinestory.csproj @@ -0,0 +1,57 @@ + + + net7.0 + false + bin\$(Configuration)\Mods\mod + + + + + $(VINTAGE_STORY)/VintagestoryAPI.dll + false + + + $(VINTAGE_STORY)\Vintagestory.exe + false + + + $(VINTAGE_STORY)\VintagestoryAPI.dll + false + + + $(VINTAGE_STORY)\VintagestoryLib.dll + false + + + $(VINTAGE_STORY)\Mods\VSCreativeMod.dll + false + + + $(VINTAGE_STORY)\Mods\VSSurvivalMod.dll + false + + + $(VINTAGE_STORY)\Mods\VSEssentials.dll + false + + + + + + PreserveNewest + + + PreserveNewest + + + + + $(VINTAGE_STORY)/Lib/Newtonsoft.Json.dll + False + + + $(VINTAGE_STORY)/Lib/SkiaSharp.dll + False + + + \ No newline at end of file diff --git a/code_mods/WorldGen_AlpineStory/alpinestory/assets/alpinestory/worldgen/processed.png b/code_mods/WorldGen_AlpineStory/alpinestory/assets/alpinestory/worldgen/processed.png new file mode 100644 index 00000000..6766f4fc Binary files /dev/null and b/code_mods/WorldGen_AlpineStory/alpinestory/assets/alpinestory/worldgen/processed.png differ diff --git a/code_mods/WorldGen_AlpineStory/alpinestory/modinfo.json b/code_mods/WorldGen_AlpineStory/alpinestory/modinfo.json new file mode 100644 index 00000000..d5d4f128 --- /dev/null +++ b/code_mods/WorldGen_AlpineStory/alpinestory/modinfo.json @@ -0,0 +1,13 @@ +{ + "type": "code", + "modid": "alpinestory", + "name": "Alpine Story", + "authors": [ + "Zelfior" + ], + "description": "Immerse yourself in an alpine world.", + "version": "0.1.0", + "dependencies": { + "game": "" + } +} \ No newline at end of file diff --git a/code_mods/WorldGen_AlpineStory/alpinestory/src/0_AlpineTerrain.cs b/code_mods/WorldGen_AlpineStory/alpinestory/src/0_AlpineTerrain.cs new file mode 100644 index 00000000..814ecc78 --- /dev/null +++ b/code_mods/WorldGen_AlpineStory/alpinestory/src/0_AlpineTerrain.cs @@ -0,0 +1,163 @@ +using System; +using Vintagestory.API.Common; +using Vintagestory.API.Server; +using Vintagestory.ServerMods; +using Vintagestory.ServerMods.NoObf; +using System.Collections; +using System.Threading; +using System.Threading.Tasks; +using SkiaSharp; + +public class AlpineTerrain: ModStdWorldGen +{ + ICoreServerAPI api; + int maxThreads; + internal SKBitmap height_map; + internal float data_width_per_pixel; + internal int max_height_custom; + internal int min_height_custom; + internal UtilTool uTool; + public AlpineTerrain(){} + public AlpineTerrain(ICoreServerAPI api, SKBitmap height_map, float data_width_per_pixel, int min_height_custom, UtilTool uTool) + { + LoadGlobalConfig(api); + + this.api = api; + this.height_map = height_map; + + // The ColumnResult object will contain the data of the chunks to generate + columnResults = new ColumnResult[chunksize * chunksize]; + for (int i = 0; i < chunksize * chunksize; i++) columnResults[i].ColumnBlockSolidities = new BitArray(api.WorldManager.MapSizeY); + + // Initiating the number of threads to fasten the generation + maxThreads = Math.Min(Environment.ProcessorCount, api.Server.Config.HostedMode ? 4 : 10); + + max_height_custom = api.WorldManager.MapSizeY; + this.data_width_per_pixel = data_width_per_pixel; + this.min_height_custom = min_height_custom; + + // Tools dedicated to this mod mainly to interpolate between pixels and pre-process the heightmap + this.uTool = uTool; + } + public override double ExecuteOrder() + { + return 0.1; + } + ColumnResult[] columnResults; + public void OnChunkColumnGen(IChunkColumnGenerateRequest request) + { + generate(request.Chunks, request.ChunkX, request.ChunkZ, request.RequiresChunkBorderSmoothing); + + } + + private void generate(IServerChunk[] chunks, int chunkX, int chunkZ, bool requiresChunkBorderSmoothing) + { + int chunksize = this.chunksize; + + int rockID = api.World.GetBlock(new AssetLocation("rock-granite")).Id ; + + // // Store heightmap in the map chunk that can be used for ingame weather processing. + ushort[] rainheightmap = chunks[0].MapChunk.RainHeightMap; + ushort[] terrainheightmap = chunks[0].MapChunk.WorldGenTerrainHeightMap; + + // Storing here the results for each X - Z coordinates (Y being the vertical) of the map pre-processing + int[] list_max_height = new int[chunksize*chunksize]; + + // Pre-processing the map : storing the height map per X-Z coordinate + for (int lZ = 0; lZ < chunksize; lZ++) + { + int worldZ = chunkZ * chunksize + lZ; + for (int lX = 0; lX < chunksize; lX++) + { + int worldX = chunkX * chunksize + lX; + int current_index = uTool.ChunkIndex2d(lX, lZ, chunksize); + + int fakeWorldX = worldX + uTool.offsetX; + int fakeWorldZ = worldZ + uTool.offsetZ; + + list_max_height[current_index] = (int) (min_height_custom + (max_height_custom - min_height_custom) * uTool.LerpPosHeight(fakeWorldX, fakeWorldZ, 0, data_width_per_pixel, height_map)); + + // Lowering the ground at rivers + if (uTool.LerpPosHeight(fakeWorldX, fakeWorldZ, 2, data_width_per_pixel, height_map) > 0.1){ + list_max_height[current_index] -= 3; + } + } + } + + // We find here all 2 high gap to increase the height there, it can prevent having 2 blocks wide steps, but is not necessary + int[] to_increase = uTool.analyse_chunk(list_max_height, chunkX, chunkZ, chunksize, min_height_custom, max_height_custom, data_width_per_pixel, height_map, 0); + + for (int lZ = 0; lZ < chunksize*chunksize; lZ++){ + if (to_increase[lZ] == 1){ + list_max_height[lZ] += 1; + } + } + + // For each X - Z coordinate of the chunk, storing the data in the column result. Multithreaded for faster process + Parallel.For(0, chunksize * chunksize, new ParallelOptions() { MaxDegreeOfParallelism = maxThreads }, chunkIndex2d => { + + int current_thread = Thread.CurrentThread.ManagedThreadId; + + int lX = chunkIndex2d % chunksize; + int lZ = chunkIndex2d / chunksize; + int worldX = chunkX * chunksize + lX; + int worldZ = chunkZ * chunksize + lZ; + + BitArray columnBlockSolidities = columnResults[chunkIndex2d].ColumnBlockSolidities; + + for (int posY = 1; posY < max_height_custom - 1; posY++)//80; posY++) + { + // The block solidity tells if the block will not be empty after the first pass. + columnBlockSolidities[posY] = posY < list_max_height[chunkIndex2d]; + } + }); + + // Fills the chunk at height 0 of mantle blocks (indestructible block at the bottom of the map) + chunks[0].Data.SetBlockBulk(0, chunksize, chunksize, GlobalConfig.mantleBlockId); + + /** + Setting the blocks data here. + + The content of the chunks is stored in chunks[verticalChunkId].Data, which is an int array of size chunksize^3. + + The Id to provide can be given by the following function, "rock-granite" being the name of a block for example. + api.World.GetBlock(new AssetLocation("rock-granite")).Id ; + + */ + for (int posY = 1; posY < max_height_custom - 1; posY++) + { + for (int lZ = 0; lZ < chunksize; lZ++) + { + int worldZ = chunkZ * chunksize + lZ; + for (int lX = 0; lX < chunksize; lX++) + { + int worldX = chunkX * chunksize + lX; + + int mapIndex = uTool.ChunkIndex2d(lX, lZ, chunksize); + + ColumnResult columnResult = columnResults[mapIndex]; + bool isSolid = columnResult.ColumnBlockSolidities[posY]; + + if (isSolid) + { + // The rain maps help calculate where should it rain in the world + terrainheightmap[mapIndex] = (ushort)posY; + rainheightmap[mapIndex] = (ushort)posY; + + // A function of the UtilTool class sets the block + // It is not as optimal as done in the vanilla worldgen, but more readable + uTool.setBlockId(lX, posY, lZ, chunksize, chunks, rockID); + } + } + } + } + + ushort ymax = 0; + for (int i = 0; i < rainheightmap.Length; i++) + { + ymax = Math.Max(ymax, rainheightmap[i]); + } + + chunks[0].MapChunk.YMax = ymax; + } +} \ No newline at end of file diff --git a/code_mods/WorldGen_AlpineStory/alpinestory/src/1_AlpineStrata.cs b/code_mods/WorldGen_AlpineStory/alpinestory/src/1_AlpineStrata.cs new file mode 100644 index 00000000..c63123fd --- /dev/null +++ b/code_mods/WorldGen_AlpineStory/alpinestory/src/1_AlpineStrata.cs @@ -0,0 +1,144 @@ +using System; +using Vintagestory.API.Common; +using Vintagestory.API.Datastructures; +using Vintagestory.API.Server; +using Vintagestory.ServerMods; +using System.Threading.Tasks; +using SkiaSharp; + +public class AlpineStrata: ModStdWorldGen +{ + ICoreServerAPI api; + int maxThreads; + internal SKBitmap height_map; + internal float data_width_per_pixel; + internal int max_height_custom; + internal int min_height_custom; + internal UtilTool uTool; + internal MapLayerCustomPerlin[] strataNoises; + internal RockStrataConfig strata; + internal int[] rockIds; + public AlpineStrata(){} + public AlpineStrata(ICoreServerAPI api, SKBitmap height_map, float data_width_per_pixel, int min_height_custom, UtilTool uTool) + { + /** + This class will replace a layer of 10 blocks of another rock under the surface for a better gameplay experience. + + The block choice is made based on a perlin noise map associated to each rock type: + When the noise is greater than a threshold, the associated rock will replace the in place granite. + + */ + LoadGlobalConfig(api); + + this.api = api; + this.height_map = height_map; + + maxThreads = Math.Min(Environment.ProcessorCount, api.Server.Config.HostedMode ? 4 : 10); + + max_height_custom = api.WorldManager.MapSizeY; + this.data_width_per_pixel = data_width_per_pixel; + this.min_height_custom = min_height_custom; + + this.uTool = uTool; + + IAsset asset = api.Assets.Get("worldgen/rockstrata.json"); + strata = asset.ToObject(); + + LoadGlobalConfig(api); + + chunksize = api.WorldManager.ChunkSize; + + // Storing the block id of different rock types. + rockIds = new int[14]; + rockIds[0] = api.WorldManager.GetBlockId(new AssetLocation("rock-granite")); + rockIds[1] = api.WorldManager.GetBlockId(new AssetLocation("rock-basalt")); + rockIds[2] = api.WorldManager.GetBlockId(new AssetLocation("rock-andesite")); + rockIds[3] = api.WorldManager.GetBlockId(new AssetLocation("rock-peridotite")); + rockIds[4] = api.WorldManager.GetBlockId(new AssetLocation("rock-chalk")); + rockIds[5] = api.WorldManager.GetBlockId(new AssetLocation("rock-claystone")); + rockIds[6] = api.WorldManager.GetBlockId(new AssetLocation("rock-sandstone")); + rockIds[7] = api.WorldManager.GetBlockId(new AssetLocation("rock-shale")); + rockIds[8] = api.WorldManager.GetBlockId(new AssetLocation("rock-limestone")); + rockIds[9] = api.WorldManager.GetBlockId(new AssetLocation("rock-conglomerate")); + rockIds[10] = api.WorldManager.GetBlockId(new AssetLocation("rock-chert")); + rockIds[11] = api.WorldManager.GetBlockId(new AssetLocation("rock-phyllite")); + rockIds[12] = api.WorldManager.GetBlockId(new AssetLocation("rock-slate")); + rockIds[13] = api.WorldManager.GetBlockId(new AssetLocation("rock-bauxite")); + + // Instanciating the perlin noise maps that will be used for the strata choice + strataNoises = new MapLayerCustomPerlin[strata.Variants.Length]; + for (int i = 0; i < strata.Variants.Length; i++) + { + strataNoises[i] = new MapLayerCustomPerlin(api.World.Seed + 23423 + 500*i, new double[] { 14, 0, 0, 0 }, new double[] { 1 / 100.0 / 5, 1 / 50.0 / 5, 1 / 25.0 / 5, 1 / 12.5 / 5 }, new double[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}); + } + } + public override double ExecuteOrder() + { + return 0.13; + } + public void OnChunkColumnGen(IChunkColumnGenerateRequest request) + { + generate(request.Chunks, request.ChunkX, request.ChunkZ, request.RequiresChunkBorderSmoothing); + } + private void generate(IServerChunk[] chunks, int chunkX, int chunkZ, bool requiresChunkBorderSmoothing) + { + // The IntDataMap2D are 1D int containers that will store the noise map generated by the MapLayerCustomPerlin. + // It contains one value per X - Z coordinate. + + IntDataMap2D[] intmaps = new IntDataMap2D[strata.Variants.Length]; + + for (int i = 0; i < strata.Variants.Length; i++) + { + intmaps[i] = new IntDataMap2D(); + intmaps[i].Data = strataNoises[i].GenLayer( + chunkX*chunksize , + chunkZ*chunksize , + chunksize, + chunksize + ); + } + + /* + In this loop, we read the maximal height of each X - Z column from the chunks data. + */ + int[] maxHeights = new int[chunksize*chunksize]; + Parallel.For(0, chunksize*chunksize, new ParallelOptions() { MaxDegreeOfParallelism = maxThreads }, chunkIndex2d => { + int lX = chunkIndex2d % chunksize; + int lZ = chunkIndex2d / chunksize; + + int mapIndex = uTool.ChunkIndex2d(lX, lZ, chunksize); + for(int lY = max_height_custom-1; lY > 1 ; lY--){ + int chunkIndex = uTool.ChunkIndex3d(lX, lY%chunksize, lZ, chunksize); + + if(chunks[lY/chunksize].Data[chunkIndex] == rockIds[0]){ // 0 means the block is empty + maxHeights[chunkIndex2d] = lY - 1; + break; + } + } + }); + + // Setting the value of the rock in the chunks data. + for (int lZ = 0; lZ < chunksize; lZ++) + { + for (int lX = 0; lX < chunksize; lX++) + { + int mapIndex = uTool.ChunkIndex2d(lX, lZ, chunksize); + int maxHeight=maxHeights[mapIndex]; + + for (int i = 1; i < strata.Variants.Length; i++) + { + if(intmaps[i].Data[mapIndex] > 8){ + for (int j = 1; j < 10; j++) + { + if(maxHeight-j-5 > 0) + { + uTool.setBlockId(lX, maxHeight-j-5, lZ, chunksize, chunks, rockIds[Math.Min(13, i)]); + } + } + break; + } + } + } + } + } +} \ No newline at end of file diff --git a/code_mods/WorldGen_AlpineStory/alpinestory/src/2_AlpineFloor.cs b/code_mods/WorldGen_AlpineStory/alpinestory/src/2_AlpineFloor.cs new file mode 100644 index 00000000..a4b7b74a --- /dev/null +++ b/code_mods/WorldGen_AlpineStory/alpinestory/src/2_AlpineFloor.cs @@ -0,0 +1,194 @@ +using System; +using Vintagestory.API.Datastructures; +using Vintagestory.API.Server; +using Vintagestory.ServerMods; +using SkiaSharp; + +/* + Once the rocks landscape is generated, the vanilla world generation will replace the top layers of rock with dirt/sand/gravel, + and place animals based on regional maps. + + The different heights maps are defined in this object: + chunks[0].MapChunk.MapRegion + + We can notably find: + - ForestMap : Defines the forest density + - ClimateMap : Defines the climate (rain and temperature) + - ShrubMap : Defines the shurbs/foliages density + + The region maps do not give the climate in the given chunk only but on the chunk in its surrounding. + The origin of the region maps is set on the chunk coordinates (chunkX, chunkZ) modulo 16 (16 chunks in a region). + + Here is an example of what is expected in the climate map of the chunk (18, 25). the "o" are values to provide, + the "X" illustrates the position of the considered chunk. Borders are also to give to ensure the continuity of the + maps in the world. + + 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 + 14 o o | o o o o o o o o o o o o o o o o | o o + 15 o o | o o o o o o o o o o o o o o o o | o o + ------------------------------------------------------------------------------------- + 16 o o | o o o o o o o o o o o o o o o o | o o + 17 o o | o o o o o o o o o o o o o o o o | o o + 18 o o | o o o o o o o o o o o o o o o o | o o + 19 o o | o o o o o o o o o o o o o o o o | o o + 20 o o | o o o o o o o o o o o o o o o o | o o + 21 o o | o o o o o o o o o o o o o o o o | o o + 22 o o | o o o o o o o o o o o o o o o o | o o + 23 o o | o o o o o o o o o o o o o o o o | o o + 24 o o | o o o o o o o o o o o o o o o o | o o + 25 o o | o o X o o o o o o o o o o o o o | o o + 26 o o | o o o o o o o o o o o o o o o o | o o + 27 o o | o o o o o o o o o o o o o o o o | o o + 28 o o | o o o o o o o o o o o o o o o o | o o + 29 o o | o o o o o o o o o o o o o o o o | o o + 30 o o | o o o o o o o o o o o o o o o o | o o + 31 o o | o o o o o o o o o o o o o o o o | o o + ------------------------------------------------------------------------------------- + 32 o o | o o o o o o o o o o o o o o o o | o o + 33 o o | o o o o o o o o o o o o o o o o | o o + + This map is stored in a 1D array using the same (X, Z) -> 1D mapping as used before. + + However, it appears the origin of the map (low X and Z) is not at 0, 0 but at (length - 1, length - 1), + so the coordinates have to be flipped if the maps are set manually. + +*/ +public class AlpineFloor: ModStdWorldGen +{ + ICoreServerAPI api; + internal SKBitmap height_map; + internal float data_width_per_pixel; + internal int max_height_custom; + internal int bare_land_height_custom; + internal int min_height_custom; + internal UtilTool uTool; + int regionToChunkRatio; + internal int[] regionMap, lakeMap; + public AlpineFloor(){} + public AlpineFloor(ICoreServerAPI api, SKBitmap height_map, float data_width_per_pixel, int min_height_custom, int[] regionMap, int[] lakeMap, UtilTool uTool) + { + LoadGlobalConfig(api); + + this.api = api; + this.height_map = height_map; + regionToChunkRatio = height_map.Width/chunksize; + + max_height_custom = api.WorldManager.MapSizeY; + + // We decide that above 90% of the world height, there shouldn't be any tree/bush anymore + bare_land_height_custom = (int) (max_height_custom*0.9); + + this.data_width_per_pixel = data_width_per_pixel; + this.min_height_custom = min_height_custom; + this.regionMap = regionMap; + this.lakeMap = lakeMap; + + this.uTool = uTool; + } + public void OnChunkColumnGen(IChunkColumnGenerateRequest request) + { + generate(request.Chunks, request.ChunkX, request.ChunkZ, request.RequiresChunkBorderSmoothing); + } + + public override double ExecuteOrder() + { + return 0.15; + } + private void generate(IServerChunk[] chunks, int chunkX, int chunkZ, bool requiresChunkBorderSmoothing) + { + // Setting the global forestation to zero to better control the forest density + ITreeAttribute worldConfig = api.WorldManager.SaveGame.WorldConfiguration; + worldConfig.SetString("globalForestation", "0."); + + // This value tells how many chunks are in a "region" + int globalRegionSize = api.WorldManager.RegionSize / chunksize; + + // Offsetting the chunk by the same offset as defined in AlpineStoryModModSystem + int fakeChunkX = chunkX + uTool.offsetX/chunksize; + int fakeChunkZ = chunkZ + uTool.offsetZ/chunksize; + + // Holds a forest density map, from 0 to 255 + IntDataMap2D forestMap = chunks[0].MapChunk.MapRegion.ForestMap; + + // build_mini_region_map builds a local region map as understood by the MapRegion tools. + int[] forest_height_map = uTool.build_mini_region_map(forestMap, fakeChunkX, fakeChunkZ, regionToChunkRatio, regionMap, globalRegionSize, 1); + int[] lake_height_map = uTool.build_mini_region_map(forestMap, fakeChunkX, fakeChunkZ, regionToChunkRatio, lakeMap, globalRegionSize, 1); + + // If we are in a lake : no forest + // The forest takes the vanilla generated value, if the altitude is not too high + for(int i = 0; i < chunks[0].MapChunk.MapRegion.ForestMap.Data.Length; i++){ + if (lake_height_map[i] == min_height_custom){ + forestMap.Data[i] = Math.Clamp(forestMap.Data[i]-1, 0, getForestFromHeight(forest_height_map[i])) ; + } + else{ + forestMap.Data[i] = 0; + } + } + + // Holds temperature and rain fall. + // 16-23 bits = Red = temperature - 0 : frozen, 255 : all hail the cactus. (Height dependance strongly adds to this parameter) + // 8-15 bits = Green = rain + // 0-7 bits = Blue = unused + IntDataMap2D climateMap = chunks[0].MapChunk.MapRegion.ClimateMap; + climateMap.Data = uTool.build_mini_region_map(climateMap, fakeChunkX, fakeChunkZ, regionToChunkRatio, regionMap, globalRegionSize, 1); + + for(int i = 0; i < climateMap.Data.Length; i++){ + climateMap.Data[i] = (int)(0 + getRainFromHeight(climateMap.Data[i])*Math.Pow(2, 8) + getTemperatureFromHeight(climateMap.Data[i])*Math.Pow(2, 16)) ; + } + + // Holds a beach density map + // No beach here, so all array set to 0 + IntDataMap2D beachMap = chunks[0].MapChunk.MapRegion.BeachMap; + beachMap.Data = new int[beachMap.Size*beachMap.Size]; + + // Bushes density map, from 0 to 255 + // The bushes density decreases with height + IntDataMap2D shrubMap = chunks[0].MapChunk.MapRegion.ShrubMap; + shrubMap.Data = uTool.build_mini_region_map(shrubMap, fakeChunkX, fakeChunkZ, regionToChunkRatio, regionMap, globalRegionSize, 0.5); + + for(int i = 0; i < shrubMap.Data.Length; i++){ + shrubMap.Data[i] = (int)getShrubFromHeight(shrubMap.Data[i]) ; + } + } + private float getRelativeHeight(int height){ + return (float)(height - min_height_custom) / (float)(bare_land_height_custom - min_height_custom); + } + public int getRainFromHeight(int height){ + /* + Returns 20000 at min_height_custom, linear increase with height + desert below 90 + */ + int min_value = 70; + int max_value = 200; + return (int)(min_value + (max_value - min_value) * getRelativeHeight(height)); + } + + public int getTemperatureFromHeight(int height){ + /* + 150 at min height + 80 at max height + */ + int min_value = 80; + int max_value = 150; + return (int)(150 - (max_value - min_value) * getRelativeHeight(height)); + } + public int getShrubFromHeight(int height){ + if (height > bare_land_height_custom) return 0; + + return (int)(80 *(1 - getRelativeHeight(height))); + } + public int getForestFromHeight(int height){ + /* + no forest above 60% of the bare_land_height_custom + linear increase below this height + */ + float relative_height = getRelativeHeight(height); + + if (relative_height > 0.6){ + return 0; + } + else{ + return (int)(relative_height/0.6*255); + } + } +} \ No newline at end of file diff --git a/code_mods/WorldGen_AlpineStory/alpinestory/src/3_AlpineFloorCorrection.cs b/code_mods/WorldGen_AlpineStory/alpinestory/src/3_AlpineFloorCorrection.cs new file mode 100644 index 00000000..6f8e6aac --- /dev/null +++ b/code_mods/WorldGen_AlpineStory/alpinestory/src/3_AlpineFloorCorrection.cs @@ -0,0 +1,252 @@ +using System; +using Vintagestory.API.Common; +using Vintagestory.API.Server; +using Vintagestory.ServerMods; +using SkiaSharp; +using System.Linq; + +public class AlpineFloorCorrection: ModStdWorldGen +{ + ICoreServerAPI api; + int maxThreads; + internal SKBitmap height_map; + internal float data_width_per_pixel; + internal int max_height_custom; + internal int bare_land_height_custom; + internal int min_height_custom; + internal UtilTool uTool; + internal int[] regionMap; + internal Random rand; + public AlpineFloorCorrection(){} + public AlpineFloorCorrection(ICoreServerAPI api, SKBitmap height_map, float data_width_per_pixel, int min_height_custom, int[] regionMap, Random rand, UtilTool uTool) + { + LoadGlobalConfig(api); + + this.api = api; + this.height_map = height_map; + + maxThreads = Math.Min(Environment.ProcessorCount, api.Server.Config.HostedMode ? 4 : 10); + max_height_custom = api.WorldManager.MapSizeY; + bare_land_height_custom = (int) (max_height_custom*0.9); + + this.data_width_per_pixel = data_width_per_pixel; + this.min_height_custom = min_height_custom; + this.regionMap = regionMap; + this.rand = rand; + + this.uTool = uTool; + } + public void OnChunkColumnGen(IChunkColumnGenerateRequest request) + { + generate(request.Chunks, request.ChunkX, request.ChunkZ, request.RequiresChunkBorderSmoothing); + } + + public override double ExecuteOrder() + { + return 0.55; + } + private void generate(IServerChunk[] chunks, int chunkX, int chunkZ, bool requiresChunkBorderSmoothing) + { + int[] soil_array = new int[]{api.World.GetBlock(new AssetLocation("soil-medium-normal")).Id, + api.World.GetBlock(new AssetLocation("soil-medium-sparse")).Id , + api.World.GetBlock(new AssetLocation("soil-medium-verysparse")).Id , + api.World.GetBlock(new AssetLocation("soil-medium-none")).Id, + api.World.GetBlock(new AssetLocation("soil-low-normal")).Id, + api.World.GetBlock(new AssetLocation("soil-low-sparse")).Id, + api.World.GetBlock(new AssetLocation("soil-low-verysparse")).Id, + api.World.GetBlock(new AssetLocation("soil-low-none")).Id} ; + + int rockID = api.World.GetBlock(new AssetLocation("rock-granite")).Id ; + int gravelID = api.World.GetBlock(new AssetLocation("gravel-granite")).Id ; + int muddyGravelID = api.World.GetBlock(new AssetLocation("muddygravel")).Id ; + + int snowID = api.World.GetBlock(new AssetLocation("snowblock")).Id ; + int glacierID = api.World.GetBlock(new AssetLocation("glacierice")).Id ; + int waterID = api.World.GetBlock(new AssetLocation("water-still-7")).Id ; + + int haliteID = api.World.GetBlock(new AssetLocation("rock-halite")).Id; + + // Builds the chunk height map, and reduces the dirt layer thickness on the go to let the rocks appear on cliffs + int[] list_max_height = countHeightMap(chunks, rockID, snowID, glacierID, soil_array); + + int[] grass_map = uTool.analyse_chunk(list_max_height, chunkX, chunkZ, chunksize, min_height_custom, max_height_custom, data_width_per_pixel, height_map, 1); + int[] remove_snow_map = uTool.analyse_chunk(list_max_height, chunkX, chunkZ, chunksize, min_height_custom, max_height_custom, data_width_per_pixel, height_map, 2); + + // Replacing dirt by grass if the landscape is too steep + clearSteepGrass(chunks, grass_map, list_max_height, gravelID); + + // Replacing now and ice by rock if the landscape is too steep + clearSteepSnow(chunks, remove_snow_map, gravelID); + + // Removing glaciers that are not overlayed by snow + clearGlacier(chunks, rockID, glacierID, snowID); + + // Make river beds + makeRiverBed(chunks, chunkX, chunkZ, waterID, muddyGravelID); + + // Make lakes + uTool.makeLakes(chunks, chunkX, chunkZ, chunksize, waterID, muddyGravelID, min_height_custom, max_height_custom, data_width_per_pixel, height_map); + + // Spawn Halite every 30 chunk + if (rand.Next(30) == 1){ + spawnHalite(chunks, haliteID); + } + } + public void spawnHalite(IServerChunk[] chunks, int haliteID){ + int posY; + int firstVoidFound; + int lX, lZ; + int haliteRadius = 4; // halite column radius + + // Here, we loop on coluns to find a cave below the ground + // If we find one, we create a halite column starting at that height + 5 to the bottom of the map + for (int blockColumn = 0; blockColumn < chunksize*chunksize; blockColumn++){ + lX = blockColumn%chunksize; + lZ = blockColumn/chunksize; + if (lX > haliteRadius && lZ > haliteRadius && lX < chunksize - haliteRadius && lZ < chunksize - haliteRadius){ + posY = 1; + + while(uTool.getBlockId(blockColumn%chunksize, posY, blockColumn/chunksize, chunksize, chunks) !=0) posY++; + firstVoidFound = posY; + + while(posY < 0.9*max_height_custom && uTool.getBlockId(blockColumn%chunksize, posY, blockColumn/chunksize, chunksize, chunks) ==0) posY++; + if(posY < 0.9*max_height_custom){ + for(int i=-haliteRadius; i 0.1){ + altitude = (int) (min_height_custom + (max_height_custom - min_height_custom) * uTool.LerpPosHeight(worldX, worldZ, 0, data_width_per_pixel, height_map)); + + localRiverHeight = uTool.getRiverHeight(worldX, worldZ, min_height_custom, max_height_custom, data_width_per_pixel, height_map); + + for(int posY = altitude-2; posY < localRiverHeight; posY++){ + uTool.setBlockId(lZ%chunksize, posY, lZ/chunksize, chunksize, chunks, waterID, fluid:true); + } + + if (altitude-2 == localRiverHeight){ + uTool.setBlockId(lZ%chunksize, localRiverHeight, lZ/chunksize, chunksize, chunks, waterID, fluid:true); + } + + uTool.setBlockId(lZ%chunksize, altitude-4, lZ/chunksize, chunksize, chunks, gravelID); + uTool.setBlockId(lZ%chunksize, altitude-3, lZ/chunksize, chunksize, chunks, gravelID); + } + } + } + public void clearGlacier(IServerChunk[] chunks, int rockID, int glacierID, int snowID){ + int posY; + + // Removes the glacier blocks that doesn't have snow block on it (looks more natural) + for (int lZ = 0; lZ < chunksize*chunksize; lZ++){ + posY = max_height_custom - 1; + + while(uTool.getBlockId(lZ%chunksize, posY, lZ/chunksize, chunksize, chunks) != snowID && + posY > 0.6*max_height_custom) { + if(uTool.getBlockId(lZ%chunksize, posY, lZ/chunksize, chunksize, chunks) == glacierID){ + uTool.setBlockId(lZ%chunksize, posY, lZ/chunksize, chunksize, chunks, rockID); + } + posY --; + } + } + } + public void clearSteepSnow(IServerChunk[] chunks, int[] remove_snow_map, int rockID){ + int posY; + + // Replacing snow by rock if the landscape is too steep + for (int lZ = 0; lZ < chunksize*chunksize; lZ++){ + if (remove_snow_map[lZ] == 1){ + posY = max_height_custom - 1; + + while(uTool.getBlockId(lZ%chunksize, posY, lZ/chunksize, chunksize, chunks) == 0) posY --; + + while(uTool.getBlockId(lZ%chunksize, posY, lZ/chunksize, chunksize, chunks) != rockID + && uTool.getBlockId(lZ%chunksize, posY, lZ/chunksize, chunksize, chunks) != 0 && + posY > 2) { + uTool.setBlockId(lZ%chunksize, posY, lZ/chunksize, chunksize, chunks, rockID); + posY --; + } + } + } + } + public void clearSteepGrass(IServerChunk[] chunks, int[] grass_map, int[] list_max_height, int gravelID){ + for (int lZ = 0; lZ < chunksize*chunksize; lZ++){ + if (grass_map[lZ] == 1){ + // Replacing dirt by gravel if the landscape is too steep + uTool.setBlockId(lZ%chunksize, list_max_height[lZ], lZ/chunksize, chunksize, chunks, gravelID); + + if(list_max_height[lZ] < max_height_custom - 3){ + if (uTool.getBlockId(lZ%chunksize, list_max_height[lZ] + 1, lZ/chunksize, chunksize, chunks) != 0 + && uTool.getBlockId(lZ%chunksize, list_max_height[lZ] + 3, lZ/chunksize, chunksize, chunks) == 0 ){ + uTool.setBlockId(lZ%chunksize, list_max_height[lZ] + 1, lZ/chunksize, chunksize, chunks, 0); + uTool.setBlockId(lZ%chunksize, list_max_height[lZ] + 2, lZ/chunksize, chunksize, chunks, 0); + } + } + } + } + } + public int[] countHeightMap(IServerChunk[] chunks, int rockID, int snowID, int glacierID, int[] soil_array){ + int posY; + int currentBlock; + int mapIndex; + int[] list_max_height = new int[chunksize*chunksize]; + + for (int lZ = 0; lZ < chunksize; lZ++) + { + for (int lX = 0; lX < chunksize; lX++) + { + mapIndex = uTool.ChunkIndex2d(lX, lZ, chunksize); + posY = max_height_custom - 1; + + while(uTool.getBlockId(lX, posY, lZ, chunksize, chunks) == 0) posY --; + + currentBlock = uTool.getBlockId(lX, posY, lZ, chunksize, chunks); + + while(currentBlock != rockID && !soil_array.Contains(currentBlock) && currentBlock != 0 && posY > 2) { + posY --; + currentBlock = uTool.getBlockId(lX, posY, lZ, chunksize, chunks); + if(currentBlock == snowID || currentBlock == glacierID){ + list_max_height[mapIndex] = posY; + } + } + + list_max_height[mapIndex] = Math.Max(posY, list_max_height[mapIndex]); + + // Reducing the dirt layer thickness + if(soil_array.Contains(currentBlock)){ + posY -- ; + + currentBlock = uTool.getBlockId(lX, posY, lZ, chunksize, chunks); + while (currentBlock != rockID && currentBlock != 0 && posY > 2){ + uTool.setBlockId(lX, posY, lZ, chunksize, chunks, rockID); + posY --; + currentBlock = uTool.getBlockId(lX, posY, lZ, chunksize, chunks); + } + } + } + } + return list_max_height; + } +} \ No newline at end of file diff --git a/code_mods/WorldGen_AlpineStory/alpinestory/src/4_AlpineRiver.cs b/code_mods/WorldGen_AlpineStory/alpinestory/src/4_AlpineRiver.cs new file mode 100644 index 00000000..bf775929 --- /dev/null +++ b/code_mods/WorldGen_AlpineStory/alpinestory/src/4_AlpineRiver.cs @@ -0,0 +1,95 @@ +using System; +using Vintagestory.API.Common; +using Vintagestory.API.Server; +using Vintagestory.ServerMods; +using SkiaSharp; + +public class AlpineRiver: ModStdWorldGen +{ + ICoreServerAPI api; + internal SKBitmap height_map; + internal float data_width_per_pixel; + internal int max_height_custom; + internal int min_height_custom; + internal UtilTool uTool; + internal int[] regionMap; + public AlpineRiver(){} + public AlpineRiver(ICoreServerAPI api, SKBitmap height_map, float data_width_per_pixel, int min_height_custom, int[] regionMap, UtilTool uTool) + { + LoadGlobalConfig(api); + + this.api = api; + this.height_map = height_map; + + this.min_height_custom = min_height_custom; + this.max_height_custom = api.WorldManager.MapSizeY; + + this.data_width_per_pixel = data_width_per_pixel; + this.regionMap = regionMap; + + this.uTool = uTool; + } + public void OnChunkColumnGen(IChunkColumnGenerateRequest request) + { + generate(request.Chunks, request.ChunkX, request.ChunkZ, request.RequiresChunkBorderSmoothing); + } + + public override double ExecuteOrder() + { + return 0.05; + } + private void generate(IServerChunk[] chunks, int chunkX, int chunkZ, bool requiresChunkBorderSmoothing) + { + // We reiterate the river and lake making here, to remove the plants generated underwater by the vanilla worldgen. + int muddyGravelID = api.World.GetBlock(new AssetLocation("muddygravel")).Id ; + int waterID = api.World.GetBlock(new AssetLocation("water-still-7")).Id ; + + // Clean river beds + cleanRiverBed(chunks, chunkX, chunkZ, waterID, muddyGravelID); + + // Clean river beds + uTool.makeLakes(chunks, chunkX, chunkZ, chunksize, waterID, muddyGravelID, min_height_custom, max_height_custom, data_width_per_pixel, height_map); + + } + public void cleanRiverBed(IServerChunk[] chunks, int chunkX, int chunkZ, int waterID, int gravelID){ + float hasRiver; + int altitude; + int localRiverHeight; + + for (int lZ = 0; lZ < chunksize*chunksize; lZ++){ + int worldX = chunkX * chunksize + lZ%chunksize+ uTool.offsetX; + int worldZ = chunkZ * chunksize + lZ/chunksize + uTool.offsetZ; + + hasRiver = uTool.LerpPosHeight(worldX, worldZ, 2, data_width_per_pixel, height_map); + + if(hasRiver > 0.1){ + altitude = (int) (min_height_custom + (max_height_custom - min_height_custom) * uTool.LerpPosHeight(worldX, worldZ, 0, data_width_per_pixel, height_map)); + + // Checking if we are not removing a tree + if(uTool.getBlockId(lZ%chunksize, altitude-3, lZ/chunksize, chunksize, chunks) == 0 || + (uTool.getBlockId(lZ%chunksize, altitude-3, lZ/chunksize, chunksize, chunks) != + uTool.getBlockId(lZ%chunksize, altitude+1, lZ/chunksize, chunksize, chunks))){ + + localRiverHeight = uTool.getRiverHeight(worldX, worldZ, min_height_custom, max_height_custom, data_width_per_pixel, height_map); + + for(int posY = altitude-2; posY < localRiverHeight; posY++){ + uTool.SetBlockAir(lZ%chunksize, posY, lZ/chunksize, chunksize, chunks); + uTool.setBlockId(lZ%chunksize, posY, lZ/chunksize, chunksize, chunks, waterID, fluid:true); + } + + if (altitude-2 == localRiverHeight){ + uTool.SetBlockAir(lZ%chunksize, localRiverHeight, lZ/chunksize, chunksize, chunks); + uTool.setBlockId(lZ%chunksize, localRiverHeight, lZ/chunksize, chunksize, chunks, waterID, fluid:true); + } + + for(int posY = localRiverHeight; posY < localRiverHeight+3; posY++){ + uTool.SetBlockAir(lZ%chunksize, posY, lZ/chunksize, chunksize, chunks); + } + } + + uTool.setBlockId(lZ%chunksize, altitude-4, lZ/chunksize, chunksize, chunks, gravelID); + uTool.setBlockId(lZ%chunksize, altitude-3, lZ/chunksize, chunksize, chunks, gravelID); + } + } + } +} \ No newline at end of file diff --git a/code_mods/WorldGen_AlpineStory/alpinestory/src/Bonus_BiomeGrid.cs b/code_mods/WorldGen_AlpineStory/alpinestory/src/Bonus_BiomeGrid.cs new file mode 100644 index 00000000..c020c6dd --- /dev/null +++ b/code_mods/WorldGen_AlpineStory/alpinestory/src/Bonus_BiomeGrid.cs @@ -0,0 +1,167 @@ +using System; +using Vintagestory.API.Common; +using Vintagestory.API.Datastructures; +using Vintagestory.API.Server; +using Vintagestory.ServerMods; +using Vintagestory.ServerMods.NoObf; +using System.Collections; +using System.Threading; +using System.Threading.Tasks; +using SkiaSharp; + +public class BiomeGrid: ModStdWorldGen +{ + ICoreServerAPI api; + int maxThreads; + internal SKBitmap height_map; + internal float data_width_per_pixel; + internal int max_height_custom; + internal int bare_land_height_custom; + internal int min_height_custom; + internal UtilTool uTool; + ColumnResult[] columnResults; + public BiomeGrid(){} + public BiomeGrid(ICoreServerAPI api, SKBitmap height_map, float data_width_per_pixel, int min_height_custom) + { + LoadGlobalConfig(api); + + this.api = api; + this.height_map = height_map; + + // The ColumnResult object will contain the data of the chunks to generate + columnResults = new ColumnResult[chunksize * chunksize]; + for (int i = 0; i < chunksize * chunksize; i++) columnResults[i].ColumnBlockSolidities = new BitArray(api.WorldManager.MapSizeY); + + + this.api = api; + this.height_map = height_map; + + maxThreads = Math.Min(Environment.ProcessorCount, api.Server.Config.HostedMode ? 4 : 10); + max_height_custom = api.WorldManager.MapSizeY; + bare_land_height_custom = (int) (max_height_custom*0.9); + + this.data_width_per_pixel = data_width_per_pixel; + this.min_height_custom = min_height_custom; + + uTool = new UtilTool(); + + } + public void OnChunkColumnGen(IChunkColumnGenerateRequest request) + { + generate(request.Chunks, request.ChunkX, request.ChunkZ, request.RequiresChunkBorderSmoothing); + } + + public override double ExecuteOrder() + { + return 1.5; + } + private void generate(IServerChunk[] chunks, int chunkX, int chunkZ, bool requiresChunkBorderSmoothing) + { + int chunksize = this.chunksize; + + int rockID = api.World.GetBlock(new AssetLocation("rock-granite")).Id ; + + // // Store heightmap in the map chunk that can be used for ingame weather processing. + ushort[] rainheightmap = chunks[0].MapChunk.RainHeightMap; + ushort[] terrainheightmap = chunks[0].MapChunk.WorldGenTerrainHeightMap; + + // For each X - Z coordinate of the chunk, storing the data in the column result. Multithreaded for faster process + Parallel.For(0, chunksize * chunksize, new ParallelOptions() { MaxDegreeOfParallelism = maxThreads }, chunkIndex2d => { + + int current_thread = Thread.CurrentThread.ManagedThreadId; + + int lX = chunkIndex2d % chunksize; + int lZ = chunkIndex2d / chunksize; + int worldX = chunkX * chunksize + lX; + int worldZ = chunkZ * chunksize + lZ; + + BitArray columnBlockSolidities = columnResults[chunkIndex2d].ColumnBlockSolidities; + + for (int posY = 1; posY < max_height_custom - 1; posY++) + { + // The block solidity tells if the block will not be empty after the first pass. + columnBlockSolidities[posY] = posY < 150; + } + }); + + chunks[0].Data.SetBlockBulk(0, chunksize, chunksize, GlobalConfig.mantleBlockId); + + /** + Setting the blocks data here. + + The content of the chunks is stored in chunks[verticalChunkId].Data, which is an int array of size chunksize^3. + + The Id to provide can be given by the following function, "rock-granite" being the name of a block for example. + api.World.GetBlock(new AssetLocation("rock-granite")).Id ; + + */ + for (int posY = 1; posY < max_height_custom - 1; posY++) + { + for (int lZ = 0; lZ < chunksize; lZ++) + { + int worldZ = chunkZ * chunksize + lZ; + for (int lX = 0; lX < chunksize; lX++) + { + int worldX = chunkX * chunksize + lX; + + int mapIndex = uTool.ChunkIndex2d(lX, lZ, chunksize); + + ColumnResult columnResult = columnResults[mapIndex]; + bool isSolid = columnResult.ColumnBlockSolidities[posY]; + + if (isSolid) + { + terrainheightmap[mapIndex] = (ushort)posY; + rainheightmap[mapIndex] = (ushort)posY; + + uTool.setBlockId(lX, posY, lZ, chunksize, chunks, rockID); + } + } + } + } + + ushort ymax = 0; + for (int i = 0; i < rainheightmap.Length; i++) + { + ymax = Math.Max(ymax, rainheightmap[i]); + } + + chunks[0].MapChunk.YMax = ymax; + + // Holds a forest density map, from 0 to 255 + IntDataMap2D forestMap = chunks[0].MapChunk.MapRegion.ForestMap; + for(int i = 0; i < forestMap.Data.Length; i++){ + forestMap.Data[i] = 150; + } + + // Holds temperature and rain fall. + // 16-23 bits = Red = temperature - 0 : frozen, 255 : all hail the cactus. (Height dependance adds to this parameter) + // 8-15 bits = Green = rain + // 0-7 bits = Blue = unused + IntDataMap2D climateMap = chunks[0].MapChunk.MapRegion.ClimateMap; + + int grid_size = 16; + float factor = 255/(grid_size-1); + + for(int i = 0; i < climateMap.Data.Length; i++){ + int fakeChunkX = chunkX - chunkX%16 - climateMap.BottomRightPadding + i%climateMap.Size; + int fakeChunkZ = chunkZ - chunkZ%16 - climateMap.BottomRightPadding + i/climateMap.Size; + + int rain = fakeChunkX%grid_size; + int temp = fakeChunkZ%grid_size; + + climateMap.Data[i] = (int)(0 + Math.Min(255, (int)(factor*rain))*Math.Pow(2, 8) + Math.Min(255, (int)(factor*temp))*Math.Pow(2, 16)) ; + } + + // // Holds a beach density map + IntDataMap2D beachMap = chunks[0].MapChunk.MapRegion.BeachMap; + beachMap.Data = new int[beachMap.Size*beachMap.Size]; + + // // Bushes density map, from 0 to 255 + IntDataMap2D shrubMap = chunks[0].MapChunk.MapRegion.ShrubMap; + + for(int i = 0; i < shrubMap.Data.Length; i++){ + shrubMap.Data[i] = 255; + } + } +} \ No newline at end of file diff --git a/code_mods/WorldGen_AlpineStory/alpinestory/src/utils.cs b/code_mods/WorldGen_AlpineStory/alpinestory/src/utils.cs new file mode 100644 index 00000000..0f0cb9b1 --- /dev/null +++ b/code_mods/WorldGen_AlpineStory/alpinestory/src/utils.cs @@ -0,0 +1,304 @@ +using SkiaSharp; +using System; +using System.Collections; +using System.Linq; +using Vintagestory.API.Datastructures; +using Vintagestory.API.Server; + +public class UtilTool +{ + internal ICoreServerAPI api; + internal int offsetX; + internal int offsetZ; + public UtilTool(){} + public UtilTool(ICoreServerAPI api, int offsetX, int offsetZ){ + this.api = api; + this.offsetX = offsetX; + this.offsetZ = offsetZ; + } + public int ChunkIndex3d(int x, int y, int z, int chunksize) + { + /* + Vanilla function that gives the 1D index of a (x, y, z) coordinate in a chunk + */ + return (y * chunksize + z) * chunksize + x; + } + public int ChunkIndex2d(int x, int z, int chunksize) + { + /* + Vanilla function that gives the 1D index of a (x, z) coordinate in a chunk column + */ + return z * chunksize + x; + } + public void setBlockId(int lX, int lY, int lZ, int chunksize, IServerChunk[] chunks, int blockId, bool fluid=false){ + /* + Sets the block at (lX, lY, lZ) coordinate in a chunk column to the given value. + If fluid is at true, the value is also assigned to the fluid data layer. + */ + chunks[lY/chunksize].Data[ChunkIndex3d(lX, lY%chunksize, lZ, chunksize)] = blockId; + + if(fluid){ + chunks[lY/chunksize].Data[ChunkIndex3d(lX, lY%chunksize, lZ, chunksize)] = blockId; + chunks[lY/chunksize].Data.SetFluid(ChunkIndex3d(lX, lY%chunksize, lZ, chunksize), blockId); + } + } + public int getFluid(int lX, int lY, int lZ, int chunksize, IServerChunk[] chunks){ + /* + Returns the fluid block ID at (lX, lY, lZ) coordinate in a chunk column. + */ + return chunks[lY/chunksize].Data.GetFluid(ChunkIndex3d(lX, lY%chunksize, lZ, chunksize)); + } + public int getBlockId(int lX, int lY, int lZ, int chunksize, IServerChunk[] chunks){ + /* + Returns the block ID at (lX, lY, lZ) coordinate in a chunk column. + */ + return chunks[lY/chunksize].Data[ChunkIndex3d(lX, lY%chunksize, lZ, chunksize)]; + } + public void SetBlockAir(int lX, int lY, int lZ, int chunksize, IServerChunk[] chunks){ + /* + Sets the block at (lX, lY, lZ) coordinate in a chunk column to an air block. + */ + chunks[lY/chunksize].Data.SetBlockAir(ChunkIndex3d(lX, lY%chunksize, lZ, chunksize)); + } + int mod(int x, int m) { + /* + Modulo function that necessarilly returns a value in [0, m[. + */ + return (x%m + m)%m; + } + public int getOutBoundX(int x, int mapsize){ + /* + Makes the symetry + modulo of the height map to fake an infinite world. + */ + return (int)(Math.Abs(mapsize - 0.5 - mod(x, 2*mapsize))-0.5); + } + public float LerpPosHeight(int worldX, int worldZ, int color, float data_width_per_pixel, SKBitmap height_map){ + /* + Lerp the Z value at the (worldX, worldZ) coordinates on the height map, on the given color chanel. + */ + float current_x = (float)(worldX)/data_width_per_pixel ; + float current_z = (float)(worldZ)/data_width_per_pixel ; + + float h0_0 = 0, h1_0 = 0, h0_1 = 0, h1_1 = 0; + + // ICI TODO Cest a chier z0 a 4000, des trous a 2000 en worldX + int x0 = getOutBoundX((int)current_x, height_map.Width); + int z0 = getOutBoundX((int)current_z, height_map.Height); + + int x1 = getOutBoundX((int)current_x+1, height_map.Width); + int z1 = getOutBoundX((int)current_z+1, height_map.Height); + + if(color==0){ + h0_0 = height_map.GetPixel(x0, z0).Red; + h1_0 = height_map.GetPixel(x1, z0).Red; + + h0_1 = height_map.GetPixel(x0, z1).Red; + h1_1 = height_map.GetPixel(x1, z1).Red; + } + else if (color==1){ + h0_0 = height_map.GetPixel(x0, z0).Green; + h1_0 = height_map.GetPixel(x1, z0).Green; + + h0_1 = height_map.GetPixel(x0, z1).Green; + h1_1 = height_map.GetPixel(x1, z1).Green; + + } + else if (color==2){ + h0_0 = height_map.GetPixel(x0, z0).Blue; + h1_0 = height_map.GetPixel(x1, z0).Blue; + + h0_1 = height_map.GetPixel(x0, z1).Blue; + h1_1 = height_map.GetPixel(x1, z1).Blue; + } + + float x = current_x%1; + float z = current_z%1; + + return ((1-x)*(1-z)*h0_0 + x*(1-z)*h1_0 + (1-x)*z*h0_1 + x*z*h1_1)/255; + } + + public int[] analyse_chunk(int[] list_max_height, int chunkX, int chunkZ, int chunksize, int min_height_custom, int max_height_custom, float data_width_per_pixel, SKBitmap height_map, int criterion){ + /* + Detects specific features on the height map, features description below. + */ + int[] to_increase = new int[chunksize*chunksize]; + + for (int lZ = 0; lZ < chunksize; lZ++) + { + for(int lX = 0; lX < chunksize; lX++){ + int[] neighbours = new int[4]; + + if ((lX - 1 >= 0) && (lZ - 1 >= 0)){ + neighbours[0] = list_max_height[ChunkIndex2d(lX-1, lZ-1, chunksize)]; + } + + if ((lX - 1 >= 0) && (lZ + 1 < chunksize)){ + neighbours[1] = list_max_height[ChunkIndex2d(lX-1, lZ+1, chunksize)]; + } + + if ((lX + 1 < chunksize) && (lZ + 1 < chunksize)){ + neighbours[2] = list_max_height[ChunkIndex2d(lX+1, lZ+1, chunksize)]; + } + + if ((lX + 1 < chunksize) && (lZ - 1 >= 0)){ + neighbours[3] = list_max_height[ChunkIndex2d(lX+1, lZ-1, chunksize)]; + } + + for(int i=0; i<4; i++){ + if(neighbours[i] == 0) neighbours[i] = list_max_height[ChunkIndex2d(lX, lZ, chunksize)]; + } + + // Finds 2 blocks high steps + if (criterion == 0){ + if(neighbours.Max() > list_max_height[ChunkIndex2d(lX, lZ, chunksize)] + 1){ + to_increase[ChunkIndex2d(lX, lZ, chunksize)] = 1; + } + } + // Finds steep clifs + else if (criterion == 1){ + if(neighbours.Max() - neighbours.Min() > 3){ + to_increase[ChunkIndex2d(lX, lZ, chunksize)] = 1; + } + } + // Finds steep cliffs without its edges + else if (criterion == 2){ + if(Math.Max(neighbours.Max() - list_max_height[ChunkIndex2d(lX, lZ, chunksize)], list_max_height[ChunkIndex2d(lX, lZ, chunksize)] - neighbours.Min()) > 2 + && Math.Min(neighbours.Max() - list_max_height[ChunkIndex2d(lX, lZ, chunksize)], list_max_height[ChunkIndex2d(lX, lZ, chunksize)] - neighbours.Min()) > 1 ){ + to_increase[ChunkIndex2d(lX, lZ, chunksize)] = 1; + } + } + } + } + return to_increase; + } + + /** + Size height map: 4000 * 4000 + Chunk size 32 + + region map 125 * 125, each value gives the height average of the chunk + */ + public int[] build_region_map(SKBitmap height_map, int chunkSize, float data_width_per_pixel, int min_height_custom, int max_height_custom, int index, bool average=false){ + int regionToChunkRatio = height_map.Width/chunkSize; + int[] regionMap = new int[regionToChunkRatio*regionToChunkRatio]; + + for(int chunkX=0; chunkX < regionToChunkRatio; chunkX++){ + for(int chunkZ=0; chunkZ < regionToChunkRatio; chunkZ++){ + + int averageLocalHeight = 0; + int maximumLocalHeight = 0; + + for(int i=0; i < chunkSize; i++){ + for(int j=0; j < chunkSize; j++){ + int localHeight = (int) (min_height_custom + (max_height_custom - min_height_custom) * LerpPosHeight(chunkX*chunkSize + i - chunkSize/2, chunkZ*chunkSize + j - chunkSize/2, index, data_width_per_pixel, height_map)); + + if(average){ + averageLocalHeight += localHeight; + } + else { + maximumLocalHeight = Math.Max(maximumLocalHeight, localHeight); + } + + } + } + + int fakeChunkX = regionToChunkRatio-1-chunkX; + int fakeChunkZ = regionToChunkRatio-1-chunkZ; + + // For some reason, the data maps are transposed compared to the world map, hence the regionToChunkRatio - 1 - XXX + if(average){ + regionMap[ChunkIndex2d(fakeChunkX, fakeChunkZ, regionToChunkRatio)] = averageLocalHeight / (chunkSize*chunkSize); + } + else { + regionMap[ChunkIndex2d(fakeChunkX, fakeChunkZ, regionToChunkRatio)] = maximumLocalHeight; + } + } + } + return regionMap; + } + + /** + region map: 4000 * 4000 + Chunk size 32 + + region map 125 * 125, each value gives the height average of the chunk + */ + public int[] build_mini_region_map(IntDataMap2D referenceMap, int chunkX, int chunkZ, int regionToChunkRatio, int[] regionMap, int globalRegionSize, double ratio){ + /* + Builds the local region map at the given chunk coordinates, at the referenceMap dimensions. + */ + int regionSize = referenceMap.Size; + int regionOffset = referenceMap.TopLeftPadding; + + int[] miniRegionMap = new int[regionSize*regionSize]; + + for(int i = 0; i < miniRegionMap.Length; i++){ + int fakeChunkX = (int)(chunkX + (- mod(chunkX, globalRegionSize) - regionOffset + i%regionSize)*ratio); + int fakeChunkZ = (int)(chunkZ + (- mod(chunkZ, globalRegionSize) - regionOffset + i/regionSize)*ratio); + + miniRegionMap[i] = regionMap[getOutBoundX(fakeChunkX, regionToChunkRatio) + regionToChunkRatio*getOutBoundX(fakeChunkZ, regionToChunkRatio)]; + } + return miniRegionMap; + } + public int getRiverHeight(int worldX, int worldZ, int min_height_custom, int max_height_custom, float data_width_per_pixel, SKBitmap height_map){ + /* + Takes the river height at the (worldX, worldZ) coordinates. + + Returns the minimum within a radius, to prevent artifacts at the water surface. + */ + int radius = 2; + int[] localRiverHeights = new int[(2*radius+1)*(2*radius+1)]; + + for(int i=-radius; i 0){ + localRiverHeights[i+radius+(j+radius)*(2*radius+1)] = (int) (min_height_custom + (max_height_custom - min_height_custom) * LerpPosHeight(worldX + i, worldZ + j, 0, data_width_per_pixel, height_map)); + } + else{ + localRiverHeights[i+radius+(j+radius)*(2*radius+1)] = max_height_custom; + } + } + } + + return localRiverHeights.Min(); + } + + public void makeLakes(IServerChunk[] chunks, int chunkX, int chunkZ, int chunksize, int waterID, int gravelID, int min_height_custom, int max_height_custom, float data_width_per_pixel, SKBitmap height_map){ + /* + Fills the potential lakes in the given chunk. + */ + int lakeHeight; + int groundHeight; + + for (int lZ = 0; lZ < chunksize*chunksize; lZ++){ + int worldX = chunkX * chunksize + lZ%chunksize+ offsetX; + int worldZ = chunkZ * chunksize + lZ/chunksize + offsetZ; + + groundHeight = (int) (min_height_custom + (max_height_custom - min_height_custom) * LerpPosHeight(worldX, worldZ, 0, data_width_per_pixel, height_map)); + lakeHeight = (int) (min_height_custom + (max_height_custom - min_height_custom) * LerpPosHeight(worldX, worldZ, 1, data_width_per_pixel, height_map)); + + if(lakeHeight > groundHeight){ + for(int posY = groundHeight; posY < lakeHeight; posY ++){ + setBlockId(lZ%chunksize, posY, lZ/chunksize, chunksize, chunks, waterID, fluid:true); + } + + setBlockId(lZ%chunksize, groundHeight - 1, lZ/chunksize, chunksize, chunks, gravelID); + setBlockId(lZ%chunksize, groundHeight - 2, lZ/chunksize, chunksize, chunks, gravelID); + + for(int posY = lakeHeight; posY < 30 + lakeHeight; posY ++){ + SetBlockAir(lZ%chunksize, posY, lZ/chunksize, chunksize, chunks); + } + } + } + } + public void print(string str){ + /** + Just a print + */ + api.Logger.Notification(str); + } +} +struct ColumnResult +{ + public BitArray ColumnBlockSolidities; +} \ No newline at end of file diff --git a/code_mods/WorldGen_AlpineStory/build.ps1 b/code_mods/WorldGen_AlpineStory/build.ps1 new file mode 100644 index 00000000..6be84e30 --- /dev/null +++ b/code_mods/WorldGen_AlpineStory/build.ps1 @@ -0,0 +1,2 @@ +dotnet run --project CakeBuild/CakeBuild.csproj -- $args +exit $LASTEXITCODE; \ No newline at end of file diff --git a/code_mods/WorldGen_AlpineStory/build.sh b/code_mods/WorldGen_AlpineStory/build.sh new file mode 100644 index 00000000..41f254aa --- /dev/null +++ b/code_mods/WorldGen_AlpineStory/build.sh @@ -0,0 +1 @@ +dotnet run --project ./CakeBuild/CakeBuild.csproj -- "$@"