/** * @file effects.cpp * * Implementation of functions for loading and playing sounds. */ #include "effects.h" #include #include #include #include "data/file.hpp" #include "data/iterators.hpp" #include "data/record_reader.hpp" #include "engine/random.hpp" #include "engine/sound.h" #include "engine/sound_defs.hpp" #include "engine/sound_position.hpp" #include "game_mode.hpp" #include "player.h" #include "utils/is_of.hpp" namespace devilution { int sfxdelay; SfxID sfxdnum = SfxID::None; namespace { #ifndef DISABLE_STREAMING_SOUNDS constexpr bool AllowStreaming = true; #else constexpr bool AllowStreaming = false; #endif /** Specifies the sound file and the playback state of the current sound effect. */ TSFX *sgpStreamSFX = nullptr; /** List of all sounds, except monsters and music */ std::vector sgSFX; void StreamPlay(TSFX *pSFX, int lVolume, int lPan) { assert(pSFX); assert(pSFX->bFlags & sfx_STREAM); stream_stop(); if (lVolume >= VOLUME_MIN) { if (lVolume > VOLUME_MAX) lVolume = VOLUME_MAX; if (pSFX->pSnd == nullptr) pSFX->pSnd = sound_file_load(pSFX->pszName.c_str(), AllowStreaming); if (pSFX->pSnd->DSB.IsLoaded()) pSFX->pSnd->DSB.PlayWithVolumeAndPan(lVolume, sound_get_or_set_sound_volume(1), lPan); sgpStreamSFX = pSFX; } } void StreamUpdate() { if (sgpStreamSFX != nullptr && !sgpStreamSFX->pSnd->isPlaying()) { stream_stop(); } } void PlaySfxPriv(TSFX *pSFX, bool loc, Point position) { if (MyPlayer->pLvlLoad != 0 && gbIsMultiplayer) { return; } if (!gbSndInited || !gbSoundOn || gbBufferMsgs != 0) { return; } if ((pSFX->bFlags & (sfx_STREAM | sfx_MISC)) == 0 && pSFX->pSnd != nullptr && pSFX->pSnd->isPlaying()) { return; } int lVolume = 0; int lPan = 0; if (loc && !CalculateSoundPosition(position, &lVolume, &lPan)) { return; } if ((pSFX->bFlags & sfx_STREAM) != 0) { StreamPlay(pSFX, lVolume, lPan); return; } if (pSFX->pSnd == nullptr) pSFX->pSnd = sound_file_load(pSFX->pszName.c_str()); if (pSFX->pSnd != nullptr && pSFX->pSnd->DSB.IsLoaded()) snd_play_snd(pSFX->pSnd.get(), lVolume, lPan); } SfxID RndSFX(SfxID psfx) { switch (psfx) { case SfxID::Warrior69: case SfxID::Sorceror69: case SfxID::Rogue69: case SfxID::Monk69: case SfxID::Swing: case SfxID::SpellAcid: case SfxID::OperateShrine: return PickRandomlyAmong({ psfx, static_cast(static_cast(psfx) + 1) }); case SfxID::Warrior14: case SfxID::Warrior15: case SfxID::Warrior16: case SfxID::Warrior2: case SfxID::Rogue14: case SfxID::Sorceror14: case SfxID::Monk14: return PickRandomlyAmong({ psfx, static_cast(static_cast(psfx) + 1), static_cast(static_cast(psfx) + 2) }); default: return psfx; } } tl::expected ParseSfxFlag(std::string_view value) { if (value == "Stream") return sfx_STREAM; if (value == "Misc") return sfx_MISC; if (value == "Ui") return sfx_UI; if (value == "Monk") return sfx_MONK; if (value == "Rogue") return sfx_ROGUE; if (value == "Warrior") return sfx_WARRIOR; if (value == "Sorcerer") return sfx_SORCERER; if (value == "Hellfire") return sfx_HELLFIRE; return tl::make_unexpected("Unknown enum value"); } void LoadEffectsData() { const std::string_view filename = "txtdata\\sound\\effects.tsv"; DataFile dataFile = DataFile::loadOrDie(filename); dataFile.skipHeaderOrDie(filename); sgSFX.clear(); sgSFX.reserve(dataFile.numRecords()); for (DataFileRecord record : dataFile) { RecordReader reader { record, filename }; TSFX &item = sgSFX.emplace_back(); reader.advance(); // Skip the first column (effect ID). reader.readEnumList("flags", item.bFlags, ParseSfxFlag); reader.readString("path", item.pszName); } sgSFX.shrink_to_fit(); // We're not actually parsing the IDs yet, thus this sanity check here. assert(static_cast(SfxID::LAST) + 1 == sgSFX.size()); } void PrivSoundInit(uint8_t bLoadMask) { if (!gbSndInited) { return; } if (sgSFX.empty()) LoadEffectsData(); for (auto &sfx : sgSFX) { if (sfx.bFlags == 0 || sfx.pSnd != nullptr) { continue; } if ((sfx.bFlags & sfx_STREAM) != 0) { continue; } if ((sfx.bFlags & bLoadMask) == 0) { continue; } if (!gbIsHellfire && (sfx.bFlags & sfx_HELLFIRE) != 0) { continue; } sfx.pSnd = sound_file_load(sfx.pszName.c_str()); } } } // namespace bool effect_is_playing(SfxID nSFX) { if (!gbSndInited) return false; TSFX *sfx = &sgSFX[static_cast(nSFX)]; if (sfx->pSnd != nullptr) return sfx->pSnd->isPlaying(); if ((sfx->bFlags & sfx_STREAM) != 0) return sfx == sgpStreamSFX; return false; } void stream_stop() { if (sgpStreamSFX != nullptr) { sgpStreamSFX->pSnd = nullptr; sgpStreamSFX = nullptr; } } void PlaySFX(SfxID psfx) { psfx = RndSFX(psfx); if (!gbSndInited) return; PlaySfxPriv(&sgSFX[static_cast(psfx)], false, { 0, 0 }); } void PlaySfxLoc(SfxID psfx, Point position, bool randomizeByCategory) { if (randomizeByCategory) { psfx = RndSFX(psfx); } if (!gbSndInited) return; if (IsAnyOf(psfx, SfxID::Walk, SfxID::ShootBow, SfxID::CastSpell, SfxID::Swing)) { TSnd *pSnd = sgSFX[static_cast(psfx)].pSnd.get(); if (pSnd != nullptr) pSnd->start_tc = 0; } PlaySfxPriv(&sgSFX[static_cast(psfx)], true, position); } void sound_stop() { if (!gbSndInited) return; ClearDuplicateSounds(); for (auto &sfx : sgSFX) { if (sfx.pSnd != nullptr && sfx.pSnd->DSB.IsLoaded()) { sfx.pSnd->DSB.Stop(); } } } void sound_update() { if (!gbSndInited) { return; } StreamUpdate(); } void effects_cleanup_sfx() { sound_stop(); for (auto &sfx : sgSFX) sfx.pSnd = nullptr; } void sound_init() { uint8_t mask = sfx_MISC; if (gbIsMultiplayer) { mask |= sfx_WARRIOR; if (!gbIsSpawn) mask |= (sfx_ROGUE | sfx_SORCERER); if (gbIsHellfire) mask |= sfx_MONK; } else { switch (MyPlayer->_pClass) { case HeroClass::Warrior: case HeroClass::Barbarian: mask |= sfx_WARRIOR; break; case HeroClass::Rogue: case HeroClass::Bard: mask |= sfx_ROGUE; break; case HeroClass::Sorcerer: mask |= sfx_SORCERER; break; case HeroClass::Monk: mask |= sfx_MONK; break; default: app_fatal("effects:1"); } } PrivSoundInit(mask); } void ui_sound_init() { PrivSoundInit(sfx_UI); } void effects_play_sound(SfxID id) { if (!gbSndInited || !gbSoundOn) { return; } TSFX &sfx = sgSFX[static_cast(id)]; if (sfx.pSnd != nullptr && !sfx.pSnd->isPlaying()) { snd_play_snd(sfx.pSnd.get(), 0, 0); } } int GetSFXLength(SfxID nSFX) { TSFX &sfx = sgSFX[static_cast(nSFX)]; if (sfx.pSnd == nullptr) sfx.pSnd = sound_file_load(sfx.pszName.c_str(), /*stream=*/AllowStreaming && (sfx.bFlags & sfx_STREAM) != 0); return sfx.pSnd->DSB.GetLength(); } } // namespace devilution