From 1709d9d63645e795640a6d21bf16ee096b0a0674 Mon Sep 17 00:00:00 2001 From: yello Date: Sun, 4 Oct 2026 21:20:30 +0200 Subject: [PATCH 1/4] Add music track registry and biome mood/music tags --- src/assets.zig | 7 +++ src/main.zig | 2 + src/mood.zig | 12 +++++ src/musicTracks.zig | 90 +++++++++++++++++++++++++++++++++++ src/server/terrain/biomes.zig | 11 ++++- src/tag.zig | 19 ++++++++ 6 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 src/mood.zig create mode 100644 src/musicTracks.zig diff --git a/src/assets.zig b/src/assets.zig index 989ac911f2..a20f3e726a 100644 --- a/src/assets.zig +++ b/src/assets.zig @@ -33,6 +33,7 @@ pub const Assets = struct { // MARK: Assets entityComponents: ZonHashMap, entityComponentMigrations: AddonNameToZonMap, structureTables: ZonHashMap, + musicTracks: ZonHashMap, recipes: ZonHashMap, blockModels: BytesHashMap, blockModelsZon: ZonHashMap, @@ -56,6 +57,7 @@ pub const Assets = struct { // MARK: Assets .entityComponents = .{}, .entityComponentMigrations = .{}, .structureTables = .{}, + .musicTracks = .{}, .recipes = .{}, .blockModels = .{}, .blockModelsZon = .{}, @@ -79,6 +81,7 @@ pub const Assets = struct { // MARK: Assets self.entityComponents.deinit(allocator.allocator); self.entityComponentMigrations.deinit(allocator.allocator); self.structureTables.deinit(allocator.allocator); + self.musicTracks.deinit(allocator.allocator); self.recipes.deinit(allocator.allocator); self.blockModels.deinit(allocator.allocator); self.blockModelsZon.deinit(allocator.allocator); @@ -102,6 +105,7 @@ pub const Assets = struct { // MARK: Assets .entityComponents = self.entityComponents.clone(allocator.allocator) catch unreachable, .entityComponentMigrations = self.entityComponentMigrations.clone(allocator.allocator) catch unreachable, .structureTables = self.structureTables.clone(allocator.allocator) catch unreachable, + .musicTracks = self.musicTracks.clone(allocator.allocator) catch unreachable, .recipes = self.recipes.clone(allocator.allocator) catch unreachable, .blockModels = self.blockModels.clone(allocator.allocator) catch unreachable, .blockModelsZon = self.blockModelsZon.clone(allocator.allocator) catch unreachable, @@ -123,6 +127,7 @@ pub const Assets = struct { // MARK: Assets addon.readAllZon(allocator, "items", true, &self.items, &self.itemMigrations); addon.readAllZon(allocator, "tools", true, &self.proceduralItems, null); addon.readAllZon(allocator, "structure_tables", false, &self.structureTables, null); + addon.readAllZon(allocator, "music_tracks", true, &self.musicTracks, null); addon.readAllZon(allocator, "biomes", true, &self.biomes, &self.biomeMigrations); addon.readAllZon(allocator, "cave_layers", true, &self.caveLayers, null); addon.readAllZon(allocator, "recipes", false, &self.recipes, null); @@ -718,6 +723,7 @@ pub fn loadWorldAssets(assetFolder: []const u8, blockPalette: *Palette, itemPale try sbb.registerBlueprints(&worldAssets.blueprints); try sbb.registerSBB(&worldAssets.structureBuildingBlocks); try main.server.terrain.structures.registerStructureTables(&worldAssets.structureTables); + main.musicTracks.registerTracks(&worldAssets.musicTracks); iterator = worldAssets.particles.iterator(); while (iterator.next()) |entry| { @@ -804,6 +810,7 @@ pub fn unloadAssets() void { // MARK: unloadAssets() biomes.reset(); main.server.terrain.cave_layers.reset(); main.server.terrain.structures.reset(); + main.musicTracks.reset(); main.models.reset(); main.particles.ParticleManager.reset(); main.rotation.reset(); diff --git a/src/main.zig b/src/main.zig index d7e75925d4..3f86aadfbd 100644 --- a/src/main.zig +++ b/src/main.zig @@ -26,6 +26,8 @@ pub const log = @import("log.zig"); pub const meta = @import("meta.zig"); pub const migrations = @import("migrations.zig"); pub const models = @import("models.zig"); +pub const mood = @import("mood.zig"); +pub const musicTracks = @import("musicTracks.zig"); pub const network = @import("network.zig"); pub const particles = @import("particles.zig"); pub const physics = @import("physics.zig"); diff --git a/src/mood.zig b/src/mood.zig new file mode 100644 index 0000000000..f2c7c8b4d1 --- /dev/null +++ b/src/mood.zig @@ -0,0 +1,12 @@ +pub const Mood = struct { + anxiety: f32 = 0.3, + energy: f32 = 0.3, + sanity: f32 = 0.7, + + pub fn distanceTo(self: Mood, other: Mood) f32 { + const da = self.anxiety - other.anxiety; + const de = self.energy - other.energy; + const ds = self.sanity - other.sanity; + return @sqrt(da*da + de*de + ds*ds); + } +}; diff --git a/src/musicTracks.zig b/src/musicTracks.zig new file mode 100644 index 0000000000..9ed39ce2b4 --- /dev/null +++ b/src/musicTracks.zig @@ -0,0 +1,90 @@ +const std = @import("std"); + +const main = @import("main"); +const ZonElement = main.ZonElement; +const Assets = main.assets.Assets; +const Tag = main.Tag; +const Mood = main.mood.Mood; + +pub const MusicTrack = struct { + id: []const u8, + audioId: []const u8, + tags: []const Tag, + moodTarget: Mood, + moodTolerance: f32, + weight: f32, + + pub fn init(id: []const u8, zon: ZonElement) ?MusicTrack { + const file = zon.get([]const u8, "file") orelse { + std.log.err("Music track {s} is missing a 'file' field.", .{id}); + return null; + }; + const colonIndex = std.mem.indexOfScalar(u8, id, ':') orelse { + std.log.err("Invalid music track id: {s}. Must be addon:name", .{id}); + return null; + }; + const addon = id[0..colonIndex]; + return MusicTrack{ + .id = main.worldArena.dupe(u8, id), + .audioId = main.worldArena.print("{s}:{s}", .{addon, file}), + .tags = Tag.loadTagsFromZon(main.worldArena, zon.getChild("tags")), + .moodTarget = .{ + .anxiety = zon.getChild("moodTarget").get(f32, "anxiety") orelse 0.3, + .energy = zon.getChild("moodTarget").get(f32, "energy") orelse 0.3, + .sanity = zon.getChild("moodTarget").get(f32, "sanity") orelse 0.7, + }, + .moodTolerance = zon.get(f32, "moodTolerance") orelse 0.5, + .weight = zon.get(f32, "weight") orelse 1.0, + }; + } +}; + +var finishedLoading: bool = false; +var tracks: main.List(MusicTrack) = .empty; +var tracksById: std.StringHashMapUnmanaged(*MusicTrack) = .{}; +pub var anyTag: Tag = undefined; + +fn register(id: []const u8, zon: ZonElement) void { + const track = MusicTrack.init(id, zon) orelse return; + tracks.append(main.worldArena, track); + std.log.debug("Registered music track: '{s}'", .{id}); +} + +pub fn registerTracks(tracksZon: *Assets.ZonHashMap) void { + anyTag = Tag.find("any"); + var iterator = tracksZon.iterator(); + while (iterator.next()) |entry| { + register(entry.key_ptr.*, entry.value_ptr.*); + } + finishLoading(); +} + +fn compareMusicTracks(_: void, lhs: MusicTrack, rhs: MusicTrack) bool { + return std.ascii.orderIgnoreCase(lhs.id, rhs.id) == .gt; +} + +pub fn finishLoading() void { + std.debug.assert(!finishedLoading); + finishedLoading = true; + + std.mem.sort(MusicTrack, tracks.items, {}, compareMusicTracks); + tracksById.ensureTotalCapacity(main.worldArena.allocator, @intCast(tracks.items.len)) catch unreachable; + for (tracks.items) |*track| { + tracksById.putAssumeCapacity(track.id, track); + } +} + +pub fn getById(id: []const u8) ?*MusicTrack { + std.debug.assert(finishedLoading); + return tracksById.get(id); +} + +pub fn getSlice() []MusicTrack { + return tracks.items; +} + +pub fn reset() void { + finishedLoading = false; + tracks = .empty; + tracksById = .{}; +} diff --git a/src/server/terrain/biomes.zig b/src/server/terrain/biomes.zig index eaf1e7c243..9a4ed9279f 100644 --- a/src/server/terrain/biomes.zig +++ b/src/server/terrain/biomes.zig @@ -258,6 +258,8 @@ pub const Biome = struct { // MARK: Biome maxSubBiomeCount: f32, subBiomeTotalChance: f32 = 0, preferredMusic: []const u8, // TODO: Support multiple possibilities that are chosen based on time and danger. + baseMood: main.mood.Mood, + musicTags: []const Tag.WeightedTag, isValidPlayerSpawn: bool, chance: f32, tags: []const Tag, @@ -265,11 +267,12 @@ pub const Biome = struct { // MARK: Biome pub fn init(self: *Biome, id: []const u8, paletteId: u32, zon: ZonElement) void { const minRadius = zon.get(f32, "radius") orelse zon.get(f32, "minRadius") orelse 256; const maxRadius = zon.get(f32, "maxRadius") orelse minRadius; + const isCave = zon.get(bool, "isCave") orelse false; self.* = Biome{ .id = main.worldArena.dupe(u8, id), .paletteId = paletteId, .climate = ClimateProperties.fromZon(zon.getChild("climate"), true), - .isCave = zon.get(bool, "isCave") orelse false, + .isCave = isCave, .radius = (maxRadius + minRadius)/2, .radiusVariation = (maxRadius - minRadius)/2, .stoneBlock = blocks.parseBlock(zon.get([]const u8, "stoneBlock") orelse "cubyz:slate/smooth"), @@ -298,6 +301,12 @@ pub const Biome = struct { // MARK: Biome .smoothBeaches = zon.get(bool, "smoothBeaches") orelse false, .supportsRivers = zon.get(bool, "rivers") orelse false, .preferredMusic = main.worldArena.dupe(u8, zon.get([]const u8, "music") orelse "cubyz:totaldemented/cubyz"), + .baseMood = .{ + .anxiety = zon.getChild("baseMood").get(f32, "anxiety") orelse if (isCave) 0.7 else 0.3, + .energy = zon.getChild("baseMood").get(f32, "energy") orelse 0.3, + .sanity = zon.getChild("baseMood").get(f32, "sanity") orelse 0.7, + }, + .musicTags = Tag.loadWeightedTagsFromZon(main.worldArena, zon.getChild("musicTags")), .isValidPlayerSpawn = zon.get(bool, "validPlayerSpawn") orelse false, .chance = zon.get(f32, "chance") orelse if (zon == .null) 0 else 1, .maxSubBiomeCount = zon.get(f32, "maxSubBiomeCount") orelse std.math.floatMax(f32), diff --git a/src/tag.zig b/src/tag.zig index 5474f581af..a10b42cc32 100644 --- a/src/tag.zig +++ b/src/tag.zig @@ -52,4 +52,23 @@ pub const Tag = enum(u32) { pub fn getName(tag: Tag) []const u8 { return tagList.items[@intFromEnum(tag)]; } + + pub const WeightedTag = struct { + tag: Tag, + weight: f32, + }; + + pub fn loadWeightedTagsFromZon(_allocator: main.heap.NeverFailingAllocator, zon: main.ZonElement) []WeightedTag { + if (zon != .object) return &.{}; + const result = _allocator.alloc(WeightedTag, zon.object.count()); + var iterator = zon.object.iterator(); + var i: usize = 0; + while (iterator.next()) |entry| : (i += 1) { + result[i] = .{ + .tag = Tag.find(entry.key_ptr.*), + .weight = entry.value_ptr.as(f32) orelse 1.0, + }; + } + return result; + } }; From 77faefea3eb8d2e51e65526a5c1b69c21c9c171f Mon Sep 17 00:00:00 2001 From: yello Date: Tue, 6 Oct 2026 16:49:18 +0200 Subject: [PATCH 2/4] Move music metadata next to audio and make music tags hard restrictions --- src/assets.zig | 6 ++-- src/audio.zig | 2 ++ .../music_tracks.zig} | 32 +++++++++---------- src/main.zig | 2 -- src/mood.zig | 12 ------- src/server/terrain/biomes.zig | 5 ++- src/tag.zig | 19 ----------- 7 files changed, 22 insertions(+), 56 deletions(-) rename src/{musicTracks.zig => audio/music_tracks.zig} (74%) delete mode 100644 src/mood.zig diff --git a/src/assets.zig b/src/assets.zig index a20f3e726a..cfee37450b 100644 --- a/src/assets.zig +++ b/src/assets.zig @@ -127,7 +127,7 @@ pub const Assets = struct { // MARK: Assets addon.readAllZon(allocator, "items", true, &self.items, &self.itemMigrations); addon.readAllZon(allocator, "tools", true, &self.proceduralItems, null); addon.readAllZon(allocator, "structure_tables", false, &self.structureTables, null); - addon.readAllZon(allocator, "music_tracks", true, &self.musicTracks, null); + addon.readAllZon(allocator, "music", true, &self.musicTracks, null); addon.readAllZon(allocator, "biomes", true, &self.biomes, &self.biomeMigrations); addon.readAllZon(allocator, "cave_layers", true, &self.caveLayers, null); addon.readAllZon(allocator, "recipes", false, &self.recipes, null); @@ -723,7 +723,7 @@ pub fn loadWorldAssets(assetFolder: []const u8, blockPalette: *Palette, itemPale try sbb.registerBlueprints(&worldAssets.blueprints); try sbb.registerSBB(&worldAssets.structureBuildingBlocks); try main.server.terrain.structures.registerStructureTables(&worldAssets.structureTables); - main.musicTracks.registerTracks(&worldAssets.musicTracks); + main.audio.music_tracks.registerTracks(&worldAssets.musicTracks); iterator = worldAssets.particles.iterator(); while (iterator.next()) |entry| { @@ -810,7 +810,7 @@ pub fn unloadAssets() void { // MARK: unloadAssets() biomes.reset(); main.server.terrain.cave_layers.reset(); main.server.terrain.structures.reset(); - main.musicTracks.reset(); + main.audio.music_tracks.reset(); main.models.reset(); main.particles.ParticleManager.reset(); main.rotation.reset(); diff --git a/src/audio.zig b/src/audio.zig index d834df5e76..39c6e5bf03 100644 --- a/src/audio.zig +++ b/src/audio.zig @@ -5,6 +5,8 @@ const utils = main.utils; const c = @import("c"); +pub const music_tracks = @import("audio/music_tracks.zig"); + const StbVorbisErrorEnum = enum(c_int) { unknown_error = -1, no_error = 0, diff --git a/src/musicTracks.zig b/src/audio/music_tracks.zig similarity index 74% rename from src/musicTracks.zig rename to src/audio/music_tracks.zig index 9ed39ce2b4..a90fa59ba9 100644 --- a/src/musicTracks.zig +++ b/src/audio/music_tracks.zig @@ -4,29 +4,30 @@ const main = @import("main"); const ZonElement = main.ZonElement; const Assets = main.assets.Assets; const Tag = main.Tag; -const Mood = main.mood.Mood; pub const MusicTrack = struct { + pub const Mood = struct { + anxiety: f32 = 0.3, + energy: f32 = 0.3, + sanity: f32 = 0.7, + + pub fn distanceTo(self: Mood, other: Mood) f32 { + const da = self.anxiety - other.anxiety; + const de = self.energy - other.energy; + const ds = self.sanity - other.sanity; + return @sqrt(da*da + de*de + ds*ds); + } + }; + id: []const u8, - audioId: []const u8, tags: []const Tag, moodTarget: Mood, moodTolerance: f32, weight: f32, - pub fn init(id: []const u8, zon: ZonElement) ?MusicTrack { - const file = zon.get([]const u8, "file") orelse { - std.log.err("Music track {s} is missing a 'file' field.", .{id}); - return null; - }; - const colonIndex = std.mem.indexOfScalar(u8, id, ':') orelse { - std.log.err("Invalid music track id: {s}. Must be addon:name", .{id}); - return null; - }; - const addon = id[0..colonIndex]; + pub fn init(id: []const u8, zon: ZonElement) MusicTrack { return MusicTrack{ .id = main.worldArena.dupe(u8, id), - .audioId = main.worldArena.print("{s}:{s}", .{addon, file}), .tags = Tag.loadTagsFromZon(main.worldArena, zon.getChild("tags")), .moodTarget = .{ .anxiety = zon.getChild("moodTarget").get(f32, "anxiety") orelse 0.3, @@ -42,16 +43,13 @@ pub const MusicTrack = struct { var finishedLoading: bool = false; var tracks: main.List(MusicTrack) = .empty; var tracksById: std.StringHashMapUnmanaged(*MusicTrack) = .{}; -pub var anyTag: Tag = undefined; fn register(id: []const u8, zon: ZonElement) void { - const track = MusicTrack.init(id, zon) orelse return; - tracks.append(main.worldArena, track); + tracks.append(main.worldArena, MusicTrack.init(id, zon)); std.log.debug("Registered music track: '{s}'", .{id}); } pub fn registerTracks(tracksZon: *Assets.ZonHashMap) void { - anyTag = Tag.find("any"); var iterator = tracksZon.iterator(); while (iterator.next()) |entry| { register(entry.key_ptr.*, entry.value_ptr.*); diff --git a/src/main.zig b/src/main.zig index 3f86aadfbd..d7e75925d4 100644 --- a/src/main.zig +++ b/src/main.zig @@ -26,8 +26,6 @@ pub const log = @import("log.zig"); pub const meta = @import("meta.zig"); pub const migrations = @import("migrations.zig"); pub const models = @import("models.zig"); -pub const mood = @import("mood.zig"); -pub const musicTracks = @import("musicTracks.zig"); pub const network = @import("network.zig"); pub const particles = @import("particles.zig"); pub const physics = @import("physics.zig"); diff --git a/src/mood.zig b/src/mood.zig deleted file mode 100644 index f2c7c8b4d1..0000000000 --- a/src/mood.zig +++ /dev/null @@ -1,12 +0,0 @@ -pub const Mood = struct { - anxiety: f32 = 0.3, - energy: f32 = 0.3, - sanity: f32 = 0.7, - - pub fn distanceTo(self: Mood, other: Mood) f32 { - const da = self.anxiety - other.anxiety; - const de = self.energy - other.energy; - const ds = self.sanity - other.sanity; - return @sqrt(da*da + de*de + ds*ds); - } -}; diff --git a/src/server/terrain/biomes.zig b/src/server/terrain/biomes.zig index 9a4ed9279f..fa47249295 100644 --- a/src/server/terrain/biomes.zig +++ b/src/server/terrain/biomes.zig @@ -11,6 +11,7 @@ const Vec3f = main.vec.Vec3f; const Vec3d = main.vec.Vec3d; const Tag = main.Tag; +const Mood = main.audio.music_tracks.MusicTrack.Mood; const StructureTable = terrain.structures.StructureTable; pub const SimpleStructureModel = terrain.structures.SimpleStructureModel; @@ -258,8 +259,7 @@ pub const Biome = struct { // MARK: Biome maxSubBiomeCount: f32, subBiomeTotalChance: f32 = 0, preferredMusic: []const u8, // TODO: Support multiple possibilities that are chosen based on time and danger. - baseMood: main.mood.Mood, - musicTags: []const Tag.WeightedTag, + baseMood: Mood, isValidPlayerSpawn: bool, chance: f32, tags: []const Tag, @@ -306,7 +306,6 @@ pub const Biome = struct { // MARK: Biome .energy = zon.getChild("baseMood").get(f32, "energy") orelse 0.3, .sanity = zon.getChild("baseMood").get(f32, "sanity") orelse 0.7, }, - .musicTags = Tag.loadWeightedTagsFromZon(main.worldArena, zon.getChild("musicTags")), .isValidPlayerSpawn = zon.get(bool, "validPlayerSpawn") orelse false, .chance = zon.get(f32, "chance") orelse if (zon == .null) 0 else 1, .maxSubBiomeCount = zon.get(f32, "maxSubBiomeCount") orelse std.math.floatMax(f32), diff --git a/src/tag.zig b/src/tag.zig index a10b42cc32..5474f581af 100644 --- a/src/tag.zig +++ b/src/tag.zig @@ -52,23 +52,4 @@ pub const Tag = enum(u32) { pub fn getName(tag: Tag) []const u8 { return tagList.items[@intFromEnum(tag)]; } - - pub const WeightedTag = struct { - tag: Tag, - weight: f32, - }; - - pub fn loadWeightedTagsFromZon(_allocator: main.heap.NeverFailingAllocator, zon: main.ZonElement) []WeightedTag { - if (zon != .object) return &.{}; - const result = _allocator.alloc(WeightedTag, zon.object.count()); - var iterator = zon.object.iterator(); - var i: usize = 0; - while (iterator.next()) |entry| : (i += 1) { - result[i] = .{ - .tag = Tag.find(entry.key_ptr.*), - .weight = entry.value_ptr.as(f32) orelse 1.0, - }; - } - return result; - } }; From 4ebc35781eaa19b738f3543cb568a875ea72e7bf Mon Sep 17 00:00:00 2001 From: yello Date: Wed, 7 Oct 2026 16:11:54 +0200 Subject: [PATCH 3/4] Cleanup --- assets/cubyz/biomes/forest/birch.zig.zon | 1 + src/audio/music_tracks.zig | 54 ++++++++---------------- src/server/terrain/biomes.zig | 6 +-- 3 files changed, 19 insertions(+), 42 deletions(-) diff --git a/assets/cubyz/biomes/forest/birch.zig.zon b/assets/cubyz/biomes/forest/birch.zig.zon index 7910139404..77facac3f2 100644 --- a/assets/cubyz/biomes/forest/birch.zig.zon +++ b/assets/cubyz/biomes/forest/birch.zig.zon @@ -13,6 +13,7 @@ .roughness = 10, .hills = 12, .chance = 0.66, + .baseMood = .{.anxiety = 0.2, .energy = 0.3, .sanity = 0.8}, .music = "cubyz:totaldemented/leaves", .validPlayerSpawn = true, .ground_structure = .{ diff --git a/src/audio/music_tracks.zig b/src/audio/music_tracks.zig index a90fa59ba9..e8d36c3e9c 100644 --- a/src/audio/music_tracks.zig +++ b/src/audio/music_tracks.zig @@ -7,9 +7,17 @@ const Tag = main.Tag; pub const MusicTrack = struct { pub const Mood = struct { - anxiety: f32 = 0.3, - energy: f32 = 0.3, - sanity: f32 = 0.7, + anxiety: f32, + energy: f32, + sanity: f32, + + pub fn loadFromZon(zon: ZonElement, isCave: ?bool) Mood { + return .{ + .anxiety = zon.get(f32, "anxiety") orelse if (isCave orelse false) 0.7 else 0.3, + .energy = zon.get(f32, "energy") orelse 0.3, + .sanity = zon.get(f32, "sanity") orelse 0.7, + }; + } pub fn distanceTo(self: Mood, other: Mood) f32 { const da = self.anxiety - other.anxiety; @@ -23,66 +31,38 @@ pub const MusicTrack = struct { tags: []const Tag, moodTarget: Mood, moodTolerance: f32, - weight: f32, + chance: f32, pub fn init(id: []const u8, zon: ZonElement) MusicTrack { return MusicTrack{ .id = main.worldArena.dupe(u8, id), .tags = Tag.loadTagsFromZon(main.worldArena, zon.getChild("tags")), - .moodTarget = .{ - .anxiety = zon.getChild("moodTarget").get(f32, "anxiety") orelse 0.3, - .energy = zon.getChild("moodTarget").get(f32, "energy") orelse 0.3, - .sanity = zon.getChild("moodTarget").get(f32, "sanity") orelse 0.7, - }, + .moodTarget = .loadFromZon(zon.getChild("moodTarget"), null), .moodTolerance = zon.get(f32, "moodTolerance") orelse 0.5, - .weight = zon.get(f32, "weight") orelse 1.0, + .chance = zon.get(f32, "chance") orelse 1.0, }; } }; -var finishedLoading: bool = false; var tracks: main.List(MusicTrack) = .empty; -var tracksById: std.StringHashMapUnmanaged(*MusicTrack) = .{}; fn register(id: []const u8, zon: ZonElement) void { - tracks.append(main.worldArena, MusicTrack.init(id, zon)); + tracks.appendAssumeCapacity(MusicTrack.init(id, zon)); std.log.debug("Registered music track: '{s}'", .{id}); } pub fn registerTracks(tracksZon: *Assets.ZonHashMap) void { + tracks.ensureCapacity(main.worldArena, tracksZon.count()); var iterator = tracksZon.iterator(); while (iterator.next()) |entry| { register(entry.key_ptr.*, entry.value_ptr.*); } - finishLoading(); -} - -fn compareMusicTracks(_: void, lhs: MusicTrack, rhs: MusicTrack) bool { - return std.ascii.orderIgnoreCase(lhs.id, rhs.id) == .gt; -} - -pub fn finishLoading() void { - std.debug.assert(!finishedLoading); - finishedLoading = true; - - std.mem.sort(MusicTrack, tracks.items, {}, compareMusicTracks); - tracksById.ensureTotalCapacity(main.worldArena.allocator, @intCast(tracks.items.len)) catch unreachable; - for (tracks.items) |*track| { - tracksById.putAssumeCapacity(track.id, track); - } -} - -pub fn getById(id: []const u8) ?*MusicTrack { - std.debug.assert(finishedLoading); - return tracksById.get(id); } -pub fn getSlice() []MusicTrack { +pub fn getSlice() []const MusicTrack { return tracks.items; } pub fn reset() void { - finishedLoading = false; tracks = .empty; - tracksById = .{}; } diff --git a/src/server/terrain/biomes.zig b/src/server/terrain/biomes.zig index fa47249295..9fcc80c551 100644 --- a/src/server/terrain/biomes.zig +++ b/src/server/terrain/biomes.zig @@ -301,11 +301,7 @@ pub const Biome = struct { // MARK: Biome .smoothBeaches = zon.get(bool, "smoothBeaches") orelse false, .supportsRivers = zon.get(bool, "rivers") orelse false, .preferredMusic = main.worldArena.dupe(u8, zon.get([]const u8, "music") orelse "cubyz:totaldemented/cubyz"), - .baseMood = .{ - .anxiety = zon.getChild("baseMood").get(f32, "anxiety") orelse if (isCave) 0.7 else 0.3, - .energy = zon.getChild("baseMood").get(f32, "energy") orelse 0.3, - .sanity = zon.getChild("baseMood").get(f32, "sanity") orelse 0.7, - }, + .baseMood = Mood.loadFromZon(zon.getChild("baseMood"), isCave), .isValidPlayerSpawn = zon.get(bool, "validPlayerSpawn") orelse false, .chance = zon.get(f32, "chance") orelse if (zon == .null) 0 else 1, .maxSubBiomeCount = zon.get(f32, "maxSubBiomeCount") orelse std.math.floatMax(f32), From 5523bc3d2925830330525cd52823c0a51e4f2d2b Mon Sep 17 00:00:00 2001 From: yello Date: Wed, 7 Oct 2026 18:15:03 +0200 Subject: [PATCH 4/4] Pass Mood.loadFromZon options as struct instead of optional bool --- src/audio/music_tracks.zig | 6 +++--- src/server/terrain/biomes.zig | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/audio/music_tracks.zig b/src/audio/music_tracks.zig index e8d36c3e9c..a22ac6ec59 100644 --- a/src/audio/music_tracks.zig +++ b/src/audio/music_tracks.zig @@ -11,9 +11,9 @@ pub const MusicTrack = struct { energy: f32, sanity: f32, - pub fn loadFromZon(zon: ZonElement, isCave: ?bool) Mood { + pub fn loadFromZon(zon: ZonElement, options: struct { isCave: bool = false }) Mood { return .{ - .anxiety = zon.get(f32, "anxiety") orelse if (isCave orelse false) 0.7 else 0.3, + .anxiety = zon.get(f32, "anxiety") orelse if (options.isCave) 0.7 else 0.3, .energy = zon.get(f32, "energy") orelse 0.3, .sanity = zon.get(f32, "sanity") orelse 0.7, }; @@ -37,7 +37,7 @@ pub const MusicTrack = struct { return MusicTrack{ .id = main.worldArena.dupe(u8, id), .tags = Tag.loadTagsFromZon(main.worldArena, zon.getChild("tags")), - .moodTarget = .loadFromZon(zon.getChild("moodTarget"), null), + .moodTarget = .loadFromZon(zon.getChild("moodTarget"), .{}), .moodTolerance = zon.get(f32, "moodTolerance") orelse 0.5, .chance = zon.get(f32, "chance") orelse 1.0, }; diff --git a/src/server/terrain/biomes.zig b/src/server/terrain/biomes.zig index 9fcc80c551..d80b4838bb 100644 --- a/src/server/terrain/biomes.zig +++ b/src/server/terrain/biomes.zig @@ -301,7 +301,7 @@ pub const Biome = struct { // MARK: Biome .smoothBeaches = zon.get(bool, "smoothBeaches") orelse false, .supportsRivers = zon.get(bool, "rivers") orelse false, .preferredMusic = main.worldArena.dupe(u8, zon.get([]const u8, "music") orelse "cubyz:totaldemented/cubyz"), - .baseMood = Mood.loadFromZon(zon.getChild("baseMood"), isCave), + .baseMood = Mood.loadFromZon(zon.getChild("baseMood"), .{.isCave = isCave}), .isValidPlayerSpawn = zon.get(bool, "validPlayerSpawn") orelse false, .chance = zon.get(f32, "chance") orelse if (zon == .null) 0 else 1, .maxSubBiomeCount = zon.get(f32, "maxSubBiomeCount") orelse std.math.floatMax(f32),