From 0276a5f72054004fde875e605039c68ab4b4ccb1 Mon Sep 17 00:00:00 2001 From: Zbizu Date: Tue, 28 Jan 2025 20:49:56 +0100 Subject: [PATCH] feat: load sounds with protobuf This is a continuation of bringing expanded protobuf support to otclient. This isn't a complete solution but moves us very close to forwarding protobuf soundbanks to the client These things are "to do", that won't be part of this PR: path detection for ogg files (currently the client only knows the file name) playing sound effects from server packets playing sound effects from items on screen getting the performance right so it doesn't lag when multiple sounds are played --- data/sounds/.gitignore | 3 + data/sounds/README.md | 9 + data/things/.gitignore | 1 + data/things/README.md | 13 ++ meta.lua | 12 +- .../default_configs/vBot_4.8/vBot/alarms.lua | 24 +-- mods/game_bot/functions/sound.lua | 2 +- .../game_bot}/sounds/Creature_Detected.ogg | Bin {data => mods/game_bot}/sounds/Low_Health.ogg | Bin {data => mods/game_bot}/sounds/Low_Mana.ogg | Bin .../game_bot}/sounds/Player_Attack.ogg | Bin .../game_bot}/sounds/Player_Detected.ogg | Bin .../game_bot}/sounds/Private_Message.ogg | Bin {data => mods/game_bot}/sounds/alarm.ogg | Bin {data => mods/game_bot}/sounds/magnum.ogg | Bin modules/client/client.lua | 3 +- {data => modules/client}/sounds/startup.ogg | Bin modules/game_things/things.lua | 36 ++-- src/client/luafunctions.cpp | 1 - src/client/thingtypemanager.cpp | 8 - src/client/thingtypemanager.h | 1 - src/framework/luafunctions.cpp | 2 + src/framework/sound/soundmanager.cpp | 198 +++++++++++++++++- src/framework/sound/soundmanager.h | 96 +++++++++ src/protobuf/sounds.proto | 4 +- 25 files changed, 365 insertions(+), 48 deletions(-) create mode 100644 data/sounds/.gitignore create mode 100644 data/sounds/README.md create mode 100644 data/things/README.md rename {data => mods/game_bot}/sounds/Creature_Detected.ogg (100%) rename {data => mods/game_bot}/sounds/Low_Health.ogg (100%) rename {data => mods/game_bot}/sounds/Low_Mana.ogg (100%) rename {data => mods/game_bot}/sounds/Player_Attack.ogg (100%) rename {data => mods/game_bot}/sounds/Player_Detected.ogg (100%) rename {data => mods/game_bot}/sounds/Private_Message.ogg (100%) rename {data => mods/game_bot}/sounds/alarm.ogg (100%) rename {data => mods/game_bot}/sounds/magnum.ogg (100%) rename {data => modules/client}/sounds/startup.ogg (100%) diff --git a/data/sounds/.gitignore b/data/sounds/.gitignore new file mode 100644 index 0000000000..7c9d611b59 --- /dev/null +++ b/data/sounds/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!README.md diff --git a/data/sounds/README.md b/data/sounds/README.md new file mode 100644 index 0000000000..8fbbb7d748 --- /dev/null +++ b/data/sounds/README.md @@ -0,0 +1,9 @@ +# Adding client versions + +Create a version folder and drop your soundbank in it. + +For the list of supported client versions see `modules/gamelib/game.lua` + +# Example configuration + +`data/sounds/1340/ (catalog-sound.json and all sound files)` diff --git a/data/things/.gitignore b/data/things/.gitignore index d6b7ef32c8..7c9d611b59 100644 --- a/data/things/.gitignore +++ b/data/things/.gitignore @@ -1,2 +1,3 @@ * !.gitignore +!README.md diff --git a/data/things/README.md b/data/things/README.md new file mode 100644 index 0000000000..731a9c149c --- /dev/null +++ b/data/things/README.md @@ -0,0 +1,13 @@ +# Adding client versions + +Create a version folder and drop your sprite files or assets in it. + +For the list of supported client versions see `modules/gamelib/game.lua` + +# Example configurations + +spr/dat: +`data/things/860/ (spr and dat files there)` + +assets: +`data/things/1340/ (catalog-content.json and all asset files)` diff --git a/meta.lua b/meta.lua index a6c4269b8d..f5ba6de77e 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,14 @@ function g_sounds.createSoundEffect() end ---@return boolean function g_sounds.isEaxEnabled() end +---@param file string +---@return boolean +function g_sounds.loadClientFiles(directory) end + +---@param audioFileId string +---@return string +function g_sounds.getAudioFileNameById(audioFileId) end + -------------------------------- --------- SoundSource ---------- -------------------------------- diff --git a/mods/game_bot/default_configs/vBot_4.8/vBot/alarms.lua b/mods/game_bot/default_configs/vBot_4.8/vBot/alarms.lua index 80c95409e9..290868be38 100644 --- a/mods/game_bot/default_configs/vBot_4.8/vBot/alarms.lua +++ b/mods/game_bot/default_configs/vBot_4.8/vBot/alarms.lua @@ -114,18 +114,16 @@ addAlarm("creatureDetected", "Creature Detected", false, 1, 1) addAlarm("playerDetected", "Player Detected", false, 1, 1) addAlarm("creatureName", "Creature Name:", "", 3, 1, "You can add a name or part of it, that if found in any visible creature name will trigger alert.\nYou can add many, just separate them by comma.") - local lastCall = now local function alarm(file, windowText) if now - lastCall < 2000 then return end -- 2s delay lastCall = now if not g_resources.fileExists(file) then - file = "/sounds/alarm.ogg" + file = "sounds/alarm.ogg" lastCall = now + 4000 -- alarm.ogg length is 6s end - if modules.game_bot.g_app.getOs() == "windows" and config.flashClient.enabled then g_window.flash() end @@ -137,7 +135,7 @@ end onTextMessage(function(mode, text) if not config.enabled then return end if mode == 22 and config.damageTaken.enabled then - return alarm('/sounds/magnum.ogg', "Damage Received!") + return alarm('sounds/magnum.ogg', "Damage Received!") end if config.customMessage.enabled then @@ -152,7 +150,7 @@ onTextMessage(function(mode, text) part = part:lower() if text:find(part) then - return alarm('/sounds/magnum.ogg', "Special Message!") + return alarm('sounds/magnum.ogg', "Special Message!") end end end @@ -166,11 +164,11 @@ onTalk(function(name, level, mode, text, channelId, pos) if config.ignoreFriends.enabled and isFriend(name) then return end -- ignore friends if enabled if mode == 1 and config.defaultMsg.enabled then - return alarm("/sounds/magnum.ogg", "Default Message!") + return alarm("sounds/magnum.ogg", "Default Message!") end if mode == 4 and config.privateMsg.enabled then - return alarm("/sounds/Private_Message.ogg", "Private Message!") + return alarm("sounds/Private_Message.ogg", "Private Message!") end end) @@ -179,13 +177,13 @@ macro(100, function() if not config.enabled then return end if config.lowHealth.enabled then if hppercent() < config.lowHealth.value then - return alarm("/sounds/Low_Health.ogg", "Low Health!") + return alarm("sounds/Low_Health.ogg", "Low Health!") end end if config.lowMana.enabled then if hppercent() < config.lowMana.value then - return alarm("/sounds/Low_Mana.ogg", "Low Mana!") + return alarm("sounds/Low_Mana.ogg", "Low Mana!") end end @@ -193,15 +191,15 @@ macro(100, function() if not spec:isLocalPlayer() and not (config.ignoreFriends.enabled and isFriend(spec)) then if config.creatureDetected.enabled then - return alarm("/sounds/magnum.ogg", "Creature Detected!") + return alarm("sounds/magnum.ogg", "Creature Detected!") end if spec:isPlayer() then if spec:isTimedSquareVisible() and config.playerAttack.enabled then - return alarm("/sounds/Player_Attack.ogg", "Player Attack!") + return alarm("sounds/Player_Attack.ogg", "Player Attack!") end if config.playerDetected.enabled then - return alarm("/sounds/Player_Detected.ogg", "Player Detected!") + return alarm("sounds/Player_Detected.ogg", "Player Detected!") end end @@ -213,7 +211,7 @@ macro(100, function() local frag = fragments[i]:trim():lower() if name:lower():find(frag) then - return alarm("/sounds/alarm.ogg", "Special Creature Detected!") + return alarm("sounds/alarm.ogg", "Special Creature Detected!") end end end diff --git a/mods/game_bot/functions/sound.lua b/mods/game_bot/functions/sound.lua index c7fffeefd4..62ceee53e5 100644 --- a/mods/game_bot/functions/sound.lua +++ b/mods/game_bot/functions/sound.lua @@ -27,5 +27,5 @@ context.stopSound = function() end context.playAlarm = function() - return context.playSound("/sounds/alarm.ogg") + return context.playSound("sounds/alarm.ogg") end diff --git a/data/sounds/Creature_Detected.ogg b/mods/game_bot/sounds/Creature_Detected.ogg similarity index 100% rename from data/sounds/Creature_Detected.ogg rename to mods/game_bot/sounds/Creature_Detected.ogg diff --git a/data/sounds/Low_Health.ogg b/mods/game_bot/sounds/Low_Health.ogg similarity index 100% rename from data/sounds/Low_Health.ogg rename to mods/game_bot/sounds/Low_Health.ogg diff --git a/data/sounds/Low_Mana.ogg b/mods/game_bot/sounds/Low_Mana.ogg similarity index 100% rename from data/sounds/Low_Mana.ogg rename to mods/game_bot/sounds/Low_Mana.ogg diff --git a/data/sounds/Player_Attack.ogg b/mods/game_bot/sounds/Player_Attack.ogg similarity index 100% rename from data/sounds/Player_Attack.ogg rename to mods/game_bot/sounds/Player_Attack.ogg diff --git a/data/sounds/Player_Detected.ogg b/mods/game_bot/sounds/Player_Detected.ogg similarity index 100% rename from data/sounds/Player_Detected.ogg rename to mods/game_bot/sounds/Player_Detected.ogg diff --git a/data/sounds/Private_Message.ogg b/mods/game_bot/sounds/Private_Message.ogg similarity index 100% rename from data/sounds/Private_Message.ogg rename to mods/game_bot/sounds/Private_Message.ogg diff --git a/data/sounds/alarm.ogg b/mods/game_bot/sounds/alarm.ogg similarity index 100% rename from data/sounds/alarm.ogg rename to mods/game_bot/sounds/alarm.ogg diff --git a/data/sounds/magnum.ogg b/mods/game_bot/sounds/magnum.ogg similarity index 100% rename from data/sounds/magnum.ogg rename to mods/game_bot/sounds/magnum.ogg diff --git a/modules/client/client.lua b/modules/client/client.lua index fcdccc3bca..ce83bf84ff 100644 --- a/modules/client/client.lua +++ b/modules/client/client.lua @@ -1,4 +1,4 @@ -local musicFilename = '/sounds/startup' +local musicFilename = 'sounds/startup' local musicChannel = nil if g_sounds then musicChannel = g_sounds.getChannel(SoundChannels.Music) @@ -14,7 +14,6 @@ function setMusic(filename) end function startup() - -- Play startup music (The Silver Tree, by Mattias Westlund) if musicChannel then musicChannel:enqueue(musicFilename, 3) connect(g_game, { diff --git a/data/sounds/startup.ogg b/modules/client/sounds/startup.ogg similarity index 100% rename from data/sounds/startup.ogg rename to modules/client/sounds/startup.ogg diff --git a/modules/game_things/things.lua b/modules/game_things/things.lua index 35c1bd2973..a042e87fe8 100644 --- a/modules/game_things/things.lua +++ b/modules/game_things/things.lua @@ -59,21 +59,27 @@ function load(version) end loaded = #errorList == 0 + if loaded then + -- loading client files was successful, try to load sounds now + -- sound files are optional, this means that failing to load them + -- will not block logging into game + g_sounds.loadClientFiles(resolvepath(string.format('/sounds/%d/', version))) + return + end - if not loaded then - local messageBox = displayErrorBox(tr('Error'), table.concat(errorList, "\n")) - addEvent(function() - messageBox:raise() - messageBox:focus() - end) + -- loading client files failed, show an error + local messageBox = displayErrorBox(tr('Error'), table.concat(errorList, "\n")) + addEvent(function() + messageBox:raise() + messageBox:focus() + end) - disconnect(g_game, { - onClientVersionChange = load - }) - g_game.setClientVersion(0) - g_game.setProtocolVersion(0) - connect(g_game, { - onClientVersionChange = load - }) - end + disconnect(g_game, { + onClientVersionChange = load + }) + g_game.setClientVersion(0) + g_game.setProtocolVersion(0) + connect(g_game, { + onClientVersionChange = load + }) end 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..5d594be4a6 100644 --- a/src/client/thingtypemanager.cpp +++ b/src/client/thingtypemanager.cpp @@ -38,7 +38,6 @@ #include #include -#include #include @@ -288,13 +287,6 @@ bool ThingTypeManager::loadStaticData(const std::string& file) return false; } -bool ThingTypeManager::loadSounds(const std::string& /* file */) -{ - // to be implemented - // to be moved to g_sounds - return false; -} - const ThingTypeList& ThingTypeManager::getThingTypes(const ThingCategory category) { if (category < ThingLastCategory) diff --git a/src/client/thingtypemanager.h b/src/client/thingtypemanager.h index 120b77c633..daec118e84 100644 --- a/src/client/thingtypemanager.h +++ b/src/client/thingtypemanager.h @@ -43,7 +43,6 @@ class ThingTypeManager bool loadOtml(std::string file); bool loadAppearances(const std::string& file); bool loadStaticData(const std::string& file); - bool loadSounds(const std::string& file); #ifdef FRAMEWORK_EDITOR void parseItemType(uint16_t id, pugi::xml_node node); diff --git a/src/framework/luafunctions.cpp b/src/framework/luafunctions.cpp index e7042b5317..78a0b813aa 100644 --- a/src/framework/luafunctions.cpp +++ b/src/framework/luafunctions.cpp @@ -1004,6 +1004,8 @@ 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.bindSingletonFunction("g_sounds", "getAudioFileNameById", &SoundManager::getAudioFileNameById, &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..dfbf32efe6 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,194 @@ 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 { + 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) { + if (g_game.getClientVersion() >= 1300) { + g_logger.warning(stdext::format("Failed to load '%s' (Sounds): %s", directory, e.what())); + } + + return false; + } +} + +std::string SoundManager::getAudioFileNameById(int32_t audioFileId) +{ + if (m_clientSoundFiles.contains(audioFileId)) { + return m_clientSoundFiles[audioFileId]; + } + + return ""; +} diff --git a/src/framework/sound/soundmanager.h b/src/framework/sound/soundmanager.h index 79222b15f5..fbc753f85c 100644 --- a/src/framework/sound/soundmanager.h +++ b/src/framework/sound/soundmanager.h @@ -23,10 +23,96 @@ #pragma once #include "declarations.h" +#include "client/game.h" #include "soundsource.h" #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 +133,8 @@ class SoundManager void stopAll(); void setPosition(const Point& pos); bool isEaxEnabled(); + bool loadClientFiles(const std::string& directory); + std::string getAudioFileNameById(int32_t audioFileId); void preload(std::string filename); SoundSourcePtr play(const std::string& filename, float fadetime = 0, float gain = 0, float pitch = 0); @@ -58,6 +146,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 +158,13 @@ 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; }