diff --git a/src/assets.zig b/src/assets.zig index bcc9cf3c..77e7a5fe 100644 --- a/src/assets.zig +++ b/src/assets.zig @@ -21,6 +21,7 @@ var commonBiomes: std.StringHashMap(ZonElement) = undefined; var commonBiomeMigrations: std.StringHashMap(ZonElement) = undefined; var commonRecipes: std.StringHashMap(ZonElement) = undefined; var commonModels: std.StringHashMap([]const u8) = undefined; +var commonStructureBuildingBlocks: std.StringHashMap(ZonElement) = undefined; var commonBlueprints: std.StringHashMap([]u8) = undefined; pub fn init() void { @@ -37,6 +38,7 @@ pub fn init() void { commonBiomeMigrations = .init(arenaAllocator.allocator); commonRecipes = .init(arenaAllocator.allocator); commonModels = .init(arenaAllocator.allocator); + commonStructureBuildingBlocks = .init(arenaAllocator.allocator); commonBlueprints = .init(arenaAllocator.allocator); readAssets( @@ -50,12 +52,13 @@ pub fn init() void { &commonBiomeMigrations, &commonRecipes, &commonModels, + &commonStructureBuildingBlocks, &commonBlueprints, ); std.log.info( - "Finished assets init with {} blocks ({} migrations), {} items, {} tools, {} biomes ({} migrations), {} recipes, and {} blueprints", - .{commonBlocks.count(), commonBlockMigrations.count(), commonItems.count(), commonTools.count(), commonBiomes.count(), commonBiomeMigrations.count(), commonRecipes.count(), commonBlueprints.count()}, + "Finished assets init with {} blocks ({} migrations), {} items, {} tools, {} biomes ({} migrations), {} recipes, {} structure building blocks and {} blueprints", + .{commonBlocks.count(), commonBlockMigrations.count(), commonItems.count(), commonTools.count(), commonBiomes.count(), commonBiomeMigrations.count(), commonRecipes.count(), commonStructureBuildingBlocks.count(), commonBlueprints.count()}, ); } @@ -267,6 +270,7 @@ pub fn readAssets( biomeMigrations: *std.StringHashMap(ZonElement), recipes: *std.StringHashMap(ZonElement), models: *std.StringHashMap([]const u8), + structureBuildingBlocks: *std.StringHashMap(ZonElement), blueprints: *std.StringHashMap([]u8), ) void { var addons = main.List(std.fs.Dir).init(main.stackAllocator); @@ -305,6 +309,7 @@ pub fn readAssets( readAllZonFilesInAddons(externalAllocator, addons, addonNames, "biomes", true, biomes, biomeMigrations); readAllZonFilesInAddons(externalAllocator, addons, addonNames, "recipes", false, recipes, null); readAllObjFilesInAddonsHashmap(externalAllocator, addons, addonNames, "models", models); + readAllZonFilesInAddons(externalAllocator, addons, addonNames, "sbb", true, structureBuildingBlocks, null); readAllBlueprintFilesInAddons(externalAllocator, addons, addonNames, "blueprints", blueprints); } @@ -472,6 +477,8 @@ pub fn loadWorldAssets(assetFolder: []const u8, blockPalette: *Palette, itemPale defer recipes.clearAndFree(); var models = commonModels.cloneWithAllocator(main.stackAllocator.allocator) catch unreachable; defer models.clearAndFree(); + var structureBuildingBlocks = commonStructureBuildingBlocks.cloneWithAllocator(main.stackAllocator.allocator) catch unreachable; + defer structureBuildingBlocks.clearAndFree(); var blueprints = commonBlueprints.cloneWithAllocator(main.stackAllocator.allocator) catch unreachable; defer blueprints.clearAndFree(); @@ -486,6 +493,7 @@ pub fn loadWorldAssets(assetFolder: []const u8, blockPalette: *Palette, itemPale &biomeMigrations, &recipes, &models, + &structureBuildingBlocks, &blueprints, ); errdefer unloadAssets(); @@ -594,6 +602,7 @@ pub fn loadWorldAssets(assetFolder: []const u8, blockPalette: *Palette, itemPale } try sbb.registerBlueprints(&blueprints); + try sbb.registerSBB(&structureBuildingBlocks); // Biomes: var nextBiomeNumericId: u32 = 0; diff --git a/src/server/terrain/structure_building_blocks.zig b/src/server/terrain/structure_building_blocks.zig index e6e1b5bf..188341f1 100644 --- a/src/server/terrain/structure_building_blocks.zig +++ b/src/server/terrain/structure_building_blocks.zig @@ -14,6 +14,7 @@ const NeverFailingAllocator = main.heap.NeverFailingAllocator; var arena = main.heap.NeverFailingArenaAllocator.init(main.globalAllocator); const arenaAllocator = arena.allocator(); +var structureCache: std.StringHashMapUnmanaged(StructureBuildingBlock) = .{}; var blueprintCache: std.StringHashMapUnmanaged(*[4]BlueprintEntry) = .{}; var childrenToResolve: List(struct {parentId: []const u8, colorName: []const u8, colorIndex: usize, childIndex: usize, structureId: []const u8}) = undefined; @@ -101,6 +102,103 @@ pub fn isOriginBlock(block: Block) bool { return block.typ == originBlockNumericId; } +pub const StructureBuildingBlock = struct { + children: []AliasTable(Child), + blueprints: *[4]BlueprintEntry, + + fn initFromZon(stringId: []const u8, zon: ZonElement) !StructureBuildingBlock { + const blueprintId = zon.get(?[]const u8, "blueprint", null) orelse { + std.log.err("['{s}'] Missing blueprint field.", .{stringId}); + return error.MissingBlueprintIdField; + }; + const blueprints = blueprintCache.get(blueprintId) orelse { + std.log.err("['{s}'] Could not find blueprint '{s}'.", .{stringId, blueprintId}); + return error.MissingBlueprint; + }; + const self = StructureBuildingBlock{ + .children = arenaAllocator.alloc(AliasTable(Child), childBlockStringId.items.len), + .blueprints = blueprints, + }; + const childrenZon = zon.getChild("children"); + for(childBlockStringId.items, 0..) |colorName, colorIndex| { + self.children[colorIndex] = try initChildTableFromZon(stringId, colorName, colorIndex, childrenZon.getChild(colorName)); + } + return self; + } + pub fn getBlueprint(self: StructureBuildingBlock, rotation: Degrees) *BlueprintEntry { + return &self.blueprints[@intFromEnum(rotation)]; + } + pub fn pickChild(self: StructureBuildingBlock, block: BlueprintEntry.StructureBlock, seed: *u64) *const StructureBuildingBlock { + return self.children[block.index].sample(seed).structure; + } +}; + +fn initChildTableFromZon(parentId: []const u8, colorName: []const u8, colorIndex: usize, zon: ZonElement) !AliasTable(Child) { + if(zon == .null) return .init(arenaAllocator, &.{}); + if(zon != .array) { + std.log.err("['{s}'->'{s}'] Incorrect child data structure, array expected.", .{parentId, colorName}); + return .init(arenaAllocator, &.{}); + } + if(zon.array.items.len == 0) { + std.log.err("['{s}'->'{s}'] Empty children list.", .{parentId, colorName}); + return error.EmptyChildrenList; + } + const list = arenaAllocator.alloc(Child, zon.array.items.len); + for(zon.array.items, 0..) |entry, childIndex| { + list[childIndex] = try Child.initFromZon(parentId, colorName, colorIndex, childIndex, entry); + } + return .init(arenaAllocator, list); +} + +const Child = struct { + structure: *StructureBuildingBlock, + chance: f32, + + fn initFromZon(parentId: []const u8, colorName: []const u8, colorIndex: usize, childIndex: usize, zon: ZonElement) !Child { + const structureId = zon.get([]const u8, "structure", ""); + if(structureId.len == 0) { + std.log.err("['{s}'->'{s}'->'{d}'] Child node has empty structure field, parent structure will be discarded.", .{parentId, colorName, childIndex}); + return error.EmptyStructureId; + } + childrenToResolve.append(.{.parentId = parentId, .colorName = colorName, .colorIndex = colorIndex, .childIndex = childIndex, .structureId = structureId}); + return .{ + .structure = undefined, + .chance = zon.get(f32, "chance", 1.0), + }; + } +}; + +pub fn registerSBB(structures: *std.StringHashMap(ZonElement)) !void { + std.debug.assert(structureCache.capacity() == 0); + structureCache.ensureTotalCapacity(arenaAllocator.allocator, structures.count()) catch unreachable; + childrenToResolve = .init(main.stackAllocator); + { + var iterator = structures.iterator(); + while(iterator.next()) |entry| { + const value = StructureBuildingBlock.initFromZon(entry.key_ptr.*, entry.value_ptr.*) catch |err| { + std.log.err("Could not register structure building block '{s}' ({s})", .{entry.key_ptr.*, @errorName(err)}); + continue; + }; + const key = arenaAllocator.dupe(u8, entry.key_ptr.*); + structureCache.put(arenaAllocator.allocator, key, value) catch unreachable; + std.log.debug("Registered structure building block: '{s}'", .{entry.key_ptr.*}); + } + } + { + for(childrenToResolve.items) |entry| { + const parent = structureCache.getPtr(entry.parentId).?; + const child = structureCache.getPtr(entry.structureId) orelse { + std.log.err("Could not find child structure '{s}' for child resolution.", .{entry.structureId}); + continue; + }; + + std.log.debug("Resolved child structure '{s}'->'{s}'->'{d}' to '{s}'", .{entry.parentId, entry.colorName, entry.childIndex, entry.structureId}); + parent.children[entry.colorIndex].items[entry.childIndex].structure = child; + } + childrenToResolve.deinit(); + } +} + pub fn registerChildBlock(numericId: u16, stringId: []const u8) void { const index: u16 = @intCast(childBlockNumericIdMap.count()); childBlockNumericIdMap.put(arenaAllocator.allocator, numericId, index) catch unreachable; @@ -123,7 +221,7 @@ pub fn registerBlueprints(blueprints: *std.StringHashMap([]u8)) !void { while(iterator.next()) |entry| { const stringId = entry.key_ptr.*; const blueprint0 = Blueprint.load(arenaAllocator, entry.value_ptr.*) catch |err| { - std.log.err("Could not load blueprint {s}: {s}", .{stringId, @errorName(err)}); + std.log.err("Could not load blueprint '{s}' ({s})", .{stringId, @errorName(err)}); continue; }; @@ -140,9 +238,14 @@ pub fn registerBlueprints(blueprints: *std.StringHashMap([]u8)) !void { } } +pub fn getByStringId(stringId: []const u8) ?*StructureBuildingBlock { + return structureCache.getPtr(stringId); +} + pub fn reset() void { childBlockNumericIdMap = .{}; childBlockStringId = .{}; + structureCache = .{}; blueprintCache = .{}; _ = arena.reset(.free_all);