From b6150b0f82946f673c613e59bdf39dbd40f33957 Mon Sep 17 00:00:00 2001 From: BoySanic Date: Fri, 24 Oct 2025 09:36:03 -0700 Subject: [PATCH 1/3] Refactor a bit to start to allow for structure tables outside of biomes --- src/server/terrain/biomes.zig | 147 +--------- .../terrain/simple_structures/Boulder.zig | 2 +- .../terrain/simple_structures/FallenTree.zig | 2 +- .../terrain/simple_structures/FlowerPatch.zig | 2 +- .../terrain/simple_structures/GroundPatch.zig | 2 +- .../terrain/simple_structures/SbbGen.zig | 2 +- .../simple_structures/SimpleTreeModel.zig | 2 +- .../simple_structures/SimpleVegetation.zig | 2 +- .../terrain/simple_structures/Stalagmite.zig | 2 +- .../structuremapgen/SimpleStructureGen.zig | 3 +- src/server/terrain/structures.zig | 126 +++++++++ src/server/terrain/terrain.zig | 262 +++++++++--------- src/utils.zig | 1 + src/utils/hash.zig | 82 ++++++ 14 files changed, 356 insertions(+), 281 deletions(-) create mode 100644 src/server/terrain/structures.zig create mode 100644 src/utils/hash.zig diff --git a/src/server/terrain/biomes.zig b/src/server/terrain/biomes.zig index 0d9360e76d..d7cdaf64f1 100644 --- a/src/server/terrain/biomes.zig +++ b/src/server/terrain/biomes.zig @@ -9,72 +9,10 @@ const NeverFailingAllocator = main.heap.NeverFailingAllocator; const vec = @import("main.vec"); const Vec3f = main.vec.Vec3f; const Vec3d = main.vec.Vec3d; - -pub const SimpleStructureModel = struct { // MARK: SimpleStructureModel - pub const GenerationMode = enum { - floor, - ceiling, - floor_and_ceiling, - air, - underground, - water_surface, - }; - const VTable = struct { - loadModel: *const fn(arena: NeverFailingAllocator, parameters: ZonElement) *anyopaque, - generate: *const fn(self: *anyopaque, generationMode: GenerationMode, x: i32, y: i32, z: i32, chunk: *ServerChunk, caveMap: terrain.CaveMap.CaveMapView, biomeMap: terrain.CaveBiomeMap.CaveBiomeMapView, seed: *u64, isCeiling: bool) void, - hashFunction: *const fn(self: *anyopaque) u64, - generationMode: GenerationMode, - }; - - vtable: VTable, - data: *anyopaque, - chance: f32, - priority: f32, - generationMode: GenerationMode, - - pub fn initModel(parameters: ZonElement) ?SimpleStructureModel { - const id = parameters.get([]const u8, "id", ""); - const vtable = modelRegistry.get(id) orelse { - std.log.err("Couldn't find structure model with id {s}", .{id}); - return null; - }; - return SimpleStructureModel{ - .vtable = vtable, - .data = vtable.loadModel(arenaAllocator.allocator(), parameters), - .chance = parameters.get(f32, "chance", 0.1), - .priority = parameters.get(f32, "priority", 1), - .generationMode = std.meta.stringToEnum(GenerationMode, parameters.get([]const u8, "generationMode", "")) orelse vtable.generationMode, - }; - } - - pub fn generate(self: SimpleStructureModel, x: i32, y: i32, z: i32, chunk: *ServerChunk, caveMap: terrain.CaveMap.CaveMapView, biomeMap: terrain.CaveBiomeMap.CaveBiomeMapView, seed: *u64, isCeiling: bool) void { - self.vtable.generate(self.data, self.generationMode, x, y, z, chunk, caveMap, biomeMap, seed, isCeiling); - } - - var modelRegistry: std.StringHashMapUnmanaged(VTable) = .{}; - var arenaAllocator: main.heap.NeverFailingArenaAllocator = .init(main.globalAllocator); - - pub fn reset() void { - std.debug.assert(arenaAllocator.reset(.free_all)); - } - - pub fn registerGenerator(comptime Generator: type) void { - var self: VTable = undefined; - self.loadModel = main.utils.castFunctionReturnToAnyopaque(Generator.loadModel); - self.generate = main.utils.castFunctionSelfToAnyopaque(Generator.generate); - self.hashFunction = main.utils.castFunctionSelfToAnyopaque(struct { - fn hash(ptr: *Generator) u64 { - return hashGeneric(ptr.*); - } - }.hash); - self.generationMode = Generator.generationMode; - modelRegistry.put(main.globalAllocator.allocator, Generator.id, self) catch unreachable; - } - - fn getHash(self: SimpleStructureModel) u64 { - return self.vtable.hashFunction(self.data); - } -}; +const hash = main.utils.hash_zig; +const structures_zig = @import("structures.zig"); +const SimpleStructureModel = structures_zig.SimpleStructureModel; +const StructureTable = structures_zig.StructureTable; const Stripe = struct { // MARK: Stripe direction: ?Vec3d, @@ -140,80 +78,6 @@ const Stripe = struct { // MARK: Stripe } }; -fn hashGeneric(input: anytype) u64 { - const T = @TypeOf(input); - return switch(@typeInfo(T)) { - .bool => hashCombine(hashInt(@intFromBool(input)), 0xbf58476d1ce4e5b9), - .@"enum" => hashCombine(hashInt(@as(u64, @intFromEnum(input))), 0x94d049bb133111eb), - .int, .float => blk: { - const value = @as(std.meta.Int(.unsigned, @bitSizeOf(T)), @bitCast(input)); - break :blk hashInt(@as(u64, value)); - }, - .@"struct" => blk: { - if(@hasDecl(T, "getHash")) { - break :blk input.getHash(); - } - var result: u64 = hashGeneric(@typeName(T)); - inline for(@typeInfo(T).@"struct".fields) |field| { - const keyHash = hashGeneric(@as([]const u8, field.name)); - const valueHash = hashGeneric(@field(input, field.name)); - const keyValueHash = hashCombine(keyHash, valueHash); - result = hashCombine(result, keyValueHash); - } - break :blk result; - }, - .optional => if(input) |_input| hashGeneric(_input) else 0, - .pointer => switch(@typeInfo(T).pointer.size) { - .one => blk: { - if(@typeInfo(@typeInfo(T).pointer.child) == .@"fn") break :blk 0; - if(@typeInfo(T).pointer.child == Biome) return hashGeneric(input.id); - if(@typeInfo(T).pointer.child == anyopaque) break :blk 0; - break :blk hashGeneric(input.*); - }, - .slice => blk: { - var result: u64 = hashInt(input.len); - for(input) |val| { - const valueHash = hashGeneric(val); - result = hashCombine(result, valueHash); - } - break :blk result; - }, - else => @compileError("Unsupported type " ++ @typeName(T)), - }, - .array => blk: { - var result: u64 = 0xbf58476d1ce4e5b9; - for(input) |val| { - const valueHash = hashGeneric(val); - result = hashCombine(result, valueHash); - } - break :blk result; - }, - .vector => blk: { - var result: u64 = 0x94d049bb133111eb; - inline for(0..@typeInfo(T).vector.len) |i| { - const valueHash = hashGeneric(input[i]); - result = hashCombine(result, valueHash); - } - break :blk result; - }, - else => @compileError("Unsupported type " ++ @typeName(T)), - }; -} - -// https://stackoverflow.com/questions/5889238/why-is-xor-the-default-way-to-combine-hashes -fn hashCombine(left: u64, right: u64) u64 { - return left ^ (right +% 0x517cc1b727220a95 +% (left << 6) +% (left >> 2)); -} - -// https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key -fn hashInt(input: u64) u64 { - var x = input; - x = (x ^ (x >> 30))*%0xbf58476d1ce4e5b9; - x = (x ^ (x >> 27))*%0x94d049bb133111eb; - x = x ^ (x >> 31); - return x; -} - pub const Interpolation = enum(u8) { none, linear, @@ -420,7 +284,7 @@ pub const Biome = struct { // MARK: Biome } fn getCheckSum(self: *Biome) u64 { - return hashGeneric(self.*); + return hash.hashGeneric(self.*); } }; @@ -680,7 +544,6 @@ pub fn deinit() void { biomesById.deinit(); biomesByIndex.deinit(main.globalAllocator); // TODO? byTypeBiomes.deinit(main.globalAllocator); - SimpleStructureModel.modelRegistry.clearAndFree(main.globalAllocator.allocator); } pub fn register(id: []const u8, paletteId: u32, zon: ZonElement) void { diff --git a/src/server/terrain/simple_structures/Boulder.zig b/src/server/terrain/simple_structures/Boulder.zig index e17176188a..ade48938c1 100644 --- a/src/server/terrain/simple_structures/Boulder.zig +++ b/src/server/terrain/simple_structures/Boulder.zig @@ -6,7 +6,7 @@ const ZonElement = main.ZonElement; const terrain = main.server.terrain; const CaveBiomeMapView = terrain.CaveBiomeMap.CaveBiomeMapView; const CaveMapView = terrain.CaveMap.CaveMapView; -const GenerationMode = terrain.biomes.SimpleStructureModel.GenerationMode; +const GenerationMode = terrain.structures.SimpleStructureModel.GenerationMode; const vec = main.vec; const Vec3d = vec.Vec3d; const Vec3f = vec.Vec3f; diff --git a/src/server/terrain/simple_structures/FallenTree.zig b/src/server/terrain/simple_structures/FallenTree.zig index a4a1d6c5d7..e0eb857a99 100644 --- a/src/server/terrain/simple_structures/FallenTree.zig +++ b/src/server/terrain/simple_structures/FallenTree.zig @@ -6,7 +6,7 @@ const ZonElement = main.ZonElement; const terrain = main.server.terrain; const CaveBiomeMapView = terrain.CaveBiomeMap.CaveBiomeMapView; const CaveMapView = terrain.CaveMap.CaveMapView; -const GenerationMode = terrain.biomes.SimpleStructureModel.GenerationMode; +const GenerationMode = terrain.structures.SimpleStructureModel.GenerationMode; const vec = main.vec; const Vec3d = vec.Vec3d; const Vec3f = vec.Vec3f; diff --git a/src/server/terrain/simple_structures/FlowerPatch.zig b/src/server/terrain/simple_structures/FlowerPatch.zig index 22183a3410..4f8f5efdba 100644 --- a/src/server/terrain/simple_structures/FlowerPatch.zig +++ b/src/server/terrain/simple_structures/FlowerPatch.zig @@ -6,7 +6,7 @@ const ZonElement = main.ZonElement; const terrain = main.server.terrain; const CaveBiomeMapView = terrain.CaveBiomeMap.CaveBiomeMapView; const CaveMapView = terrain.CaveMap.CaveMapView; -const GenerationMode = terrain.biomes.SimpleStructureModel.GenerationMode; +const GenerationMode = terrain.structures.SimpleStructureModel.GenerationMode; const vec = main.vec; const Vec3d = vec.Vec3d; const Vec3f = vec.Vec3f; diff --git a/src/server/terrain/simple_structures/GroundPatch.zig b/src/server/terrain/simple_structures/GroundPatch.zig index 489213cdba..ff0c61a48b 100644 --- a/src/server/terrain/simple_structures/GroundPatch.zig +++ b/src/server/terrain/simple_structures/GroundPatch.zig @@ -6,7 +6,7 @@ const ZonElement = main.ZonElement; const terrain = main.server.terrain; const CaveBiomeMapView = terrain.CaveBiomeMap.CaveBiomeMapView; const CaveMapView = terrain.CaveMap.CaveMapView; -const GenerationMode = terrain.biomes.SimpleStructureModel.GenerationMode; +const GenerationMode = terrain.structures.SimpleStructureModel.GenerationMode; const vec = main.vec; const Vec3d = vec.Vec3d; const Vec3f = vec.Vec3f; diff --git a/src/server/terrain/simple_structures/SbbGen.zig b/src/server/terrain/simple_structures/SbbGen.zig index 9983e00c34..b64a5cb3ef 100644 --- a/src/server/terrain/simple_structures/SbbGen.zig +++ b/src/server/terrain/simple_structures/SbbGen.zig @@ -3,7 +3,7 @@ const std = @import("std"); const main = @import("main"); const terrain = main.server.terrain; const Vec3i = main.vec.Vec3i; -const GenerationMode = terrain.biomes.SimpleStructureModel.GenerationMode; +const GenerationMode = terrain.structures.SimpleStructureModel.GenerationMode; const CaveMapView = terrain.CaveMap.CaveMapView; const CaveBiomeMapView = terrain.CaveBiomeMap.CaveBiomeMapView; const sbb = terrain.structure_building_blocks; diff --git a/src/server/terrain/simple_structures/SimpleTreeModel.zig b/src/server/terrain/simple_structures/SimpleTreeModel.zig index 99c542137e..d27b073908 100644 --- a/src/server/terrain/simple_structures/SimpleTreeModel.zig +++ b/src/server/terrain/simple_structures/SimpleTreeModel.zig @@ -6,7 +6,7 @@ const ZonElement = main.ZonElement; const terrain = main.server.terrain; const CaveBiomeMapView = terrain.CaveBiomeMap.CaveBiomeMapView; const CaveMapView = terrain.CaveMap.CaveMapView; -const GenerationMode = terrain.biomes.SimpleStructureModel.GenerationMode; +const GenerationMode = terrain.structures.SimpleStructureModel.GenerationMode; const vec = main.vec; const Vec3d = vec.Vec3d; const Vec3f = vec.Vec3f; diff --git a/src/server/terrain/simple_structures/SimpleVegetation.zig b/src/server/terrain/simple_structures/SimpleVegetation.zig index e58671768f..50b03ecc05 100644 --- a/src/server/terrain/simple_structures/SimpleVegetation.zig +++ b/src/server/terrain/simple_structures/SimpleVegetation.zig @@ -6,7 +6,7 @@ const ZonElement = main.ZonElement; const terrain = main.server.terrain; const CaveBiomeMapView = terrain.CaveBiomeMap.CaveBiomeMapView; const CaveMapView = terrain.CaveMap.CaveMapView; -const GenerationMode = terrain.biomes.SimpleStructureModel.GenerationMode; +const GenerationMode = terrain.structures.SimpleStructureModel.GenerationMode; const vec = main.vec; const Vec3d = vec.Vec3d; const Vec3f = vec.Vec3f; diff --git a/src/server/terrain/simple_structures/Stalagmite.zig b/src/server/terrain/simple_structures/Stalagmite.zig index 48a864327c..fc5201a1b1 100644 --- a/src/server/terrain/simple_structures/Stalagmite.zig +++ b/src/server/terrain/simple_structures/Stalagmite.zig @@ -6,7 +6,7 @@ const ZonElement = main.ZonElement; const terrain = main.server.terrain; const CaveBiomeMapView = terrain.CaveBiomeMap.CaveBiomeMapView; const CaveMapView = terrain.CaveMap.CaveMapView; -const GenerationMode = terrain.biomes.SimpleStructureModel.GenerationMode; +const GenerationMode = terrain.structures.SimpleStructureModel.GenerationMode; const vec = main.vec; const Vec3d = vec.Vec3d; const Vec3f = vec.Vec3f; diff --git a/src/server/terrain/structuremapgen/SimpleStructureGen.zig b/src/server/terrain/structuremapgen/SimpleStructureGen.zig index 72bc6e97d9..fce5f5ca87 100644 --- a/src/server/terrain/structuremapgen/SimpleStructureGen.zig +++ b/src/server/terrain/structuremapgen/SimpleStructureGen.zig @@ -7,6 +7,7 @@ const ZonElement = main.ZonElement; const terrain = main.server.terrain; const biomes = terrain.biomes; const noise = terrain.noise; +const structures = terrain.structures; const StructureMapFragment = terrain.StructureMap.StructureMapFragment; const SurfaceMap = terrain.SurfaceMap; const MapFragment = SurfaceMap.MapFragment; @@ -123,7 +124,7 @@ pub fn generate(map: *StructureMapFragment, worldSeed: u64) void { } const SimpleStructure = struct { - model: *const biomes.SimpleStructureModel, + model: *const structures.SimpleStructureModel, seed: u64, wx: i32, wy: i32, diff --git a/src/server/terrain/structures.zig b/src/server/terrain/structures.zig new file mode 100644 index 0000000000..a8a62233bb --- /dev/null +++ b/src/server/terrain/structures.zig @@ -0,0 +1,126 @@ +const std = @import("std"); + +const main = @import("main"); +const ZonElement = main.ZonElement; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; +const ServerChunk = main.chunk.ServerChunk; +const terrain = main.server.terrain; +const Biome = main.server.terrain.biomes; +const Hash = main.utils.hash_zig; + +pub const SimpleStructureModel = struct { // MARK: SimpleStructureModel + pub const GenerationMode = enum { + floor, + ceiling, + floor_and_ceiling, + air, + underground, + water_surface, + }; + const VTable = struct { + loadModel: *const fn(arena: NeverFailingAllocator, parameters: ZonElement) *anyopaque, + generate: *const fn(self: *anyopaque, generationMode: GenerationMode, x: i32, y: i32, z: i32, chunk: *ServerChunk, caveMap: terrain.CaveMap.CaveMapView, biomeMap: terrain.CaveBiomeMap.CaveBiomeMapView, seed: *u64, isCeiling: bool) void, + hashFunction: *const fn(self: *anyopaque) u64, + generationMode: GenerationMode, + }; + + vtable: VTable, + data: *anyopaque, + chance: f32, + priority: f32, + generationMode: GenerationMode, + + pub fn initModel(parameters: ZonElement) ?SimpleStructureModel { + const id = parameters.get([]const u8, "id", ""); + const vtable = modelRegistry.get(id) orelse { + std.log.err("Couldn't find structure model with id {s}", .{id}); + return null; + }; + return SimpleStructureModel{ + .vtable = vtable, + .data = vtable.loadModel(arenaAllocator.allocator(), parameters), + .chance = parameters.get(f32, "chance", 0.1), + .priority = parameters.get(f32, "priority", 1), + .generationMode = std.meta.stringToEnum(GenerationMode, parameters.get([]const u8, "generationMode", "")) orelse vtable.generationMode, + }; + } + + pub fn generate(self: SimpleStructureModel, x: i32, y: i32, z: i32, chunk: *ServerChunk, caveMap: terrain.CaveMap.CaveMapView, biomeMap: terrain.CaveBiomeMap.CaveBiomeMapView, seed: *u64, isCeiling: bool) void { + self.vtable.generate(self.data, self.generationMode, x, y, z, chunk, caveMap, biomeMap, seed, isCeiling); + } + + var modelRegistry: std.StringHashMapUnmanaged(VTable) = .{}; + var arenaAllocator: main.heap.NeverFailingArenaAllocator = .init(main.globalAllocator); + + pub fn reset() void { + std.debug.assert(arenaAllocator.reset(.free_all)); + } + + pub fn registerGenerator(comptime Generator: type) void { + var self: VTable = undefined; + self.loadModel = main.utils.castFunctionReturnToAnyopaque(Generator.loadModel); + self.generate = main.utils.castFunctionSelfToAnyopaque(Generator.generate); + self.hashFunction = main.utils.castFunctionSelfToAnyopaque(struct { + fn hash(ptr: *Generator) u64 { + return Hash.hashGeneric(ptr.*); + } + }.hash); + self.generationMode = Generator.generationMode; + modelRegistry.put(main.globalAllocator.allocator, Generator.id, self) catch unreachable; + } + + fn getHash(self: SimpleStructureModel) u64 { + return self.vtable.hashFunction(self.data); + } +}; + +pub const StructureTable = struct { + id: []const u8, + biomeTags: [][]const u8, + structures: []SimpleStructureModel = &.{}, + + pub fn init(self: *StructureTable, id: []const u8, zon: ZonElement) void { + self.* = .{ + .id = main.globalAllocator.dupe(u8, id), + .biomeTags = zon.getChild("biomeTags"), + }; + const structures = zon.getChild("structures"); + var structure_list = main.ListUnmanaged(SimpleStructureModel){}; + var total_chance: f32 = 0; + defer structure_list.deinit(main.stackAllocator); + + for(structures.toSlice()) |elem| { + if(SimpleStructureModel.initModel(elem)) |model| { + structure_list.append(main.stackAllocator, model); + total_chance += model.chance; + } + } + if(total_chance > 1) { + for(structure_list.items) |*model| { + model.chance /= total_chance; + } + } + self.structures = main.globalAllocator.dupe(SimpleStructureModel, structure_list.items); + } +}; + +var structureTables: main.List(StructureTable) = undefined; +var structureTablesById: std.StringHashMap(*StructureTable) = undefined; +pub fn init() void { + structureTables = .init(main.globalAllocator); + structureTablesById = .init(main.globalAllocator.allocator); + for(structureTables.items) |structureTable| { + structureTablesById.put(structureTable.id, structureTable); + } +} + +pub fn getById(id: []const u8) *const StructureTable { + return structureTablesById.get(id) orelse { + std.log.err("Couldn't find structure table with id {s}. Replacing it with some other Structure table.", .{id}); + return &structureTables[0]; + }; +} + +pub fn deinit() void { + SimpleStructureModel.modelRegistry.clearAndFree(main.globalAllocator.allocator); +} diff --git a/src/server/terrain/terrain.zig b/src/server/terrain/terrain.zig index 31ec852774..7a93e31504 100644 --- a/src/server/terrain/terrain.zig +++ b/src/server/terrain/terrain.zig @@ -6,6 +6,8 @@ const NeverFailingAllocator = main.heap.NeverFailingAllocator; pub const biomes = @import("biomes.zig"); pub const noise = @import("noise/noise.zig"); +pub const structures = @import("structures.zig"); + const Biome = biomes.Biome; pub const ClimateMap = @import("ClimateMap.zig"); @@ -24,152 +26,152 @@ pub const structure_building_blocks = @import("structure_building_blocks.zig"); /// A generator for setting the actual Blocks in each Chunk. pub const BlockGenerator = struct { - init: *const fn(parameters: ZonElement) void, - deinit: *const fn() void, - generate: *const fn(seed: u64, chunk: *main.chunk.ServerChunk, caveMap: CaveMap.CaveMapView, biomeMap: CaveBiomeMap.CaveBiomeMapView) void, - /// Used to prioritize certain generators over others. - priority: i32, - /// To avoid duplicate seeds in similar generation algorithms, the SurfaceGenerator xors the world-seed with the generator specific seed. - generatorSeed: u64, - - var generatorRegistry: std.StringHashMapUnmanaged(BlockGenerator) = .{}; - - pub fn registerGenerator(comptime GeneratorType: type) void { - const self = BlockGenerator{ - .init = &GeneratorType.init, - .deinit = &GeneratorType.deinit, - .generate = &GeneratorType.generate, - .priority = GeneratorType.priority, - .generatorSeed = GeneratorType.generatorSeed, - }; - generatorRegistry.put(main.globalAllocator.allocator, GeneratorType.id, self) catch unreachable; - } - - fn getAndInitGenerators(allocator: NeverFailingAllocator, settings: ZonElement) []BlockGenerator { - const list = allocator.alloc(BlockGenerator, generatorRegistry.size); - var iterator = generatorRegistry.iterator(); - var i: usize = 0; - while(iterator.next()) |generator| { - list[i] = generator.value_ptr.*; - list[i].init(settings.getChild(generator.key_ptr.*)); - i += 1; - } - const lessThan = struct { - fn lessThan(_: void, lhs: BlockGenerator, rhs: BlockGenerator) bool { - return lhs.priority < rhs.priority; - } - }.lessThan; - std.sort.insertion(BlockGenerator, list, {}, lessThan); - return list; - } + init: *const fn (parameters: ZonElement) void, + deinit: *const fn () void, + generate: *const fn (seed: u64, chunk: *main.chunk.ServerChunk, caveMap: CaveMap.CaveMapView, biomeMap: CaveBiomeMap.CaveBiomeMapView) void, + /// Used to prioritize certain generators over others. + priority: i32, + /// To avoid duplicate seeds in similar generation algorithms, the SurfaceGenerator xors the world-seed with the generator specific seed. + generatorSeed: u64, + + var generatorRegistry: std.StringHashMapUnmanaged(BlockGenerator) = .{}; + + pub fn registerGenerator(comptime GeneratorType: type) void { + const self = BlockGenerator{ + .init = &GeneratorType.init, + .deinit = &GeneratorType.deinit, + .generate = &GeneratorType.generate, + .priority = GeneratorType.priority, + .generatorSeed = GeneratorType.generatorSeed, + }; + generatorRegistry.put(main.globalAllocator.allocator, GeneratorType.id, self) catch unreachable; + } + + fn getAndInitGenerators(allocator: NeverFailingAllocator, settings: ZonElement) []BlockGenerator { + const list = allocator.alloc(BlockGenerator, generatorRegistry.size); + var iterator = generatorRegistry.iterator(); + var i: usize = 0; + while (iterator.next()) |generator| { + list[i] = generator.value_ptr.*; + list[i].init(settings.getChild(generator.key_ptr.*)); + i += 1; + } + const lessThan = struct { + fn lessThan(_: void, lhs: BlockGenerator, rhs: BlockGenerator) bool { + return lhs.priority < rhs.priority; + } + }.lessThan; + std.sort.insertion(BlockGenerator, list, {}, lessThan); + return list; + } }; /// Lists all the Generators and Biomes that should be used for a given world. /// TODO: Generator/Biome blackslisting (from the world creation menu). /// TODO: Generator settings (from the world creation menu). pub const TerrainGenerationProfile = struct { - mapFragmentGenerator: SurfaceMap.MapGenerator = undefined, - climateGenerator: ClimateMap.ClimateMapGenerator = undefined, - caveBiomeGenerators: []CaveBiomeMap.CaveBiomeGenerator = undefined, - caveGenerators: []CaveMap.CaveGenerator = undefined, - structureMapGenerators: []StructureMap.StructureMapGenerator = undefined, - generators: []BlockGenerator = undefined, - climateWavelengths: [5]f32 = undefined, - seed: u64, - - pub fn init(settings: ZonElement, seed: u64) !TerrainGenerationProfile { - var self = TerrainGenerationProfile{ - .seed = seed, - }; - var generator = settings.getChild("mapGenerator"); - self.mapFragmentGenerator = try SurfaceMap.MapGenerator.getGeneratorById(generator.get([]const u8, "id", "cubyz:mapgen_v1")); - self.mapFragmentGenerator.init(generator); - - generator = settings.getChild("climateGenerator"); - self.climateGenerator = try ClimateMap.ClimateMapGenerator.getGeneratorById(generator.get([]const u8, "id", "cubyz:polar_circles")); - self.climateGenerator.init(generator); - - generator = settings.getChild("caveBiomeGenerators"); - self.caveBiomeGenerators = CaveBiomeMap.CaveBiomeGenerator.getAndInitGenerators(main.globalAllocator, generator); - - generator = settings.getChild("caveGenerators"); - self.caveGenerators = CaveMap.CaveGenerator.getAndInitGenerators(main.globalAllocator, generator); - - generator = settings.getChild("structureMapGenerators"); - self.structureMapGenerators = StructureMap.StructureMapGenerator.getAndInitGenerators(main.globalAllocator, generator); - - generator = settings.getChild("generators"); - self.generators = BlockGenerator.getAndInitGenerators(main.globalAllocator, generator); - - const climateWavelengths = settings.getChild("climateWavelengths"); - self.climateWavelengths[0] = climateWavelengths.get(f32, "hot_cold", 2400); - self.climateWavelengths[1] = climateWavelengths.get(f32, "land_ocean", 3200); - self.climateWavelengths[2] = climateWavelengths.get(f32, "wet_dry", 2400); - self.climateWavelengths[3] = climateWavelengths.get(f32, "vegetation", 2400); - self.climateWavelengths[4] = climateWavelengths.get(f32, "mountain", 500); - - return self; - } - - pub fn deinit(self: TerrainGenerationProfile) void { - self.mapFragmentGenerator.deinit(); - self.climateGenerator.deinit(); - for(self.caveBiomeGenerators) |generator| { - generator.deinit(); - } - main.globalAllocator.free(self.caveBiomeGenerators); - for(self.caveGenerators) |generator| { - generator.deinit(); - } - main.globalAllocator.free(self.caveGenerators); - for(self.structureMapGenerators) |generator| { - generator.deinit(); - } - main.globalAllocator.free(self.structureMapGenerators); - for(self.generators) |generator| { - generator.deinit(); - } - main.globalAllocator.free(self.generators); - } + mapFragmentGenerator: SurfaceMap.MapGenerator = undefined, + climateGenerator: ClimateMap.ClimateMapGenerator = undefined, + caveBiomeGenerators: []CaveBiomeMap.CaveBiomeGenerator = undefined, + caveGenerators: []CaveMap.CaveGenerator = undefined, + structureMapGenerators: []StructureMap.StructureMapGenerator = undefined, + generators: []BlockGenerator = undefined, + climateWavelengths: [5]f32 = undefined, + seed: u64, + + pub fn init(settings: ZonElement, seed: u64) !TerrainGenerationProfile { + var self = TerrainGenerationProfile{ + .seed = seed, + }; + var generator = settings.getChild("mapGenerator"); + self.mapFragmentGenerator = try SurfaceMap.MapGenerator.getGeneratorById(generator.get([]const u8, "id", "cubyz:mapgen_v1")); + self.mapFragmentGenerator.init(generator); + + generator = settings.getChild("climateGenerator"); + self.climateGenerator = try ClimateMap.ClimateMapGenerator.getGeneratorById(generator.get([]const u8, "id", "cubyz:polar_circles")); + self.climateGenerator.init(generator); + + generator = settings.getChild("caveBiomeGenerators"); + self.caveBiomeGenerators = CaveBiomeMap.CaveBiomeGenerator.getAndInitGenerators(main.globalAllocator, generator); + + generator = settings.getChild("caveGenerators"); + self.caveGenerators = CaveMap.CaveGenerator.getAndInitGenerators(main.globalAllocator, generator); + + generator = settings.getChild("structureMapGenerators"); + self.structureMapGenerators = StructureMap.StructureMapGenerator.getAndInitGenerators(main.globalAllocator, generator); + + generator = settings.getChild("generators"); + self.generators = BlockGenerator.getAndInitGenerators(main.globalAllocator, generator); + + const climateWavelengths = settings.getChild("climateWavelengths"); + self.climateWavelengths[0] = climateWavelengths.get(f32, "hot_cold", 2400); + self.climateWavelengths[1] = climateWavelengths.get(f32, "land_ocean", 3200); + self.climateWavelengths[2] = climateWavelengths.get(f32, "wet_dry", 2400); + self.climateWavelengths[3] = climateWavelengths.get(f32, "vegetation", 2400); + self.climateWavelengths[4] = climateWavelengths.get(f32, "mountain", 500); + + return self; + } + + pub fn deinit(self: TerrainGenerationProfile) void { + self.mapFragmentGenerator.deinit(); + self.climateGenerator.deinit(); + for (self.caveBiomeGenerators) |generator| { + generator.deinit(); + } + main.globalAllocator.free(self.caveBiomeGenerators); + for (self.caveGenerators) |generator| { + generator.deinit(); + } + main.globalAllocator.free(self.caveGenerators); + for (self.structureMapGenerators) |generator| { + generator.deinit(); + } + main.globalAllocator.free(self.structureMapGenerators); + for (self.generators) |generator| { + generator.deinit(); + } + main.globalAllocator.free(self.generators); + } }; pub fn globalInit() void { - SurfaceMap.globalInit(); - ClimateMap.globalInit(); - CaveBiomeMap.globalInit(); - CaveMap.globalInit(); - StructureMap.globalInit(); - const list = @import("chunkgen/_list.zig"); - inline for(@typeInfo(list).@"struct".decls) |decl| { - BlockGenerator.registerGenerator(@field(list, decl.name)); - } - const t1 = std.time.milliTimestamp(); - noise.BlueNoise.load(); - std.log.info("Blue noise took {} ms to load", .{std.time.milliTimestamp() -% t1}); + SurfaceMap.globalInit(); + ClimateMap.globalInit(); + CaveBiomeMap.globalInit(); + CaveMap.globalInit(); + StructureMap.globalInit(); + const list = @import("chunkgen/_list.zig"); + inline for (@typeInfo(list).@"struct".decls) |decl| { + BlockGenerator.registerGenerator(@field(list, decl.name)); + } + const t1 = std.time.milliTimestamp(); + noise.BlueNoise.load(); + std.log.info("Blue noise took {} ms to load", .{std.time.milliTimestamp() -% t1}); } pub fn globalDeinit() void { - CaveBiomeMap.globalDeinit(); - CaveMap.globalDeinit(); - StructureMap.globalDeinit(); - ClimateMap.globalDeinit(); - SurfaceMap.globalDeinit(); - BlockGenerator.generatorRegistry.clearAndFree(main.globalAllocator.allocator); + CaveBiomeMap.globalDeinit(); + CaveMap.globalDeinit(); + StructureMap.globalDeinit(); + ClimateMap.globalDeinit(); + SurfaceMap.globalDeinit(); + BlockGenerator.generatorRegistry.clearAndFree(main.globalAllocator.allocator); } pub fn init(profile: TerrainGenerationProfile) void { - CaveBiomeMap.init(profile); - CaveMap.init(profile); - StructureMap.init(profile); - ClimateMap.init(profile); - SurfaceMap.init(profile); + CaveBiomeMap.init(profile); + CaveMap.init(profile); + StructureMap.init(profile); + ClimateMap.init(profile); + SurfaceMap.init(profile); } pub fn deinit() void { - CaveBiomeMap.deinit(); - CaveMap.deinit(); - StructureMap.deinit(); - ClimateMap.deinit(); - SurfaceMap.deinit(); - LightMap.deinit(); + CaveBiomeMap.deinit(); + CaveMap.deinit(); + StructureMap.deinit(); + ClimateMap.deinit(); + SurfaceMap.deinit(); + LightMap.deinit(); } diff --git a/src/utils.zig b/src/utils.zig index 888f1e381f..3eab0f2de8 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -8,6 +8,7 @@ const NeverFailingAllocator = main.heap.NeverFailingAllocator; pub const file_monitor = @import("utils/file_monitor.zig"); pub const VirtualList = @import("utils/virtual_mem.zig").VirtualList; +pub const hash_zig = @import("utils/hash.zig"); pub const Compression = struct { // MARK: Compression pub fn deflate(allocator: NeverFailingAllocator, data: []const u8, level: std.compress.flate.deflate.Level) []u8 { diff --git a/src/utils/hash.zig b/src/utils/hash.zig new file mode 100644 index 0000000000..bd9a0c8897 --- /dev/null +++ b/src/utils/hash.zig @@ -0,0 +1,82 @@ +const std = @import("std"); +const main = @import("main"); + +const Biome = main.server.terrain.biomes; +const Structures = main.server.terrain.structures; +const StructureTable = Structures.StructureTable; + +pub fn hashGeneric(input: anytype) u64 { + const T = @TypeOf(input); + return switch(@typeInfo(T)) { + .bool => hashCombine(hashInt(@intFromBool(input)), 0xbf58476d1ce4e5b9), + .@"enum" => hashCombine(hashInt(@as(u64, @intFromEnum(input))), 0x94d049bb133111eb), + .int, .float => blk: { + const value = @as(std.meta.Int(.unsigned, @bitSizeOf(T)), @bitCast(input)); + break :blk hashInt(@as(u64, value)); + }, + .@"struct" => blk: { + if(@hasDecl(T, "getHash")) { + break :blk input.getHash(); + } + var result: u64 = hashGeneric(@typeName(T)); + inline for(@typeInfo(T).@"struct".fields) |field| { + const keyHash = hashGeneric(@as([]const u8, field.name)); + const valueHash = hashGeneric(@field(input, field.name)); + const keyValueHash = hashCombine(keyHash, valueHash); + result = hashCombine(result, keyValueHash); + } + break :blk result; + }, + .optional => if(input) |_input| hashGeneric(_input) else 0, + .pointer => switch(@typeInfo(T).pointer.size) { + .one => blk: { + if(@typeInfo(@typeInfo(T).pointer.child) == .@"fn") break :blk 0; + if(@typeInfo(T).pointer.child == Biome) return hashGeneric(input.id); + if(@typeInfo(T).pointer.child == anyopaque) break :blk 0; + if(@typeInfo(T).pointer.child == Structures) return hashGeneric(input.id); + if(@typeInfo(T).pointer.child == StructureTable) return hashGeneric(input.id); + break :blk hashGeneric(input.*); + }, + .slice => blk: { + var result: u64 = hashInt(input.len); + for(input) |val| { + const valueHash = hashGeneric(val); + result = hashCombine(result, valueHash); + } + break :blk result; + }, + else => @compileError("Unsupported type " ++ @typeName(T)), + }, + .array => blk: { + var result: u64 = 0xbf58476d1ce4e5b9; + for(input) |val| { + const valueHash = hashGeneric(val); + result = hashCombine(result, valueHash); + } + break :blk result; + }, + .vector => blk: { + var result: u64 = 0x94d049bb133111eb; + inline for(0..@typeInfo(T).vector.len) |i| { + const valueHash = hashGeneric(input[i]); + result = hashCombine(result, valueHash); + } + break :blk result; + }, + else => @compileError("Unsupported type " ++ @typeName(T)), + }; +} + +// https://stackoverflow.com/questions/5889238/why-is-xor-the-default-way-to-combine-hashes +pub fn hashCombine(left: u64, right: u64) u64 { + return left ^ (right +% 0x517cc1b727220a95 +% (left << 6) +% (left >> 2)); +} + +// https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key +pub fn hashInt(input: u64) u64 { + var x = input; + x = (x ^ (x >> 30))*%0xbf58476d1ce4e5b9; + x = (x ^ (x >> 27))*%0x94d049bb133111eb; + x = x ^ (x >> 31); + return x; +} From ea6b9571e16d7b2174913f5f70bd3b9f9cff4c0e Mon Sep 17 00:00:00 2001 From: BoySanic Date: Fri, 24 Oct 2025 16:56:37 -0700 Subject: [PATCH 2/3] Implement biome-independent structure tables, fixes #1805 --- src/assets.zig | 36 ++++- src/game.zig | 4 +- src/server/terrain/biomes.zig | 33 ++++ src/server/terrain/structures.zig | 34 +++- src/server/terrain/terrain.zig | 260 +++++++++++++++--------------- src/server/world.zig | 5 +- 6 files changed, 233 insertions(+), 139 deletions(-) diff --git a/src/assets.zig b/src/assets.zig index 9fd571e4bf..756fa260ef 100644 --- a/src/assets.zig +++ b/src/assets.zig @@ -14,6 +14,7 @@ const NeverFailingAllocator = main.heap.NeverFailingAllocator; const NeverFailingArenaAllocator = main.heap.NeverFailingArenaAllocator; const ListUnmanaged = main.ListUnmanaged; const files = main.files; +const structures_zig = main.server.terrain.structures; var commonAssetArena: NeverFailingAllocator = undefined; var common: Assets = undefined; @@ -30,6 +31,8 @@ pub const Assets = struct { tools: ZonHashMap, biomes: ZonHashMap, biomeMigrations: AddonNameToZonMap, + structureTables: ZonHashMap, + structureTableMigrations: AddonNameToZonMap, recipes: ZonHashMap, models: BytesHashMap, structureBuildingBlocks: ZonHashMap, @@ -45,6 +48,8 @@ pub const Assets = struct { .tools = .{}, .biomes = .{}, .biomeMigrations = .{}, + .structureTables = .{}, + .structureTableMigrations = .{}, .recipes = .{}, .models = .{}, .structureBuildingBlocks = .{}, @@ -60,6 +65,8 @@ pub const Assets = struct { self.tools.deinit(allocator.allocator); self.biomes.deinit(allocator.allocator); self.biomeMigrations.deinit(allocator.allocator); + self.structureTables.deinit(allocator.allocator); + self.structureTableMigrations.deinit(allocator.allocator); self.recipes.deinit(allocator.allocator); self.models.deinit(allocator.allocator); self.structureBuildingBlocks.deinit(allocator.allocator); @@ -75,6 +82,8 @@ pub const Assets = struct { .tools = self.tools.clone(allocator.allocator) catch unreachable, .biomes = self.biomes.clone(allocator.allocator) catch unreachable, .biomeMigrations = self.biomeMigrations.clone(allocator.allocator) catch unreachable, + .structureTables = self.structureTables.clone(allocator.allocator) catch unreachable, + .structureTableMigrations = self.structureTables.clone(allocator.allocator) catch unreachable, .recipes = self.recipes.clone(allocator.allocator) catch unreachable, .models = self.models.clone(allocator.allocator) catch unreachable, .structureBuildingBlocks = self.structureBuildingBlocks.clone(allocator.allocator) catch unreachable, @@ -91,6 +100,7 @@ pub const Assets = struct { addon.readAllZon(allocator, "blocks", true, &self.blocks, &self.blockMigrations); addon.readAllZon(allocator, "items", true, &self.items, &self.itemMigrations); addon.readAllZon(allocator, "tools", true, &self.tools, null); + addon.readAllZon(allocator, "structure_tables", true, &self.structureTables, &self.structureTableMigrations); addon.readAllZon(allocator, "biomes", true, &self.biomes, &self.biomeMigrations); addon.readAllZon(allocator, "recipes", false, &self.recipes, null); addon.readAllZon(allocator, "sbb", true, &self.structureBuildingBlocks, null); @@ -101,8 +111,8 @@ pub const Assets = struct { } fn log(self: *Assets, typ: enum {common, world}) void { std.log.info( - "Finished {s} assets reading with {} blocks ({} migrations), {} items ({} migrations), {} tools, {} biomes ({} migrations), {} recipes, {} structure building blocks, {} blueprints and {} particles", - .{@tagName(typ), self.blocks.count(), self.blockMigrations.count(), self.items.count(), self.itemMigrations.count(), self.tools.count(), self.biomes.count(), self.biomeMigrations.count(), self.recipes.count(), self.structureBuildingBlocks.count(), self.blueprints.count(), self.particles.count()}, + "Finished {s} assets reading with {} blocks ({} migrations), {} items ({} migrations), {} tools, {} biomes ({} migrations), {} structure tables ({} migrations), {} recipes, {} structure building blocks, {} blueprints and {} particles", + .{@tagName(typ), self.blocks.count(), self.blockMigrations.count(), self.items.count(), self.itemMigrations.count(), self.tools.count(), self.biomes.count(), self.biomeMigrations.count(), self.structureTables.count(), self.structureTableMigrations.count(), self.recipes.count(), self.structureBuildingBlocks.count(), self.blueprints.count(), self.particles.count()}, ); } @@ -321,6 +331,7 @@ fn createAssetStringID( } pub fn init() void { + structures_zig.init(); biomes_zig.init(); blocks_zig.init(); migrations_zig.init(); @@ -370,6 +381,10 @@ fn registerBiome(numericId: u32, stringId: []const u8, zon: ZonElement) void { biomes_zig.register(stringId, numericId, zon); } +fn registerStructureTable(numericId: u32, stringId: []const u8, zon: ZonElement) void { + if(zon == .null) std.log.err("Missing StructureTable: {s}. Will not replace.", .{stringId}); + structures_zig.register(stringId, numericId, zon); +} fn registerRecipesFromZon(zon: ZonElement) void { items_zig.registerRecipes(zon); } @@ -478,7 +493,7 @@ pub const Palette = struct { // MARK: Palette var loadedAssets: bool = false; -pub fn loadWorldAssets(assetFolder: []const u8, blockPalette: *Palette, itemPalette: *Palette, toolPalette: *Palette, biomePalette: *Palette) !void { // MARK: loadWorldAssets() +pub fn loadWorldAssets(assetFolder: []const u8, blockPalette: *Palette, itemPalette: *Palette, toolPalette: *Palette, biomePalette: *Palette, structureTablePalette: *Palette) !void { // MARK: loadWorldAssets() if(loadedAssets) return; // The assets already got loaded by the server. loadedAssets = true; @@ -608,6 +623,21 @@ pub fn loadWorldAssets(assetFolder: []const u8, blockPalette: *Palette, itemPale while(iterator.next()) |entry| { particles_zig.ParticleManager.register(assetFolder, entry.key_ptr.*, entry.value_ptr.*); } + // StructureTables: + + var nextStructureTableNumericId: u32 = 0; + for(structureTablePalette.palette.items) |id| { + registerStructureTable(nextStructureTableNumericId, id, worldAssets.structureTables.get(id) orelse .null); + nextStructureTableNumericId += 1; + } + iterator = worldAssets.structureTables.iterator(); + while(iterator.next()) |entry| { + if(structures_zig.hasRegistered(entry.key_ptr.*)) continue; + registerStructureTable(nextStructureTableNumericId, entry.key_ptr.*, entry.value_ptr.*); + structureTablePalette.add(entry.key_ptr.*); + nextStructureTableNumericId += 1; + } + // TODO: structures_zig.finishloading(); -- Is this needed? // Biomes: var nextBiomeNumericId: u32 = 0; diff --git a/src/game.zig b/src/game.zig index c5057350b9..ef020026ca 100644 --- a/src/game.zig +++ b/src/game.zig @@ -611,6 +611,7 @@ pub const World = struct { // MARK: World itemPalette: *assets.Palette = undefined, toolPalette: *assets.Palette = undefined, biomePalette: *assets.Palette = undefined, + structureTablePalette: *assets.Palette = undefined, itemDrops: ClientItemDropManager = undefined, playerBiome: Atomic(*const main.server.terrain.biomes.Biome) = undefined, @@ -668,6 +669,7 @@ pub const World = struct { // MARK: World pub fn finishHandshake(self: *World, zon: ZonElement) !void { // TODO: Consider using a per-world allocator. + self.structureTablePalette = try assets.Palette.init(main.globalAllocator, zon.getChild("structureTablePalette"), null); self.blockPalette = try assets.Palette.init(main.globalAllocator, zon.getChild("blockPalette"), "cubyz:air"); errdefer self.blockPalette.deinit(); self.biomePalette = try assets.Palette.init(main.globalAllocator, zon.getChild("biomePalette"), null); @@ -680,7 +682,7 @@ pub const World = struct { // MARK: World const path = std.fmt.allocPrint(main.stackAllocator.allocator, "{s}/serverAssets", .{main.files.cubyzDirStr()}) catch unreachable; defer main.stackAllocator.free(path); - try assets.loadWorldAssets(path, self.blockPalette, self.itemPalette, self.toolPalette, self.biomePalette); + try assets.loadWorldAssets(path, self.blockPalette, self.itemPalette, self.toolPalette, self.biomePalette, self.structureTablePalette); Player.id = zon.get(u32, "player_id", std.math.maxInt(u32)); Player.inventory = Inventory.init(main.globalAllocator, Player.inventorySize, .normal, .{.playerInventory = Player.id}, .{}); Player.loadFrom(zon.getChild("player")); diff --git a/src/server/terrain/biomes.zig b/src/server/terrain/biomes.zig index d7cdaf64f1..bed1efabf5 100644 --- a/src/server/terrain/biomes.zig +++ b/src/server/terrain/biomes.zig @@ -179,10 +179,18 @@ pub const Biome = struct { // MARK: Biome preferredMusic: []const u8, // TODO: Support multiple possibilities that are chosen based on time and danger. isValidPlayerSpawn: bool, chance: f32, + biomeTags: [][]const u8, pub fn init(self: *Biome, id: []const u8, paletteId: u32, zon: ZonElement) void { const minRadius = zon.get(f32, "radius", zon.get(f32, "minRadius", 256)); const maxRadius = zon.get(f32, "maxRadius", minRadius); + const biome_tags = zon.getChild("biomeTags"); + var tags_list = main.ListUnmanaged([]const u8){}; + if(biome_tags.toSlice().len > 0) { + for(biome_tags.toSlice()) |tag| { + tags_list.append(main.globalAllocator, tag.toString(main.globalAllocator)); + } + } self.* = Biome{ .id = main.globalAllocator.dupe(u8, id), .paletteId = paletteId, @@ -218,6 +226,7 @@ pub const Biome = struct { // MARK: Biome .isValidPlayerSpawn = zon.get(bool, "validPlayerSpawn", false), .chance = zon.get(f32, "chance", if(zon == .null) 0 else 1), .maxSubBiomeCount = zon.get(f32, "maxSubBiomeCount", std.math.floatMax(f32)), + .biomeTags = tags_list.items, }; if(self.minHeight > self.maxHeight) { std.log.err("Biome {s} has invalid height range ({}, {})", .{self.id, self.minHeight, self.maxHeight}); @@ -253,12 +262,36 @@ pub const Biome = struct { // MARK: Biome var vegetation = main.ListUnmanaged(SimpleStructureModel){}; var totalChance: f32 = 0; defer vegetation.deinit(main.stackAllocator); + // Add structures from the biome's internal structure table for(structures.toSlice()) |elem| { if(SimpleStructureModel.initModel(elem)) |model| { vegetation.append(main.stackAllocator, model); totalChance += model.chance; } } + // Add structures from structure tables outside of the biome's internal table. + const structure_tables = main.server.terrain.structures.getSlice(); + for(structure_tables) |table| { + std.log.debug("structure table biomeTags len: {}", .{table.biomeTags.len}); + if(self.biomeTags.len > 0) { + std.log.debug("Biome tags len: {}", .{self.biomeTags.len}); + for(self.biomeTags) |tag| { + for(table.biomeTags) |st_tag| { + if(std.mem.eql(u8, tag, st_tag)) { + for(table.structures) |model| { + vegetation.append(main.stackAllocator, model); + totalChance += model.chance; + } + } + } + } + } else if(table.biomeTags.len == 0) { + for(table.structures) |model| { + vegetation.append(main.stackAllocator, model); + totalChance += model.chance; + } + } + } if(totalChance > 1) { for(vegetation.items) |*model| { model.chance /= totalChance; diff --git a/src/server/terrain/structures.zig b/src/server/terrain/structures.zig index a8a62233bb..39a6938fe8 100644 --- a/src/server/terrain/structures.zig +++ b/src/server/terrain/structures.zig @@ -78,12 +78,21 @@ pub const StructureTable = struct { id: []const u8, biomeTags: [][]const u8, structures: []SimpleStructureModel = &.{}, + paletteId: u32, + + pub fn init(self: *StructureTable, id: []const u8, paletteId: u32, zon: ZonElement) void { + const biome_tags = zon.getChild("biomeTags"); + var tags_list = main.ListUnmanaged([]const u8){}; + for(biome_tags.toSlice()) |tag| { + tags_list.append(main.globalAllocator, tag.toString(main.globalAllocator)); + } - pub fn init(self: *StructureTable, id: []const u8, zon: ZonElement) void { self.* = .{ .id = main.globalAllocator.dupe(u8, id), - .biomeTags = zon.getChild("biomeTags"), + .paletteId = paletteId, + .biomeTags = tags_list.items, }; + const structures = zon.getChild("structures"); var structure_list = main.ListUnmanaged(SimpleStructureModel){}; var total_chance: f32 = 0; @@ -105,21 +114,38 @@ pub const StructureTable = struct { }; var structureTables: main.List(StructureTable) = undefined; -var structureTablesById: std.StringHashMap(*StructureTable) = undefined; +var structureTablesById: std.StringHashMap(StructureTable) = undefined; pub fn init() void { structureTables = .init(main.globalAllocator); structureTablesById = .init(main.globalAllocator.allocator); for(structureTables.items) |structureTable| { - structureTablesById.put(structureTable.id, structureTable); + structureTablesById.put(structureTable.id, structureTable) catch unreachable; } } +pub fn register(id: []const u8, paletteId: u32, zon: ZonElement) void { + var structure_table: StructureTable = undefined; + structure_table.init(id, paletteId, zon); + structureTables.append(structure_table); +} +pub fn hasRegistered(id: []const u8) bool { + for(structureTables.items) |entry| { + if(std.mem.eql(u8, id, entry.id)) { + return true; + } + } + return false; +} + pub fn getById(id: []const u8) *const StructureTable { return structureTablesById.get(id) orelse { std.log.err("Couldn't find structure table with id {s}. Replacing it with some other Structure table.", .{id}); return &structureTables[0]; }; } +pub fn getSlice() []StructureTable { + return structureTables.items; +} pub fn deinit() void { SimpleStructureModel.modelRegistry.clearAndFree(main.globalAllocator.allocator); diff --git a/src/server/terrain/terrain.zig b/src/server/terrain/terrain.zig index 7a93e31504..0687a7389d 100644 --- a/src/server/terrain/terrain.zig +++ b/src/server/terrain/terrain.zig @@ -26,152 +26,152 @@ pub const structure_building_blocks = @import("structure_building_blocks.zig"); /// A generator for setting the actual Blocks in each Chunk. pub const BlockGenerator = struct { - init: *const fn (parameters: ZonElement) void, - deinit: *const fn () void, - generate: *const fn (seed: u64, chunk: *main.chunk.ServerChunk, caveMap: CaveMap.CaveMapView, biomeMap: CaveBiomeMap.CaveBiomeMapView) void, - /// Used to prioritize certain generators over others. - priority: i32, - /// To avoid duplicate seeds in similar generation algorithms, the SurfaceGenerator xors the world-seed with the generator specific seed. - generatorSeed: u64, - - var generatorRegistry: std.StringHashMapUnmanaged(BlockGenerator) = .{}; - - pub fn registerGenerator(comptime GeneratorType: type) void { - const self = BlockGenerator{ - .init = &GeneratorType.init, - .deinit = &GeneratorType.deinit, - .generate = &GeneratorType.generate, - .priority = GeneratorType.priority, - .generatorSeed = GeneratorType.generatorSeed, - }; - generatorRegistry.put(main.globalAllocator.allocator, GeneratorType.id, self) catch unreachable; - } - - fn getAndInitGenerators(allocator: NeverFailingAllocator, settings: ZonElement) []BlockGenerator { - const list = allocator.alloc(BlockGenerator, generatorRegistry.size); - var iterator = generatorRegistry.iterator(); - var i: usize = 0; - while (iterator.next()) |generator| { - list[i] = generator.value_ptr.*; - list[i].init(settings.getChild(generator.key_ptr.*)); - i += 1; - } - const lessThan = struct { - fn lessThan(_: void, lhs: BlockGenerator, rhs: BlockGenerator) bool { - return lhs.priority < rhs.priority; - } - }.lessThan; - std.sort.insertion(BlockGenerator, list, {}, lessThan); - return list; - } + init: *const fn(parameters: ZonElement) void, + deinit: *const fn() void, + generate: *const fn(seed: u64, chunk: *main.chunk.ServerChunk, caveMap: CaveMap.CaveMapView, biomeMap: CaveBiomeMap.CaveBiomeMapView) void, + /// Used to prioritize certain generators over others. + priority: i32, + /// To avoid duplicate seeds in similar generation algorithms, the SurfaceGenerator xors the world-seed with the generator specific seed. + generatorSeed: u64, + + var generatorRegistry: std.StringHashMapUnmanaged(BlockGenerator) = .{}; + + pub fn registerGenerator(comptime GeneratorType: type) void { + const self = BlockGenerator{ + .init = &GeneratorType.init, + .deinit = &GeneratorType.deinit, + .generate = &GeneratorType.generate, + .priority = GeneratorType.priority, + .generatorSeed = GeneratorType.generatorSeed, + }; + generatorRegistry.put(main.globalAllocator.allocator, GeneratorType.id, self) catch unreachable; + } + + fn getAndInitGenerators(allocator: NeverFailingAllocator, settings: ZonElement) []BlockGenerator { + const list = allocator.alloc(BlockGenerator, generatorRegistry.size); + var iterator = generatorRegistry.iterator(); + var i: usize = 0; + while(iterator.next()) |generator| { + list[i] = generator.value_ptr.*; + list[i].init(settings.getChild(generator.key_ptr.*)); + i += 1; + } + const lessThan = struct { + fn lessThan(_: void, lhs: BlockGenerator, rhs: BlockGenerator) bool { + return lhs.priority < rhs.priority; + } + }.lessThan; + std.sort.insertion(BlockGenerator, list, {}, lessThan); + return list; + } }; /// Lists all the Generators and Biomes that should be used for a given world. /// TODO: Generator/Biome blackslisting (from the world creation menu). /// TODO: Generator settings (from the world creation menu). pub const TerrainGenerationProfile = struct { - mapFragmentGenerator: SurfaceMap.MapGenerator = undefined, - climateGenerator: ClimateMap.ClimateMapGenerator = undefined, - caveBiomeGenerators: []CaveBiomeMap.CaveBiomeGenerator = undefined, - caveGenerators: []CaveMap.CaveGenerator = undefined, - structureMapGenerators: []StructureMap.StructureMapGenerator = undefined, - generators: []BlockGenerator = undefined, - climateWavelengths: [5]f32 = undefined, - seed: u64, - - pub fn init(settings: ZonElement, seed: u64) !TerrainGenerationProfile { - var self = TerrainGenerationProfile{ - .seed = seed, - }; - var generator = settings.getChild("mapGenerator"); - self.mapFragmentGenerator = try SurfaceMap.MapGenerator.getGeneratorById(generator.get([]const u8, "id", "cubyz:mapgen_v1")); - self.mapFragmentGenerator.init(generator); - - generator = settings.getChild("climateGenerator"); - self.climateGenerator = try ClimateMap.ClimateMapGenerator.getGeneratorById(generator.get([]const u8, "id", "cubyz:polar_circles")); - self.climateGenerator.init(generator); - - generator = settings.getChild("caveBiomeGenerators"); - self.caveBiomeGenerators = CaveBiomeMap.CaveBiomeGenerator.getAndInitGenerators(main.globalAllocator, generator); - - generator = settings.getChild("caveGenerators"); - self.caveGenerators = CaveMap.CaveGenerator.getAndInitGenerators(main.globalAllocator, generator); - - generator = settings.getChild("structureMapGenerators"); - self.structureMapGenerators = StructureMap.StructureMapGenerator.getAndInitGenerators(main.globalAllocator, generator); - - generator = settings.getChild("generators"); - self.generators = BlockGenerator.getAndInitGenerators(main.globalAllocator, generator); - - const climateWavelengths = settings.getChild("climateWavelengths"); - self.climateWavelengths[0] = climateWavelengths.get(f32, "hot_cold", 2400); - self.climateWavelengths[1] = climateWavelengths.get(f32, "land_ocean", 3200); - self.climateWavelengths[2] = climateWavelengths.get(f32, "wet_dry", 2400); - self.climateWavelengths[3] = climateWavelengths.get(f32, "vegetation", 2400); - self.climateWavelengths[4] = climateWavelengths.get(f32, "mountain", 500); - - return self; - } - - pub fn deinit(self: TerrainGenerationProfile) void { - self.mapFragmentGenerator.deinit(); - self.climateGenerator.deinit(); - for (self.caveBiomeGenerators) |generator| { - generator.deinit(); - } - main.globalAllocator.free(self.caveBiomeGenerators); - for (self.caveGenerators) |generator| { - generator.deinit(); - } - main.globalAllocator.free(self.caveGenerators); - for (self.structureMapGenerators) |generator| { - generator.deinit(); - } - main.globalAllocator.free(self.structureMapGenerators); - for (self.generators) |generator| { - generator.deinit(); - } - main.globalAllocator.free(self.generators); - } + mapFragmentGenerator: SurfaceMap.MapGenerator = undefined, + climateGenerator: ClimateMap.ClimateMapGenerator = undefined, + caveBiomeGenerators: []CaveBiomeMap.CaveBiomeGenerator = undefined, + caveGenerators: []CaveMap.CaveGenerator = undefined, + structureMapGenerators: []StructureMap.StructureMapGenerator = undefined, + generators: []BlockGenerator = undefined, + climateWavelengths: [5]f32 = undefined, + seed: u64, + + pub fn init(settings: ZonElement, seed: u64) !TerrainGenerationProfile { + var self = TerrainGenerationProfile{ + .seed = seed, + }; + var generator = settings.getChild("mapGenerator"); + self.mapFragmentGenerator = try SurfaceMap.MapGenerator.getGeneratorById(generator.get([]const u8, "id", "cubyz:mapgen_v1")); + self.mapFragmentGenerator.init(generator); + + generator = settings.getChild("climateGenerator"); + self.climateGenerator = try ClimateMap.ClimateMapGenerator.getGeneratorById(generator.get([]const u8, "id", "cubyz:polar_circles")); + self.climateGenerator.init(generator); + + generator = settings.getChild("caveBiomeGenerators"); + self.caveBiomeGenerators = CaveBiomeMap.CaveBiomeGenerator.getAndInitGenerators(main.globalAllocator, generator); + + generator = settings.getChild("caveGenerators"); + self.caveGenerators = CaveMap.CaveGenerator.getAndInitGenerators(main.globalAllocator, generator); + + generator = settings.getChild("structureMapGenerators"); + self.structureMapGenerators = StructureMap.StructureMapGenerator.getAndInitGenerators(main.globalAllocator, generator); + + generator = settings.getChild("generators"); + self.generators = BlockGenerator.getAndInitGenerators(main.globalAllocator, generator); + + const climateWavelengths = settings.getChild("climateWavelengths"); + self.climateWavelengths[0] = climateWavelengths.get(f32, "hot_cold", 2400); + self.climateWavelengths[1] = climateWavelengths.get(f32, "land_ocean", 3200); + self.climateWavelengths[2] = climateWavelengths.get(f32, "wet_dry", 2400); + self.climateWavelengths[3] = climateWavelengths.get(f32, "vegetation", 2400); + self.climateWavelengths[4] = climateWavelengths.get(f32, "mountain", 500); + + return self; + } + + pub fn deinit(self: TerrainGenerationProfile) void { + self.mapFragmentGenerator.deinit(); + self.climateGenerator.deinit(); + for(self.caveBiomeGenerators) |generator| { + generator.deinit(); + } + main.globalAllocator.free(self.caveBiomeGenerators); + for(self.caveGenerators) |generator| { + generator.deinit(); + } + main.globalAllocator.free(self.caveGenerators); + for(self.structureMapGenerators) |generator| { + generator.deinit(); + } + main.globalAllocator.free(self.structureMapGenerators); + for(self.generators) |generator| { + generator.deinit(); + } + main.globalAllocator.free(self.generators); + } }; pub fn globalInit() void { - SurfaceMap.globalInit(); - ClimateMap.globalInit(); - CaveBiomeMap.globalInit(); - CaveMap.globalInit(); - StructureMap.globalInit(); - const list = @import("chunkgen/_list.zig"); - inline for (@typeInfo(list).@"struct".decls) |decl| { - BlockGenerator.registerGenerator(@field(list, decl.name)); - } - const t1 = std.time.milliTimestamp(); - noise.BlueNoise.load(); - std.log.info("Blue noise took {} ms to load", .{std.time.milliTimestamp() -% t1}); + SurfaceMap.globalInit(); + ClimateMap.globalInit(); + CaveBiomeMap.globalInit(); + CaveMap.globalInit(); + StructureMap.globalInit(); + const list = @import("chunkgen/_list.zig"); + inline for(@typeInfo(list).@"struct".decls) |decl| { + BlockGenerator.registerGenerator(@field(list, decl.name)); + } + const t1 = std.time.milliTimestamp(); + noise.BlueNoise.load(); + std.log.info("Blue noise took {} ms to load", .{std.time.milliTimestamp() -% t1}); } pub fn globalDeinit() void { - CaveBiomeMap.globalDeinit(); - CaveMap.globalDeinit(); - StructureMap.globalDeinit(); - ClimateMap.globalDeinit(); - SurfaceMap.globalDeinit(); - BlockGenerator.generatorRegistry.clearAndFree(main.globalAllocator.allocator); + CaveBiomeMap.globalDeinit(); + CaveMap.globalDeinit(); + StructureMap.globalDeinit(); + ClimateMap.globalDeinit(); + SurfaceMap.globalDeinit(); + BlockGenerator.generatorRegistry.clearAndFree(main.globalAllocator.allocator); } pub fn init(profile: TerrainGenerationProfile) void { - CaveBiomeMap.init(profile); - CaveMap.init(profile); - StructureMap.init(profile); - ClimateMap.init(profile); - SurfaceMap.init(profile); + CaveBiomeMap.init(profile); + CaveMap.init(profile); + StructureMap.init(profile); + ClimateMap.init(profile); + SurfaceMap.init(profile); } pub fn deinit() void { - CaveBiomeMap.deinit(); - CaveMap.deinit(); - StructureMap.deinit(); - ClimateMap.deinit(); - SurfaceMap.deinit(); - LightMap.deinit(); + CaveBiomeMap.deinit(); + CaveMap.deinit(); + StructureMap.deinit(); + ClimateMap.deinit(); + SurfaceMap.deinit(); + LightMap.deinit(); } diff --git a/src/server/world.zig b/src/server/world.zig index 6fd4bf8d6c..2bdc1ddfe4 100644 --- a/src/server/world.zig +++ b/src/server/world.zig @@ -514,6 +514,7 @@ pub const ServerWorld = struct { // MARK: ServerWorld itemPalette: *main.assets.Palette = undefined, toolPalette: *main.assets.Palette = undefined, biomePalette: *main.assets.Palette = undefined, + structureTablePalette: *main.assets.Palette = undefined, chunkManager: ChunkManager = undefined, gameTime: i64 = 0, @@ -590,13 +591,15 @@ pub const ServerWorld = struct { // MARK: ServerWorld self.toolPalette = try loadPalette(arena, path, "tool_palette", null); errdefer self.toolPalette.deinit(); + self.structureTablePalette = try loadPalette(arena, path, "structureTable_palette", null); + errdefer self.structureTablePalette.deinit(); self.biomePalette = try loadPalette(arena, path, "biome_palette", null); errdefer self.biomePalette.deinit(); errdefer main.assets.unloadAssets(); self.seed = try self.wio.loadWorldSeed(); - try main.assets.loadWorldAssets(try std.fmt.allocPrint(arena.allocator, "{s}/saves/{s}/assets/", .{files.cubyzDirStr(), path}), self.blockPalette, self.itemPalette, self.toolPalette, self.biomePalette); + try main.assets.loadWorldAssets(try std.fmt.allocPrint(arena.allocator, "{s}/saves/{s}/assets/", .{files.cubyzDirStr(), path}), self.blockPalette, self.itemPalette, self.toolPalette, self.biomePalette, self.structureTablePalette); // Store the block palette now that everything is loaded. try files.cubyzDir().writeZon(try std.fmt.allocPrint(arena.allocator, "saves/{s}/palette.zig.zon", .{path}), self.blockPalette.storeToZon(arena)); try files.cubyzDir().writeZon(try std.fmt.allocPrint(arena.allocator, "saves/{s}/item_palette.zig.zon", .{path}), self.itemPalette.storeToZon(arena)); From 123d76710db85368db616dcf25ac3d03fe8874fa Mon Sep 17 00:00:00 2001 From: BoySanic Date: Fri, 24 Oct 2025 17:08:53 -0700 Subject: [PATCH 3/3] Forgot to add the structure_table example... --- .../structure_tables/torches_everywhere.zig.zon | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 assets/cubyz/structure_tables/torches_everywhere.zig.zon diff --git a/assets/cubyz/structure_tables/torches_everywhere.zig.zon b/assets/cubyz/structure_tables/torches_everywhere.zig.zon new file mode 100644 index 0000000000..6a03dcfd54 --- /dev/null +++ b/assets/cubyz/structure_tables/torches_everywhere.zig.zon @@ -0,0 +1,14 @@ +.{ + .id = "cubyz:torches_everywhere", + .biomeTags = .{}, + + .structures = .{ + .{ + .id = "cubyz:simple_vegetation", + .chance = 0.1, + .block = "cubyz:torch", + .height = 1, + .height_variation = 0, + }, + }, +}