You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

324 lines
7.1 KiB

/**
* @file effects.cpp
*
* Implementation of functions for loading and playing sounds.
*/
#include "effects.h"
#include <cstdint>
#include <string_view>
#include <expected.hpp>
#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 "init.h"
#include "player.h"
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<TSFX> 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<SfxID>(static_cast<int16_t>(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<SfxID>(static_cast<int16_t>(psfx) + 1), static_cast<SfxID>(static_cast<int16_t>(psfx) + 2) });
default:
7 years ago
return psfx;
}
}
tl::expected<sfx_flag, std::string> 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";
tl::expected<DataFile, DataFile::Error> dataFileResult = DataFile::load(filename);
if (!dataFileResult.has_value()) {
DataFile::reportFatalError(dataFileResult.error(), filename);
}
DataFile &dataFile = dataFileResult.value();
if (tl::expected<void, DataFile::Error> result = dataFile.skipHeader();
!result.has_value()) {
DataFile::reportFatalError(result.error(), 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();
}
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)
{
TSFX *sfx = &sgSFX[static_cast<int16_t>(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);
PlaySfxPriv(&sgSFX[static_cast<int16_t>(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<int16_t>(psfx)].pSnd.get();
if (pSnd != nullptr)
pSnd->start_tc = 0;
}
PlaySfxPriv(&sgSFX[static_cast<int16_t>(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) {
5 years ago
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<int16_t>(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<int16_t>(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