Browse Source

- 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

Ivan Savenko 14 years ago
parent
commit
8054c85091

+ 1 - 1
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);
 

+ 2 - 1
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);
 }

+ 6 - 5
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;

+ 0 - 1
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);
 }
 

+ 165 - 74
client/CMusicHandler.cpp

@@ -28,7 +28,13 @@ using namespace boost::assign;
 static boost::bimap<soundBase::soundID, std::string> 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<soundBase::soundID, Mix_Chunk *>::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<void()> function)
+{
+	std::map<int, boost::function<void()> >::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<int, boost::function<void()> >::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) {
-		Mix_HookMusicFinished(NULL);
-
-		musicMutex.lock();
-
-		if (currentMusic)
-		{
-			Mix_HaltMusic();
-			Mix_FreeMusic(currentMusic);
-		}
+	if (initialized)
+	{
+		boost::mutex::scoped_lock guard(musicMutex);
 
-		if (nextMusic)
-			Mix_FreeMusic(nextMusic);
+		Mix_HookMusicFinished(NULL);
 
-		musicMutex.unlock();
+		current.reset();
+		next.reset();
 	}
 
 	CAudioBase::release();
@@ -356,42 +394,39 @@ void CMusicHandler::release()
 // loop: -1 always repeats, 0=do not play, 1+=number of loops
 void CMusicHandler::playMusic(musicBase::musicID musicID, int loop)
 {
-	if (!initialized)
+	if (current.get() != NULL && *current == musicID)
 		return;
 
-	std::string filename = DATA_DIR "/Mp3/";
-	filename += musics[musicID];
+	queueNext(new MusicEntry(this, musicID, loop));
+}
 
-	musicMutex.lock();
+// Helper. Randomly plays tracks from music_vec
+void CMusicHandler::playMusicFromSet(std::vector<musicBase::musicID> &music_vec, int loop)
+{
+	if (current.get() != NULL && *current == music_vec)
+		return;
 
-	if (nextMusic)
-	{
-		// There's already a music queued, so remove it
-		Mix_FreeMusic(nextMusic);
-		nextMusic = NULL;
-	}
+	queueNext(new MusicEntry(this, music_vec, loop));
+}
+
+void CMusicHandler::queueNext(MusicEntry *queued)
+{
+	if (!initialized)
+		return;
+
+	boost::mutex::scoped_lock guard(musicMutex);
+
+	next.reset(queued);
 
-	if (currentMusic)
+	if (current.get() != NULL)
 	{
-		// 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<musicBase::musicID> &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();
+MusicEntry::MusicEntry(CMusicHandler *_owner, musicBase::musicID _musicID, int _loopCount):
+	owner(_owner),
+	music(NULL),
+	loopCount(_loopCount)
+{
+	load(_musicID);
 }
 
-Mix_Music * CMusicHandler::LoadMUS(const char *file)
+MusicEntry::MusicEntry(CMusicHandler *_owner, std::vector<musicBase::musicID> &_musicVec, int _loopCount):
+	currentID(musicBase::music_todo),
+	owner(_owner),
+	music(NULL),
+	loopCount(_loopCount),
+	musicVec(_musicVec)
 {
-	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;
+	//In this case music will be loaded only on playing - no need to call load() here
+}
+
+MusicEntry::~MusicEntry()
+{
+	tlog5<<"Del-ing music file "<<filename<<"\n";
+	if (music)
+		Mix_FreeMusic(music);
+}
+
+void MusicEntry::load(musicBase::musicID ID)
+{
+	currentID = ID;
+	filename = DATA_DIR "/Mp3/";
+	filename += owner->musics[ID];
+
+	tlog5<<"Loading music file "<<filename<<"\n";
+	if (music)
+		Mix_FreeMusic(music);
+
+	music = Mix_LoadMUS(filename.c_str());
 
 #ifdef _WIN32
 	//The assertion will fail if old MSVC libraries pack .dll is used
-	assert(Mix_GetMusicType(ret) == MUS_MP3_MAD);
+	assert(Mix_GetMusicType(music) == MUS_MP3_MAD);
 #endif
-	return ret;
 }
 
-int CMusicHandler::PlayMusic(Mix_Music *music, int loops)
+bool MusicEntry::play()
 {
-	int ret = Mix_PlayMusic(music, loops);
-	if(ret == -1)
+	tlog5<<"Playing music file "<<filename<<"\n";
+	if (loopCount == 0)
+		return false;
+
+	if (loopCount > 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;
+}
 
-	return ret;
+void MusicEntry::stop(int fade_ms)
+{
+	tlog5<<"Stoping music file "<<filename<<"\n";
+	loopCount = 0;
+	Mix_FadeOutMusic(fade_ms);
 }
+
+bool MusicEntry::operator == (musicBase::musicID _musicID) const
+{
+	return musicVec.empty() && currentID == _musicID;
+}
+
+bool MusicEntry::operator == (std::vector<musicBase::musicID> &_musicVec) const
+{
+	return musicVec == _musicVec;
+}

+ 43 - 5
client/CMusicHandler.h

@@ -2,6 +2,9 @@
 #define __CMUSICHANDLER_H__
 
 #include <boost/thread/mutex.hpp>
+#include <boost/function.hpp>
+
+#include <memory>
 
 #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<int, boost::function<void()> > callbacks;
+
 public:
 	CSoundHandler();
 
@@ -87,29 +94,60 @@ public:
 	int playSound(soundBase::soundID soundID, int repeats=0);
 	int playSoundFromSet(std::vector<soundBase::soundID> &sound_vec);
 	void stopSound(int handler);
+
+	void setCallback(int channel, boost::function<void()> function);
+	void soundFinishedCallback(int channel);
+
 	std::vector <struct CreaturesBattleSounds> CBattleSounds;
 	std::map<const CSpell*, soundBase::soundID> spellSounds;
 
 	// Sets
 	std::vector<soundBase::soundID> pickupSounds;
 	std::vector<soundBase::soundID> horseSounds;
+	std::vector<soundBase::soundID> 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<musicBase::musicID> musicVec;
+
+	void load(musicBase::musicID);
+
+public:
+	bool operator == (musicBase::musicID musicID) const;
+	bool operator == (std::vector<musicBase::musicID> &_musicVec) const;
+
+	MusicEntry(CMusicHandler *owner, musicBase::musicID musicID, int _loopCount);
+	MusicEntry(CMusicHandler *owner, std::vector<musicBase::musicID> &_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<MusicEntry> current;
+	std::auto_ptr<MusicEntry> next;
+
+	void queueNext(MusicEntry *queued);
 public:
 	CMusicHandler();
 

+ 17 - 5
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<boost::recursive_mutex> 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<boost::recursive_mutex> 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);
 

+ 1 - 1
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);