Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/server/terrain/biomes.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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| {
Expand Down Expand Up @@ -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 {
Expand Down
106 changes: 95 additions & 11 deletions src/server/terrain/chunkgen/TerrainGenerator.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
6 changes: 3 additions & 3 deletions src/server/terrain/structuremapgen/SimpleStructureGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 = .{
Expand Down Expand Up @@ -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,
Expand Down
Loading