diff --git a/Obsidian.API/Noise/BiomeSelector.cs b/Obsidian.API/Noise/BiomeSelector.cs index ac9c052c9..26a2edc47 100644 --- a/Obsidian.API/Noise/BiomeSelector.cs +++ b/Obsidian.API/Noise/BiomeSelector.cs @@ -53,28 +53,28 @@ public override double GetValue(double x, double y, double z) // 5 heights, 4 temps, 3 humidities var tempIndex = (int)((SourceModules[0].GetValue(x, 0, z) + 0.999d) * 2.0d); var humidityIndex = (int)((SourceModules[1].GetValue(x, 0, z) + 0.999d) * 1.5d); - var erosionVal = SourceModules[3].GetValue(x, 0, z) + 2.0; + var erosionVal = SourceModules[3].GetValue(x, 0, z) + 1.0; var height = SourceModules[2].GetValue(x, 0, z); if (height >= -0.01) { // Check river var riverVal = SourceModules[4].GetValue(x, 0, z); - if (riverVal < 0) - { - return (double)Biome.River; - } + if (riverVal < 0.04) + return tempIndex < 1 ? (double)Biome.FrozenRiver : (double)Biome.River; } - if (height >= -0.1 && height < 0.025) { return (double)Biome.Beach; } + if (height >= -0.1 && height < 0.04) + return tempIndex <= 1 ? (double)Biome.SnowyBeach : (double)Biome.Beach; + if (height > 0.1) // If above ocean, add erosion and rivers { - erosionVal = (height - 0.1) * erosionVal; + erosionVal = (height - 0.1) * (erosionVal + 1.3); height += erosionVal; } if (height >= 0.6) // Add mountain peaks/valleys { var peakVal = (height - 0.6) * Math.Max(SourceModules[5].GetValue(x, 0, z) + 1.6, 1.0) * 0.5; - height += peakVal;// * (erosionVal - 0.5); + height += peakVal * (erosionVal + 0.5); } var heightIndex = height switch @@ -82,7 +82,7 @@ public override double GetValue(double x, double y, double z) double v when v < -0.6 => 0, double v when v >= -0.6 && v < 0 => 1, double v when v >= 0 && v < 0.3 => 2, - double v when v >= 0.3 && v < 1 => 3, + double v when v >= 0.3 && v < 1.5 => 3, _ => 4, }; diff --git a/Obsidian.API/Noise/OverworldTerrain.cs b/Obsidian.API/Noise/OverworldTerrain.cs index 4e4e77183..2ce012bf6 100644 --- a/Obsidian.API/Noise/OverworldTerrain.cs +++ b/Obsidian.API/Noise/OverworldTerrain.cs @@ -20,9 +20,9 @@ public OverworldTerrain(Module height, Module squash, Module erosion, Module riv terrainPerlin = new Perlin() { Frequency = 0.333 / TerrainStretch, - OctaveCount = 3, - Lacunarity = 0.8899, - Persistence = 0.1334, + OctaveCount = 5, + Lacunarity = 1.7899, + Persistence = 0.2334, Quality = SharpNoise.NoiseQuality.Fast, Seed = Seed + 2 }; @@ -32,13 +32,12 @@ public override double GetValue(double x, double y, double z) { var squash = SourceModules[1].GetValue(x, 0, z) + 1.1d; // Can't be zero var height = SourceModules[0].GetValue(x, 0, z); - var erosionVal = SourceModules[2].GetValue(x, 0, z) + 2.0; + var erosionVal = SourceModules[2].GetValue(x, 0, z) + 1.0; if (height > 0.1) // If above ocean, add erosion and rivers { - erosionVal = (height - 0.1) * erosionVal; - height += erosionVal; + height += (height - 0.1) * (erosionVal + 1.3); } if (height > -0.1) @@ -48,11 +47,11 @@ public override double GetValue(double x, double y, double z) } // Beash/Ocean flat, everything else amplified - squash = height < 0.1 ? squash * 0.3d : 1.12 * Math.Pow(squash,3); + squash = height < 0 ? squash * 0.3d : Math.Pow(-(3 * squash - 1.5), 4) + 1.0; if (height >= 0.6) // Add mountain peaks/valleys { var peakVal = (height - 0.6) * Math.Max(SourceModules[4].GetValue(x, 0, z) + 1.6, 1.0)*0.5; - height += peakVal;// * (erosionVal - 0.5); + height += peakVal * (erosionVal + 0.5); } double yOffset = y + (height * 128 * -1); diff --git a/Obsidian.API/Noise/RiverSelector.cs b/Obsidian.API/Noise/RiverSelector.cs index 2c41d7a1c..4dac9ff75 100644 --- a/Obsidian.API/Noise/RiverSelector.cs +++ b/Obsidian.API/Noise/RiverSelector.cs @@ -13,6 +13,7 @@ public RiverSelector() : base(0) public override double GetValue(double x, double y, double z) { var n = Math.Abs(RiverNoise.GetValue(x, y, z)); - return n >= 0.432 ? 1 : Math.Max(3 * n + 0.07 * Math.Sin(40 * n) - 0.225, -0.1); + // Desmos: 5x\ +\ 0.1\sin\left(50x+1\right)\ -0.19\ \left\{0\ <\ x\right\} + return n >= 0.3484 ? 1.5 : Math.Max(5 * n + 0.1 * Math.Sin(50 * n + 0.75) - 0.19, -0.085); } } diff --git a/Obsidian.API/_Interfaces/IServerConfiguration.cs b/Obsidian.API/_Interfaces/IServerConfiguration.cs index ccacca743..f2f6b5078 100644 --- a/Obsidian.API/_Interfaces/IServerConfiguration.cs +++ b/Obsidian.API/_Interfaces/IServerConfiguration.cs @@ -52,7 +52,6 @@ public interface IServerConfiguration /// Maximum amount of players that is allowed to be connected at the same time. /// public int MaxPlayers { get; set; } - public int PregenerateChunkRange { get; set; } /// diff --git a/Obsidian.ConsoleApp/Program.cs b/Obsidian.ConsoleApp/Program.cs index b4d38b119..d1c753815 100644 --- a/Obsidian.ConsoleApp/Program.cs +++ b/Obsidian.ConsoleApp/Program.cs @@ -33,6 +33,11 @@ loggingBuilder.SetMinimumLevel(env.Configuration.LogLevel); }); +builder.Logging.AddFilter((provider, category, logLevel) => +{ + return !category.Contains("Microsoft") || logLevel != LogLevel.Debug; +}); + builder.Services.AddObsidian(env); // Give the server some time to shut down after CTRL-C or SIGTERM. diff --git a/Obsidian/Entities/Player.cs b/Obsidian/Entities/Player.cs index 654619e4e..a02899f59 100644 --- a/Obsidian/Entities/Player.cs +++ b/Obsidian/Entities/Player.cs @@ -977,9 +977,9 @@ internal async Task UpdateChunksAsync(bool unloadAll = false, int distance clientUnneededChunks.ForEach(c => client.LoadedChunks.TryRemove(c)); - await Parallel.ForEachAsync(clientNeededChunks, async (chunkLoc, _) => + foreach (var (X, Z) in clientNeededChunks) { - var chunk = await world.GetChunkAsync(chunkLoc.X, chunkLoc.Z); + var chunk = await world.GetChunkAsync(X, Z); if (chunk is not null && chunk.IsGenerated) { await client.SendChunkAsync(chunk); @@ -989,7 +989,8 @@ await Parallel.ForEachAsync(clientNeededChunks, async (chunkLoc, _) => { sentAll = false; } - }); + } + return sentAll; } diff --git a/Obsidian/Net/Packets/Play/Serverbound/SetPlayerPositionAndRotationPacket.cs b/Obsidian/Net/Packets/Play/Serverbound/SetPlayerPositionAndRotationPacket.cs index e92b457a8..86da3faf7 100644 --- a/Obsidian/Net/Packets/Play/Serverbound/SetPlayerPositionAndRotationPacket.cs +++ b/Obsidian/Net/Packets/Play/Serverbound/SetPlayerPositionAndRotationPacket.cs @@ -22,6 +22,9 @@ public partial class SetPlayerPositionAndRotationPacket : IServerboundPacket public async ValueTask HandleAsync(Server server, Player player) { + // The first time we get this packet, it doesn't make sense so we should ignore it. + if (player.LastPosition == Vector.Zero) { return; } + await player.UpdateAsync(Position, Yaw, Pitch, OnGround); if (player.Position.ToChunkCoord() != player.LastPosition.ToChunkCoord()) { diff --git a/Obsidian/Utilities/Extensions.cs b/Obsidian/Utilities/Extensions.cs index f8f02c17b..11c765960 100644 --- a/Obsidian/Utilities/Extensions.cs +++ b/Obsidian/Utilities/Extensions.cs @@ -7,6 +7,8 @@ using System.Globalization; using System.IO; using System.Linq.Expressions; +using System.Numerics; +using System.Runtime.CompilerServices; using System.Security.Cryptography; using System.Text.Json; using System.Threading; @@ -127,4 +129,41 @@ public static string MinecraftShaDigest(this IEnumerable data) JsonSerializer.DeserializeAsync(stream, options ?? Globals.JsonOptions, cancellationToken); public static Task ToJsonAsync(this object? value, Stream stream, CancellationToken cancellationToken = default) => JsonSerializer.SerializeAsync(stream, value, Globals.JsonOptions, cancellationToken); + + public static bool Contains(this ReadOnlySpan span, T value) where T : unmanaged + { + for (int i = 0; i < span.Length; i++) + { + if (span[i].Equals(value)) + { + return true; + } + } + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Equals(this T a, T b) where T : unmanaged + { + if (Unsafe.SizeOf() == sizeof(byte)) + { + return Unsafe.As(ref a) == Unsafe.As(ref b); + } + else if (Unsafe.SizeOf() == sizeof(ushort)) + { + return Unsafe.As(ref a) == Unsafe.As(ref b); + } + else if (Unsafe.SizeOf() == sizeof(uint)) + { + return Unsafe.As(ref a) == Unsafe.As(ref b); + } + else if (Unsafe.SizeOf() == sizeof(ulong)) + { + return Unsafe.As(ref a) == Unsafe.As(ref b); + } + else + { + return EqualityComparer.Default.Equals(a, b); + } + } } diff --git a/Obsidian/WorldData/Generators/Overworld/ChunkBuilder.cs b/Obsidian/WorldData/Generators/Overworld/ChunkBuilder.cs index a16e633c9..3587f735a 100644 --- a/Obsidian/WorldData/Generators/Overworld/ChunkBuilder.cs +++ b/Obsidian/WorldData/Generators/Overworld/ChunkBuilder.cs @@ -1,6 +1,6 @@ using Obsidian.ChunkData; using Obsidian.Registries; -using System.Linq; +using System.Diagnostics; namespace Obsidian.WorldData.Generators.Overworld; @@ -54,6 +54,9 @@ internal static class ChunkBuilder BlocksRegistry.DeepslateDiamondOre, ]; + private static ReadOnlySpan EmeraldBiomes => [Biome.WindsweptHills, Biome.WindsweptGravellyHills, Biome.Meadow, Biome.Grove, Biome.SnowySlopes, Biome.FrozenPeaks, Biome.JaggedPeaks, Biome.StonyPeaks]; + private static ReadOnlySpan OreTypes => [OreType.Coal, OreType.Iron, OreType.Copper, OreType.Gold, OreType.Lapis, OreType.Redstone, OreType.Emerald, OreType.Diamond]; + internal enum OreType : int { Coal, @@ -65,9 +68,10 @@ internal enum OreType : int Emerald, Diamond } - + internal static void Biomes(GenHelper helper, Chunk chunk) { + for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) @@ -175,61 +179,62 @@ internal static void Surface(GenHelper helper, Chunk chunk) internal static bool GenerateOreCheck(int height, OreType type) => type switch { - OreType.Coal => height >= 0 && height <= 320, - OreType.Iron => (height >= -63 && height <= 72) || (height >= 80 && height <= 320), - OreType.Copper => height >= -16 && height <= 112, - OreType.Gold => height >= -63 && height <= 30, - OreType.Lapis => height >= -63 && height <= 64, - OreType.Redstone => height >= -63 && height <= 16, - OreType.Emerald => height >= -16 && height <= 320, - OreType.Diamond => height >= -63 && height <= 16, + OreType.Coal => height is >= 0 and <= 320, + OreType.Iron => (height is >= -63 and <= 72) || (height is >= 80 and <= 320), + OreType.Copper => height is >= -16 and <= 112, + OreType.Gold => height is >= -63 and <= 30, + OreType.Lapis => height is >= -63 and <= 64, + OreType.Redstone => height is >= -63 and <= 16, + OreType.Emerald => height is >= -16 and <= 320, + OreType.Diamond => height is >= -63 and <= 16, _ => true }; - + internal static void CavesAndOres(GenHelper helper, Chunk chunk) { int chunkOffsetX = chunk.X * 16; int chunkOffsetZ = chunk.Z * 16; - List emeraldBiomes = new List(){Biome.WindsweptHills, Biome.WindsweptGravellyHills, Biome.Meadow, Biome.Grove, Biome.SnowySlopes, Biome.FrozenPeaks, Biome.JaggedPeaks, Biome.StonyPeaks}; - for (int x = 0; x < 16; x++) + + for (int z = 0; z < 16; z++) { - for (int z = 0; z < 16; z++) + for (int x = 0; x < 16; x++) { int terrainY = chunk.Heightmaps[HeightmapType.WorldSurfaceWG].GetHeight(x, z); var (worldX, worldZ) = (x + chunkOffsetX, z + chunkOffsetZ); - for (int y = -60; y <= terrainY; y++) + for (int y = -60; y <= terrainY-6; y++) { bool isCave = helper.Noise.Cave.GetValue(x + chunkOffsetX, y, z + chunkOffsetZ) > 1 - CaveSize; if (isCave) { - if (chunk.GetBlock(x, y + 1, z) is IBlock b && !b.IsLiquid) + if (chunk.GetBlock(x, y + 1, z) is { IsLiquid: false }) chunk.SetBlock(x, y, z, BlocksRegistry.CaveAir); continue; } + if (y > terrainY - 5) { continue; } - var orePlaced = false; //Thanks Jonpro03 for line 210 to 237! - foreach (OreType i in Enum.GetValues(typeof(OreType))) + var orePlaced = false; + foreach (OreType ore in OreTypes) { // Check if we should be placing a given ore at this Y level - if (!GenerateOreCheck(y, i)) + if (!GenerateOreCheck(y, ore)) { // move on to the next ore continue; } - - var b = chunk.GetBiome(x, y, z); + + var chunkBiome = chunk.GetBiome(x, y, z); // Check that Emerald is only placed in the right biomes - if (i == OreType.Emerald && !emeraldBiomes.Contains(b)) + if (ore == OreType.Emerald && !EmeraldBiomes.Contains(chunkBiome)) { continue; } - - var oreNoise1 = helper.Noise.Ore((int)i).GetValue(worldX, y, worldZ); - var oreNoise2 = helper.Noise.Ore((int)i + ores.Length).GetValue(worldX, y, worldZ); + + var oreNoise1 = helper.Noise.Ore((int)ore).GetValue(worldX, y, worldZ); + var oreNoise2 = helper.Noise.Ore((int)ore + ores.Length).GetValue(worldX, y, worldZ); if (oreNoise1 > 1.0 - OreSize && oreNoise2 > 1.0 - OreSize) { // If Y is below 0, switch to deepore varients - chunk.SetBlock(worldX, y, worldZ, y > 0 ? ores[(int)i] : deepores[(int)i]); + chunk.SetBlock(worldX, y, worldZ, y > 0 ? ores[(int)ore] : deepores[(int)ore]); orePlaced = true; break; } diff --git a/Obsidian/WorldData/Generators/Overworld/OverworldTerrainNoise.cs b/Obsidian/WorldData/Generators/Overworld/OverworldTerrainNoise.cs index a1bc66672..e2bd7f98d 100644 --- a/Obsidian/WorldData/Generators/Overworld/OverworldTerrainNoise.cs +++ b/Obsidian/WorldData/Generators/Overworld/OverworldTerrainNoise.cs @@ -43,67 +43,59 @@ public OverworldTerrainNoise(int seed) Seed = seed + 200 }); - PeakValleyNoise = new Cache() + RiverNoise = new Cache { - Source0 = new Clamp() + Source0 = new RiverSelector { - Source0 = new Perlin() + RiverNoise = new Perlin { - Seed = seed, - Frequency = 0.02, - Lacunarity = 2.132, - Quality = SharpNoise.NoiseQuality.Fast, - OctaveCount = 3 + Frequency = 0.0015, + Quality = SharpNoise.NoiseQuality.Standard, + Seed = seed + 1, + OctaveCount = 2, + Lacunarity = 10, + Persistence = 0.05 } } }; - HumidityNoise = new Cache() + TempNoise = new Cache() { Source0 = new Clamp() { - Source0 = new Blur() - { Source0 = new Perlin() { - Frequency = 0.002, - Quality = SharpNoise.NoiseQuality.Fast, - Seed = seed + 3, - OctaveCount = 3, - Lacunarity = 1.5 + Frequency = 0.0002, + Quality = SharpNoise.NoiseQuality.Standard, + Seed = seed + 2, + OctaveCount = 2, + // 1st octave smooth and low freq + // 2nd octave low powered by high freq multi + // to add ripples + Lacunarity = 120, + Persistence = 0.01 } - } - } - }; - - RiverNoise = new Cache - { - Source0 = new RiverSelector - { - RiverNoise = new Perlin - { - Frequency = 0.001, - Quality = SharpNoise.NoiseQuality.Fast, - Seed = seed + 1, - OctaveCount = 3, - Lacunarity = 1.5 - } + } }; - TempNoise = new Cache() + HumidityNoise = new Cache() { Source0 = new Clamp() { - Source0 = new Blur() - { Source0 = new Perlin() { - Frequency = 0.001, - Quality = SharpNoise.NoiseQuality.Fast, - Seed = seed + 2 + Frequency = 0.00023, + Quality = SharpNoise.NoiseQuality.Standard, + Seed = seed + 3, + OctaveCount = 2, + // 1st octave smooth and low freq + // 2nd octave low powered by high freq multi + // to add ripples + Lacunarity = 120, + Persistence = 0.01 } - } + } }; @@ -114,7 +106,7 @@ public OverworldTerrainNoise(int seed) TerrainNoise = new ScaleBias { Scale = 1.0, - Bias = 0.08, + Bias = 0.08, // Favor more land than ocean Source0 = new Perlin() { Frequency = 0.0013, @@ -130,22 +122,49 @@ public OverworldTerrainNoise(int seed) { Source0 = new Clamp() { + Source0 = new ScaleBias + { + Scale = 1.0, + Bias = -0.1, // Favor smoother terrain + Source0 = new Perlin() + { + Frequency = 0.001, + Quality = SharpNoise.NoiseQuality.Fast, + Seed = seed + 6, + Lacunarity = 1.1 + } + } + } + }; + + SquashNoise = new Cache + { + Source0 = new ScaleBias + { + Scale = 1.0, + Bias = -0.1, // Favor smoother terrain Source0 = new Perlin() { - Frequency = 0.002, + Frequency = 0.0005, Quality = SharpNoise.NoiseQuality.Fast, - Seed = seed + 5 + Seed = seed + 98, + Lacunarity = 1.5 } } }; - SquashNoise = new Cache + PeakValleyNoise = new Cache() { - Source0 = new Perlin() + Source0 = new Clamp() { - Frequency = 0.0005, - Quality = SharpNoise.NoiseQuality.Fast, - Seed = seed + 6 + Source0 = new Perlin() + { + Seed = seed, + Frequency = 0.02, + Lacunarity = 2.132, + Quality = SharpNoise.NoiseQuality.Fast, + OctaveCount = 3 + } } }; diff --git a/Obsidian/WorldData/Region.cs b/Obsidian/WorldData/Region.cs index b650fe513..f7b535292 100644 --- a/Obsidian/WorldData/Region.cs +++ b/Obsidian/WorldData/Region.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.Logging; using Obsidian.API.Utilities; -using Obsidian.Blocks; using Obsidian.ChunkData; using Obsidian.Entities; using Obsidian.Nbt; @@ -63,7 +62,7 @@ internal async Task FlushAsync() foreach (Chunk c in loadedChunks) await SerializeChunkAsync(c); - await regionFile.FlushAsync(); + regionFile.Flush(); } internal async ValueTask GetChunkAsync(int x, int z) @@ -196,7 +195,6 @@ private static Chunk DeserializeChunk(NbtCompound chunkCompound) foreach (NbtCompound entry in blockStatesPalette!) { var id = entry.GetInt("Id"); - chunkSecPalette.GetOrAddId(BlocksRegistry.Get(id));//TODO PROCESS ADDED PROPERTIES TO GET CORRECT BLOCK STATE } @@ -207,7 +205,6 @@ private static Chunk DeserializeChunk(NbtCompound chunkCompound) if (statesCompound.TryGetTag("data", out var dataArrayTag)) { var data = dataArrayTag as NbtArray; - section.BlockStateContainer.DataArray.storage = data!.GetArray(); } @@ -220,6 +217,14 @@ private static Chunk DeserializeChunk(NbtCompound chunkCompound) if (Enum.TryParse(biome.Value.TrimResourceTag(), true, out var value)) biomePalette.GetOrAddId(value); } + if (biomesPalette.Count > 1) + { + if (biomesCompound.TryGetTag("data", out var biomeDataArrayTag)) + { + var data = biomeDataArrayTag as NbtArray; + section.BiomeContainer.DataArray.storage = data!.GetArray(); + } + } if (sectionCompound.TryGetTag("SkyLight", out var skyLightTag)) { @@ -263,11 +268,8 @@ private static NbtCompound SerializeChunk(Chunk chunk) if (section.YBase is null) throw new UnreachableException("Section Ybase should not be null");//THIS should never happen - var biomesCompound = new NbtCompound("biomes"); var blockStatesCompound = new NbtCompound("block_states"); - if (section.BlockStateContainer.DataArray.storage.Any(x => x > 0)) - blockStatesCompound.Add(new NbtArray("data", section.BlockStateContainer.DataArray.storage)); if (section.BlockStateContainer.Palette is IndirectPalette indirect) { @@ -287,8 +289,10 @@ private static NbtCompound SerializeChunk(Chunk chunk) } blockStatesCompound.Add(palette); + blockStatesCompound.Add(new NbtArray("data", section.BlockStateContainer.DataArray.storage)); } + var biomesCompound = new NbtCompound("biomes"); if (section.BiomeContainer.Palette is BaseIndirectPalette indirectBiomePalette) { var palette = new NbtList(NbtTagType.String, "palette"); @@ -296,13 +300,15 @@ private static NbtCompound SerializeChunk(Chunk chunk) foreach (var id in indirectBiomePalette.Values) { var biome = (Biome)id; - palette.Add(new NbtTag(string.Empty, $"minecraft:{biome.ToString().ToLower()}")); } biomesCompound.Add(palette); + if (indirectBiomePalette.Values.Length > 1) + biomesCompound.Add(new NbtArray("data", section.BiomeContainer.DataArray.storage)); } + sectionsCompound.Add(new NbtCompound { new NbtTag("Y", (byte)section.YBase), diff --git a/Obsidian/WorldData/RegionFile.cs b/Obsidian/WorldData/RegionFile.cs index 81fe2aa3e..b95002936 100644 --- a/Obsidian/WorldData/RegionFile.cs +++ b/Obsidian/WorldData/RegionFile.cs @@ -215,10 +215,14 @@ await this.WriteChunkAsync(new() return uncompressedData.ToArray(); } - public async Task FlushAsync() + public void Flush() { + this.semaphore.Wait(); this.Pad(); - await this.regionFileStream.FlushAsync(); + // Using Flush with true forces C# to dump to disk. + // It otherwise wouldn't + this.regionFileStream.Flush(true); + this.semaphore.Release(); } private async Task WriteChunkAsync(Sector sector) @@ -376,7 +380,7 @@ private async Task DisposeAsync(bool disposing) { if (disposing) { - await this.FlushAsync(); + this.Flush(); await this.regionFileStream.DisposeAsync(); this.semaphore.Dispose(); } diff --git a/Obsidian/WorldData/World.cs b/Obsidian/WorldData/World.cs index 275c32c1e..780809338 100644 --- a/Obsidian/WorldData/World.cs +++ b/Obsidian/WorldData/World.cs @@ -876,6 +876,9 @@ await Parallel.ForEachAsync(Enumerable.Range(-regionPregenRange, regionPregenRan var cps = completedChunks / (stopwatch.ElapsedMilliseconds / 1000.0); int remain = ChunksToGenCount / (int)cps; Console.Write("\r{0} chunks/second - {1}% complete - {2} seconds remaining ", cps.ToString("###.00"), pctComplete, remain); + if (completedChunks % 1024 == 0) { // For Jon when he's doing large world gens + await FlushRegionsAsync(); + } } Console.WriteLine(); await FlushRegionsAsync(); @@ -897,46 +900,44 @@ internal async Task SetWorldSpawnAsync() if (LevelData.SpawnPosition.Y != 0) { return; } var pregenRange = this.Configuration.PregenerateChunkRange; - foreach (var region in Regions.Values) + var region = GetRegionForLocation(VectorF.Zero)!; + foreach (var chunk in region.GeneratedChunks()) { - foreach (var chunk in region.GeneratedChunks()) + for (int bx = 0; bx < 16; bx++) { - for (int bx = 0; bx < 16; bx++) + for (int bz = 0; bz < 16; bz++) { - for (int bz = 0; bz < 16; bz++) - { - // Get topmost block - var by = chunk.Heightmaps[ChunkData.HeightmapType.MotionBlocking].GetHeight(bx, bz); - IBlock block = chunk.GetBlock(bx, by, bz); + // Get topmost block + var by = chunk.Heightmaps[ChunkData.HeightmapType.MotionBlocking].GetHeight(bx, bz); + IBlock block = chunk.GetBlock(bx, by, bz); - // Block must be high enough and either grass or sand - if (by < 64 || !block.Is(Material.GrassBlock) && !block.Is(Material.Sand)) - { - continue; - } + // Block must be high enough and either grass or sand + if (by < 64 || !block.Is(Material.GrassBlock) && !block.Is(Material.Sand)) + { + continue; + } - // Block must have enough empty space above for player to spawn in - if (!chunk.GetBlock(bx, by + 1, bz).IsAir || !chunk.GetBlock(bx, by + 2, bz).IsAir) - { - continue; - } + // Block must have enough empty space above for player to spawn in + if (!chunk.GetBlock(bx, by + 1, bz).IsAir || !chunk.GetBlock(bx, by + 2, bz).IsAir) + { + continue; + } - var worldPos = new VectorF(bx + 0.5f + (chunk.X * 16), by + 1, bz + 0.5f + (chunk.Z * 16)); - LevelData.SpawnPosition = worldPos; - Logger.LogInformation("World Spawn set to {worldPos}", worldPos); + var worldPos = new VectorF(bx + 0.5f + (chunk.X * 16), by + 1, bz + 0.5f + (chunk.Z * 16)); + LevelData.SpawnPosition = worldPos; + Logger.LogInformation("World Spawn set to {worldPos}", worldPos); - // Should spawn be far from (0,0), queue up chunks in generation range. - // Just feign a request for a chunk and if it doesn't exist, it'll get queued for gen. - for (int x = chunk.X - pregenRange; x < chunk.X + pregenRange; x++) + // Should spawn be far from (0,0), queue up chunks in generation range. + // Just feign a request for a chunk and if it doesn't exist, it'll get queued for gen. + for (int x = chunk.X - pregenRange; x < chunk.X + pregenRange; x++) + { + for (int z = chunk.Z - pregenRange; z < chunk.Z + pregenRange; z++) { - for (int z = chunk.Z - pregenRange; z < chunk.Z + pregenRange; z++) - { - await GetChunkAsync(x, z); - } + await GetChunkAsync(x, z); } - - return; } + + return; } } } diff --git a/Obsidian/WorldData/WorldLight.cs b/Obsidian/WorldData/WorldLight.cs index 38d3bb2a6..5da89cd50 100644 --- a/Obsidian/WorldData/WorldLight.cs +++ b/Obsidian/WorldData/WorldLight.cs @@ -64,13 +64,24 @@ public static void SetLightAndSpread(Vector pos, LightType lt, int level, Chunk chunk.SetLightLevel(pos, lt, level); } + var highY = 320; + for (int csy = 22; csy >= 0; csy--) + { + if (!chunk.Sections[csy].IsEmpty) + { + // Light needs to go in the first empty section + // too so neighbor chunks can place tree leaves + // that are lit. Would subtract 4 here for negative + // sections but 3 instead (also why 22 above instead 23). + highY = ((csy - 3) << 4) + 15; + break; + } + } + // Can spread up with no loss of level // as long as there is a neighbor that's non-transparent. - for (int spreadY = 1; spreadY < 320 - pos.Y; spreadY++) + for (int spreadY = 1; spreadY < highY - pos.Y; spreadY++) { - var secIndex = ((pos.Y + spreadY) >> 4) + 4; - if (chunk.Sections[secIndex].IsEmpty) { break; } - foreach (Vector dir in Vector.CardinalDirs) { if (chunk.GetBlock(pos + (0, spreadY, 0) + dir) is IBlock b && !(b.IsLiquid || b.IsAir)) @@ -99,8 +110,6 @@ public static void SetLightAndSpread(Vector pos, LightType lt, int level, Chunk continue; } - var highY = chunk.Heightmaps[ChunkData.HeightmapType.MotionBlocking].GetHeight(pos.X, pos.Z) + 1; - // Spread up for (int spreadY = 1; spreadY < (highY - pos.Y); spreadY++) { @@ -111,10 +120,13 @@ public static void SetLightAndSpread(Vector pos, LightType lt, int level, Chunk var scanPos = pos + dir + (0, spreadY, 0); if (TagsRegistry.Blocks.Transparent.Entries.Contains(chunk.GetBlock(scanPos).RegistryId)) { - chunk.SetLightLevel(scanPos, lt, level); if (!TagsRegistry.Blocks.Transparent.Entries.Contains(chunk.GetBlock(scanPos + Vector.Down).RegistryId)) { - SetLightAndSpread(scanPos + Vector.Down, lt, level, chunk); + SetLightAndSpread(scanPos, lt, level, chunk); + } + else + { + chunk.SetLightLevel(scanPos, lt, level); } } } @@ -129,12 +141,12 @@ public static void SetLightAndSpread(Vector pos, LightType lt, int level, Chunk var scanPos = pos + dir + (0, spreadY, 0); if (!TagsRegistry.Blocks.Transparent.Entries.Contains(chunk.GetBlock(scanPos).RegistryId)) { - SetLightAndSpread(scanPos, lt, level, chunk); + SetLightAndSpread(scanPos + Vector.Up, lt, level, chunk); break; } else { - chunk.SetLightLevel(scanPos, lt, level); + chunk.SetLightLevel(scanPos + Vector.Up, lt, level); } } }