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/assets.zig b/src/assets.zig index 989ac911f2..cfee37450b 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", 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.audio.music_tracks.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.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/audio/music_tracks.zig b/src/audio/music_tracks.zig new file mode 100644 index 0000000000..a22ac6ec59 --- /dev/null +++ b/src/audio/music_tracks.zig @@ -0,0 +1,68 @@ +const std = @import("std"); + +const main = @import("main"); +const ZonElement = main.ZonElement; +const Assets = main.assets.Assets; +const Tag = main.Tag; + +pub const MusicTrack = struct { + pub const Mood = struct { + anxiety: f32, + energy: f32, + sanity: f32, + + pub fn loadFromZon(zon: ZonElement, options: struct { isCave: bool = false }) Mood { + return .{ + .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, + }; + } + + 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, + tags: []const Tag, + moodTarget: Mood, + moodTolerance: 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 = .loadFromZon(zon.getChild("moodTarget"), .{}), + .moodTolerance = zon.get(f32, "moodTolerance") orelse 0.5, + .chance = zon.get(f32, "chance") orelse 1.0, + }; + } +}; + +var tracks: main.List(MusicTrack) = .empty; + +fn register(id: []const u8, zon: ZonElement) void { + 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.*); + } +} + +pub fn getSlice() []const MusicTrack { + return tracks.items; +} + +pub fn reset() void { + tracks = .empty; +} diff --git a/src/server/terrain/biomes.zig b/src/server/terrain/biomes.zig index eaf1e7c243..d80b4838bb 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,6 +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: Mood, 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,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 = 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),