From 80ba60a2e74a8802f458b1607f16efc5f2a75de8 Mon Sep 17 00:00:00 2001 From: Zbizu Date: Fri, 24 Jan 2025 04:04:50 +0100 Subject: [PATCH] parse sound metadata --- meta.lua | 8 +- src/client/luafunctions.cpp | 1 - src/client/thingtypemanager.cpp | 1 - src/framework/luafunctions.cpp | 1 + src/framework/sound/soundmanager.cpp | 187 ++++++++++++++++++++++++++- src/framework/sound/soundmanager.h | 95 ++++++++++++++ src/protobuf/sounds.proto | 4 +- 7 files changed, 288 insertions(+), 9 deletions(-) diff --git a/meta.lua b/meta.lua index a6c4269b8d..cda8ff2afc 100644 --- a/meta.lua +++ b/meta.lua @@ -157,10 +157,6 @@ function g_things.loadAppearances(file) end ---@return boolean function g_things.loadStaticData(file) end ----@param file string ----@return boolean -function g_things.loadSounds(file) end - ---@param file string ---@return boolean function g_things.loadDat(file) end @@ -5835,6 +5831,10 @@ function g_sounds.createSoundEffect() end ---@return boolean function g_sounds.isEaxEnabled() end +---@param file string +---@return boolean +function g_sounds.loadClientFiles(directory) end + -------------------------------- --------- SoundSource ---------- -------------------------------- diff --git a/src/client/luafunctions.cpp b/src/client/luafunctions.cpp index d2ce76dfa9..74c9494b95 100644 --- a/src/client/luafunctions.cpp +++ b/src/client/luafunctions.cpp @@ -68,7 +68,6 @@ void Client::registerLuaFunctions() g_lua.registerSingletonClass("g_things"); g_lua.bindSingletonFunction("g_things", "loadAppearances", &ThingTypeManager::loadAppearances, &g_things); g_lua.bindSingletonFunction("g_things", "loadStaticData", &ThingTypeManager::loadStaticData, &g_things); - g_lua.bindSingletonFunction("g_things", "loadSounds", &ThingTypeManager::loadSounds, &g_things); g_lua.bindSingletonFunction("g_things", "loadDat", &ThingTypeManager::loadDat, &g_things); g_lua.bindSingletonFunction("g_things", "loadOtml", &ThingTypeManager::loadOtml, &g_things); g_lua.bindSingletonFunction("g_things", "isDatLoaded", &ThingTypeManager::isDatLoaded, &g_things); diff --git a/src/client/thingtypemanager.cpp b/src/client/thingtypemanager.cpp index 8edc69c76f..3385907669 100644 --- a/src/client/thingtypemanager.cpp +++ b/src/client/thingtypemanager.cpp @@ -38,7 +38,6 @@ #include #include -#include #include diff --git a/src/framework/luafunctions.cpp b/src/framework/luafunctions.cpp index e7042b5317..6c203009a5 100644 --- a/src/framework/luafunctions.cpp +++ b/src/framework/luafunctions.cpp @@ -1004,6 +1004,7 @@ void Application::registerLuaFunctions() g_lua.bindSingletonFunction("g_sounds", "setPosition", &SoundManager::setPosition, &g_sounds); g_lua.bindSingletonFunction("g_sounds", "createSoundEffect", &SoundManager::createSoundEffect, &g_sounds); g_lua.bindSingletonFunction("g_sounds", "isEaxEnabled", &SoundManager::isEaxEnabled, &g_sounds); + g_lua.bindSingletonFunction("g_sounds", "loadClientFiles", &SoundManager::loadClientFiles, &g_sounds); g_lua.registerClass(); g_lua.bindClassStaticFunction("create", [] { return std::make_shared(); }); diff --git a/src/framework/sound/soundmanager.cpp b/src/framework/sound/soundmanager.cpp index 086c1ffd89..70990f4236 100644 --- a/src/framework/sound/soundmanager.cpp +++ b/src/framework/sound/soundmanager.cpp @@ -31,9 +31,15 @@ #include #include #include +#include +#include #include "soundchannel.h" +using namespace otclient::protobuf; + +using json = nlohmann::json; + class StreamSoundSource; class CombinedSoundSource; class SoundFile; @@ -339,4 +345,183 @@ bool SoundManager::isEaxEnabled() return true; } return false; -} \ No newline at end of file +} + +using ProtobufSoundFiles = google::protobuf::RepeatedPtrField; +using ProtobufSoundEffects = google::protobuf::RepeatedPtrField; +using ProtobufLocationAmbiences = google::protobuf::RepeatedPtrField; +using ProtobufItemAmbiences = google::protobuf::RepeatedPtrField; +using ProtobufMusicTracks = google::protobuf::RepeatedPtrField; + +bool SoundManager::loadFromProtobuf(const std::string& directory, const std::string& fileName) +{ + /* + ////// file structure + Sounds + | + | + | ////// audio file id -> audio file name (ogg) + |-+- (Sound) sound + | |----> (u32) id + | |----> (string) filename (sound-abcd.ogg) + | |----> (string) original_filename (unused) + | |----> (bool) is_stream + | + | + | ////// sound effect + |-+- (NumericSoundEffect) numeric_sound_effect + | |----> (u32) id (the id you request in sound effect packet) + | |----> (enum - ENumericSoundType) numeric_sound_type + | |-+--> (MinMaxFloat) random_pitch + | | |------> (float) min + | | |------> (float) max + | | + | |-+--> (MinMaxFloat) random_volume + | | |------> (float) min + | | |------> (float) max + | | + | |-+--> (SimpleSoundEffect) simple_sound_effect + | | |------> (u32) sound_id (audio file id) + | | + | |-+--> (RandomSoundEffect) random_sound_effect + | | |------> (u32) random_sound_id (audio file id) + | + | + | ////// ambient sound for location (needs to be triggered with a packet) + |-+- (AmbienceStream) ambience_stream + | |----> (u32) id + | |----> (u32) looping_sound_id (audio file id) + | |-+--> (DelayedSoundEffect) delayed_effects + | | |------> (u32) numeric_sound_effect_id (sound effect id) + | | |------> (u32) delay_seconds + | + | + | ////// sound of items placed on the map + |-+- (AmbienceObjectStream) ambience_object_stream + | |----> (u32) id (ID OF THIS EFFECT, NOT ITEM ID!) + | |----> (u32) counted_appearance_types (ITEM CLIENT IDS that will have this sound, eg. waterfall or campfire) + | |-+--> (AppearanceTypesCountSoundEffect) sound_effects + | | |------> (u32) count (how many on the screen are required to trigger, eg. 3 are required for the swamp tiles to play sound) + | | |------> (u32) looping_sound_id (audio file id) + | |----> (u32) max_sound_distance (how far can it be heard) + | + | + | ////// music for location (needs to be triggered with a packet) + |-+- (MusicTemplate) music_template + |----> (u32) id + |----> (u32) sound_id (audio file id) + |----> (enum - EMusicType) music_type + */ + + // create the sound bank from protobuf file + try { + std::stringstream fileInputStream; + g_resources.readFileStream(g_resources.resolvePath(stdext::format("%s%s", directory, fileName)), fileInputStream); + + // read the soundbank + auto protobufSounds = sounds::Sounds(); + if (!protobufSounds.ParseFromIstream(&fileInputStream)) { + throw stdext::exception("Couldn't parse appearances lib."); + } + + // deserialize audio files + for (const auto& protobufAudioFile : protobufSounds.sound()) { + m_clientSoundFiles[protobufAudioFile.id()] = protobufAudioFile.filename(); + } + + // deserialize sound effects + for (const auto& protobufSoundEffect : protobufSounds.numeric_sound_effect()) { + const auto& pitch = protobufSoundEffect.random_pitch(); + const auto& volume = protobufSoundEffect.random_volume(); + std::vector randomSounds = {}; + if (protobufSoundEffect.has_random_sound_effect()) { + for (const uint32_t& audioFileId : protobufSoundEffect.random_sound_effect().random_sound_id()) { + randomSounds.push_back(audioFileId); + } + } + + uint32_t effectId = protobufSoundEffect.id(); + m_clientSoundEffects.emplace(effectId, ClientSoundEffect{ + effectId, + static_cast(protobufSoundEffect.numeric_sound_type()), + pitch.min_value(), + pitch.max_value(), + volume.max_value(), + volume.max_value(), + protobufSoundEffect.has_simple_sound_effect() ? protobufSoundEffect.simple_sound_effect().sound_id() : 0, + std::move(randomSounds) + }); + } + + // deserialize location ambients + for (const auto& protobufLocationAmbient : protobufSounds.ambience_stream()) { + uint32_t effectId = protobufLocationAmbient.id(); + DelayedSoundEffects effects = {}; + for (const auto& delayedEffect : protobufLocationAmbient.delayed_effects()) { + effects.push_back({delayedEffect.numeric_sound_effect_id(), delayedEffect.delay_seconds()}); + } + + m_clientAmbientEffects.emplace(effectId, ClientLocationAmbient{ + effectId, + protobufLocationAmbient.looping_sound_id(), + std::move(effects) + }); + } + + // deserialize item ambients + for (const auto& protobufItemAmbient : protobufSounds.ambience_object_stream()) { + std::vector itemClientIds = {}; + for (const auto& itemId : protobufItemAmbient.counted_appearance_types()) { + itemClientIds.push_back(itemId); + } + + ItemCountSoundEffects soundEffects = {}; + for (const auto& soundEffect : protobufItemAmbient.sound_effects()) { + soundEffects.push_back({soundEffect.looping_sound_id(), soundEffect.count()}); + } + + uint32_t effectId = protobufItemAmbient.id(); + m_clientItemAmbientEffects.emplace(effectId, ClientItemAmbient{ + effectId, + std::move(itemClientIds), + std::move(soundEffects) + }); + } + + // deserialize music + for (const auto& protobufMusicTemplate : protobufSounds.music_template()) { + uint32_t effectId = protobufMusicTemplate.id(); + m_clientMusic.emplace(effectId, ClientMusic{ + effectId, + protobufMusicTemplate.sound_id(), + static_cast(protobufMusicTemplate.music_type()) + }); + } + + return true; + } catch (const std::exception& e) { + g_logger.error(stdext::format("Failed to load soundbank '%s': %s", fileName, e.what())); + return false; + } +} + +bool SoundManager::loadClientFiles(const std::string& directory) +{ + // find catalog from json file + try { + int spritesCount = 0; + json document = json::parse(g_resources.readFileContents(g_resources.resolvePath(g_resources.guessFilePath(directory + "catalog-sound", "json")))); + for (const auto& obj : document) { + const auto& type = obj["type"]; + if (type == "sounds") { + // dat file encoded with protobuf + loadFromProtobuf(directory, obj["file"]); + } + } + + return true; + } catch (const std::exception& e) { + g_logger.error(stdext::format("Failed to load '%s' (Sounds): %s", directory, e.what())); + return false; + } +} diff --git a/src/framework/sound/soundmanager.h b/src/framework/sound/soundmanager.h index 79222b15f5..297dbacd80 100644 --- a/src/framework/sound/soundmanager.h +++ b/src/framework/sound/soundmanager.h @@ -27,6 +27,91 @@ #include #include +using DelayedSoundEffect = std::pair; +using DelayedSoundEffects = std::vector; +using ItemCountSoundEffect = std::pair; +using ItemCountSoundEffects = std::vector; + +enum ClientSoundType +{ + NUMERIC_SOUND_TYPE_UNKNOWN = 0, + NUMERIC_SOUND_TYPE_SPELL_ATTACK = 1, + NUMERIC_SOUND_TYPE_SPELL_HEALING = 2, + NUMERIC_SOUND_TYPE_SPELL_SUPPORT = 3, + NUMERIC_SOUND_TYPE_WEAPON_ATTACK = 4, + NUMERIC_SOUND_TYPE_CREATURE_NOISE = 5, + NUMERIC_SOUND_TYPE_CREATURE_DEATH = 6, + NUMERIC_SOUND_TYPE_CREATURE_ATTACK = 7, + NUMERIC_SOUND_TYPE_AMBIENCE_STREAM = 8, + NUMERIC_SOUND_TYPE_FOOD_AND_DRINK = 9, + NUMERIC_SOUND_TYPE_ITEM_MOVEMENT = 10, + NUMERIC_SOUND_TYPE_EVENT = 11, + NUMERIC_SOUND_TYPE_UI = 12, + NUMERIC_SOUND_TYPE_WHISPER_WITHOUT_OPEN_CHAT = 13, + NUMERIC_SOUND_TYPE_CHAT_MESSAGE = 14, + NUMERIC_SOUND_TYPE_PARTY = 15, + NUMERIC_SOUND_TYPE_VIP_LIST = 16, + NUMERIC_SOUND_TYPE_RAID_ANNOUNCEMENT = 17, + NUMERIC_SOUND_TYPE_SERVER_MESSAGE = 18, + NUMERIC_SOUND_TYPE_SPELL_GENERIC = 19 +}; + +enum ClientMusicType +{ + MUSIC_TYPE_UNKNOWN = 0, + MUSIC_TYPE_MUSIC = 1, + MUSIC_TYPE_MUSIC_IMMEDIATE = 2, + MUSIC_TYPE_MUSIC_TITLE = 3, +}; + +// client sound effect parsed from the protobuf file +struct ClientSoundEffect +{ + uint32_t clientId; + ClientSoundType type; + float pitchMin; + float pitchMax; + float volumeMin; + float volumeMax; + uint32_t soundId = 0; + std::vector randomSoundId; +}; + +// client location ambient parsed from the protobuf file +struct ClientLocationAmbient +{ + uint32_t clientId; + uint32_t loopedAudioFileId; + + // vector of pairs, where the pair is: + // < effect clientId, delay in seconds > + DelayedSoundEffects delayedSoundEffects; +}; + +// client item ambient parsed from the protobuf file +struct ClientItemAmbient +{ + uint32_t id; + std::vector clientIds; + + // this is a very specific client mechanic + // depending on how many items are on the game screen + // a different looped ambient effect will be played + // for example, configuration like this: + // 1 -> 630 + // 5 -> 625 + // means that when there is one item on the screen, an audio file number 630 should play + // once there are 5 of them, the client should play an audio file number 625 + ItemCountSoundEffects itemCountSoundEffects; +}; + +struct ClientMusic +{ + uint32_t id; // track id + uint32_t audioFileId; // audio file id + ClientMusicType musicType; +}; + //@bindsingleton g_sounds class SoundManager { @@ -47,6 +132,7 @@ class SoundManager void stopAll(); void setPosition(const Point& pos); bool isEaxEnabled(); + bool loadClientFiles(const std::string& directory); void preload(std::string filename); SoundSourcePtr play(const std::string& filename, float fadetime = 0, float gain = 0, float pitch = 0); @@ -58,6 +144,7 @@ class SoundManager private: SoundSourcePtr createSoundSource(const std::string& name); + bool loadFromProtobuf(const std::string& directory, const std::string& fileName); ALCdevice* m_device{}; ALCcontext* m_context{}; @@ -69,6 +156,14 @@ class SoundManager std::unordered_map m_channels; std::unordered_map m_effects; + // soundbanks for protocol 13 and newer + std::map m_clientSoundFiles; + std::map m_clientSoundEffects; + std::map m_clientAmbientEffects; + std::map m_clientItemAmbientEffects; + std::map m_clientMusic; + + std::vector m_sources; bool m_audioEnabled{ true }; }; diff --git a/src/protobuf/sounds.proto b/src/protobuf/sounds.proto index 784728fef4..2833887dbc 100644 --- a/src/protobuf/sounds.proto +++ b/src/protobuf/sounds.proto @@ -432,6 +432,6 @@ message AppearanceTypesCountSoundEffect { } message MinMaxFloat { - optional float min = 1; - optional float max = 2; + optional float min_value = 1; + optional float max_value = 2; }