mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-25 22:42:04 +02:00
Reorganized music, video and sound players:
- All XXXplayers are now in client/media directory - Reogranized code on one class per file basis - Extracted interfaces from handlers. Handlers now implement corresponding interface. - CCS now only stores pointer to an interface
This commit is contained in:
@@ -36,21 +36,21 @@ class CMap;
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class CMapHandler;
|
||||
class CSoundHandler;
|
||||
class CMusicHandler;
|
||||
class ISoundPlayer;
|
||||
class IMusicPlayer;
|
||||
class CursorHandler;
|
||||
class IMainVideoPlayer;
|
||||
class IVideoPlayer;
|
||||
class CServerHandler;
|
||||
|
||||
//a class for non-mechanical client GUI classes
|
||||
class CClientState
|
||||
{
|
||||
public:
|
||||
CSoundHandler * soundh;
|
||||
CMusicHandler * musich;
|
||||
ISoundPlayer * soundh;
|
||||
IMusicPlayer * musich;
|
||||
CConsoleHandler * consoleh;
|
||||
CursorHandler * curh;
|
||||
IMainVideoPlayer * videoh;
|
||||
IVideoPlayer * videoh;
|
||||
};
|
||||
extern CClientState * CCS;
|
||||
|
||||
|
||||
@@ -14,11 +14,13 @@
|
||||
|
||||
#include "CGameInfo.h"
|
||||
#include "mainmenu/CMainMenu.h"
|
||||
#include "media/CEmptyVideoPlayer.h"
|
||||
#include "media/CMusicHandler.h"
|
||||
#include "media/CSoundHandler.h"
|
||||
#include "media/CVideoHandler.h"
|
||||
#include "gui/CursorHandler.h"
|
||||
#include "eventsSDL/InputHandler.h"
|
||||
#include "CPlayerInterface.h"
|
||||
#include "CVideoHandler.h"
|
||||
#include "CMusicHandler.h"
|
||||
#include "gui/CGuiHandler.h"
|
||||
#include "gui/WindowHandler.h"
|
||||
#include "CServerHandler.h"
|
||||
@@ -292,10 +294,8 @@ int main(int argc, char * argv[])
|
||||
{
|
||||
//initializing audio
|
||||
CCS->soundh = new CSoundHandler();
|
||||
CCS->soundh->init();
|
||||
CCS->soundh->setVolume((ui32)settings["general"]["sound"].Float());
|
||||
CCS->musich = new CMusicHandler();
|
||||
CCS->musich->init();
|
||||
CCS->musich->setVolume((ui32)settings["general"]["music"].Float());
|
||||
logGlobal->info("Initializing screen and sound handling: %d ms", pomtime.getDiff());
|
||||
}
|
||||
@@ -457,9 +457,6 @@ static void mainLoop()
|
||||
// cleanup, mostly to remove false leaks from analyzer
|
||||
if(CCS)
|
||||
{
|
||||
CCS->musich->release();
|
||||
CCS->soundh->release();
|
||||
|
||||
delete CCS->consoleh;
|
||||
delete CCS->curh;
|
||||
delete CCS->videoh;
|
||||
|
||||
@@ -75,6 +75,11 @@ set(client_SRCS
|
||||
mapView/MapViewModel.cpp
|
||||
mapView/mapHandler.cpp
|
||||
|
||||
media/CAudioBase.cpp
|
||||
media/CMusicHandler.cpp
|
||||
media/CSoundHandler.cpp
|
||||
media/CVideoHandler.cpp
|
||||
|
||||
render/CAnimation.cpp
|
||||
render/CBitmapHandler.cpp
|
||||
render/CDefFile.cpp
|
||||
@@ -163,11 +168,9 @@ set(client_SRCS
|
||||
|
||||
CGameInfo.cpp
|
||||
CMT.cpp
|
||||
CMusicHandler.cpp
|
||||
CPlayerInterface.cpp
|
||||
PlayerLocalState.cpp
|
||||
CServerHandler.cpp
|
||||
CVideoHandler.cpp
|
||||
Client.cpp
|
||||
ClientCommandManager.cpp
|
||||
GameChatHandler.cpp
|
||||
@@ -260,6 +263,15 @@ set(client_HEADERS
|
||||
mapView/MapViewModel.h
|
||||
mapView/mapHandler.h
|
||||
|
||||
media/CAudioBase.h
|
||||
media/CEmptyVideoPlayer.h
|
||||
media/CMusicHandler.h
|
||||
media/CSoundHandler.h
|
||||
media/CVideoHandler.h
|
||||
media/IMusicPlayer.h
|
||||
media/ISoundPlayer.h
|
||||
media/IVideoPlayer.h
|
||||
|
||||
render/CAnimation.h
|
||||
render/CBitmapHandler.h
|
||||
render/CDefFile.h
|
||||
@@ -357,11 +369,9 @@ set(client_HEADERS
|
||||
|
||||
CGameInfo.h
|
||||
CMT.h
|
||||
CMusicHandler.h
|
||||
CPlayerInterface.h
|
||||
PlayerLocalState.h
|
||||
CServerHandler.h
|
||||
CVideoHandler.h
|
||||
Client.h
|
||||
ClientCommandManager.h
|
||||
ClientNetPackVisitors.h
|
||||
|
||||
@@ -1,753 +0,0 @@
|
||||
/*
|
||||
* CMusicHandler.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include <SDL_mixer.h>
|
||||
#include <SDL_timer.h>
|
||||
|
||||
#include "CMusicHandler.h"
|
||||
#include "CGameInfo.h"
|
||||
#include "renderSDL/SDLRWwrapper.h"
|
||||
#include "eventsSDL/InputHandler.h"
|
||||
#include "gui/CGuiHandler.h"
|
||||
|
||||
#include "../lib/GameConstants.h"
|
||||
#include "../lib/filesystem/Filesystem.h"
|
||||
#include "../lib/constants/StringConstants.h"
|
||||
#include "../lib/CRandomGenerator.h"
|
||||
#include "../lib/VCMIDirs.h"
|
||||
#include "../lib/TerrainHandler.h"
|
||||
|
||||
|
||||
#define VCMI_SOUND_NAME(x)
|
||||
#define VCMI_SOUND_FILE(y) #y,
|
||||
|
||||
// sounds mapped to soundBase enum
|
||||
static const std::string sounds[] = {
|
||||
"", // invalid
|
||||
"", // todo
|
||||
VCMI_SOUND_LIST
|
||||
};
|
||||
#undef VCMI_SOUND_NAME
|
||||
#undef VCMI_SOUND_FILE
|
||||
|
||||
void CAudioBase::init()
|
||||
{
|
||||
if (initialized)
|
||||
return;
|
||||
|
||||
if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 1024)==-1)
|
||||
{
|
||||
logGlobal->error("Mix_OpenAudio error: %s", Mix_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
void CAudioBase::release()
|
||||
{
|
||||
if(!(CCS->soundh->initialized && CCS->musich->initialized))
|
||||
Mix_CloseAudio();
|
||||
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
void CAudioBase::setVolume(ui32 percent)
|
||||
{
|
||||
if (percent > 100)
|
||||
percent = 100;
|
||||
|
||||
volume = percent;
|
||||
}
|
||||
|
||||
void CSoundHandler::onVolumeChange(const JsonNode &volumeNode)
|
||||
{
|
||||
setVolume((ui32)volumeNode.Float());
|
||||
}
|
||||
|
||||
CSoundHandler::CSoundHandler():
|
||||
listener(settings.listen["general"]["sound"]),
|
||||
ambientConfig(JsonPath::builtin("config/ambientSounds.json"))
|
||||
{
|
||||
listener(std::bind(&CSoundHandler::onVolumeChange, this, _1));
|
||||
|
||||
battleIntroSounds =
|
||||
{
|
||||
soundBase::battle00, soundBase::battle01,
|
||||
soundBase::battle02, soundBase::battle03, soundBase::battle04,
|
||||
soundBase::battle05, soundBase::battle06, soundBase::battle07
|
||||
};
|
||||
}
|
||||
|
||||
void CSoundHandler::init()
|
||||
{
|
||||
CAudioBase::init();
|
||||
if(ambientConfig["allocateChannels"].isNumber())
|
||||
Mix_AllocateChannels((int)ambientConfig["allocateChannels"].Integer());
|
||||
|
||||
if (initialized)
|
||||
{
|
||||
Mix_ChannelFinished([](int channel)
|
||||
{
|
||||
CCS->soundh->soundFinishedCallback(channel);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void CSoundHandler::release()
|
||||
{
|
||||
if (initialized)
|
||||
{
|
||||
Mix_HaltChannel(-1);
|
||||
|
||||
for (auto &chunk : soundChunks)
|
||||
{
|
||||
if (chunk.second.first)
|
||||
Mix_FreeChunk(chunk.second.first);
|
||||
}
|
||||
}
|
||||
|
||||
CAudioBase::release();
|
||||
}
|
||||
|
||||
// Allocate an SDL chunk and cache it.
|
||||
Mix_Chunk *CSoundHandler::GetSoundChunk(const AudioPath & sound, bool cache)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (cache && soundChunks.find(sound) != soundChunks.end())
|
||||
return soundChunks[sound].first;
|
||||
|
||||
auto data = CResourceHandler::get()->load(sound.addPrefix("SOUNDS/"))->readAll();
|
||||
SDL_RWops *ops = SDL_RWFromMem(data.first.get(), (int)data.second);
|
||||
Mix_Chunk *chunk = Mix_LoadWAV_RW(ops, 1); // will free ops
|
||||
|
||||
if (cache)
|
||||
soundChunks.insert({sound, std::make_pair (chunk, std::move (data.first))});
|
||||
|
||||
return chunk;
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
logGlobal->warn("Cannot get sound %s chunk: %s", sound.getOriginalName(), e.what());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Mix_Chunk *CSoundHandler::GetSoundChunk(std::pair<std::unique_ptr<ui8 []>, si64> & data, bool cache)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::vector<ui8> startBytes = std::vector<ui8>(data.first.get(), data.first.get() + std::min((si64)100, data.second));
|
||||
|
||||
if (cache && soundChunksRaw.find(startBytes) != soundChunksRaw.end())
|
||||
return soundChunksRaw[startBytes].first;
|
||||
|
||||
SDL_RWops *ops = SDL_RWFromMem(data.first.get(), (int)data.second);
|
||||
Mix_Chunk *chunk = Mix_LoadWAV_RW(ops, 1); // will free ops
|
||||
|
||||
if (cache)
|
||||
soundChunksRaw.insert({startBytes, std::make_pair (chunk, std::move (data.first))});
|
||||
|
||||
return chunk;
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
logGlobal->warn("Cannot get sound chunk: %s", e.what());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int CSoundHandler::ambientDistToVolume(int distance) const
|
||||
{
|
||||
const auto & distancesVector = ambientConfig["distances"].Vector();
|
||||
|
||||
if(distance >= distancesVector.size())
|
||||
return 0;
|
||||
|
||||
int volume = static_cast<int>(distancesVector[distance].Integer());
|
||||
return volume * (int)ambientConfig["volume"].Integer() / 100;
|
||||
}
|
||||
|
||||
void CSoundHandler::ambientStopSound(const AudioPath & soundId)
|
||||
{
|
||||
stopSound(ambientChannels[soundId]);
|
||||
setChannelVolume(ambientChannels[soundId], volume);
|
||||
}
|
||||
|
||||
uint32_t CSoundHandler::getSoundDurationMilliseconds(const AudioPath & sound)
|
||||
{
|
||||
if (!initialized || sound.empty())
|
||||
return 0;
|
||||
|
||||
auto resourcePath = sound.addPrefix("SOUNDS/");
|
||||
|
||||
if (!CResourceHandler::get()->existsResource(resourcePath))
|
||||
return 0;
|
||||
|
||||
auto data = CResourceHandler::get()->load(resourcePath)->readAll();
|
||||
|
||||
SDL_AudioSpec spec;
|
||||
uint32_t audioLen;
|
||||
uint8_t *audioBuf;
|
||||
uint32_t miliseconds = 0;
|
||||
|
||||
if(SDL_LoadWAV_RW(SDL_RWFromMem(data.first.get(), (int)data.second), 1, &spec, &audioBuf, &audioLen) != nullptr)
|
||||
{
|
||||
SDL_FreeWAV(audioBuf);
|
||||
uint32_t sampleSize = SDL_AUDIO_BITSIZE(spec.format) / 8;
|
||||
uint32_t sampleCount = audioLen / sampleSize;
|
||||
uint32_t sampleLen = sampleCount / spec.channels;
|
||||
miliseconds = 1000 * sampleLen / spec.freq;
|
||||
}
|
||||
|
||||
return miliseconds ;
|
||||
}
|
||||
|
||||
// Plays a sound, and return its channel so we can fade it out later
|
||||
int CSoundHandler::playSound(soundBase::soundID soundID, int repeats)
|
||||
{
|
||||
assert(soundID < soundBase::sound_after_last);
|
||||
auto sound = AudioPath::builtin(sounds[soundID]);
|
||||
logGlobal->trace("Attempt to play sound %d with file name %s with cache", soundID, sound.getOriginalName());
|
||||
|
||||
return playSound(sound, repeats, true);
|
||||
}
|
||||
|
||||
int CSoundHandler::playSound(const AudioPath & sound, int repeats, bool cache)
|
||||
{
|
||||
if (!initialized || sound.empty())
|
||||
return -1;
|
||||
|
||||
int channel;
|
||||
Mix_Chunk *chunk = GetSoundChunk(sound, cache);
|
||||
|
||||
if (chunk)
|
||||
{
|
||||
channel = Mix_PlayChannel(-1, chunk, repeats);
|
||||
if (channel == -1)
|
||||
{
|
||||
logGlobal->error("Unable to play sound file %s , error %s", sound.getOriginalName(), Mix_GetError());
|
||||
if (!cache)
|
||||
Mix_FreeChunk(chunk);
|
||||
}
|
||||
else if (cache)
|
||||
initCallback(channel);
|
||||
else
|
||||
initCallback(channel, [chunk](){ Mix_FreeChunk(chunk);});
|
||||
}
|
||||
else
|
||||
channel = -1;
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
int CSoundHandler::playSound(std::pair<std::unique_ptr<ui8 []>, si64> & data, int repeats, bool cache)
|
||||
{
|
||||
int channel = -1;
|
||||
if (Mix_Chunk *chunk = GetSoundChunk(data, cache))
|
||||
{
|
||||
channel = Mix_PlayChannel(-1, chunk, repeats);
|
||||
if (channel == -1)
|
||||
{
|
||||
logGlobal->error("Unable to play sound, error %s", Mix_GetError());
|
||||
if (!cache)
|
||||
Mix_FreeChunk(chunk);
|
||||
}
|
||||
else if (cache)
|
||||
initCallback(channel);
|
||||
else
|
||||
initCallback(channel, [chunk](){ Mix_FreeChunk(chunk);});
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
// Helper. Randomly select a sound from an array and play it
|
||||
int CSoundHandler::playSoundFromSet(std::vector<soundBase::soundID> &sound_vec)
|
||||
{
|
||||
return playSound(*RandomGeneratorUtil::nextItem(sound_vec, CRandomGenerator::getDefault()));
|
||||
}
|
||||
|
||||
void CSoundHandler::stopSound(int handler)
|
||||
{
|
||||
if (initialized && handler != -1)
|
||||
Mix_HaltChannel(handler);
|
||||
}
|
||||
|
||||
// Sets the sound volume, from 0 (mute) to 100
|
||||
void CSoundHandler::setVolume(ui32 percent)
|
||||
{
|
||||
CAudioBase::setVolume(percent);
|
||||
|
||||
if (initialized)
|
||||
{
|
||||
setChannelVolume(-1, volume);
|
||||
|
||||
for (auto const & channel : channelVolumes)
|
||||
updateChannelVolume(channel.first);
|
||||
}
|
||||
}
|
||||
|
||||
void CSoundHandler::updateChannelVolume(int channel)
|
||||
{
|
||||
if (channelVolumes.count(channel))
|
||||
setChannelVolume(channel, getVolume() * channelVolumes[channel] / 100);
|
||||
else
|
||||
setChannelVolume(channel, getVolume());
|
||||
}
|
||||
|
||||
// Sets the sound volume, from 0 (mute) to 100
|
||||
void CSoundHandler::setChannelVolume(int channel, ui32 percent)
|
||||
{
|
||||
Mix_Volume(channel, (MIX_MAX_VOLUME * percent)/100);
|
||||
}
|
||||
|
||||
void CSoundHandler::setCallback(int channel, std::function<void()> function)
|
||||
{
|
||||
boost::mutex::scoped_lock lockGuard(mutexCallbacks);
|
||||
|
||||
auto iter = callbacks.find(channel);
|
||||
|
||||
//channel not found. It may have finished so fire callback now
|
||||
if(iter == callbacks.end())
|
||||
function();
|
||||
else
|
||||
iter->second.push_back(function);
|
||||
}
|
||||
|
||||
void CSoundHandler::resetCallback(int channel)
|
||||
{
|
||||
boost::mutex::scoped_lock lockGuard(mutexCallbacks);
|
||||
|
||||
callbacks.erase(channel);
|
||||
}
|
||||
|
||||
void CSoundHandler::soundFinishedCallback(int channel)
|
||||
{
|
||||
boost::mutex::scoped_lock lockGuard(mutexCallbacks);
|
||||
|
||||
if (callbacks.count(channel) == 0)
|
||||
return;
|
||||
|
||||
// store callbacks from container locally - SDL might reuse this channel for another sound
|
||||
// but do actualy execution in separate thread, to avoid potential deadlocks in case if callback requires locks of its own
|
||||
auto callback = callbacks.at(channel);
|
||||
callbacks.erase(channel);
|
||||
|
||||
if (!callback.empty())
|
||||
{
|
||||
GH.dispatchMainThread([callback](){
|
||||
for (auto entry : callback)
|
||||
entry();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void CSoundHandler::initCallback(int channel)
|
||||
{
|
||||
boost::mutex::scoped_lock lockGuard(mutexCallbacks);
|
||||
assert(callbacks.count(channel) == 0);
|
||||
callbacks[channel] = {};
|
||||
}
|
||||
|
||||
void CSoundHandler::initCallback(int channel, const std::function<void()> & function)
|
||||
{
|
||||
boost::mutex::scoped_lock lockGuard(mutexCallbacks);
|
||||
assert(callbacks.count(channel) == 0);
|
||||
callbacks[channel].push_back(function);
|
||||
}
|
||||
|
||||
int CSoundHandler::ambientGetRange() const
|
||||
{
|
||||
return static_cast<int>(ambientConfig["range"].Integer());
|
||||
}
|
||||
|
||||
void CSoundHandler::ambientUpdateChannels(std::map<AudioPath, int> soundsArg)
|
||||
{
|
||||
boost::mutex::scoped_lock guard(mutex);
|
||||
|
||||
std::vector<AudioPath> stoppedSounds;
|
||||
for(auto & pair : ambientChannels)
|
||||
{
|
||||
const auto & soundId = pair.first;
|
||||
const int channel = pair.second;
|
||||
|
||||
if(!vstd::contains(soundsArg, soundId))
|
||||
{
|
||||
ambientStopSound(soundId);
|
||||
stoppedSounds.push_back(soundId);
|
||||
}
|
||||
else
|
||||
{
|
||||
int volume = ambientDistToVolume(soundsArg[soundId]);
|
||||
channelVolumes[channel] = volume;
|
||||
updateChannelVolume(channel);
|
||||
}
|
||||
}
|
||||
for(auto soundId : stoppedSounds)
|
||||
{
|
||||
channelVolumes.erase(ambientChannels[soundId]);
|
||||
ambientChannels.erase(soundId);
|
||||
}
|
||||
|
||||
for(auto & pair : soundsArg)
|
||||
{
|
||||
const auto & soundId = pair.first;
|
||||
const int distance = pair.second;
|
||||
|
||||
if(!vstd::contains(ambientChannels, soundId))
|
||||
{
|
||||
int channel = playSound(soundId, -1);
|
||||
int volume = ambientDistToVolume(distance);
|
||||
channelVolumes[channel] = volume;
|
||||
|
||||
updateChannelVolume(channel);
|
||||
ambientChannels[soundId] = channel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CSoundHandler::ambientStopAllChannels()
|
||||
{
|
||||
boost::mutex::scoped_lock guard(mutex);
|
||||
|
||||
for(auto ch : ambientChannels)
|
||||
{
|
||||
ambientStopSound(ch.first);
|
||||
}
|
||||
channelVolumes.clear();
|
||||
ambientChannels.clear();
|
||||
}
|
||||
|
||||
void CMusicHandler::onVolumeChange(const JsonNode &volumeNode)
|
||||
{
|
||||
setVolume((ui32)volumeNode.Float());
|
||||
}
|
||||
|
||||
CMusicHandler::CMusicHandler():
|
||||
listener(settings.listen["general"]["music"])
|
||||
{
|
||||
listener(std::bind(&CMusicHandler::onVolumeChange, this, _1));
|
||||
|
||||
auto mp3files = CResourceHandler::get()->getFilteredFiles([](const ResourcePath & id) -> bool
|
||||
{
|
||||
if(id.getType() != EResType::SOUND)
|
||||
return false;
|
||||
|
||||
if(!boost::algorithm::istarts_with(id.getName(), "MUSIC/"))
|
||||
return false;
|
||||
|
||||
logGlobal->trace("Found music file %s", id.getName());
|
||||
return true;
|
||||
});
|
||||
|
||||
for(const ResourcePath & file : mp3files)
|
||||
{
|
||||
if(boost::algorithm::istarts_with(file.getName(), "MUSIC/Combat"))
|
||||
addEntryToSet("battle", AudioPath::fromResource(file));
|
||||
else if(boost::algorithm::istarts_with(file.getName(), "MUSIC/AITheme"))
|
||||
addEntryToSet("enemy-turn", AudioPath::fromResource(file));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CMusicHandler::loadTerrainMusicThemes()
|
||||
{
|
||||
for (const auto & terrain : CGI->terrainTypeHandler->objects)
|
||||
{
|
||||
addEntryToSet("terrain_" + terrain->getJsonKey(), terrain->musicFilename);
|
||||
}
|
||||
}
|
||||
|
||||
void CMusicHandler::addEntryToSet(const std::string & set, const AudioPath & musicURI)
|
||||
{
|
||||
musicsSet[set].push_back(musicURI);
|
||||
}
|
||||
|
||||
void CMusicHandler::init()
|
||||
{
|
||||
CAudioBase::init();
|
||||
|
||||
if (initialized)
|
||||
{
|
||||
Mix_HookMusicFinished([]()
|
||||
{
|
||||
CCS->musich->musicFinishedCallback();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void CMusicHandler::release()
|
||||
{
|
||||
if (initialized)
|
||||
{
|
||||
boost::mutex::scoped_lock guard(mutex);
|
||||
|
||||
Mix_HookMusicFinished(nullptr);
|
||||
current->stop();
|
||||
|
||||
current.reset();
|
||||
next.reset();
|
||||
}
|
||||
|
||||
CAudioBase::release();
|
||||
}
|
||||
|
||||
void CMusicHandler::playMusic(const AudioPath & musicURI, bool loop, bool fromStart)
|
||||
{
|
||||
boost::mutex::scoped_lock guard(mutex);
|
||||
|
||||
if (current && current->isPlaying() && current->isTrack(musicURI))
|
||||
return;
|
||||
|
||||
queueNext(this, "", musicURI, loop, fromStart);
|
||||
}
|
||||
|
||||
void CMusicHandler::playMusicFromSet(const std::string & musicSet, const std::string & entryID, bool loop, bool fromStart)
|
||||
{
|
||||
playMusicFromSet(musicSet + "_" + entryID, loop, fromStart);
|
||||
}
|
||||
|
||||
void CMusicHandler::playMusicFromSet(const std::string & whichSet, bool loop, bool fromStart)
|
||||
{
|
||||
boost::mutex::scoped_lock guard(mutex);
|
||||
|
||||
auto selectedSet = musicsSet.find(whichSet);
|
||||
if (selectedSet == musicsSet.end())
|
||||
{
|
||||
logGlobal->error("Error: playing music from non-existing set: %s", whichSet);
|
||||
return;
|
||||
}
|
||||
|
||||
if (current && current->isPlaying() && current->isSet(whichSet))
|
||||
return;
|
||||
|
||||
// in this mode - play random track from set
|
||||
queueNext(this, whichSet, AudioPath(), loop, fromStart);
|
||||
}
|
||||
|
||||
void CMusicHandler::queueNext(std::unique_ptr<MusicEntry> queued)
|
||||
{
|
||||
if (!initialized)
|
||||
return;
|
||||
|
||||
next = std::move(queued);
|
||||
|
||||
if (current.get() == nullptr || !current->stop(1000))
|
||||
{
|
||||
current.reset(next.release());
|
||||
current->play();
|
||||
}
|
||||
}
|
||||
|
||||
void CMusicHandler::queueNext(CMusicHandler *owner, const std::string & setName, const AudioPath & musicURI, bool looped, bool fromStart)
|
||||
{
|
||||
queueNext(std::make_unique<MusicEntry>(owner, setName, musicURI, looped, fromStart));
|
||||
}
|
||||
|
||||
void CMusicHandler::stopMusic(int fade_ms)
|
||||
{
|
||||
if (!initialized)
|
||||
return;
|
||||
|
||||
boost::mutex::scoped_lock guard(mutex);
|
||||
|
||||
if (current.get() != nullptr)
|
||||
current->stop(fade_ms);
|
||||
next.reset();
|
||||
}
|
||||
|
||||
void CMusicHandler::setVolume(ui32 percent)
|
||||
{
|
||||
CAudioBase::setVolume(percent);
|
||||
|
||||
if (initialized)
|
||||
Mix_VolumeMusic((MIX_MAX_VOLUME * volume)/100);
|
||||
}
|
||||
|
||||
void CMusicHandler::musicFinishedCallback()
|
||||
{
|
||||
// call music restart in separate thread to avoid deadlock in some cases
|
||||
// It is possible for:
|
||||
// 1) SDL thread to call this method on end of playback
|
||||
// 2) VCMI code to call queueNext() method to queue new file
|
||||
// this leads to:
|
||||
// 1) SDL thread waiting to acquire music lock in this method (while keeping internal SDL mutex locked)
|
||||
// 2) VCMI thread waiting to acquire internal SDL mutex (while keeping music mutex locked)
|
||||
|
||||
GH.dispatchMainThread([this]()
|
||||
{
|
||||
boost::unique_lock lockGuard(mutex);
|
||||
if (current.get() != nullptr)
|
||||
{
|
||||
// if music is looped, play it again
|
||||
if (current->play())
|
||||
return;
|
||||
else
|
||||
current.reset();
|
||||
}
|
||||
|
||||
if (current.get() == nullptr && next.get() != nullptr)
|
||||
{
|
||||
current.reset(next.release());
|
||||
current->play();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, const AudioPath & musicURI, bool looped, bool fromStart):
|
||||
owner(owner),
|
||||
music(nullptr),
|
||||
playing(false),
|
||||
startTime(uint32_t(-1)),
|
||||
startPosition(0),
|
||||
loop(looped ? -1 : 1),
|
||||
fromStart(fromStart),
|
||||
setName(std::move(setName))
|
||||
{
|
||||
if (!musicURI.empty())
|
||||
load(std::move(musicURI));
|
||||
}
|
||||
MusicEntry::~MusicEntry()
|
||||
{
|
||||
if (playing && loop > 0)
|
||||
{
|
||||
assert(0);
|
||||
logGlobal->error("Attempt to delete music while playing!");
|
||||
Mix_HaltMusic();
|
||||
}
|
||||
|
||||
if (loop == 0 && Mix_FadingMusic() != MIX_NO_FADING)
|
||||
{
|
||||
assert(0);
|
||||
logGlobal->error("Attempt to delete music while fading out!");
|
||||
Mix_HaltMusic();
|
||||
}
|
||||
|
||||
logGlobal->trace("Del-ing music file %s", currentName.getOriginalName());
|
||||
if (music)
|
||||
Mix_FreeMusic(music);
|
||||
}
|
||||
|
||||
void MusicEntry::load(const AudioPath & musicURI)
|
||||
{
|
||||
if (music)
|
||||
{
|
||||
logGlobal->trace("Del-ing music file %s", currentName.getOriginalName());
|
||||
Mix_FreeMusic(music);
|
||||
music = nullptr;
|
||||
}
|
||||
|
||||
if (CResourceHandler::get()->existsResource(musicURI))
|
||||
currentName = musicURI;
|
||||
else
|
||||
currentName = musicURI.addPrefix("MUSIC/");
|
||||
|
||||
music = nullptr;
|
||||
|
||||
logGlobal->trace("Loading music file %s", currentName.getOriginalName());
|
||||
|
||||
try
|
||||
{
|
||||
auto musicFile = MakeSDLRWops(CResourceHandler::get()->load(currentName));
|
||||
music = Mix_LoadMUS_RW(musicFile, SDL_TRUE);
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
logGlobal->error("Failed to load music. setName=%s\tmusicURI=%s", setName, currentName.getOriginalName());
|
||||
logGlobal->error("Exception: %s", e.what());
|
||||
}
|
||||
|
||||
if(!music)
|
||||
{
|
||||
logGlobal->warn("Warning: Cannot open %s: %s", currentName.getOriginalName(), Mix_GetError());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool MusicEntry::play()
|
||||
{
|
||||
if (!(loop--) && music) //already played once - return
|
||||
return false;
|
||||
|
||||
if (!setName.empty())
|
||||
{
|
||||
const auto & set = owner->musicsSet[setName];
|
||||
const auto & iter = RandomGeneratorUtil::nextItem(set, CRandomGenerator::getDefault());
|
||||
load(*iter);
|
||||
}
|
||||
|
||||
logGlobal->trace("Playing music file %s", currentName.getOriginalName());
|
||||
|
||||
if (!fromStart && owner->trackPositions.count(currentName) > 0 && owner->trackPositions[currentName] > 0)
|
||||
{
|
||||
float timeToStart = owner->trackPositions[currentName];
|
||||
startPosition = std::round(timeToStart * 1000);
|
||||
|
||||
// erase stored position:
|
||||
// if music track will be interrupted again - new position will be written in stop() method
|
||||
// if music track is not interrupted and will finish by timeout/end of file - it will restart from begginning as it should
|
||||
owner->trackPositions.erase(owner->trackPositions.find(currentName));
|
||||
|
||||
if (Mix_FadeInMusicPos(music, 1, 1000, timeToStart) == -1)
|
||||
{
|
||||
logGlobal->error("Unable to play music (%s)", Mix_GetError());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
startPosition = 0;
|
||||
|
||||
if(Mix_PlayMusic(music, 1) == -1)
|
||||
{
|
||||
logGlobal->error("Unable to play music (%s)", Mix_GetError());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
startTime = GH.input().getTicks();
|
||||
|
||||
playing = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MusicEntry::stop(int fade_ms)
|
||||
{
|
||||
if (Mix_PlayingMusic())
|
||||
{
|
||||
playing = false;
|
||||
loop = 0;
|
||||
uint32_t endTime = GH.input().getTicks();
|
||||
assert(startTime != uint32_t(-1));
|
||||
float playDuration = (endTime - startTime + startPosition) / 1000.f;
|
||||
owner->trackPositions[currentName] = playDuration;
|
||||
logGlobal->trace("Stopping music file %s at %f", currentName.getOriginalName(), playDuration);
|
||||
|
||||
Mix_FadeOutMusic(fade_ms);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MusicEntry::isPlaying()
|
||||
{
|
||||
return playing;
|
||||
}
|
||||
|
||||
bool MusicEntry::isSet(std::string set)
|
||||
{
|
||||
return !setName.empty() && set == setName;
|
||||
}
|
||||
|
||||
bool MusicEntry::isTrack(const AudioPath & track)
|
||||
{
|
||||
return setName.empty() && track == currentName;
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
/*
|
||||
* CMusicHandler.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../lib/CConfigHandler.h"
|
||||
#include "../lib/CSoundBase.h"
|
||||
|
||||
struct _Mix_Music;
|
||||
struct SDL_RWops;
|
||||
using Mix_Music = struct _Mix_Music;
|
||||
struct Mix_Chunk;
|
||||
|
||||
class CAudioBase {
|
||||
protected:
|
||||
boost::mutex mutex;
|
||||
bool initialized;
|
||||
int volume; // from 0 (mute) to 100
|
||||
|
||||
CAudioBase(): initialized(false), volume(0) {};
|
||||
~CAudioBase() = default;
|
||||
public:
|
||||
virtual void init() = 0;
|
||||
virtual void release() = 0;
|
||||
|
||||
virtual void setVolume(ui32 percent);
|
||||
ui32 getVolume() const { return volume; };
|
||||
};
|
||||
|
||||
class CSoundHandler final : public CAudioBase
|
||||
{
|
||||
private:
|
||||
//update volume on configuration change
|
||||
SettingsListener listener;
|
||||
void onVolumeChange(const JsonNode &volumeNode);
|
||||
|
||||
using CachedChunk = std::pair<Mix_Chunk *, std::unique_ptr<ui8[]>>;
|
||||
std::map<AudioPath, CachedChunk> soundChunks;
|
||||
std::map<std::vector<ui8>, CachedChunk> soundChunksRaw;
|
||||
|
||||
Mix_Chunk *GetSoundChunk(const AudioPath & sound, bool cache);
|
||||
Mix_Chunk *GetSoundChunk(std::pair<std::unique_ptr<ui8 []>, si64> & data, bool cache);
|
||||
|
||||
/// have entry for every currently active channel
|
||||
/// vector will be empty if callback was not set
|
||||
std::map<int, std::vector<std::function<void()>> > callbacks;
|
||||
|
||||
/// Protects access to callbacks member to avoid data races:
|
||||
/// SDL calls sound finished callbacks from audio thread
|
||||
boost::mutex mutexCallbacks;
|
||||
|
||||
int ambientDistToVolume(int distance) const;
|
||||
void ambientStopSound(const AudioPath & soundId);
|
||||
void updateChannelVolume(int channel);
|
||||
|
||||
const JsonNode ambientConfig;
|
||||
|
||||
std::map<AudioPath, int> ambientChannels;
|
||||
std::map<int, int> channelVolumes;
|
||||
|
||||
void initCallback(int channel, const std::function<void()> & function);
|
||||
void initCallback(int channel);
|
||||
|
||||
public:
|
||||
CSoundHandler();
|
||||
|
||||
void init() override;
|
||||
void release() override;
|
||||
|
||||
void setVolume(ui32 percent) override;
|
||||
void setChannelVolume(int channel, ui32 percent);
|
||||
|
||||
// Sounds
|
||||
uint32_t getSoundDurationMilliseconds(const AudioPath & sound);
|
||||
int playSound(soundBase::soundID soundID, int repeats=0);
|
||||
int playSound(const AudioPath & sound, int repeats=0, bool cache=false);
|
||||
int playSound(std::pair<std::unique_ptr<ui8 []>, si64> & data, int repeats=0, bool cache=false);
|
||||
int playSoundFromSet(std::vector<soundBase::soundID> &sound_vec);
|
||||
void stopSound(int handler);
|
||||
|
||||
void setCallback(int channel, std::function<void()> function);
|
||||
void resetCallback(int channel);
|
||||
void soundFinishedCallback(int channel);
|
||||
|
||||
int ambientGetRange() const;
|
||||
void ambientUpdateChannels(std::map<AudioPath, int> currentSounds);
|
||||
void ambientStopAllChannels();
|
||||
|
||||
// Sets
|
||||
std::vector<soundBase::soundID> battleIntroSounds;
|
||||
};
|
||||
|
||||
class CMusicHandler;
|
||||
|
||||
//Class for handling one music file
|
||||
class MusicEntry
|
||||
{
|
||||
CMusicHandler *owner;
|
||||
Mix_Music *music;
|
||||
|
||||
int loop; // -1 = indefinite
|
||||
bool fromStart;
|
||||
bool playing;
|
||||
uint32_t startTime;
|
||||
uint32_t startPosition;
|
||||
//if not null - set from which music will be randomly selected
|
||||
std::string setName;
|
||||
AudioPath currentName;
|
||||
|
||||
void load(const AudioPath & musicURI);
|
||||
|
||||
public:
|
||||
MusicEntry(CMusicHandler *owner, std::string setName, const AudioPath & musicURI, bool looped, bool fromStart);
|
||||
~MusicEntry();
|
||||
|
||||
bool isSet(std::string setName);
|
||||
bool isTrack(const AudioPath & trackName);
|
||||
bool isPlaying();
|
||||
|
||||
bool play();
|
||||
bool stop(int fade_ms=0);
|
||||
};
|
||||
|
||||
class CMusicHandler final: public CAudioBase
|
||||
{
|
||||
private:
|
||||
//update volume on configuration change
|
||||
SettingsListener listener;
|
||||
void onVolumeChange(const JsonNode &volumeNode);
|
||||
|
||||
std::unique_ptr<MusicEntry> current;
|
||||
std::unique_ptr<MusicEntry> next;
|
||||
|
||||
void queueNext(CMusicHandler *owner, const std::string & setName, const AudioPath & musicURI, bool looped, bool fromStart);
|
||||
void queueNext(std::unique_ptr<MusicEntry> queued);
|
||||
void musicFinishedCallback();
|
||||
|
||||
/// map <set name> -> <list of URI's to tracks belonging to the said set>
|
||||
std::map<std::string, std::vector<AudioPath>> musicsSet;
|
||||
/// stored position, in seconds at which music player should resume playing this track
|
||||
std::map<AudioPath, float> trackPositions;
|
||||
|
||||
public:
|
||||
CMusicHandler();
|
||||
|
||||
/// add entry with URI musicURI in set. Track will have ID musicID
|
||||
void addEntryToSet(const std::string & set, const AudioPath & musicURI);
|
||||
|
||||
void init() override;
|
||||
void loadTerrainMusicThemes();
|
||||
void release() override;
|
||||
void setVolume(ui32 percent) override;
|
||||
|
||||
/// play track by URI, if loop = true music will be looped
|
||||
void playMusic(const AudioPath & musicURI, bool loop, bool fromStart);
|
||||
/// play random track from this set
|
||||
void playMusicFromSet(const std::string & musicSet, bool loop, bool fromStart);
|
||||
/// play random track from set (musicSet, entryID)
|
||||
void playMusicFromSet(const std::string & musicSet, const std::string & entryID, bool loop, bool fromStart);
|
||||
/// stops currently playing music by fading out it over fade_ms and starts next scheduled track, if any
|
||||
void stopMusic(int fade_ms=1000);
|
||||
|
||||
friend class MusicEntry;
|
||||
};
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
#include "CGameInfo.h"
|
||||
#include "CMT.h"
|
||||
#include "CMusicHandler.h"
|
||||
#include "CServerHandler.h"
|
||||
#include "HeroMovementController.h"
|
||||
#include "PlayerLocalState.h"
|
||||
@@ -41,6 +40,9 @@
|
||||
|
||||
#include "mapView/mapHandler.h"
|
||||
|
||||
#include "media/IMusicPlayer.h"
|
||||
#include "media/ISoundPlayer.h"
|
||||
|
||||
#include "render/CAnimation.h"
|
||||
#include "render/IImage.h"
|
||||
#include "render/IRenderHandler.h"
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* CVideoHandler.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../lib/Rect.h"
|
||||
#include "../lib/filesystem/ResourcePath.h"
|
||||
|
||||
struct SDL_Surface;
|
||||
struct SDL_Texture;
|
||||
|
||||
enum class EVideoType : ui8
|
||||
{
|
||||
INTRO = 0, // use entire window: stopOnKey = true, scale = true, overlay = false
|
||||
SPELLBOOK // overlay video: stopOnKey = false, scale = false, overlay = true
|
||||
};
|
||||
|
||||
class IVideoPlayer : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
virtual bool open(const VideoPath & name, bool scale = false)=0; //true - succes
|
||||
virtual void close()=0;
|
||||
virtual bool nextFrame()=0;
|
||||
virtual void show(int x, int y, SDL_Surface *dst, bool update = true)=0;
|
||||
virtual void redraw(int x, int y, SDL_Surface *dst, bool update = true)=0; //reblits buffer
|
||||
virtual bool wait()=0;
|
||||
virtual int curFrame() const =0;
|
||||
virtual int frameCount() const =0;
|
||||
};
|
||||
|
||||
class IMainVideoPlayer : public IVideoPlayer
|
||||
{
|
||||
public:
|
||||
virtual ~IMainVideoPlayer() = default;
|
||||
virtual void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true, std::function<void()> restart = nullptr){}
|
||||
virtual bool openAndPlayVideo(const VideoPath & name, int x, int y, EVideoType videoType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual std::pair<std::unique_ptr<ui8 []>, si64> getAudio(const VideoPath & videoToOpen) { return std::make_pair(nullptr, 0); };
|
||||
virtual Point size() { return Point(0, 0); };
|
||||
};
|
||||
|
||||
class CEmptyVideoPlayer final : public IMainVideoPlayer
|
||||
{
|
||||
public:
|
||||
int curFrame() const override {return -1;};
|
||||
int frameCount() const override {return -1;};
|
||||
void redraw( int x, int y, SDL_Surface *dst, bool update = true ) override {};
|
||||
void show( int x, int y, SDL_Surface *dst, bool update = true ) override {};
|
||||
bool nextFrame() override {return false;};
|
||||
void close() override {};
|
||||
bool wait() override {return false;};
|
||||
bool open(const VideoPath & name, bool scale = false) override {return false;};
|
||||
};
|
||||
|
||||
#ifndef DISABLE_VIDEO
|
||||
|
||||
struct AVFormatContext;
|
||||
struct AVCodecContext;
|
||||
struct AVCodec;
|
||||
struct AVFrame;
|
||||
struct AVIOContext;
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
class CInputStream;
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class CVideoPlayer final : public IMainVideoPlayer
|
||||
{
|
||||
int stream; // stream index in video
|
||||
AVFormatContext *format;
|
||||
AVCodecContext *codecContext; // codec context for stream
|
||||
const AVCodec *codec;
|
||||
AVFrame *frame;
|
||||
struct SwsContext *sws;
|
||||
|
||||
AVIOContext * context;
|
||||
|
||||
VideoPath fname; //name of current video file (empty if idle)
|
||||
|
||||
// Destination. Either overlay or dest.
|
||||
|
||||
SDL_Texture *texture;
|
||||
SDL_Surface *dest;
|
||||
Rect destRect; // valid when dest is used
|
||||
Rect pos; // destination on screen
|
||||
|
||||
/// video playback currnet progress, in seconds
|
||||
double frameTime;
|
||||
bool doLoop; // loop through video
|
||||
|
||||
bool playVideo(int x, int y, bool stopOnKey, bool overlay);
|
||||
bool open(const VideoPath & fname, bool loop, bool useOverlay = false, bool scale = false);
|
||||
public:
|
||||
CVideoPlayer();
|
||||
~CVideoPlayer();
|
||||
|
||||
bool init();
|
||||
bool open(const VideoPath & fname, bool scale = false) override;
|
||||
void close() override;
|
||||
bool nextFrame() override; // display next frame
|
||||
|
||||
void show(int x, int y, SDL_Surface *dst, bool update = true) override; //blit current frame
|
||||
void redraw(int x, int y, SDL_Surface *dst, bool update = true) override; //reblits buffer
|
||||
void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true, std::function<void()> onVideoRestart = nullptr) override; //moves to next frame if appropriate, and blits it or blits only if redraw parameter is set true
|
||||
|
||||
// Opens video, calls playVideo, closes video; returns playVideo result (if whole video has been played)
|
||||
bool openAndPlayVideo(const VideoPath & name, int x, int y, EVideoType videoType) override;
|
||||
|
||||
std::pair<std::unique_ptr<ui8 []>, si64> getAudio(const VideoPath & videoToOpen) override;
|
||||
|
||||
Point size() override;
|
||||
|
||||
//TODO:
|
||||
bool wait() override {return false;};
|
||||
int curFrame() const override {return -1;};
|
||||
int frameCount() const override {return -1;};
|
||||
|
||||
// public to allow access from ffmpeg IO functions
|
||||
std::unique_ptr<CInputStream> data;
|
||||
std::unique_ptr<CInputStream> dataAudio;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "HeroMovementController.h"
|
||||
|
||||
#include "CGameInfo.h"
|
||||
#include "CMusicHandler.h"
|
||||
#include "CPlayerInterface.h"
|
||||
#include "PlayerLocalState.h"
|
||||
#include "adventureMap/AdventureMapInterface.h"
|
||||
@@ -19,10 +18,12 @@
|
||||
#include "gui/CGuiHandler.h"
|
||||
#include "gui/CursorHandler.h"
|
||||
#include "mapView/mapHandler.h"
|
||||
#include "media/ISoundPlayer.h"
|
||||
|
||||
#include "../CCallback.h"
|
||||
|
||||
#include "../lib/CondSh.h"
|
||||
#include "../lib/CConfigHandler.h"
|
||||
#include "../lib/pathfinder/CGPathNode.h"
|
||||
#include "../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../lib/networkPacks/PacksForClient.h"
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "CInGameConsole.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../CServerHandler.h"
|
||||
#include "../GameChatHandler.h"
|
||||
@@ -21,6 +20,7 @@
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
#include "../gui/TextAlignment.h"
|
||||
#include "../media/ISoundPlayer.h"
|
||||
#include "../render/Colors.h"
|
||||
#include "../render/Canvas.h"
|
||||
#include "../render/IScreenHandler.h"
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
#include "../widgets/MiscWidgets.h"
|
||||
#include "../windows/InfoWindows.h"
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../PlayerLocalState.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../media/ISoundPlayer.h"
|
||||
#include "../render/IScreenHandler.h"
|
||||
|
||||
#include "../../CCallback.h"
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
|
||||
#include "../CCallback.h"
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../mapView/mapHandler.h"
|
||||
#include "../media/IMusicPlayer.h"
|
||||
#include "../media/ISoundPlayer.h"
|
||||
|
||||
#include "../../lib/TerrainHandler.h"
|
||||
#include "../../lib/mapObjects/CArmedInstance.h"
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
#include "TurnTimerWidget.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../battle/BattleInterface.h"
|
||||
#include "../battle/BattleStacksController.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../media/ISoundPlayer.h"
|
||||
#include "../render/Graphics.h"
|
||||
#include "../widgets/Images.h"
|
||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
#include "CreatureAnimation.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../gui/CursorHandler.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../media/ISoundPlayer.h"
|
||||
#include "../render/IRenderHandler.h"
|
||||
|
||||
#include "../../CCallback.h"
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
#include "BattleStacksController.h"
|
||||
#include "BattleRenderer.h"
|
||||
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../media/ISoundPlayer.h"
|
||||
#include "../render/Canvas.h"
|
||||
#include "../render/CAnimation.h"
|
||||
#include "../render/Graphics.h"
|
||||
|
||||
@@ -24,11 +24,12 @@
|
||||
#include "BattleRenderer.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../gui/CursorHandler.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../media/IMusicPlayer.h"
|
||||
#include "../media/ISoundPlayer.h"
|
||||
#include "../windows/CTutorialWindow.h"
|
||||
#include "../render/Canvas.h"
|
||||
#include "../adventureMap/AdventureMapInterface.h"
|
||||
@@ -113,7 +114,14 @@ void BattleInterface::playIntroSoundAndUnlockInterface()
|
||||
onIntroSoundPlayed();
|
||||
};
|
||||
|
||||
int battleIntroSoundChannel = CCS->soundh->playSoundFromSet(CCS->soundh->battleIntroSounds);
|
||||
std::vector<soundBase::soundID> battleIntroSounds =
|
||||
{
|
||||
soundBase::battle00, soundBase::battle01,
|
||||
soundBase::battle02, soundBase::battle03, soundBase::battle04,
|
||||
soundBase::battle05, soundBase::battle06, soundBase::battle07
|
||||
};
|
||||
|
||||
int battleIntroSoundChannel = CCS->soundh->playSoundFromSet(battleIntroSounds);
|
||||
if (battleIntroSoundChannel != -1)
|
||||
{
|
||||
CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed);
|
||||
|
||||
@@ -19,14 +19,14 @@
|
||||
#include "BattleWindow.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../CVideoHandler.h"
|
||||
#include "../gui/CursorHandler.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
#include "../gui/MouseButton.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../media/IMusicPlayer.h"
|
||||
#include "../media/IVideoPlayer.h"
|
||||
#include "../render/Canvas.h"
|
||||
#include "../render/IImage.h"
|
||||
#include "../render/IFont.h"
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
#include "BattleRenderer.h"
|
||||
#include "CreatureAnimation.h"
|
||||
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../media/ISoundPlayer.h"
|
||||
#include "../render/Canvas.h"
|
||||
#include "../render/IRenderHandler.h"
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
#include "BattleFieldController.h"
|
||||
#include "BattleRenderer.h"
|
||||
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../media/ISoundPlayer.h"
|
||||
#include "../render/Canvas.h"
|
||||
#include "../render/IImage.h"
|
||||
#include "../render/IRenderHandler.h"
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
#include "CreatureAnimation.h"
|
||||
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CGameInfo.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../media/ISoundPlayer.h"
|
||||
#include "../render/Colors.h"
|
||||
#include "../render/Canvas.h"
|
||||
#include "../render/IRenderHandler.h"
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../gui/CursorHandler.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
|
||||
@@ -22,10 +22,11 @@
|
||||
#include "../gui/CursorHandler.h"
|
||||
#include "../gui/EventDispatcher.h"
|
||||
#include "../gui/MouseButton.h"
|
||||
#include "../media/IMusicPlayer.h"
|
||||
#include "../media/ISoundPlayer.h"
|
||||
#include "../CMT.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CMusicHandler.h"
|
||||
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
#include "GlobalLobbyWindow.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CServerHandler.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../mainmenu/CMainMenu.h"
|
||||
#include "../media/ISoundPlayer.h"
|
||||
#include "../windows/InfoWindows.h"
|
||||
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
#include "GlobalLobbyRoomWindow.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CServerHandler.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../media/ISoundPlayer.h"
|
||||
#include "../render/Colors.h"
|
||||
#include "../widgets/Buttons.h"
|
||||
#include "../widgets/CTextInput.h"
|
||||
|
||||
@@ -18,12 +18,11 @@
|
||||
#include "ExtraOptionsTab.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CVideoHandler.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../CServerHandler.h"
|
||||
#include "../mainmenu/CMainMenu.h"
|
||||
#include "../mainmenu/CPrologEpilogVideo.h"
|
||||
#include "../media/IMusicPlayer.h"
|
||||
#include "../widgets/CComponent.h"
|
||||
#include "../widgets/Buttons.h"
|
||||
#include "../widgets/MiscWidgets.h"
|
||||
@@ -41,9 +40,9 @@
|
||||
|
||||
#include "../../lib/filesystem/Filesystem.h"
|
||||
#include "../../lib/CGeneralTextHandler.h"
|
||||
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CBuildingHandler.h"
|
||||
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CSkillHandler.h"
|
||||
#include "../../lib/CTownHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
|
||||
@@ -20,14 +20,13 @@
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CVideoHandler.h"
|
||||
#include "../CServerHandler.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../globalLobby/GlobalLobbyClient.h"
|
||||
#include "../mainmenu/CMainMenu.h"
|
||||
#include "../media/ISoundPlayer.h"
|
||||
#include "../widgets/Buttons.h"
|
||||
#include "../widgets/CComponent.h"
|
||||
#include "../widgets/CTextInput.h"
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CServerHandler.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../render/Graphics.h"
|
||||
#include "../render/IFont.h"
|
||||
#include "../media/ISoundPlayer.h"
|
||||
#include "../widgets/CComponent.h"
|
||||
#include "../widgets/ComboBox.h"
|
||||
#include "../widgets/CTextInput.h"
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "../../lib/networkPacks/PacksForLobby.h"
|
||||
#include "../../lib/CGeneralTextHandler.h"
|
||||
#include "../../lib/CArtHandler.h"
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CTownHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/mapping/CMapInfo.h"
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
#include "CMainMenu.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CVideoHandler.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../CServerHandler.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
#include "../media/IMusicPlayer.h"
|
||||
#include "../media/IVideoPlayer.h"
|
||||
#include "../render/Canvas.h"
|
||||
#include "../widgets/CComponent.h"
|
||||
#include "../widgets/Buttons.h"
|
||||
@@ -36,7 +36,7 @@
|
||||
#include "../../lib/CArtHandler.h"
|
||||
#include "../../lib/CBuildingHandler.h"
|
||||
#include "../../lib/spells/CSpellHandler.h"
|
||||
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CSkillHandler.h"
|
||||
#include "../../lib/CTownHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
#include "../media/IMusicPlayer.h"
|
||||
#include "../media/ISoundPlayer.h"
|
||||
#include "../media/IVideoPlayer.h"
|
||||
#include "../widgets/Buttons.h"
|
||||
#include "../widgets/CTextInput.h"
|
||||
#include "../widgets/Images.h"
|
||||
@@ -23,8 +26,6 @@
|
||||
#include "../render/Canvas.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CVideoHandler.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../../lib/CGeneralTextHandler.h"
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#include "../lobby/CBonusSelection.h"
|
||||
#include "../lobby/CSelectionBase.h"
|
||||
#include "../lobby/CLobbyScreen.h"
|
||||
#include "../media/IMusicPlayer.h"
|
||||
#include "../media/IVideoPlayer.h"
|
||||
#include "../gui/CursorHandler.h"
|
||||
#include "../windows/GUIClasses.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
@@ -37,8 +39,6 @@
|
||||
#include "../CServerHandler.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CVideoHandler.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../Client.h"
|
||||
#include "../CMT.h"
|
||||
|
||||
@@ -12,8 +12,9 @@
|
||||
|
||||
#include "CPrologEpilogVideo.h"
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CVideoHandler.h"
|
||||
#include "../media/IMusicPlayer.h"
|
||||
#include "../media/ISoundPlayer.h"
|
||||
#include "../media/IVideoPlayer.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/FramerateManager.h"
|
||||
|
||||
45
client/media/CAudioBase.cpp
Normal file
45
client/media/CAudioBase.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* CAudioBase.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "CAudioBase.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
|
||||
#include <SDL_mixer.h>
|
||||
|
||||
int CAudioBase::initializationCounter = 0;
|
||||
bool CAudioBase::initializeSuccess = false;
|
||||
|
||||
CAudioBase::CAudioBase()
|
||||
{
|
||||
if(initializationCounter == 0)
|
||||
{
|
||||
if(Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 1024) == -1)
|
||||
logGlobal->error("Mix_OpenAudio error: %s", Mix_GetError());
|
||||
else
|
||||
initializeSuccess = true;
|
||||
}
|
||||
++initializationCounter;
|
||||
}
|
||||
|
||||
bool CAudioBase::isInitialized() const
|
||||
{
|
||||
return initializeSuccess;
|
||||
}
|
||||
|
||||
CAudioBase::~CAudioBase()
|
||||
{
|
||||
--initializationCounter;
|
||||
|
||||
if(initializationCounter == 0 && initializeSuccess)
|
||||
Mix_CloseAudio();
|
||||
|
||||
initializeSuccess = false;
|
||||
}
|
||||
21
client/media/CAudioBase.h
Normal file
21
client/media/CAudioBase.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* CAudioBase.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class CAudioBase
|
||||
{
|
||||
static int initializationCounter;
|
||||
static bool initializeSuccess;
|
||||
protected:
|
||||
bool isInitialized() const;
|
||||
|
||||
CAudioBase();
|
||||
~CAudioBase();
|
||||
};
|
||||
30
client/media/CEmptyVideoPlayer.h
Normal file
30
client/media/CEmptyVideoPlayer.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* CEmptyVideoPlayer.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "IVideoPlayer.h"
|
||||
#include "../lib/Point.h"
|
||||
|
||||
class CEmptyVideoPlayer final : public IVideoPlayer
|
||||
{
|
||||
public:
|
||||
int curFrame() const override {return -1;};
|
||||
int frameCount() const override {return -1;};
|
||||
void redraw( int x, int y, SDL_Surface *dst, bool update) override {};
|
||||
void show( int x, int y, SDL_Surface *dst, bool update) override {};
|
||||
bool nextFrame() override {return false;};
|
||||
void close() override {};
|
||||
bool wait() override {return false;};
|
||||
bool open(const VideoPath & name, bool scale) override {return false;};
|
||||
void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update, std::function<void()> restart) override {}
|
||||
bool openAndPlayVideo(const VideoPath & name, int x, int y, EVideoType videoType) override { return false; }
|
||||
std::pair<std::unique_ptr<ui8 []>, si64> getAudio(const VideoPath & videoToOpen) override { return std::make_pair(nullptr, 0); };
|
||||
Point size() override { return Point(0, 0); };
|
||||
};
|
||||
346
client/media/CMusicHandler.cpp
Normal file
346
client/media/CMusicHandler.cpp
Normal file
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
* CMusicHandler.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "CMusicHandler.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../renderSDL/SDLRWwrapper.h"
|
||||
#include "../eventsSDL/InputHandler.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
|
||||
#include "../../lib/filesystem/Filesystem.h"
|
||||
#include "../../lib/CRandomGenerator.h"
|
||||
#include "../../lib/TerrainHandler.h"
|
||||
|
||||
#include <SDL_mixer.h>
|
||||
|
||||
void CMusicHandler::onVolumeChange(const JsonNode &volumeNode)
|
||||
{
|
||||
setVolume((ui32)volumeNode.Float());
|
||||
}
|
||||
|
||||
CMusicHandler::CMusicHandler():
|
||||
listener(settings.listen["general"]["music"])
|
||||
{
|
||||
listener(std::bind(&CMusicHandler::onVolumeChange, this, _1));
|
||||
|
||||
auto mp3files = CResourceHandler::get()->getFilteredFiles([](const ResourcePath & id) -> bool
|
||||
{
|
||||
if(id.getType() != EResType::SOUND)
|
||||
return false;
|
||||
|
||||
if(!boost::algorithm::istarts_with(id.getName(), "MUSIC/"))
|
||||
return false;
|
||||
|
||||
logGlobal->trace("Found music file %s", id.getName());
|
||||
return true;
|
||||
});
|
||||
|
||||
for(const ResourcePath & file : mp3files)
|
||||
{
|
||||
if(boost::algorithm::istarts_with(file.getName(), "MUSIC/Combat"))
|
||||
addEntryToSet("battle", AudioPath::fromResource(file));
|
||||
else if(boost::algorithm::istarts_with(file.getName(), "MUSIC/AITheme"))
|
||||
addEntryToSet("enemy-turn", AudioPath::fromResource(file));
|
||||
}
|
||||
|
||||
if (isInitialized())
|
||||
{
|
||||
Mix_HookMusicFinished([]()
|
||||
{
|
||||
CCS->musich->musicFinishedCallback();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CMusicHandler::loadTerrainMusicThemes()
|
||||
{
|
||||
for (const auto & terrain : CGI->terrainTypeHandler->objects)
|
||||
{
|
||||
addEntryToSet("terrain_" + terrain->getJsonKey(), terrain->musicFilename);
|
||||
}
|
||||
}
|
||||
|
||||
void CMusicHandler::addEntryToSet(const std::string & set, const AudioPath & musicURI)
|
||||
{
|
||||
musicsSet[set].push_back(musicURI);
|
||||
}
|
||||
|
||||
CMusicHandler::~CMusicHandler()
|
||||
{
|
||||
if (isInitialized())
|
||||
{
|
||||
boost::mutex::scoped_lock guard(mutex);
|
||||
|
||||
Mix_HookMusicFinished(nullptr);
|
||||
current->stop();
|
||||
|
||||
current.reset();
|
||||
next.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void CMusicHandler::playMusic(const AudioPath & musicURI, bool loop, bool fromStart)
|
||||
{
|
||||
boost::mutex::scoped_lock guard(mutex);
|
||||
|
||||
if (current && current->isPlaying() && current->isTrack(musicURI))
|
||||
return;
|
||||
|
||||
queueNext(this, "", musicURI, loop, fromStart);
|
||||
}
|
||||
|
||||
void CMusicHandler::playMusicFromSet(const std::string & musicSet, const std::string & entryID, bool loop, bool fromStart)
|
||||
{
|
||||
playMusicFromSet(musicSet + "_" + entryID, loop, fromStart);
|
||||
}
|
||||
|
||||
void CMusicHandler::playMusicFromSet(const std::string & whichSet, bool loop, bool fromStart)
|
||||
{
|
||||
boost::mutex::scoped_lock guard(mutex);
|
||||
|
||||
auto selectedSet = musicsSet.find(whichSet);
|
||||
if (selectedSet == musicsSet.end())
|
||||
{
|
||||
logGlobal->error("Error: playing music from non-existing set: %s", whichSet);
|
||||
return;
|
||||
}
|
||||
|
||||
if (current && current->isPlaying() && current->isSet(whichSet))
|
||||
return;
|
||||
|
||||
// in this mode - play random track from set
|
||||
queueNext(this, whichSet, AudioPath(), loop, fromStart);
|
||||
}
|
||||
|
||||
void CMusicHandler::queueNext(std::unique_ptr<MusicEntry> queued)
|
||||
{
|
||||
if (!isInitialized())
|
||||
return;
|
||||
|
||||
next = std::move(queued);
|
||||
|
||||
if (current.get() == nullptr || !current->stop(1000))
|
||||
{
|
||||
current.reset(next.release());
|
||||
current->play();
|
||||
}
|
||||
}
|
||||
|
||||
void CMusicHandler::queueNext(CMusicHandler *owner, const std::string & setName, const AudioPath & musicURI, bool looped, bool fromStart)
|
||||
{
|
||||
queueNext(std::make_unique<MusicEntry>(owner, setName, musicURI, looped, fromStart));
|
||||
}
|
||||
|
||||
void CMusicHandler::stopMusic(int fade_ms)
|
||||
{
|
||||
if (!isInitialized())
|
||||
return;
|
||||
|
||||
boost::mutex::scoped_lock guard(mutex);
|
||||
|
||||
if (current.get() != nullptr)
|
||||
current->stop(fade_ms);
|
||||
next.reset();
|
||||
}
|
||||
|
||||
ui32 CMusicHandler::getVolume() const
|
||||
{
|
||||
return volume;
|
||||
}
|
||||
|
||||
void CMusicHandler::setVolume(ui32 percent)
|
||||
{
|
||||
volume = std::min(100u, percent);
|
||||
|
||||
if (isInitialized())
|
||||
Mix_VolumeMusic((MIX_MAX_VOLUME * volume)/100);
|
||||
}
|
||||
|
||||
void CMusicHandler::musicFinishedCallback()
|
||||
{
|
||||
// call music restart in separate thread to avoid deadlock in some cases
|
||||
// It is possible for:
|
||||
// 1) SDL thread to call this method on end of playback
|
||||
// 2) VCMI code to call queueNext() method to queue new file
|
||||
// this leads to:
|
||||
// 1) SDL thread waiting to acquire music lock in this method (while keeping internal SDL mutex locked)
|
||||
// 2) VCMI thread waiting to acquire internal SDL mutex (while keeping music mutex locked)
|
||||
|
||||
GH.dispatchMainThread([this]()
|
||||
{
|
||||
boost::unique_lock lockGuard(mutex);
|
||||
if (current.get() != nullptr)
|
||||
{
|
||||
// if music is looped, play it again
|
||||
if (current->play())
|
||||
return;
|
||||
else
|
||||
current.reset();
|
||||
}
|
||||
|
||||
if (current.get() == nullptr && next.get() != nullptr)
|
||||
{
|
||||
current.reset(next.release());
|
||||
current->play();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, const AudioPath & musicURI, bool looped, bool fromStart):
|
||||
owner(owner),
|
||||
music(nullptr),
|
||||
playing(false),
|
||||
startTime(uint32_t(-1)),
|
||||
startPosition(0),
|
||||
loop(looped ? -1 : 1),
|
||||
fromStart(fromStart),
|
||||
setName(std::move(setName))
|
||||
{
|
||||
if (!musicURI.empty())
|
||||
load(std::move(musicURI));
|
||||
}
|
||||
MusicEntry::~MusicEntry()
|
||||
{
|
||||
if (playing && loop > 0)
|
||||
{
|
||||
assert(0);
|
||||
logGlobal->error("Attempt to delete music while playing!");
|
||||
Mix_HaltMusic();
|
||||
}
|
||||
|
||||
if (loop == 0 && Mix_FadingMusic() != MIX_NO_FADING)
|
||||
{
|
||||
assert(0);
|
||||
logGlobal->error("Attempt to delete music while fading out!");
|
||||
Mix_HaltMusic();
|
||||
}
|
||||
|
||||
logGlobal->trace("Del-ing music file %s", currentName.getOriginalName());
|
||||
if (music)
|
||||
Mix_FreeMusic(music);
|
||||
}
|
||||
|
||||
void MusicEntry::load(const AudioPath & musicURI)
|
||||
{
|
||||
if (music)
|
||||
{
|
||||
logGlobal->trace("Del-ing music file %s", currentName.getOriginalName());
|
||||
Mix_FreeMusic(music);
|
||||
music = nullptr;
|
||||
}
|
||||
|
||||
if (CResourceHandler::get()->existsResource(musicURI))
|
||||
currentName = musicURI;
|
||||
else
|
||||
currentName = musicURI.addPrefix("MUSIC/");
|
||||
|
||||
music = nullptr;
|
||||
|
||||
logGlobal->trace("Loading music file %s", currentName.getOriginalName());
|
||||
|
||||
try
|
||||
{
|
||||
auto musicFile = MakeSDLRWops(CResourceHandler::get()->load(currentName));
|
||||
music = Mix_LoadMUS_RW(musicFile, SDL_TRUE);
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
logGlobal->error("Failed to load music. setName=%s\tmusicURI=%s", setName, currentName.getOriginalName());
|
||||
logGlobal->error("Exception: %s", e.what());
|
||||
}
|
||||
|
||||
if(!music)
|
||||
{
|
||||
logGlobal->warn("Warning: Cannot open %s: %s", currentName.getOriginalName(), Mix_GetError());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool MusicEntry::play()
|
||||
{
|
||||
if (!(loop--) && music) //already played once - return
|
||||
return false;
|
||||
|
||||
if (!setName.empty())
|
||||
{
|
||||
const auto & set = owner->musicsSet[setName];
|
||||
const auto & iter = RandomGeneratorUtil::nextItem(set, CRandomGenerator::getDefault());
|
||||
load(*iter);
|
||||
}
|
||||
|
||||
logGlobal->trace("Playing music file %s", currentName.getOriginalName());
|
||||
|
||||
if (!fromStart && owner->trackPositions.count(currentName) > 0 && owner->trackPositions[currentName] > 0)
|
||||
{
|
||||
float timeToStart = owner->trackPositions[currentName];
|
||||
startPosition = std::round(timeToStart * 1000);
|
||||
|
||||
// erase stored position:
|
||||
// if music track will be interrupted again - new position will be written in stop() method
|
||||
// if music track is not interrupted and will finish by timeout/end of file - it will restart from begginning as it should
|
||||
owner->trackPositions.erase(owner->trackPositions.find(currentName));
|
||||
|
||||
if (Mix_FadeInMusicPos(music, 1, 1000, timeToStart) == -1)
|
||||
{
|
||||
logGlobal->error("Unable to play music (%s)", Mix_GetError());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
startPosition = 0;
|
||||
|
||||
if(Mix_PlayMusic(music, 1) == -1)
|
||||
{
|
||||
logGlobal->error("Unable to play music (%s)", Mix_GetError());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
startTime = GH.input().getTicks();
|
||||
|
||||
playing = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MusicEntry::stop(int fade_ms)
|
||||
{
|
||||
if (Mix_PlayingMusic())
|
||||
{
|
||||
playing = false;
|
||||
loop = 0;
|
||||
uint32_t endTime = GH.input().getTicks();
|
||||
assert(startTime != uint32_t(-1));
|
||||
float playDuration = (endTime - startTime + startPosition) / 1000.f;
|
||||
owner->trackPositions[currentName] = playDuration;
|
||||
logGlobal->trace("Stopping music file %s at %f", currentName.getOriginalName(), playDuration);
|
||||
|
||||
Mix_FadeOutMusic(fade_ms);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MusicEntry::isPlaying()
|
||||
{
|
||||
return playing;
|
||||
}
|
||||
|
||||
bool MusicEntry::isSet(std::string set)
|
||||
{
|
||||
return !setName.empty() && set == setName;
|
||||
}
|
||||
|
||||
bool MusicEntry::isTrack(const AudioPath & track)
|
||||
{
|
||||
return setName.empty() && track == currentName;
|
||||
}
|
||||
94
client/media/CMusicHandler.h
Normal file
94
client/media/CMusicHandler.h
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* CMusicHandler.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "CAudioBase.h"
|
||||
#include "IMusicPlayer.h"
|
||||
|
||||
#include "../lib/CConfigHandler.h"
|
||||
|
||||
struct _Mix_Music;
|
||||
using Mix_Music = struct _Mix_Music;
|
||||
|
||||
class CMusicHandler;
|
||||
|
||||
//Class for handling one music file
|
||||
class MusicEntry
|
||||
{
|
||||
CMusicHandler *owner;
|
||||
Mix_Music *music;
|
||||
|
||||
int loop; // -1 = indefinite
|
||||
bool fromStart;
|
||||
bool playing;
|
||||
uint32_t startTime;
|
||||
uint32_t startPosition;
|
||||
//if not null - set from which music will be randomly selected
|
||||
std::string setName;
|
||||
AudioPath currentName;
|
||||
|
||||
void load(const AudioPath & musicURI);
|
||||
|
||||
public:
|
||||
MusicEntry(CMusicHandler *owner, std::string setName, const AudioPath & musicURI, bool looped, bool fromStart);
|
||||
~MusicEntry();
|
||||
|
||||
bool isSet(std::string setName);
|
||||
bool isTrack(const AudioPath & trackName);
|
||||
bool isPlaying();
|
||||
|
||||
bool play();
|
||||
bool stop(int fade_ms=0);
|
||||
};
|
||||
|
||||
class CMusicHandler final: public CAudioBase, public IMusicPlayer
|
||||
{
|
||||
private:
|
||||
//update volume on configuration change
|
||||
SettingsListener listener;
|
||||
void onVolumeChange(const JsonNode &volumeNode);
|
||||
|
||||
std::unique_ptr<MusicEntry> current;
|
||||
std::unique_ptr<MusicEntry> next;
|
||||
|
||||
boost::mutex mutex;
|
||||
int volume = 0; // from 0 (mute) to 100
|
||||
|
||||
void queueNext(CMusicHandler *owner, const std::string & setName, const AudioPath & musicURI, bool looped, bool fromStart);
|
||||
void queueNext(std::unique_ptr<MusicEntry> queued);
|
||||
void musicFinishedCallback() final;
|
||||
|
||||
/// map <set name> -> <list of URI's to tracks belonging to the said set>
|
||||
std::map<std::string, std::vector<AudioPath>> musicsSet;
|
||||
/// stored position, in seconds at which music player should resume playing this track
|
||||
std::map<AudioPath, float> trackPositions;
|
||||
|
||||
public:
|
||||
CMusicHandler();
|
||||
~CMusicHandler();
|
||||
|
||||
/// add entry with URI musicURI in set. Track will have ID musicID
|
||||
void addEntryToSet(const std::string & set, const AudioPath & musicURI);
|
||||
|
||||
void loadTerrainMusicThemes() final;
|
||||
void setVolume(ui32 percent) final;
|
||||
ui32 getVolume() const final;
|
||||
|
||||
/// play track by URI, if loop = true music will be looped
|
||||
void playMusic(const AudioPath & musicURI, bool loop, bool fromStart) final;
|
||||
/// play random track from this set
|
||||
void playMusicFromSet(const std::string & musicSet, bool loop, bool fromStart) final;
|
||||
/// play random track from set (musicSet, entryID)
|
||||
void playMusicFromSet(const std::string & musicSet, const std::string & entryID, bool loop, bool fromStart) final;
|
||||
/// stops currently playing music by fading out it over fade_ms and starts next scheduled track, if any
|
||||
void stopMusic(int fade_ms=1000) final;
|
||||
|
||||
friend class MusicEntry;
|
||||
};
|
||||
383
client/media/CSoundHandler.cpp
Normal file
383
client/media/CSoundHandler.cpp
Normal file
@@ -0,0 +1,383 @@
|
||||
/*
|
||||
* CMusicHandler.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "CSoundHandler.h"
|
||||
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../CGameInfo.h"
|
||||
|
||||
#include "../lib/filesystem/Filesystem.h"
|
||||
#include "../lib/CRandomGenerator.h"
|
||||
|
||||
#include <SDL_mixer.h>
|
||||
|
||||
#define VCMI_SOUND_NAME(x)
|
||||
#define VCMI_SOUND_FILE(y) #y,
|
||||
|
||||
// sounds mapped to soundBase enum
|
||||
static const std::string sounds[] = {
|
||||
"", // invalid
|
||||
"", // todo
|
||||
VCMI_SOUND_LIST
|
||||
};
|
||||
#undef VCMI_SOUND_NAME
|
||||
#undef VCMI_SOUND_FILE
|
||||
|
||||
|
||||
void CSoundHandler::onVolumeChange(const JsonNode &volumeNode)
|
||||
{
|
||||
setVolume((ui32)volumeNode.Float());
|
||||
}
|
||||
|
||||
CSoundHandler::CSoundHandler():
|
||||
listener(settings.listen["general"]["sound"]),
|
||||
ambientConfig(JsonPath::builtin("config/ambientSounds.json"))
|
||||
{
|
||||
listener(std::bind(&CSoundHandler::onVolumeChange, this, _1));
|
||||
|
||||
if(ambientConfig["allocateChannels"].isNumber())
|
||||
Mix_AllocateChannels((int)ambientConfig["allocateChannels"].Integer());
|
||||
|
||||
if (isInitialized())
|
||||
{
|
||||
Mix_ChannelFinished([](int channel)
|
||||
{
|
||||
CCS->soundh->soundFinishedCallback(channel);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
CSoundHandler::~CSoundHandler()
|
||||
{
|
||||
if (isInitialized())
|
||||
{
|
||||
Mix_HaltChannel(-1);
|
||||
|
||||
for (auto &chunk : soundChunks)
|
||||
{
|
||||
if (chunk.second.first)
|
||||
Mix_FreeChunk(chunk.second.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate an SDL chunk and cache it.
|
||||
Mix_Chunk *CSoundHandler::GetSoundChunk(const AudioPath & sound, bool cache)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (cache && soundChunks.find(sound) != soundChunks.end())
|
||||
return soundChunks[sound].first;
|
||||
|
||||
auto data = CResourceHandler::get()->load(sound.addPrefix("SOUNDS/"))->readAll();
|
||||
SDL_RWops *ops = SDL_RWFromMem(data.first.get(), (int)data.second);
|
||||
Mix_Chunk *chunk = Mix_LoadWAV_RW(ops, 1); // will free ops
|
||||
|
||||
if (cache)
|
||||
soundChunks.insert({sound, std::make_pair (chunk, std::move (data.first))});
|
||||
|
||||
return chunk;
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
logGlobal->warn("Cannot get sound %s chunk: %s", sound.getOriginalName(), e.what());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Mix_Chunk *CSoundHandler::GetSoundChunk(std::pair<std::unique_ptr<ui8 []>, si64> & data, bool cache)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::vector<ui8> startBytes = std::vector<ui8>(data.first.get(), data.first.get() + std::min((si64)100, data.second));
|
||||
|
||||
if (cache && soundChunksRaw.find(startBytes) != soundChunksRaw.end())
|
||||
return soundChunksRaw[startBytes].first;
|
||||
|
||||
SDL_RWops *ops = SDL_RWFromMem(data.first.get(), (int)data.second);
|
||||
Mix_Chunk *chunk = Mix_LoadWAV_RW(ops, 1); // will free ops
|
||||
|
||||
if (cache)
|
||||
soundChunksRaw.insert({startBytes, std::make_pair (chunk, std::move (data.first))});
|
||||
|
||||
return chunk;
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
logGlobal->warn("Cannot get sound chunk: %s", e.what());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int CSoundHandler::ambientDistToVolume(int distance) const
|
||||
{
|
||||
const auto & distancesVector = ambientConfig["distances"].Vector();
|
||||
|
||||
if(distance >= distancesVector.size())
|
||||
return 0;
|
||||
|
||||
int volume = static_cast<int>(distancesVector[distance].Integer());
|
||||
return volume * (int)ambientConfig["volume"].Integer() / 100;
|
||||
}
|
||||
|
||||
void CSoundHandler::ambientStopSound(const AudioPath & soundId)
|
||||
{
|
||||
stopSound(ambientChannels[soundId]);
|
||||
setChannelVolume(ambientChannels[soundId], volume);
|
||||
}
|
||||
|
||||
uint32_t CSoundHandler::getSoundDurationMilliseconds(const AudioPath & sound)
|
||||
{
|
||||
if (!isInitialized() || sound.empty())
|
||||
return 0;
|
||||
|
||||
auto resourcePath = sound.addPrefix("SOUNDS/");
|
||||
|
||||
if (!CResourceHandler::get()->existsResource(resourcePath))
|
||||
return 0;
|
||||
|
||||
auto data = CResourceHandler::get()->load(resourcePath)->readAll();
|
||||
|
||||
SDL_AudioSpec spec;
|
||||
uint32_t audioLen;
|
||||
uint8_t *audioBuf;
|
||||
uint32_t miliseconds = 0;
|
||||
|
||||
if(SDL_LoadWAV_RW(SDL_RWFromMem(data.first.get(), (int)data.second), 1, &spec, &audioBuf, &audioLen) != nullptr)
|
||||
{
|
||||
SDL_FreeWAV(audioBuf);
|
||||
uint32_t sampleSize = SDL_AUDIO_BITSIZE(spec.format) / 8;
|
||||
uint32_t sampleCount = audioLen / sampleSize;
|
||||
uint32_t sampleLen = sampleCount / spec.channels;
|
||||
miliseconds = 1000 * sampleLen / spec.freq;
|
||||
}
|
||||
|
||||
return miliseconds ;
|
||||
}
|
||||
|
||||
// Plays a sound, and return its channel so we can fade it out later
|
||||
int CSoundHandler::playSound(soundBase::soundID soundID, int repeats)
|
||||
{
|
||||
assert(soundID < soundBase::sound_after_last);
|
||||
auto sound = AudioPath::builtin(sounds[soundID]);
|
||||
logGlobal->trace("Attempt to play sound %d with file name %s with cache", soundID, sound.getOriginalName());
|
||||
|
||||
return playSound(sound, repeats, true);
|
||||
}
|
||||
|
||||
int CSoundHandler::playSound(const AudioPath & sound, int repeats, bool cache)
|
||||
{
|
||||
if (!isInitialized() || sound.empty())
|
||||
return -1;
|
||||
|
||||
int channel;
|
||||
Mix_Chunk *chunk = GetSoundChunk(sound, cache);
|
||||
|
||||
if (chunk)
|
||||
{
|
||||
channel = Mix_PlayChannel(-1, chunk, repeats);
|
||||
if (channel == -1)
|
||||
{
|
||||
logGlobal->error("Unable to play sound file %s , error %s", sound.getOriginalName(), Mix_GetError());
|
||||
if (!cache)
|
||||
Mix_FreeChunk(chunk);
|
||||
}
|
||||
else if (cache)
|
||||
initCallback(channel);
|
||||
else
|
||||
initCallback(channel, [chunk](){ Mix_FreeChunk(chunk);});
|
||||
}
|
||||
else
|
||||
channel = -1;
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
int CSoundHandler::playSound(std::pair<std::unique_ptr<ui8 []>, si64> & data, int repeats, bool cache)
|
||||
{
|
||||
int channel = -1;
|
||||
if (Mix_Chunk *chunk = GetSoundChunk(data, cache))
|
||||
{
|
||||
channel = Mix_PlayChannel(-1, chunk, repeats);
|
||||
if (channel == -1)
|
||||
{
|
||||
logGlobal->error("Unable to play sound, error %s", Mix_GetError());
|
||||
if (!cache)
|
||||
Mix_FreeChunk(chunk);
|
||||
}
|
||||
else if (cache)
|
||||
initCallback(channel);
|
||||
else
|
||||
initCallback(channel, [chunk](){ Mix_FreeChunk(chunk);});
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
// Helper. Randomly select a sound from an array and play it
|
||||
int CSoundHandler::playSoundFromSet(std::vector<soundBase::soundID> &sound_vec)
|
||||
{
|
||||
return playSound(*RandomGeneratorUtil::nextItem(sound_vec, CRandomGenerator::getDefault()));
|
||||
}
|
||||
|
||||
void CSoundHandler::stopSound(int handler)
|
||||
{
|
||||
if (isInitialized() && handler != -1)
|
||||
Mix_HaltChannel(handler);
|
||||
}
|
||||
|
||||
ui32 CSoundHandler::getVolume() const
|
||||
{
|
||||
return volume;
|
||||
}
|
||||
|
||||
// Sets the sound volume, from 0 (mute) to 100
|
||||
void CSoundHandler::setVolume(ui32 percent)
|
||||
{
|
||||
volume = std::min(100u, percent);
|
||||
|
||||
if (isInitialized())
|
||||
{
|
||||
setChannelVolume(-1, volume);
|
||||
|
||||
for (auto const & channel : channelVolumes)
|
||||
updateChannelVolume(channel.first);
|
||||
}
|
||||
}
|
||||
|
||||
void CSoundHandler::updateChannelVolume(int channel)
|
||||
{
|
||||
if (channelVolumes.count(channel))
|
||||
setChannelVolume(channel, getVolume() * channelVolumes[channel] / 100);
|
||||
else
|
||||
setChannelVolume(channel, getVolume());
|
||||
}
|
||||
|
||||
// Sets the sound volume, from 0 (mute) to 100
|
||||
void CSoundHandler::setChannelVolume(int channel, ui32 percent)
|
||||
{
|
||||
Mix_Volume(channel, (MIX_MAX_VOLUME * percent)/100);
|
||||
}
|
||||
|
||||
void CSoundHandler::setCallback(int channel, std::function<void()> function)
|
||||
{
|
||||
boost::mutex::scoped_lock lockGuard(mutexCallbacks);
|
||||
|
||||
auto iter = callbacks.find(channel);
|
||||
|
||||
//channel not found. It may have finished so fire callback now
|
||||
if(iter == callbacks.end())
|
||||
function();
|
||||
else
|
||||
iter->second.push_back(function);
|
||||
}
|
||||
|
||||
void CSoundHandler::resetCallback(int channel)
|
||||
{
|
||||
boost::mutex::scoped_lock lockGuard(mutexCallbacks);
|
||||
|
||||
callbacks.erase(channel);
|
||||
}
|
||||
|
||||
void CSoundHandler::soundFinishedCallback(int channel)
|
||||
{
|
||||
boost::mutex::scoped_lock lockGuard(mutexCallbacks);
|
||||
|
||||
if (callbacks.count(channel) == 0)
|
||||
return;
|
||||
|
||||
// store callbacks from container locally - SDL might reuse this channel for another sound
|
||||
// but do actualy execution in separate thread, to avoid potential deadlocks in case if callback requires locks of its own
|
||||
auto callback = callbacks.at(channel);
|
||||
callbacks.erase(channel);
|
||||
|
||||
if (!callback.empty())
|
||||
{
|
||||
GH.dispatchMainThread([callback](){
|
||||
for (auto entry : callback)
|
||||
entry();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void CSoundHandler::initCallback(int channel)
|
||||
{
|
||||
boost::mutex::scoped_lock lockGuard(mutexCallbacks);
|
||||
assert(callbacks.count(channel) == 0);
|
||||
callbacks[channel] = {};
|
||||
}
|
||||
|
||||
void CSoundHandler::initCallback(int channel, const std::function<void()> & function)
|
||||
{
|
||||
boost::mutex::scoped_lock lockGuard(mutexCallbacks);
|
||||
assert(callbacks.count(channel) == 0);
|
||||
callbacks[channel].push_back(function);
|
||||
}
|
||||
|
||||
int CSoundHandler::ambientGetRange() const
|
||||
{
|
||||
return static_cast<int>(ambientConfig["range"].Integer());
|
||||
}
|
||||
|
||||
void CSoundHandler::ambientUpdateChannels(std::map<AudioPath, int> soundsArg)
|
||||
{
|
||||
boost::mutex::scoped_lock guard(mutex);
|
||||
|
||||
std::vector<AudioPath> stoppedSounds;
|
||||
for(auto & pair : ambientChannels)
|
||||
{
|
||||
const auto & soundId = pair.first;
|
||||
const int channel = pair.second;
|
||||
|
||||
if(!vstd::contains(soundsArg, soundId))
|
||||
{
|
||||
ambientStopSound(soundId);
|
||||
stoppedSounds.push_back(soundId);
|
||||
}
|
||||
else
|
||||
{
|
||||
int volume = ambientDistToVolume(soundsArg[soundId]);
|
||||
channelVolumes[channel] = volume;
|
||||
updateChannelVolume(channel);
|
||||
}
|
||||
}
|
||||
for(auto soundId : stoppedSounds)
|
||||
{
|
||||
channelVolumes.erase(ambientChannels[soundId]);
|
||||
ambientChannels.erase(soundId);
|
||||
}
|
||||
|
||||
for(auto & pair : soundsArg)
|
||||
{
|
||||
const auto & soundId = pair.first;
|
||||
const int distance = pair.second;
|
||||
|
||||
if(!vstd::contains(ambientChannels, soundId))
|
||||
{
|
||||
int channel = playSound(soundId, -1);
|
||||
int volume = ambientDistToVolume(distance);
|
||||
channelVolumes[channel] = volume;
|
||||
|
||||
updateChannelVolume(channel);
|
||||
ambientChannels[soundId] = channel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CSoundHandler::ambientStopAllChannels()
|
||||
{
|
||||
boost::mutex::scoped_lock guard(mutex);
|
||||
|
||||
for(auto ch : ambientChannels)
|
||||
{
|
||||
ambientStopSound(ch.first);
|
||||
}
|
||||
channelVolumes.clear();
|
||||
ambientChannels.clear();
|
||||
}
|
||||
78
client/media/CSoundHandler.h
Normal file
78
client/media/CSoundHandler.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* CSoundHandler.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "CAudioBase.h"
|
||||
#include "ISoundPlayer.h"
|
||||
|
||||
#include "../lib/CConfigHandler.h"
|
||||
|
||||
struct Mix_Chunk;
|
||||
|
||||
class CSoundHandler final : public CAudioBase, public ISoundPlayer
|
||||
{
|
||||
private:
|
||||
//update volume on configuration change
|
||||
SettingsListener listener;
|
||||
void onVolumeChange(const JsonNode &volumeNode);
|
||||
|
||||
using CachedChunk = std::pair<Mix_Chunk *, std::unique_ptr<ui8[]>>;
|
||||
std::map<AudioPath, CachedChunk> soundChunks;
|
||||
std::map<std::vector<ui8>, CachedChunk> soundChunksRaw;
|
||||
|
||||
Mix_Chunk *GetSoundChunk(const AudioPath & sound, bool cache);
|
||||
Mix_Chunk *GetSoundChunk(std::pair<std::unique_ptr<ui8 []>, si64> & data, bool cache);
|
||||
|
||||
/// have entry for every currently active channel
|
||||
/// vector will be empty if callback was not set
|
||||
std::map<int, std::vector<std::function<void()>> > callbacks;
|
||||
|
||||
/// Protects access to callbacks member to avoid data races:
|
||||
/// SDL calls sound finished callbacks from audio thread
|
||||
boost::mutex mutexCallbacks;
|
||||
|
||||
int ambientDistToVolume(int distance) const;
|
||||
void ambientStopSound(const AudioPath & soundId);
|
||||
void updateChannelVolume(int channel);
|
||||
|
||||
const JsonNode ambientConfig;
|
||||
|
||||
boost::mutex mutex;
|
||||
std::map<AudioPath, int> ambientChannels;
|
||||
std::map<int, int> channelVolumes;
|
||||
int volume = 0;
|
||||
|
||||
void initCallback(int channel, const std::function<void()> & function);
|
||||
void initCallback(int channel);
|
||||
|
||||
public:
|
||||
CSoundHandler();
|
||||
~CSoundHandler();
|
||||
|
||||
ui32 getVolume() const final;
|
||||
void setVolume(ui32 percent) final;
|
||||
void setChannelVolume(int channel, ui32 percent);
|
||||
|
||||
// Sounds
|
||||
uint32_t getSoundDurationMilliseconds(const AudioPath & sound) final;
|
||||
int playSound(soundBase::soundID soundID, int repeats=0) final;
|
||||
int playSound(const AudioPath & sound, int repeats=0, bool cache=false) final;
|
||||
int playSound(std::pair<std::unique_ptr<ui8 []>, si64> & data, int repeats=0, bool cache=false) final;
|
||||
int playSoundFromSet(std::vector<soundBase::soundID> &sound_vec) final;
|
||||
void stopSound(int handler) final;
|
||||
|
||||
void setCallback(int channel, std::function<void()> function) final;
|
||||
void resetCallback(int channel) final;
|
||||
void soundFinishedCallback(int channel) final;
|
||||
|
||||
int ambientGetRange() const final;
|
||||
void ambientUpdateChannels(std::map<AudioPath, int> currentSounds) final;
|
||||
void ambientStopAllChannels() final;
|
||||
};
|
||||
@@ -10,6 +10,8 @@
|
||||
#include "StdInc.h"
|
||||
#include "CVideoHandler.h"
|
||||
|
||||
#ifndef DISABLE_VIDEO
|
||||
|
||||
#include "CMT.h"
|
||||
#include "gui/CGuiHandler.h"
|
||||
#include "eventsSDL/InputHandler.h"
|
||||
@@ -21,8 +23,6 @@
|
||||
|
||||
#include <SDL_render.h>
|
||||
|
||||
#ifndef DISABLE_VIDEO
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
@@ -43,7 +43,7 @@ static int lodRead(void* opaque, uint8_t* buf, int size)
|
||||
auto video = reinterpret_cast<CVideoPlayer *>(opaque);
|
||||
int bytes = static_cast<int>(video->data->read(buf, size));
|
||||
if(bytes == 0)
|
||||
return AVERROR_EOF;
|
||||
return AVERROR_EOF;
|
||||
|
||||
return bytes;
|
||||
}
|
||||
@@ -64,7 +64,7 @@ static int lodReadAudio(void* opaque, uint8_t* buf, int size)
|
||||
auto video = reinterpret_cast<CVideoPlayer *>(opaque);
|
||||
int bytes = static_cast<int>(video->dataAudio->read(buf, size));
|
||||
if(bytes == 0)
|
||||
return AVERROR_EOF;
|
||||
return AVERROR_EOF;
|
||||
|
||||
return bytes;
|
||||
}
|
||||
@@ -97,7 +97,7 @@ CVideoPlayer::CVideoPlayer()
|
||||
|
||||
bool CVideoPlayer::open(const VideoPath & fname, bool scale)
|
||||
{
|
||||
return open(fname, true, false);
|
||||
return open(fname, true, false, false);
|
||||
}
|
||||
|
||||
// loop = to loop through the video
|
||||
@@ -395,7 +395,7 @@ void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, boo
|
||||
if(onVideoRestart)
|
||||
onVideoRestart();
|
||||
VideoPath filenameToReopen = fname; // create copy to backup this->fname
|
||||
open(filenameToReopen);
|
||||
open(filenameToReopen, false);
|
||||
nextFrame();
|
||||
|
||||
// The y position is wrong at the first frame.
|
||||
86
client/media/CVideoHandler.h
Normal file
86
client/media/CVideoHandler.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* CVideoHandler.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifndef DISABLE_VIDEO
|
||||
|
||||
#include "IVideoPlayer.h"
|
||||
|
||||
#include "../lib/Rect.h"
|
||||
|
||||
struct SDL_Surface;
|
||||
struct SDL_Texture;
|
||||
struct AVFormatContext;
|
||||
struct AVCodecContext;
|
||||
struct AVCodec;
|
||||
struct AVFrame;
|
||||
struct AVIOContext;
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
class CInputStream;
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class CVideoPlayer final : public IVideoPlayer
|
||||
{
|
||||
int stream; // stream index in video
|
||||
AVFormatContext *format;
|
||||
AVCodecContext *codecContext; // codec context for stream
|
||||
const AVCodec *codec;
|
||||
AVFrame *frame;
|
||||
struct SwsContext *sws;
|
||||
|
||||
AVIOContext * context;
|
||||
|
||||
VideoPath fname; //name of current video file (empty if idle)
|
||||
|
||||
// Destination. Either overlay or dest.
|
||||
|
||||
SDL_Texture *texture;
|
||||
SDL_Surface *dest;
|
||||
Rect destRect; // valid when dest is used
|
||||
Rect pos; // destination on screen
|
||||
|
||||
/// video playback currnet progress, in seconds
|
||||
double frameTime;
|
||||
bool doLoop; // loop through video
|
||||
|
||||
bool playVideo(int x, int y, bool stopOnKey, bool overlay);
|
||||
bool open(const VideoPath & fname, bool loop, bool useOverlay, bool scale);
|
||||
public:
|
||||
CVideoPlayer();
|
||||
~CVideoPlayer();
|
||||
|
||||
bool init();
|
||||
bool open(const VideoPath & fname, bool scale) override;
|
||||
void close() override;
|
||||
bool nextFrame() override; // display next frame
|
||||
|
||||
void show(int x, int y, SDL_Surface *dst, bool update) override; //blit current frame
|
||||
void redraw(int x, int y, SDL_Surface *dst, bool update) override; //reblits buffer
|
||||
void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update, std::function<void()> onVideoRestart) override; //moves to next frame if appropriate, and blits it or blits only if redraw parameter is set true
|
||||
|
||||
// Opens video, calls playVideo, closes video; returns playVideo result (if whole video has been played)
|
||||
bool openAndPlayVideo(const VideoPath & name, int x, int y, EVideoType videoType) override;
|
||||
|
||||
std::pair<std::unique_ptr<ui8 []>, si64> getAudio(const VideoPath & videoToOpen) override;
|
||||
|
||||
Point size() override;
|
||||
|
||||
//TODO:
|
||||
bool wait() override {return false;};
|
||||
int curFrame() const override {return -1;};
|
||||
int frameCount() const override {return -1;};
|
||||
|
||||
// public to allow access from ffmpeg IO functions
|
||||
std::unique_ptr<CInputStream> data;
|
||||
std::unique_ptr<CInputStream> dataAudio;
|
||||
};
|
||||
|
||||
#endif
|
||||
33
client/media/IMusicPlayer.h
Normal file
33
client/media/IMusicPlayer.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* IMusicPlayer.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../lib/filesystem/ResourcePath.h"
|
||||
|
||||
class IMusicPlayer
|
||||
{
|
||||
public:
|
||||
virtual ~IMusicPlayer() = default;
|
||||
|
||||
virtual void loadTerrainMusicThemes() = 0;
|
||||
virtual void setVolume(ui32 percent) = 0;
|
||||
virtual ui32 getVolume() const = 0;
|
||||
|
||||
virtual void musicFinishedCallback() = 0;
|
||||
|
||||
/// play track by URI, if loop = true music will be looped
|
||||
virtual void playMusic(const AudioPath & musicURI, bool loop, bool fromStart) = 0;
|
||||
/// play random track from this set
|
||||
virtual void playMusicFromSet(const std::string & musicSet, bool loop, bool fromStart) = 0;
|
||||
/// play random track from set (musicSet, entryID)
|
||||
virtual void playMusicFromSet(const std::string & musicSet, const std::string & entryID, bool loop, bool fromStart) = 0;
|
||||
/// stops currently playing music by fading out it over fade_ms and starts next scheduled track, if any
|
||||
virtual void stopMusic(int fade_ms=1000) = 0;
|
||||
};
|
||||
35
client/media/ISoundPlayer.h
Normal file
35
client/media/ISoundPlayer.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* ISoundPlayer.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../lib/CSoundBase.h"
|
||||
#include "../lib/filesystem/ResourcePath.h"
|
||||
|
||||
class ISoundPlayer
|
||||
{
|
||||
public:
|
||||
virtual ~ISoundPlayer() = default;
|
||||
|
||||
virtual int playSound(soundBase::soundID soundID, int repeats = 0) = 0;
|
||||
virtual int playSound(const AudioPath & sound, int repeats = 0, bool cache = false) = 0;
|
||||
virtual int playSound(std::pair<std::unique_ptr<ui8[]>, si64> & data, int repeats = 0, bool cache = false) = 0;
|
||||
virtual int playSoundFromSet(std::vector<soundBase::soundID> & sound_vec) = 0;
|
||||
virtual void stopSound(int handler) = 0;
|
||||
|
||||
virtual ui32 getVolume() const = 0;
|
||||
virtual void setVolume(ui32 percent) = 0;
|
||||
virtual uint32_t getSoundDurationMilliseconds(const AudioPath & sound) = 0;
|
||||
virtual void setCallback(int channel, std::function<void()> function) = 0;
|
||||
virtual void resetCallback(int channel) = 0;
|
||||
virtual void soundFinishedCallback(int channel) = 0;
|
||||
virtual void ambientUpdateChannels(std::map<AudioPath, int> currentSounds) = 0;
|
||||
virtual void ambientStopAllChannels() = 0;
|
||||
virtual int ambientGetRange() const = 0;
|
||||
};
|
||||
43
client/media/IVideoPlayer.h
Normal file
43
client/media/IVideoPlayer.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* IVideoPlayer.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../lib/filesystem/ResourcePath.h"
|
||||
|
||||
struct SDL_Surface;
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
class Point;
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
enum class EVideoType : ui8
|
||||
{
|
||||
INTRO = 0, // use entire window: stopOnKey = true, scale = true, overlay = false
|
||||
SPELLBOOK // overlay video: stopOnKey = false, scale = false, overlay = true
|
||||
};
|
||||
|
||||
class IVideoPlayer : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
virtual bool open(const VideoPath & name, bool scale = false)=0; //true - succes
|
||||
virtual void close()=0;
|
||||
virtual bool nextFrame()=0;
|
||||
virtual void show(int x, int y, SDL_Surface *dst, bool update = true)=0;
|
||||
virtual void redraw(int x, int y, SDL_Surface *dst, bool update = true)=0; //reblits buffer
|
||||
virtual bool wait()=0;
|
||||
virtual int curFrame() const =0;
|
||||
virtual int frameCount() const =0;
|
||||
virtual void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true, std::function<void()> restart = nullptr) = 0;
|
||||
virtual bool openAndPlayVideo(const VideoPath & name, int x, int y, EVideoType videoType) = 0;
|
||||
virtual std::pair<std::unique_ptr<ui8 []>, si64> getAudio(const VideoPath & videoToOpen) = 0;
|
||||
virtual Point size() = 0;
|
||||
|
||||
virtual ~IVideoPlayer() = default;
|
||||
};
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "Images.h"
|
||||
#include "TextControls.h"
|
||||
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../battle/BattleInterface.h"
|
||||
@@ -23,6 +22,7 @@
|
||||
#include "../gui/MouseButton.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
#include "../gui/InterfaceObjectConfigurable.h"
|
||||
#include "../media/ISoundPlayer.h"
|
||||
#include "../windows/InfoWindows.h"
|
||||
#include "../render/CAnimation.h"
|
||||
#include "../render/Canvas.h"
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../CMusicHandler.h"
|
||||
|
||||
#include "../../CCallback.h"
|
||||
|
||||
|
||||
@@ -18,12 +18,12 @@
|
||||
#include "CCreatureWindow.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../PlayerLocalState.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../media/IMusicPlayer.h"
|
||||
#include "../widgets/MiscWidgets.h"
|
||||
#include "../widgets/CComponent.h"
|
||||
#include "../widgets/CGarrisonInt.h"
|
||||
@@ -43,6 +43,8 @@
|
||||
#include "../../CCallback.h"
|
||||
#include "../../lib/CArtHandler.h"
|
||||
#include "../../lib/CBuildingHandler.h"
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CSoundBase.h"
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
#include "../../lib/CGeneralTextHandler.h"
|
||||
#include "../../lib/GameSettings.h"
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
#include "CPuzzleWindow.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../adventureMap/CResDataBar.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/TextAlignment.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
#include "../mapView/MapView.h"
|
||||
#include "../media/ISoundPlayer.h"
|
||||
#include "../widgets/Buttons.h"
|
||||
#include "../widgets/Images.h"
|
||||
#include "../widgets/TextControls.h"
|
||||
|
||||
@@ -19,12 +19,12 @@
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../PlayerLocalState.h"
|
||||
#include "../CVideoHandler.h"
|
||||
|
||||
#include "../battle/BattleInterface.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../media/IVideoPlayer.h"
|
||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||
#include "../widgets/CComponent.h"
|
||||
#include "../widgets/CTextInput.h"
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
#include "../../lib/CGeneralTextHandler.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CVideoHandler.h"
|
||||
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../media/IVideoPlayer.h"
|
||||
#include "../widgets/Images.h"
|
||||
#include "../widgets/Buttons.h"
|
||||
#include "../widgets/TextControls.h"
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../CMusicHandler.h"
|
||||
|
||||
#include "../../CCallback.h"
|
||||
|
||||
|
||||
@@ -19,15 +19,15 @@
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CServerHandler.h"
|
||||
#include "../Client.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../CVideoHandler.h"
|
||||
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/CursorHandler.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
|
||||
#include "../media/IVideoPlayer.h"
|
||||
|
||||
#include "../widgets/CComponent.h"
|
||||
#include "../widgets/CGarrisonInt.h"
|
||||
#include "../widgets/CreatureCostBox.h"
|
||||
@@ -58,6 +58,7 @@
|
||||
#include "../lib/GameSettings.h"
|
||||
#include "../lib/CondSh.h"
|
||||
#include "../lib/CSkillHandler.h"
|
||||
#include "../lib/CSoundBase.h"
|
||||
#include "../lib/filesystem/Filesystem.h"
|
||||
#include "../lib/TextOperations.h"
|
||||
|
||||
|
||||
@@ -11,9 +11,10 @@
|
||||
#include "GeneralOptionsTab.h"
|
||||
|
||||
#include "CGameInfo.h"
|
||||
#include "CMusicHandler.h"
|
||||
#include "CPlayerInterface.h"
|
||||
#include "CServerHandler.h"
|
||||
#include "media/IMusicPlayer.h"
|
||||
#include "media/ISoundPlayer.h"
|
||||
#include "render/IScreenHandler.h"
|
||||
#include "windows/GUIClasses.h"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user