From 8054c850910e32cc4f6922328bb956bf138527ee Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 19 Aug 2011 19:50:24 +0000 Subject: [PATCH] - some work on sound and music players: -- Adventure map music will update with hero movement -- implemented battle intro sounds -- battle music tracks will be selected randomly each time - fixed #781 --- client/CAdvmapInterface.cpp | 2 +- client/CBattleInterface.cpp | 3 +- client/CCastleInterface.cpp | 11 +- client/CMT.cpp | 1 - client/CMusicHandler.cpp | 243 +++++++++++++++++++++++++----------- client/CMusicHandler.h | 48 ++++++- client/CPlayerInterface.cpp | 22 +++- client/Client.cpp | 2 +- 8 files changed, 237 insertions(+), 95 deletions(-) diff --git a/client/CAdvmapInterface.cpp b/client/CAdvmapInterface.cpp index 332b6ab86..acb6373ea 100644 --- a/client/CAdvmapInterface.cpp +++ b/client/CAdvmapInterface.cpp @@ -1450,7 +1450,7 @@ void CAdvMapInt::select(const CArmedInstance *sel, bool centerView /*= true*/) assert(sel); LOCPLINT->cb->setSelection(sel); selection = sel; - CCS->musich->playMusic(CCS->musich->terrainMusics[LOCPLINT->cb->getTile(sel->visitablePos())->tertype]); //TODO: needs to be updated upon hero movement + CCS->musich->playMusic(CCS->musich->terrainMusics[LOCPLINT->cb->getTile(sel->visitablePos())->tertype]); if(centerView) centerOn(sel); diff --git a/client/CBattleInterface.cpp b/client/CBattleInterface.cpp index fadc84589..c978b3730 100644 --- a/client/CBattleInterface.cpp +++ b/client/CBattleInterface.cpp @@ -1477,6 +1477,8 @@ CBattleInterface::~CBattleInterface() delete siegeH; curInt->battleInt = NULL; + + //TODO:restart music (can be AI or terrain). May be easier to backup and restore it instead of re-selecting } void CBattleInterface::setPrintCellBorders(bool set) @@ -2981,7 +2983,6 @@ void CBattleInterface::displayBattleFinished() CCS->curh->changeGraphic(0,0); SDL_Rect temp_rect = genRect(561, 470, (screen->w - 800)/2 + 165, (screen->h - 600)/2 + 19); - CCS->musich->stopMusic(); resWindow = new CBattleResultWindow(*bresult, temp_rect, this); GH.pushInt(resWindow); } diff --git a/client/CCastleInterface.cpp b/client/CCastleInterface.cpp index f6b7ff147..b7224aff9 100644 --- a/client/CCastleInterface.cpp +++ b/client/CCastleInterface.cpp @@ -951,7 +951,6 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, int listPos): CCastleInterface::~CCastleInterface() { LOCPLINT->castleInt = NULL; - CCS->musich->stopMusic(5000); delete bicons; } @@ -1420,10 +1419,12 @@ CHallInterface::CHallInterface(const CGTownInstance *Town): int buildingID = boxList[row][col][item]; building = CGI->buildh->buildings[town->subID][buildingID]; - if (buildingID == 18 && vstd::contains(town->builtBuildings, town->town->hordeLvl[0]+37)) - continue; - if (buildingID == 24 && vstd::contains(town->builtBuildings, town->town->hordeLvl[1]+37)) - continue; + //Creature hordes - select unupgraded version if dwelling upgrade was not build yet + if (buildingID == 18 && !vstd::contains(town->builtBuildings, town->town->hordeLvl[0]+37)) + break; + if (buildingID == 24 && !vstd::contains(town->builtBuildings, town->town->hordeLvl[1]+37)) + break; + if(vstd::contains(town->builtBuildings,buildingID)) continue; break; diff --git a/client/CMT.cpp b/client/CMT.cpp index d65a0aec8..210e6416a 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -729,7 +729,6 @@ void startGame(StartInfo * options, CConnection *serv/* = NULL*/) break; } - CCS->musich->stopMusic(); client->connectionHandler = new boost::thread(&CClient::run, client); } diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index 606785059..942a92ed6 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -28,7 +28,13 @@ using namespace boost::assign; static boost::bimap sounds; // Not pretty, but there's only one music handler object in the game. -static void musicFinishedCallbackC(void) { +static void soundFinishedCallbackC(int channel) +{ + CCS->soundh->soundFinishedCallback(channel); +} + +static void musicFinishedCallbackC(void) +{ CCS->musich->musicFinishedCallback(); } @@ -48,7 +54,8 @@ void CAudioBase::init() void CAudioBase::release() { - if (initialized) { + if (initialized) + { Mix_CloseAudio(); initialized = false; } @@ -75,32 +82,41 @@ CSoundHandler::CSoundHandler() // Vectors for helper(s) pickupSounds += soundBase::pickup01, soundBase::pickup02, soundBase::pickup03, soundBase::pickup04, soundBase::pickup05, soundBase::pickup06, soundBase::pickup07; + horseSounds += // must be the same order as terrains (see EterrainType); soundBase::horseDirt, soundBase::horseSand, soundBase::horseGrass, soundBase::horseSnow, soundBase::horseSwamp, soundBase::horseRough, soundBase::horseSubterranean, soundBase::horseLava, soundBase::horseWater, soundBase::horseRock; + + battleIntroSounds += soundBase::battle00, soundBase::battle01, + soundBase::battle02, soundBase::battle03, soundBase::battle04, + soundBase::battle05, soundBase::battle06, soundBase::battle07; }; void CSoundHandler::init() { CAudioBase::init(); - if (initialized) { + if (initialized) + { // Load sounds sndh.add_file(std::string(DATA_DIR "/Data/Heroes3.snd")); sndh.add_file(std::string(DATA_DIR "/Data/Heroes3-cd2.snd")); sndh.add_file(std::string(DATA_DIR "/Data/H3ab_ahd.snd")); + Mix_ChannelFinished(soundFinishedCallbackC); } } void CSoundHandler::release() { - if (initialized) { + if (initialized) + { Mix_HaltChannel(-1); std::map::iterator it; - for (it=soundChunks.begin(); it != soundChunks.end(); it++) { + for (it=soundChunks.begin(); it != soundChunks.end(); it++) + { if (it->second) Mix_FreeChunk(it->second); } @@ -128,7 +144,8 @@ Mix_Chunk *CSoundHandler::GetSoundChunk(soundBase::soundID soundID) Mix_Chunk *chunk; chunk = Mix_LoadWAV_RW(ops, 1); // will free ops - if (!chunk) { + if (!chunk) + { tlog1 << "Unable to mix sound" << it->second << "(" << Mix_GetError() << ")" << std::endl; return NULL; } @@ -268,8 +285,11 @@ int CSoundHandler::playSound(soundBase::soundID soundID, int repeats) channel = Mix_PlayChannel(-1, chunk, repeats); if (channel == -1) tlog1 << "Unable to play sound file " << soundID << " , error " << Mix_GetError() << std::endl; - - } else { + else + callbacks[channel];//insert empty callback + } + else + { channel = -1; } @@ -297,7 +317,32 @@ void CSoundHandler::setVolume(unsigned int percent) Mix_Volume(-1, (MIX_MAX_VOLUME * volume)/100); } -CMusicHandler::CMusicHandler(): currentMusic(NULL), nextMusic(NULL) +void CSoundHandler::setCallback(int channel, boost::function function) +{ + std::map >::iterator iter; + iter = callbacks.find(channel); + + //channel not found. It may have finished so fire callback now + if(iter == callbacks.end()) + function(); + else + iter->second = function; +} + +void CSoundHandler::soundFinishedCallback(int channel) +{ + std::map >::iterator iter; + iter = callbacks.find(channel); + + assert(iter != callbacks.end()); + + if (iter->second) + iter->second(); + + callbacks.erase(iter); +} + +CMusicHandler::CMusicHandler() { // Map music IDs #define VCMI_MUSIC_ID(x) ( musicBase::x , @@ -314,7 +359,7 @@ CMusicHandler::CMusicHandler(): currentMusic(NULL), nextMusic(NULL) townMusics += musicBase::castleTown, musicBase::rampartTown, musicBase::towerTown, musicBase::infernoTown, musicBase::necroTown, musicBase::dungeonTown, - musicBase::strongHoldTown, musicBase::fortressTown, + musicBase::strongHoldTown, musicBase::fortressTown, musicBase::elemTown; terrainMusics += musicBase::dirt, musicBase::sand, musicBase::grass, @@ -332,21 +377,14 @@ void CMusicHandler::init() void CMusicHandler::release() { - if (initialized) { + if (initialized) + { + boost::mutex::scoped_lock guard(musicMutex); + Mix_HookMusicFinished(NULL); - musicMutex.lock(); - - if (currentMusic) - { - Mix_HaltMusic(); - Mix_FreeMusic(currentMusic); - } - - if (nextMusic) - Mix_FreeMusic(nextMusic); - - musicMutex.unlock(); + current.reset(); + next.reset(); } CAudioBase::release(); @@ -355,43 +393,40 @@ void CMusicHandler::release() // Plays a music // loop: -1 always repeats, 0=do not play, 1+=number of loops void CMusicHandler::playMusic(musicBase::musicID musicID, int loop) +{ + if (current.get() != NULL && *current == musicID) + return; + + queueNext(new MusicEntry(this, musicID, loop)); +} + +// Helper. Randomly plays tracks from music_vec +void CMusicHandler::playMusicFromSet(std::vector &music_vec, int loop) +{ + if (current.get() != NULL && *current == music_vec) + return; + + queueNext(new MusicEntry(this, music_vec, loop)); +} + +void CMusicHandler::queueNext(MusicEntry *queued) { if (!initialized) return; - std::string filename = DATA_DIR "/Mp3/"; - filename += musics[musicID]; + boost::mutex::scoped_lock guard(musicMutex); - musicMutex.lock(); + next.reset(queued); - if (nextMusic) + if (current.get() != NULL) { - // There's already a music queued, so remove it - Mix_FreeMusic(nextMusic); - nextMusic = NULL; - } - - if (currentMusic) - { - // A music is already playing. Stop it and the callback will - // start the new one - nextMusic = LoadMUS(filename.c_str()); - nextMusicLoop = loop; - Mix_FadeOutMusic(1000); + current->stop(1000); } else { - currentMusic = LoadMUS(filename.c_str()); - PlayMusic(currentMusic,loop); + current = next; + current->play(); } - - musicMutex.unlock(); -} - -// Helper. Randomly select a music from an array and play it -void CMusicHandler::playMusicFromSet(std::vector &music_vec, int loop) -{ - playMusic(music_vec[rand() % music_vec.size()], loop); } // Stop and free the current music @@ -400,14 +435,12 @@ void CMusicHandler::stopMusic(int fade_ms) if (!initialized) return; - musicMutex.lock(); + boost::mutex::scoped_lock guard(musicMutex); - if (currentMusic) - { - Mix_FadeOutMusic(fade_ms); - } + if (current.get() != NULL) + current->stop(fade_ms); + next.reset(); - musicMutex.unlock(); } // Sets the music volume, from 0 (mute) to 100 @@ -422,42 +455,100 @@ void CMusicHandler::setVolume(unsigned int percent) // Called by SDL when a music finished. void CMusicHandler::musicFinishedCallback(void) { - musicMutex.lock(); + boost::mutex::scoped_lock guard(musicMutex); - if (currentMusic) + if (current.get() != NULL) { - Mix_FreeMusic(currentMusic); - currentMusic = NULL; + //return if current music still not finished + if (current->play()) + return; + else + current.reset(); } - if (nextMusic) + if (current.get() == NULL && next.get() != NULL) { - currentMusic = nextMusic; - nextMusic = NULL; - PlayMusic(currentMusic,nextMusicLoop); + current = next; + current->play(); } - - musicMutex.unlock(); } -Mix_Music * CMusicHandler::LoadMUS(const char *file) +MusicEntry::MusicEntry(CMusicHandler *_owner, musicBase::musicID _musicID, int _loopCount): + owner(_owner), + music(NULL), + loopCount(_loopCount) { - Mix_Music *ret = Mix_LoadMUS(file); - if(!ret) //load music and check for error - tlog1 << "Unable to load music file (" << file <<"). Error: " << Mix_GetError() << std::endl; + load(_musicID); +} + +MusicEntry::MusicEntry(CMusicHandler *_owner, std::vector &_musicVec, int _loopCount): + currentID(musicBase::music_todo), + owner(_owner), + music(NULL), + loopCount(_loopCount), + musicVec(_musicVec) +{ + //In this case music will be loaded only on playing - no need to call load() here +} + +MusicEntry::~MusicEntry() +{ + tlog5<<"Del-ing music file "<musics[ID]; + + tlog5<<"Loading music file "< 0) + loopCount--; + + if (!musicVec.empty()) + load(musicVec.at(rand() % musicVec.size())); + + if(Mix_PlayMusic(music, 1) == -1) + { + tlog1 << "Unable to play music (" << Mix_GetError() << ")" << std::endl; + return false; + } + return true; } + +void MusicEntry::stop(int fade_ms) +{ + tlog5<<"Stoping music file "< &_musicVec) const +{ + return musicVec == _musicVec; +} \ No newline at end of file diff --git a/client/CMusicHandler.h b/client/CMusicHandler.h index ba05799f3..a26323cda 100644 --- a/client/CMusicHandler.h +++ b/client/CMusicHandler.h @@ -2,6 +2,9 @@ #define __CMUSICHANDLER_H__ #include +#include + +#include #include "CSoundBase.h" #include "CMusicBase.h" @@ -73,6 +76,10 @@ private: Mix_Chunk *GetSoundChunk(soundBase::soundID soundID); + //have entry for every currently active channel + //boost::function will be NULL if callback was not set + std::map > callbacks; + public: CSoundHandler(); @@ -87,29 +94,60 @@ public: int playSound(soundBase::soundID soundID, int repeats=0); int playSoundFromSet(std::vector &sound_vec); void stopSound(int handler); + + void setCallback(int channel, boost::function function); + void soundFinishedCallback(int channel); + std::vector CBattleSounds; std::map spellSounds; // Sets std::vector pickupSounds; std::vector horseSounds; + std::vector battleIntroSounds; }; // Helper #define battle_sound(creature,what_sound) CCS->soundh->CBattleSounds[(creature)->idNumber].what_sound +class CMusicHandler; + +//Class for handling one music file +class MusicEntry +{ + std::string filename; //used only for debugging and console messages + musicBase::musicID currentID; + CMusicHandler *owner; + Mix_Music *music; + int loopCount; + //if not empty - vector from which music will be randomly selected + std::vector musicVec; + + void load(musicBase::musicID); + +public: + bool operator == (musicBase::musicID musicID) const; + bool operator == (std::vector &_musicVec) const; + + MusicEntry(CMusicHandler *owner, musicBase::musicID musicID, int _loopCount); + MusicEntry(CMusicHandler *owner, std::vector &_musicVec, int _loopCount); + ~MusicEntry(); + + bool play(); + void stop(int fade_ms=0); +}; + class CMusicHandler: public CAudioBase { private: // Because we use the SDL music callback, our music variables must // be protected boost::mutex musicMutex; - Mix_Music *currentMusic; - Mix_Music *nextMusic; - int nextMusicLoop; - Mix_Music * LoadMUS(const char *file); //calls Mix_LoadMUS and checks for errors - int PlayMusic(Mix_Music *music, int loops); //calls Mix_PlayMusic and checks for errors + std::auto_ptr current; + std::auto_ptr next; + + void queueNext(MusicEntry *queued); public: CMusicHandler(); diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index bc9b66233..a3ef2fcd7 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -257,6 +257,8 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details) if(makingTurn && ho->tempOwner == playerID) //we are moving our hero - we may need to update assigned path { + //We may need to change music - select new track, music handler will change it if needed + CCS->musich->playMusic(CCS->musich->terrainMusics[LOCPLINT->cb->getTile(ho->visitablePos())->tertype]); if(details.result == TryMoveHero::TELEPORTATION || details.start == details.end) { if(adventureInt->terrain.currentPath) @@ -569,7 +571,11 @@ void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet SDL_Delay(20); boost::unique_lock un(*pim); - CCS->musich->playMusicFromSet(CCS->musich->battleMusics, -1); + CCS->musich->stopMusic(); + + int channel = CCS->soundh->playSoundFromSet(CCS->soundh->battleIntroSounds); + CCS->soundh->setCallback(channel, boost::bind(&CMusicHandler::playMusicFromSet, CCS->musich, CCS->musich->battleMusics, -1)); + GH.pushInt(battleInt); } @@ -2051,13 +2057,19 @@ void CPlayerInterface::acceptTurn() boost::unique_lock un(*pim); - /* TODO: This isn't quite right. First day in game should play - * NEWDAY. And we don't play NEWMONTH. */ + //Select sound for day start + int totalDays = cb->getDate(); int day = cb->getDate(1); - if (day != 1) + int week = cb->getDate(2); + + if (totalDays == 1) CCS->soundh->playSound(soundBase::newDay); - else + else if (day != 1) + CCS->soundh->playSound(soundBase::newDay); + else if (week != 1) CCS->soundh->playSound(soundBase::newWeek); + else + CCS->soundh->playSound(soundBase::newMonth); adventureInt->infoBar.newDay(day); diff --git a/client/Client.cpp b/client/Client.cpp index 0d299ddd6..bd1f361f0 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -478,7 +478,7 @@ void CClient::serialize( Handler &h, const int version ) { if(pid == 255) { - CBattleCallback * cbc = new CBattleCallback(gs, pid, this); + //CBattleCallback * cbc = new CBattleCallback(gs, pid, this);//FIXME: unused? CBattleGameInterface *cbgi = CDynLibHandler::getNewBattleAI(dllname); battleints[pid] = cbgi; cbgi->init(cb);