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, + }, + }, +} diff --git a/src/assets.zig b/src/assets.zig index 4215dcd672..9376267be1 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 common: Assets = undefined; @@ -29,6 +30,8 @@ pub const Assets = struct { tools: ZonHashMap, biomes: ZonHashMap, biomeMigrations: AddonNameToZonMap, + structureTables: ZonHashMap, + structureTableMigrations: AddonNameToZonMap, recipes: ZonHashMap, models: BytesHashMap, structureBuildingBlocks: ZonHashMap, @@ -44,6 +47,8 @@ pub const Assets = struct { .tools = .{}, .biomes = .{}, .biomeMigrations = .{}, + .structureTables = .{}, + .structureTableMigrations = .{}, .recipes = .{}, .models = .{}, .structureBuildingBlocks = .{}, @@ -59,6 +64,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); @@ -74,6 +81,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, @@ -90,6 +99,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); @@ -100,8 +110,8 @@ pub const Assets = struct { } fn log(self: *Assets, typ: enum {common, world}) void { std.log.info( - "Finished {s} assets reading with {} blocks, {} items, {} tools, {} biomes, {} recipes, {} structure building blocks, {} blueprints and {} particles", - .{@tagName(typ), self.blocks.count(), self.items.count(), self.tools.count(), self.biomes.count(), self.recipes.count(), self.structureBuildingBlocks.count(), self.blueprints.count(), self.particles.count()}, + "Finished {s} assets reading with {} blocks, {} items, {} tools, {} biomes, {} structure tables, {} recipes, {} structure building blocks, {} blueprints and {} particles", + .{@tagName(typ), self.blocks.count(), self.items.count(), self.tools.count(), self.biomes.count(), self.structureTables.count(), self.recipes.count(), self.structureBuildingBlocks.count(), self.blueprints.count(), self.particles.count()}, ); } @@ -320,6 +330,7 @@ fn createAssetStringID( } pub fn init() void { + structures_zig.init(); biomes_zig.init(); common = .init(); @@ -365,6 +376,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); } @@ -473,7 +488,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; @@ -605,6 +620,20 @@ 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; + } // Biomes: var nextBiomeNumericId: u32 = 0; diff --git a/src/game.zig b/src/game.zig index 489a42a2ed..8a60d32de4 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, @@ -671,6 +672,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); @@ -683,7 +685,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 94c1ca4243..aee351e63f 100644 --- a/src/server/terrain/biomes.zig +++ b/src/server/terrain/biomes.zig @@ -9,67 +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(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(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) = .{}; - - 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.globalArena.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, @@ -135,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, @@ -310,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.worldArena.dupe(u8, id), .paletteId = paletteId, @@ -349,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(minRadius > maxRadius) { std.log.err("Biome {s} has invalid radius range ({d}, {d})", .{self.id, minRadius, maxRadius}); @@ -390,12 +268,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; @@ -411,7 +313,7 @@ pub const Biome = struct { // MARK: Biome } fn getCheckSum(self: *Biome) u64 { - return hashGeneric(self.*); + return hash.hashGeneric(self.*); } }; @@ -631,11 +533,36 @@ pub fn init() void { pub fn reset() void { finishedLoading = false; +<<<<<<< HEAD + for(biomes.items) |*biome| { + biome.deinit(); + } + for(caveBiomes.items) |*biome| { + biome.deinit(); + } + biomes.clearRetainingCapacity(); + caveBiomes.clearRetainingCapacity(); + biomesById.clearRetainingCapacity(); + biomesByIndex.clearRetainingCapacity(); + byTypeBiomes.deinit(main.globalAllocator); +} + +pub fn deinit() void { + for(biomes.items) |*biome| { + biome.deinit(); + } + biomes.deinit(); + caveBiomes.deinit(); + biomesById.deinit(); + biomesByIndex.deinit(main.globalAllocator); + // TODO? byTypeBiomes.deinit(main.globalAllocator); +======= biomes = .{}; caveBiomes = .{}; biomesById = .{}; biomesByIndex = .{}; byTypeBiomes = undefined; +>>>>>>> upstream } 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 c9180f8308..f58ee93cb2 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 46bed79584..d76b5a3b55 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 c99ce02596..ac977ba4bd 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 8bc801d913..fc4c186443 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 321fafef24..1372fd1a9b 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 e61d84498c..6091cc0d71 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 02a2b0f517..d8ccc14e14 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 24650f9f59..308e27c3a3 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 52e72b2245..dbc609975c 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; @@ -121,7 +122,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..39a6938fe8 --- /dev/null +++ b/src/server/terrain/structures.zig @@ -0,0 +1,152 @@ +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 = &.{}, + 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)); + } + + self.* = .{ + .id = main.globalAllocator.dupe(u8, id), + .paletteId = paletteId, + .biomeTags = tags_list.items, + }; + + 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) 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 f5f9747078..baf1d069ea 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"); diff --git a/src/server/world.zig b/src/server/world.zig index 9754599225..0d73ffc8b3 100644 --- a/src/server/world.zig +++ b/src/server/world.zig @@ -513,6 +513,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, @@ -589,13 +590,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)); 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; +}