diff --git a/src/server/terrain/biomes.zig b/src/server/terrain/biomes.zig index 1a7bf841f5..036036d403 100644 --- a/src/server/terrain/biomes.zig +++ b/src/server/terrain/biomes.zig @@ -257,6 +257,11 @@ pub const Biome = struct { // MARK: Biome isValidPlayerSpawn: bool, chance: f32, tags: []const Tag, + oceanHeight: i32, + liquidBlock: main.blocks.Block, + isOceanRelative: bool, + relativeOceanGap: u32, + relativeOceanOffset: i32, pub fn init(self: *Biome, id: []const u8, paletteId: u32, zon: ZonElement) void { const minRadius = zon.get(f32, "radius") orelse zon.get(f32, "minRadius") orelse 256; @@ -298,6 +303,11 @@ pub const Biome = struct { // MARK: Biome .chance = zon.get(f32, "chance") orelse if (zon == .null) 0 else 1, .maxSubBiomeCount = zon.get(f32, "maxSubBiomeCount") orelse std.math.floatMax(f32), .tags = Tag.loadTagsFromZon(main.worldArena, zon.getChild("tags")), + .oceanHeight = zon.get(i32, "oceanHeight") orelse 0, + .liquidBlock = blocks.parseBlock(zon.get([]const u8, "liquidBlock") orelse "cubyz:water"), + .isOceanRelative = zon.get(bool, "isOceanRelative") orelse false, + .relativeOceanGap = zon.get(u32, "relativeOceanGap") orelse 8, + .relativeOceanOffset = zon.get(i32, "relativeOceanOffset") orelse 0, }; if (self.isCave) { for (self.tags) |tag| { @@ -390,6 +400,19 @@ pub const Biome = struct { // MARK: Biome for (stripes.toSlice(), 0..) |elem, i| { self.stripes[i] = Stripe.init(elem); } + if (self.isOceanRelative) { + if (self.oceanHeight <= 0) { + std.log.err("Biome {s} has a relative ocean and an ocean height of {d}. Ocean height should be greater than 0.", .{self.id, self.oceanHeight}); + } else if (self.oceanHeight > 32) { + std.log.err("Biome {s} has a relative ocean and an ocean height of {d}. Ocean height cannot be greater than 32. Setting to 32 instead.", .{self.id, self.oceanHeight}); + self.oceanHeight = 32; + } + } + if (self.relativeOceanGap == 0) { + std.log.err("Biome {s} cannot have relative ocean gap of 0. Setting to 1 instead.", .{self.id}); + self.relativeOceanGap = 1; + } + self.relativeOceanOffset = @mod(self.relativeOceanOffset, @as(i32, @intCast(self.relativeOceanGap))); } fn getCheckSum(self: *Biome) u64 { diff --git a/src/server/terrain/chunkgen/TerrainGenerator.zig b/src/server/terrain/chunkgen/TerrainGenerator.zig index 81c99ddbc9..4f6594c528 100644 --- a/src/server/terrain/chunkgen/TerrainGenerator.zig +++ b/src/server/terrain/chunkgen/TerrainGenerator.zig @@ -31,6 +31,31 @@ pub fn init(parameters: ZonElement) void { water = main.blocks.parseBlock("cubyz:water"); } +fn getOceanHeight(biome: *const Biome, chunk: *main.chunk.ServerChunk, airBlockBelow: i32) i32 { + if (!biome.isOceanRelative) { + return biome.oceanHeight -% chunk.super.pos.wz; + } + const snapped: i32 = @divFloor(airBlockBelow +% chunk.super.pos.wz, @as(i32, @intCast(biome.relativeOceanGap)))*@as(i32, @intCast(biome.relativeOceanGap)) -% chunk.super.pos.wz; + if (snapped + biome.relativeOceanOffset > airBlockBelow) { + return snapped -% @as(i32, @intCast(biome.relativeOceanGap)) +% @as(i32, @intCast(biome.relativeOceanOffset)) +% biome.oceanHeight; + } + return snapped + biome.relativeOceanOffset +% biome.oceanHeight; +} + +fn isOceansCompatible(chunk: *main.chunk.ServerChunk, relPos: Vec3i, biome: *const Biome, biomeOceanHeight: i32, biomeMap: CaveBiomeMap.CaveBiomeMapView, caveMap: CaveMap.CaveMapView, startAirSearchFrom: i32, checkDepth: bool) bool { + if (biome.isOceanRelative and (startAirSearchFrom < -32 or relPos[2] < -32 or relPos[2] > biomeMap.width + 32)) return false; + const neighborBiome = biomeMap.getBiome(relPos[0], relPos[1], relPos[2]); + if (biome.liquidBlock != neighborBiome.liquidBlock) return false; + const neighborAirBlockBelow = if (caveMap.isSolid(relPos[0], relPos[1], startAirSearchFrom)) startAirSearchFrom else @max(caveMap.findTerrainChangeBelow(relPos[0], relPos[1], startAirSearchFrom) +% chunk.super.pos.voxelSize, -32); + const neighborOceanHeight = getOceanHeight(neighborBiome, chunk, neighborAirBlockBelow); + if (checkDepth and neighborOceanHeight != biomeOceanHeight) return false; + const neighborValidDepth = neighborOceanHeight -% neighborAirBlockBelow <= neighborBiome.oceanHeight; + if (biome.isOceanRelative and (neighborBiome.isOceanRelative and (checkDepth and !neighborValidDepth) or checkDepth and (neighborAirBlockBelow -% chunk.super.pos.voxelSize < -32 or !caveMap.isSolid(relPos[0], relPos[1], neighborAirBlockBelow -% chunk.super.pos.voxelSize)))) { + return false; + } + return true; +} + pub fn generate(worldSeed: u64, chunk: *main.chunk.ServerChunk, caveMap: CaveMap.CaveMapView, biomeMap: CaveBiomeMap.CaveBiomeMapView) void { if (chunk.super.pos.voxelSize >= 8) { var maxHeight: i32 = 0; @@ -67,15 +92,14 @@ pub fn generate(worldSeed: u64, chunk: *main.chunk.ServerChunk, caveMap: CaveMap defer zBiome = chunk.startIndex(zBiome + biomeHeight - 1 + chunk.super.pos.voxelSize); var z: i32 = @min(chunk.super.width - chunk.super.pos.voxelSize, chunk.startIndex(zBiome + biomeHeight - 1)); while (z >= zBiome) : (z -= chunk.super.pos.voxelSize) { + const cardinalDirections = [_]Vec3i{ + Vec3i{1, 0, 0}, + Vec3i{-1, 0, 0}, + Vec3i{0, 1, 0}, + Vec3i{0, -1, 0}, + }; const mask = @as(u64, 1) << @intCast(z >> voxelSizeShift); if (heightData & mask != 0) { - const cardinalDirections = [_]Vec3i{ - Vec3i{1, 0, 0}, - Vec3i{-1, 0, 0}, - Vec3i{0, 1, 0}, - Vec3i{0, -1, 0}, - }; - const surfaceBlock = caveMap.findTerrainChangeAbove(x, y, z) - chunk.super.pos.voxelSize; var maxUp: i32 = 0; var maxDown: i32 = 0; @@ -142,17 +166,77 @@ pub fn generate(worldSeed: u64, chunk: *main.chunk.ServerChunk, caveMap: CaveMap } } else { const surface = biomeMap.getSurfaceHeight(x + chunk.super.pos.wx, y + chunk.super.pos.wy) - (chunk.super.pos.voxelSize - 1) -% chunk.super.pos.wz; - const oceanHeight = 0 -% chunk.super.pos.wz; const airVolumeStart = caveMap.findTerrainChangeBelow(x, y, z) + chunk.super.pos.voxelSize; const zStart = @max(airVolumeStart, zBiome); - if (z < surface or zStart >= oceanHeight) { + const airBlockBelow = if (biome.isOceanRelative) airVolumeStart else surface; + const oceanHeight = getOceanHeight(biome, chunk, airBlockBelow); + const oceanSurfaceDifference = oceanHeight -% airBlockBelow; + if (z < airBlockBelow or zStart >= oceanHeight or biome.isOceanRelative and (oceanSurfaceDifference > biome.oceanHeight or !isOceansCompatible(chunk, Vec3i{x, y, airVolumeStart}, biome, oceanHeight, biomeMap, caveMap, zStart, true))) { chunk.updateBlockColumnInGeneration(x, y, zStart, z, .{.typ = 0, .data = 0}); } else { if (z >= oceanHeight) { chunk.updateBlockColumnInGeneration(x, y, oceanHeight, z, .{.typ = 0, .data = 0}); - z = oceanHeight - chunk.super.pos.voxelSize; + z = oceanHeight -% chunk.super.pos.voxelSize; + } + if (z < zBiome) break; + var isPlacingWall = false; + var topBlockDepth: i32 = 0; + var i: usize = 0; + blockEdges: while (true) : (i += 1) { + // if (i >= 4) break; + for (cardinalDirections) |cardinalDirection| { + const neighborCoordsTop = Vec3i{x, y, z -% topBlockDepth} + cardinalDirection; + const neighborCoordsTopFar = neighborCoordsTop + cardinalDirection; + const neighborAirBlockBelow = caveMap.findTerrainChangeBelow(neighborCoordsTop[0], neighborCoordsTop[1], neighborCoordsTop[2]) +% chunk.super.pos.voxelSize; + const neighborCoordsBottom = Vec3i{neighborCoordsTop[0], neighborCoordsTop[1], neighborAirBlockBelow}; + const isCompatibleBottom = !biome.isOceanRelative and isOceansCompatible(chunk, neighborCoordsTop, biome, oceanHeight, biomeMap, caveMap, neighborCoordsTop[2], true) or biome.isOceanRelative and isOceansCompatible(chunk, neighborCoordsBottom, biome, oceanHeight, biomeMap, caveMap, neighborCoordsTop[2], true); + if (!isCompatibleBottom or biome.isOceanRelative and (!isOceansCompatible(chunk, Vec3i{neighborCoordsTop[0], neighborCoordsTop[1], @max(-32, neighborCoordsBottom[2] -% chunk.super.pos.voxelSize)}, biome, oceanHeight, biomeMap, caveMap, neighborCoordsBottom[2], true) or !isOceansCompatible(chunk, Vec3i{neighborCoordsTop[0], neighborCoordsTop[1], @max(-32, zStart -% chunk.super.pos.voxelSize -% chunk.super.pos.voxelSize)}, biome, oceanHeight, biomeMap, caveMap, zStart, true))) { + isPlacingWall = true; + break :blockEdges; + } + if (!biome.isOceanRelative) continue; + const isCompatibleBottomFar = !biome.isOceanRelative or biome.isOceanRelative and isOceansCompatible(chunk, neighborCoordsTop, biome, getOceanHeight(biomeMap.getBiome(neighborCoordsTopFar[0], neighborCoordsTopFar[1], neighborCoordsTopFar[2]), chunk, neighborAirBlockBelow), biomeMap, caveMap, neighborCoordsTopFar[2] +% chunk.super.pos.voxelSize, true); + const isNeighborSolid = caveMap.isSolid(neighborCoordsTop[0], neighborCoordsTop[1], neighborCoordsTop[2]); + const isNeighborCompatible = isOceansCompatible(chunk, neighborCoordsTop, biome, oceanHeight, biomeMap, caveMap, neighborCoordsTop[2], true); + const notCompatibleTop = !isNeighborSolid and !isNeighborCompatible or !isCompatibleBottomFar; + if (notCompatibleTop) { + if (z -% topBlockDepth >= zStart) { + topBlockDepth = topBlockDepth +% chunk.super.pos.voxelSize; + } + while (z -% topBlockDepth > zStart and (!isOceansCompatible(chunk, Vec3i{x, y, z -% topBlockDepth} + cardinalDirection, biome, oceanHeight, biomeMap, caveMap, z -% topBlockDepth, true) or !isOceansCompatible(chunk, Vec3i{x, y, z -% topBlockDepth} + cardinalDirection + cardinalDirection, biome, oceanHeight, biomeMap, caveMap, z -% topBlockDepth, true))) { + topBlockDepth = topBlockDepth +% chunk.super.pos.voxelSize; + continue :blockEdges; + } + } + } + break :blockEdges; + } + if (zStart <= z) { + if (topBlockDepth > 0) { + const zOriginal: i32 = z; + var bseed: u64 = random.initSeed3D(worldSeed, .{chunk.super.pos.wx + x, chunk.super.pos.wy + y, chunk.super.pos.wz + z}); + z = @min(z + chunk.super.pos.voxelSize, biome.structure.addSubTerranian(chunk, z, @max(zStart -% chunk.super.pos.voxelSize, z -% topBlockDepth), 0, 1, x, y, &bseed)); + z = z -% chunk.super.pos.voxelSize; + topBlockDepth = @max(0, topBlockDepth -% chunk.super.pos.voxelSize); + if (@max(zStart, zOriginal -% topBlockDepth) <= z) { + chunk.updateBlockColumnInGeneration(x, y, @max(zStart, zOriginal -% topBlockDepth), z, biome.stoneBlock); + z = @max(zStart, zOriginal -% topBlockDepth) -% chunk.super.pos.voxelSize; + } + } + if (zStart <= z) { + if (isPlacingWall) { + if (z == oceanHeight -% chunk.super.pos.voxelSize) { + var bseed: u64 = random.initSeed3D(worldSeed, .{chunk.super.pos.wx + x, chunk.super.pos.wy + y, chunk.super.pos.wz + z}); + z = biome.structure.addSubTerranian(chunk, z, @max(zStart, zBiome - 1), 0, 1, x, y, &bseed); + z = z -% chunk.super.pos.voxelSize; + } + if (zStart <= z) chunk.updateBlockColumnInGeneration(x, y, zStart, z, biome.stoneBlock); + z = z -% chunk.super.pos.voxelSize; + } else { + chunk.updateBlockColumnInGeneration(x, y, zStart, z, biome.liquidBlock); + } + } } - chunk.updateBlockColumnInGeneration(x, y, zStart, z, water); } z = zStart; } diff --git a/src/server/terrain/structuremapgen/SimpleStructureGen.zig b/src/server/terrain/structuremapgen/SimpleStructureGen.zig index 9a21126cc9..570da24466 100644 --- a/src/server/terrain/structuremapgen/SimpleStructureGen.zig +++ b/src/server/terrain/structuremapgen/SimpleStructureGen.zig @@ -124,8 +124,8 @@ pub fn generate(map: *StructureMapFragment, worldSeed: u64) void { .isCeiling = heightFinalized.isCeiling, }; if (model.generationMode == .water_surface) { - if (wpz != 0) break; - data.wz = 0; + if (wpz != 0 or biome.isOceanRelative) break; + data.wz = biome.oceanHeight; } map.addStructure(.{ .internal = .{ @@ -170,7 +170,7 @@ pub fn generate(map: *StructureMapFragment, worldSeed: u64) void { .model = model, .isCeiling = heightFinalized.isCeiling, }; - if (model.generationMode == .water_surface) data.wz = 0; + if (model.generationMode == .water_surface and biome.isOceanRelative) data.wz = biome.oceanHeight; map.addStructure(.{ .internal = .{ .data = data,