瀏覽代碼

- music player uses URI's instead of enum from music base
- #1045 and #1046 should be fixed
- moved pregame backgrounds to config/mainmenu
- animation can be overriden with .json multiple times

Ivan Savenko 13 年之前
父節點
當前提交
708ad6ac7f

+ 1 - 1
AI/VCAI/VCAI.cpp

@@ -2576,7 +2576,7 @@ int3 whereToExplore(HeroPtr h)
 	}
 	catch(cannotFulfillGoalException &e)
 	{
-		std::vector<std::vector<int3> > tiles; //tiles[distance_to_fow], metryka taksówkowa
+		std::vector<std::vector<int3> > tiles; //tiles[distance_to_fow]
 		try
 		{
 			return ai->explorationNewPoint(radius, h, tiles);

+ 4 - 4
Global.h

@@ -268,16 +268,16 @@ namespace vstd
 
 	//checks if a is between b and c
 	template <typename t1, typename t2, typename t3>
-	bool isbetween(const t1 &a, const t2 &b, const t3 &c)
+	bool isbetween(const t1 &value, const t2 &min, const t3 &max)
 	{
-		return a > b && a < c;
+		return value > min && value < max;
 	}
 
 	//checks if a is within b and c
 	template <typename t1, typename t2, typename t3>
-	bool iswithin(const t1 &a, const t2 &b, const t3 &c)
+	bool iswithin(const t1 &value, const t2 &min, const t3 &max)
 	{
-		return a >= b && a <= c;
+		return value >= min && value <= max;
 	}
 
 	template <typename t1, typename t2>

+ 9 - 3
client/BattleInterface/CBattleInterface.cpp

@@ -371,8 +371,14 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe
 	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));
-    memset(stackCountOutsideHexes, 1, GameConstants::BFIELD_SIZE * sizeof(bool)); //initialize array with trues
+	auto onIntroPlayed = []()
+	{
+		if (LOCPLINT->battleInt)
+			CCS->musich->playMusicFromSet("battle", true);
+	};
+
+	CCS->soundh->setCallback(channel, onIntroPlayed);
+	memset(stackCountOutsideHexes, 1, GameConstants::BFIELD_SIZE * sizeof(bool)); //initialize array with trues
 
 	currentAction = INVALID;
 	selectedAction = INVALID;
@@ -446,7 +452,7 @@ CBattleInterface::~CBattleInterface()
 	if(adventureInt && adventureInt->selection)
 	{
 		int terrain = LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->tertype;
-		CCS->musich->playMusic(CCS->musich->terrainMusics[terrain], -1);
+		CCS->musich->playMusicFromSet("terrain", terrain, true);
 	}
 }
 

+ 4 - 4
client/BattleInterface/CBattleInterfaceClasses.cpp

@@ -417,7 +417,7 @@ CBattleResultWindow::CBattleResultWindow(const BattleResult &br, const SDL_Rect
 		case 2: text = 302; break;
 		}
 
-		CCS->musich->playMusic(musicBase::winBattle);
+		CCS->musich->playMusic("Music/Win Battle", false);
 		CCS->videoh->open(VIDEO_WIN);
 		std::string str = CGI->generaltexth->allTexts[text];
 
@@ -437,21 +437,21 @@ CBattleResultWindow::CBattleResultWindow(const BattleResult &br, const SDL_Rect
 		{
 		case 0: //normal victory
 			{
-				CCS->musich->playMusic(musicBase::loseCombat);
+				CCS->musich->playMusic("Music/LoseCombat", false);
 				CCS->videoh->open(VIDEO_LOSE_BATTLE_START);
 				new CLabel(235, 235, FONT_SMALL, CENTER, Colors::Cornsilk, CGI->generaltexth->allTexts[311]);
 				break;
 			}
 		case 1: //flee
 			{
-				CCS->musich->playMusic(musicBase::retreatBattle);
+				CCS->musich->playMusic("Music/Retreat Battle", false);
 				CCS->videoh->open(VIDEO_RETREAT_START);
 				new CLabel(235, 235, FONT_SMALL, CENTER, Colors::Cornsilk, CGI->generaltexth->allTexts[310]);
 				break;
 			}
 		case 2: //surrender
 			{
-				CCS->musich->playMusic(musicBase::surrenderBattle);
+				CCS->musich->playMusic("Music/Surrender Battle", false);
 				CCS->videoh->open(VIDEO_SURRENDER);
 				new CLabel(235, 235, FONT_SMALL, CENTER, Colors::Cornsilk, CGI->generaltexth->allTexts[309]);
 				break;

+ 2 - 2
client/CAdvmapInterface.cpp

@@ -987,7 +987,7 @@ void CAdvMapInt::select(const CArmedInstance *sel, bool centerView /*= true*/)
 		auto pos = sel->visitablePos();
 		auto tile = LOCPLINT->cb->getTile(pos);
 		if(tile)
-			CCS->musich->playMusic(CCS->musich->terrainMusics[tile->tertype], -1);
+			CCS->musich->playMusicFromSet("terrain", tile->tertype, true);
 	}
 	if(centerView)
 		centerOn(sel);
@@ -1487,7 +1487,7 @@ const IShipyard * CAdvMapInt::ourInaccessibleShipyard(const CGObjectInstance *ob
 void CAdvMapInt::aiTurnStarted()
 {
 	adjustActiveness(true);
-	CCS->musich->playMusicFromSet(CCS->musich->aiMusics);
+	CCS->musich->playMusicFromSet("enemy-turn", true);
 	adventureInt->minimap.setAIRadar(true);
 	adventureInt->infoBar.startEnemyTurn(LOCPLINT->cb->getCurrentPlayer());
 	adventureInt->infoBar.showAll(screen);//force refresh on inactive object

+ 10 - 2
client/CAnimation.cpp

@@ -2,6 +2,7 @@
 #include <SDL_image.h>
 
 #include "../lib/Filesystem/CResourceLoader.h"
+#include "../lib/Filesystem/ISimpleResourceLoader.h"
 #include "../lib/JsonNode.h"
 #include "../lib/vcmi_endian.h"
 
@@ -934,9 +935,16 @@ void CAnimation::init(CDefFile * file)
 			source[mapIt->first].resize(mapIt->second);
 	}
 
-	if (CResourceHandler::get()->existsResource(ResourceID(std::string("SPRITES/") + name, EResType::TEXT)))
+	auto & configList = CResourceHandler::get()->getResourcesWithName(
+	                      ResourceID(std::string("SPRITES/") + name, EResType::TEXT));
+
+	BOOST_FOREACH(auto & entry, configList)
 	{
-		const JsonNode config(ResourceID(std::string("SPRITES/") + name, EResType::TEXT));
+		auto stream = entry.getLoader()->load(entry.getResourceName());
+		std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]);
+		stream->read(textData.get(), stream->getSize());
+
+		const JsonNode config((char*)textData.get(), stream->getSize());
 
 		std::string basepath;
 		basepath = config["basepath"].String();

+ 11 - 2
client/CBitmapHandler.cpp

@@ -76,7 +76,16 @@ SDL_Surface * BitmapHandler::loadH3PCX(ui8 * pcx, size_t size)
 	}
 	else
 	{
-		ret = CSDL_Ext::createSurfaceWithBpp<3>(width, height);
+#if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
+		int bmask = 0xff0000;
+		int gmask = 0x00ff00;
+		int rmask = 0x0000ff;
+#else
+		int bmask = 0x0000ff;
+		int gmask = 0x00ff00;
+		int rmask = 0xff0000;
+#endif
+		ret = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 24, rmask, gmask, bmask, 0);
 
 		//it == 0xC;
 		for (int i=0; i<height; i++)
@@ -127,7 +136,7 @@ SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fna
 
 		ret = IMG_LoadTyped_RW(
 		          //create SDL_RW with our data (will be deleted by SDL)
-		          SDL_RWFromConstMem((void*)readFile.first.release(), readFile.second),
+		          SDL_RWFromConstMem((void*)readFile.first.get(), readFile.second),
 		          1, // mark it for auto-deleting
 		          &info.getExtension()[0] + 1); //pass extension without dot (+1 character)
 

+ 1 - 1
client/CCastleInterface.cpp

@@ -951,7 +951,7 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst
 	townlist->onSelect = boost::bind(&CCastleInterface::townChange, this);
 
 	recreateIcons();
-	CCS->musich->playMusic(CCS->musich->townMusics[town->subID], -1);
+	CCS->musich->playMusicFromSet("town-theme", town->subID, true);
 	
 	bicons = CDefHandler::giveDefEss(graphics->buildingPics[town->subID]);
 }

+ 0 - 90
client/CMusicBase.h

@@ -1,90 +0,0 @@
-#pragma once
-
-/*
- * CMusicBase.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
- *
- */
-
-// Use some magic to keep the list of files and their code name in sync.
-// FIXME: first half of this list should be read from campmusic.txt
-
-#define VCMI_MUSIC_LIST \
-	VCMI_MUSIC_ID(campainMusic01) VCMI_MUSIC_FILE("CampainMusic01.mp3") \
-	VCMI_MUSIC_ID(campainMusic02) VCMI_MUSIC_FILE("CampainMusic02.mp3") \
-	VCMI_MUSIC_ID(campainMusic03) VCMI_MUSIC_FILE("CampainMusic03.mp3") \
-	VCMI_MUSIC_ID(campainMusic04) VCMI_MUSIC_FILE("CampainMusic04.mp3") \
-	VCMI_MUSIC_ID(campainMusic05) VCMI_MUSIC_FILE("CampainMusic05.mp3") \
-	VCMI_MUSIC_ID(campainMusic06) VCMI_MUSIC_FILE("CampainMusic06.mp3") \
-	VCMI_MUSIC_ID(campainMusic07) VCMI_MUSIC_FILE("CampainMusic07.mp3") \
-	VCMI_MUSIC_ID(campainMusic08) VCMI_MUSIC_FILE("CampainMusic08.mp3") \
-	VCMI_MUSIC_ID(campainMusic09) VCMI_MUSIC_FILE("CampainMusic09.mp3") \
-	VCMI_MUSIC_ID(AITheme0) VCMI_MUSIC_FILE("AITheme0.mp3") \
-	VCMI_MUSIC_ID(AITheme1) VCMI_MUSIC_FILE("AITHEME1.MP3") \
-	VCMI_MUSIC_ID(AITheme2) VCMI_MUSIC_FILE("AITHEME2.MP3") \
-	VCMI_MUSIC_ID(combat1) VCMI_MUSIC_FILE("COMBAT01.MP3") \
-	VCMI_MUSIC_ID(combat2) VCMI_MUSIC_FILE("COMBAT02.MP3") \
-	VCMI_MUSIC_ID(combat3) VCMI_MUSIC_FILE("COMBAT03.MP3") \
-	VCMI_MUSIC_ID(combat4) VCMI_MUSIC_FILE("COMBAT04.MP3") \
-	VCMI_MUSIC_ID(castleTown) VCMI_MUSIC_FILE("CstleTown.mp3") \
-	VCMI_MUSIC_ID(towerTown) VCMI_MUSIC_FILE("TowerTown.mp3") \
-	VCMI_MUSIC_ID(rampartTown) VCMI_MUSIC_FILE("RAMPART.MP3") \
-	VCMI_MUSIC_ID(infernoTown) VCMI_MUSIC_FILE("InfernoTown.mp3") \
-	VCMI_MUSIC_ID(necroTown) VCMI_MUSIC_FILE("necroTown.mp3") \
-	VCMI_MUSIC_ID(dungeonTown) VCMI_MUSIC_FILE("DUNGEON.MP3") \
-	VCMI_MUSIC_ID(strongHoldTown) VCMI_MUSIC_FILE("StrongHold.mp3") \
-	VCMI_MUSIC_ID(fortressTown) VCMI_MUSIC_FILE("FortressTown.mp3") \
-	VCMI_MUSIC_ID(elemTown) VCMI_MUSIC_FILE("ElemTown.mp3") \
-	VCMI_MUSIC_ID(dirt) VCMI_MUSIC_FILE("DIRT.MP3") \
-	VCMI_MUSIC_ID(sand) VCMI_MUSIC_FILE("SAND.MP3") \
-	VCMI_MUSIC_ID(grass) VCMI_MUSIC_FILE("GRASS.MP3") \
-	VCMI_MUSIC_ID(snow) VCMI_MUSIC_FILE("SNOW.MP3") \
-	VCMI_MUSIC_ID(swamp) VCMI_MUSIC_FILE("SWAMP.MP3") \
-	VCMI_MUSIC_ID(rough) VCMI_MUSIC_FILE("ROUGH.MP3") \
-	VCMI_MUSIC_ID(underground) VCMI_MUSIC_FILE("Underground.mp3") \
-	VCMI_MUSIC_ID(lava) VCMI_MUSIC_FILE("LAVA.MP3") \
-	VCMI_MUSIC_ID(water) VCMI_MUSIC_FILE("WATER.MP3") \
-	VCMI_MUSIC_ID(goodTheme) VCMI_MUSIC_FILE("GoodTheme.mp3") \
-	VCMI_MUSIC_ID(neutralTheme) VCMI_MUSIC_FILE("NeutralTheme.mp3") \
-	VCMI_MUSIC_ID(evilTheme) VCMI_MUSIC_FILE("EvilTheme.mp3") \
-	VCMI_MUSIC_ID(secretTheme) VCMI_MUSIC_FILE("SecretTheme.mp3") \
-	VCMI_MUSIC_ID(loopLepr) VCMI_MUSIC_FILE("LoopLepr.mp3") \
-	VCMI_MUSIC_ID(mainMenu) VCMI_MUSIC_FILE("MAINMENU.MP3") \
-	VCMI_MUSIC_ID(winScenario) VCMI_MUSIC_FILE("Win Scenario.mp3" ) \
-	VCMI_MUSIC_ID(campainMusic10) VCMI_MUSIC_FILE("CampainMusic10.mp3") \
-	VCMI_MUSIC_ID(bladeABcampaign) VCMI_MUSIC_FILE("BladeABCampaign.mp3") \
-	VCMI_MUSIC_ID(bladeDBcampaign) VCMI_MUSIC_FILE("BladeDBCampaign.mp3") \
-	VCMI_MUSIC_ID(bladeDScampaign) VCMI_MUSIC_FILE("BladeDSCampaign.mp3") \
-	VCMI_MUSIC_ID(bladeFLcampaign) VCMI_MUSIC_FILE("BladeFLCampaign.mp3") \
-	VCMI_MUSIC_ID(bladeFWcampaign) VCMI_MUSIC_FILE("BladeFWCampaign.mp3") \
-	VCMI_MUSIC_ID(bladePWcampaign) VCMI_MUSIC_FILE("BladePFCampaign.mp3") \
-	VCMI_MUSIC_ID(campainMusic11) VCMI_MUSIC_FILE("CampainMusic11.mp3") \
-	VCMI_MUSIC_ID(loseCampain) VCMI_MUSIC_FILE("Lose Campain.mp3") \
-	VCMI_MUSIC_ID(loseCastle) VCMI_MUSIC_FILE("LoseCastle.mp3") \
-	VCMI_MUSIC_ID(loseCombat) VCMI_MUSIC_FILE("LoseCombat.mp3") \
-	VCMI_MUSIC_ID(mainMenuWoG) VCMI_MUSIC_FILE("MainMenuWoG.mp3") \
-	VCMI_MUSIC_ID(retreatBattle) VCMI_MUSIC_FILE("Retreat Battle.mp3") \
-	VCMI_MUSIC_ID(surrenderBattle) VCMI_MUSIC_FILE("Surrender Battle.mp3") \
-	VCMI_MUSIC_ID(ultimateLose) VCMI_MUSIC_FILE("UltimateLose.mp3") \
-	VCMI_MUSIC_ID(winBattle) VCMI_MUSIC_FILE("Win Battle.mp3") \
-	VCMI_MUSIC_ID(defendCastle) VCMI_MUSIC_FILE("Defend Castle.mp3") \
-	
-class musicBase
-{
-public:
-	// Make a list of enums
-#define VCMI_MUSIC_ID(x) x,
-#define VCMI_MUSIC_FILE(y)
-	enum musicID {
-		music_todo=0,			// temp entry until code is fixed
-		VCMI_MUSIC_LIST
-	};
-#undef VCMI_MUSIC_ID
-#undef VCMI_MUSIC_FILE
-};
-
-

+ 81 - 83
client/CMusicHandler.cpp

@@ -53,7 +53,7 @@ void CAudioBase::init()
 void CAudioBase::release()
 {
 	if (initialized)
-		{
+	{
 		Mix_CloseAudio();
 		initialized = false;
 	}
@@ -332,40 +332,27 @@ CMusicHandler::CMusicHandler():
 {
 	listener(boost::bind(&CMusicHandler::onVolumeChange, this, _1));
 	// Map music IDs
-
-#ifdef CPP11_USE_INITIALIZERS_LIST
-
-	#define VCMI_MUSIC_ID(x) { musicBase::x ,
-	#define VCMI_MUSIC_FILE(y) y },
-		musics = { VCMI_MUSIC_LIST};
-	#undef VCMI_MUSIC_NAME
-	#undef VCMI_MUSIC_FILE
-
-#else
-
-	#define VCMI_MUSIC_ID(x) ( musicBase::x ,
-	#define VCMI_MUSIC_FILE(y) y )
-		musics = map_list_of
-			VCMI_MUSIC_LIST;
-	#undef VCMI_MUSIC_NAME
-	#undef VCMI_MUSIC_FILE
-
-#endif
 	// Vectors for helper
-	aiMusics += musicBase::AITheme0, musicBase::AITheme1, musicBase::AITheme2;
-
-	battleMusics += musicBase::combat1, musicBase::combat2,
-		musicBase::combat3, musicBase::combat4;
+	const std::string setEnemy[] = {"AITheme0", "AITheme1", "AITheme2"};
+	const std::string setBattle[] = {"Combat01", "Combat02", "Combat03", "Combat04"};
+	const std::string setTerrain[] = {"Dirt",	"Sand",	"Grass", "Snow", "Swamp", "Rough", "Underground", "Lava", "Water"};
+	const std::string setTowns[] =  {"CstleTown", "Rampart", "TowerTown", "InfernoTown",
+	        "NecroTown", "Dungeon", "Stronghold", "FortressTown", "ElemTown"};
 
-	townMusics += musicBase::castleTown,     musicBase::rampartTown,
-	              musicBase::towerTown,      musicBase::infernoTown,
-	              musicBase::necroTown,      musicBase::dungeonTown,
-	              musicBase::strongHoldTown, musicBase::fortressTown,
-	              musicBase::elemTown;
+	auto fillSet = [=](std::string setName, const std::string list[], size_t amount)
+	{
+		for (size_t i=0; i < amount; i++)
+	        addEntryToSet(setName, i, std::string("music/") + list[i]);
+	};
+	fillSet("enemy-turn", setEnemy, ARRAY_COUNT(setEnemy));
+	fillSet("battle", setBattle, ARRAY_COUNT(setBattle));
+	fillSet("terrain", setTerrain, ARRAY_COUNT(setTerrain));
+	fillSet("town-theme", setTowns, ARRAY_COUNT(setTowns));
+}
 
-	terrainMusics += musicBase::dirt, musicBase::sand, musicBase::grass,
-		musicBase::snow, musicBase::swamp, musicBase::rough,
-		musicBase::underground, musicBase::lava,musicBase::water;
+void CMusicHandler::addEntryToSet(std::string set, int musicID, std::string musicURI)
+{
+	musicsSet[set][musicID] = musicURI;
 }
 
 void CMusicHandler::init()
@@ -391,23 +378,46 @@ void CMusicHandler::release()
 	CAudioBase::release();
 }
 
-// Plays a music
-// loop: -1 always repeats, 0=do not play, 1+=number of loops
-void CMusicHandler::playMusic(musicBase::musicID musicID, int loop)
+void CMusicHandler::playMusic(std::string musicURI, bool loop)
 {
-	if (current.get() != NULL && *current == musicID)
+	if (current && current->isTrack( musicURI))
 		return;
 
-	queueNext(new MusicEntry(this, musicID, loop));
+	queueNext(new MusicEntry(this, "", musicURI, loop));
 }
 
-// Helper. Randomly plays tracks from music_vec
-void CMusicHandler::playMusicFromSet(std::vector<musicBase::musicID> &music_vec, int loop)
+void CMusicHandler::playMusicFromSet(std::string whichSet, bool loop)
 {
-	if (current.get() != NULL && *current == music_vec)
+	auto selectedSet = musicsSet.find(whichSet);
+	if (selectedSet == musicsSet.end())
+	{
+		tlog0 << "Error: playing music from non-existing set: " << whichSet << "\n";
 		return;
+	}
 
-	queueNext(new MusicEntry(this, music_vec, loop));
+	if (current && current->isSet(whichSet))
+		return;
+
+	queueNext(new MusicEntry(this, whichSet, "", loop));
+}
+
+
+void CMusicHandler::playMusicFromSet(std::string whichSet, int entryID, bool loop)
+{
+	auto selectedSet = musicsSet.find(whichSet);
+	if (selectedSet == musicsSet.end())
+	{
+		tlog0 << "Error: playing music from non-existing set: " << whichSet << "\n";
+		return;
+	}
+
+	auto selectedEntry = selectedSet->second.find(entryID);
+	if (selectedEntry == selectedSet->second.end())
+	{
+		tlog0 << "Error: playing non-existing entry " << entryID << " from set: " << whichSet << "\n";
+		return;
+	}
+	queueNext(new MusicEntry(this, "", selectedEntry->second, loop));
 }
 
 void CMusicHandler::queueNext(MusicEntry *queued)
@@ -430,7 +440,6 @@ void CMusicHandler::queueNext(MusicEntry *queued)
 	}
 }
 
-// Stop and free the current music
 void CMusicHandler::stopMusic(int fade_ms)
 {
 	if (!initialized)
@@ -441,10 +450,8 @@ void CMusicHandler::stopMusic(int fade_ms)
 	if (current.get() != NULL)
 		current->stop(fade_ms);
 	next.reset();
-
 }
 
-// Sets the music volume, from 0 (mute) to 100
 void CMusicHandler::setVolume(ui32 percent)
 {
 	CAudioBase::setVolume(percent);
@@ -453,7 +460,6 @@ void CMusicHandler::setVolume(ui32 percent)
 		Mix_VolumeMusic((MIX_MAX_VOLUME * volume)/100);
 }
 
-// Called by SDL when a music finished.
 void CMusicHandler::musicFinishedCallback(void)
 {
 	boost::mutex::scoped_lock guard(musicMutex);
@@ -474,50 +480,39 @@ void CMusicHandler::musicFinishedCallback(void)
 	}
 }
 
-MusicEntry::MusicEntry(CMusicHandler *_owner, musicBase::musicID _musicID, int _loopCount):
-	owner(_owner),
-	music(NULL),
-	loopCount(_loopCount)
-{
-	load(_musicID);
-}
-
-MusicEntry::MusicEntry(CMusicHandler *_owner, std::vector<musicBase::musicID> &_musicVec, int _loopCount):
-	currentID(musicBase::music_todo),
-	owner(_owner),
-	music(NULL),
-	loopCount(_loopCount),
-	musicVec(_musicVec)
+MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped):
+	owner(owner),
+	music(nullptr),
+	looped(looped),
+    setName(setName)
 {
-	//In this case music will be loaded only on playing - no need to call load() here
+	if (!musicURI.empty())
+		load(musicURI);
 }
-
 MusicEntry::~MusicEntry()
 {
-	tlog5<<"Del-ing music file "<<filename<<"\n";
+	tlog5<<"Del-ing music file "<<currentName<<"\n";
 	if (music)
 		Mix_FreeMusic(music);
 }
 
-void MusicEntry::load(musicBase::musicID ID)
+void MusicEntry::load(std::string musicURI)
 {
 	if (music)
 	{
-		tlog5<<"Del-ing music file "<<filename<<"\n";
+		tlog5<<"Del-ing music file "<<currentName<<"\n";
 		Mix_FreeMusic(music);
 	}
 
-	currentID = ID;
-	filename = GameConstants::DATA_DIR + "/Mp3/";
-	filename += owner->musics[ID];
+	currentName = musicURI;
 
-	tlog5<<"Loading music file "<<filename<<"\n";
+	tlog5<<"Loading music file "<<musicURI<<"\n";
 
-	music = Mix_LoadMUS(filename.c_str());
+	music = Mix_LoadMUS(CResourceHandler::get()->getResourceName(ResourceID(musicURI, EResType::MUSIC)).c_str());
 
 	if(!music)
 	{
-		tlog3 << "Warning: Cannot open " << filename << ": " << Mix_GetError() << std::endl;
+		tlog3 << "Warning: Cannot open " << currentName << ": " << Mix_GetError() << std::endl;
 		return;
 	}
 
@@ -529,16 +524,19 @@ void MusicEntry::load(musicBase::musicID ID)
 
 bool MusicEntry::play()
 {
-	if (loopCount == 0)
+	if (!looped && music) //already played once - return
 		return false;
 
-	if (loopCount > 0)
-		loopCount--;
-
-	if (!musicVec.empty())
-		load(musicVec.at(rand() % musicVec.size()));
+	if (!setName.empty())
+	{
+		auto set = owner->musicsSet[setName];
+		size_t entryID = rand() % set.size();
+		auto iterator = set.begin();
+		std::advance(iterator, entryID);
+		load(iterator->second);
+	}
 
-	tlog5<<"Playing music file "<<filename<<"\n";
+	tlog5<<"Playing music file "<<currentName<<"\n";
 	if(Mix_PlayMusic(music, 1) == -1)
 	{
 		tlog1 << "Unable to play music (" << Mix_GetError() << ")" << std::endl;
@@ -549,17 +547,17 @@ bool MusicEntry::play()
 
 void MusicEntry::stop(int fade_ms)
 {
-	tlog5<<"Stoping music file "<<filename<<"\n";
-	loopCount = 0;
+	tlog5<<"Stoping music file "<<currentName<<"\n";
+	looped = false;
 	Mix_FadeOutMusic(fade_ms);
 }
 
-bool MusicEntry::operator == (musicBase::musicID _musicID) const
+bool MusicEntry::isSet(std::string set)
 {
-	return musicVec.empty() && currentID == _musicID;
+	return !setName.empty() && set == setName;
 }
 
-bool MusicEntry::operator == (std::vector<musicBase::musicID> &_musicVec) const
+bool MusicEntry::isTrack(std::string track)
 {
-	return musicVec == _musicVec;
+	return setName.empty() && track == currentName;
 }

+ 22 - 20
client/CMusicHandler.h

@@ -2,7 +2,6 @@
 
 #include "CConfigHandler.h"
 #include "CSoundBase.h"
-#include "CMusicBase.h"
 #include "../lib/CCreatureHandler.h"
 #include "CSndHandler.h"
 
@@ -113,22 +112,21 @@ 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;
+	bool looped;
+	//if not null - set from which music will be randomly selected
+	std::string setName;
+	std::string currentName;
 
-	void load(musicBase::musicID);
+
+	void load(std::string musicURI);
 
 public:
-	bool operator == (musicBase::musicID musicID) const;
-	bool operator == (std::vector<musicBase::musicID> &_musicVec) const;
+	bool isSet(std::string setName);
+	bool isTrack(std::string trackName);
 
-	MusicEntry(CMusicHandler *owner, musicBase::musicID musicID, int _loopCount);
-	MusicEntry(CMusicHandler *owner, std::vector<musicBase::musicID> &_musicVec, int _loopCount);
+	MusicEntry(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped);
 	~MusicEntry();
 
 	bool play();
@@ -149,22 +147,26 @@ private:
 	unique_ptr<MusicEntry> next;
 
 	void queueNext(MusicEntry *queued);
+
+	std::map<std::string, std::map<int, std::string> > musicsSet;
 public:
 	CMusicHandler();
 
+	/// add entry with URI musicURI in set. Track will have ID musicID
+	void addEntryToSet(std::string set, int musicID, std::string musicURI);
+
 	void init();
 	void release();
 	void setVolume(ui32 percent);
 
-	// Musics
-	std::map<musicBase::musicID, std::string> musics;
-	std::vector<musicBase::musicID> aiMusics;
-	std::vector<musicBase::musicID> battleMusics;
-	std::vector<musicBase::musicID> townMusics;
-	std::vector<musicBase::musicID> terrainMusics;
-
-	void playMusic(musicBase::musicID musicID, int loop=1);
-	void playMusicFromSet(std::vector<musicBase::musicID> &music_vec, int loop=1);
+	/// play track by URI, if loop = true music will be looped
+	void playMusic(std::string musicURI, bool loop);
+	/// play random track from this set
+	void playMusicFromSet(std::string musicSet, bool loop);
+	/// play specific track from set
+	void playMusicFromSet(std::string musicSet, int entryID, bool loop);
 	void stopMusic(int fade_ms=1000);
 	void musicFinishedCallback(void);
+
+	friend class MusicEntry;
 };

+ 1 - 1
client/CPlayerInterface.cpp

@@ -256,7 +256,7 @@ 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], -1);
+		CCS->musich->playMusicFromSet("terrain", LOCPLINT->cb->getTile(ho->visitablePos())->tertype, true);
 
 		if(details.result == TryMoveHero::TELEPORTATION)
 		{

+ 5 - 3
client/CPreGame.cpp

@@ -259,7 +259,7 @@ void CMenuScreen::show(SDL_Surface * to)
 
 void CMenuScreen::activate()
 {
-	CCS->musich->playMusic(musicBase::mainMenu, -1);
+	CCS->musich->playMusic("Music/MainMenu", true);
 	if (!config["video"].isNull())
 		CCS->videoh->open(config["video"]["name"].String());
 	CIntObject::activate();
@@ -567,13 +567,15 @@ CSelectionScreen::CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EMulti
 	else if(Type == CMenuScreen::campaignList)
 	{
 		bordered = false;
-		bg = new CPicture(BitmapHandler::loadBitmap("CamCust.bmp"), 0, 0, true);
+		bg = new CPicture("CamCust.bmp", 0, 0);
 		pos = bg->center();
 	}
 	else
 	{
 		bordered = true;
-		bg = new CPicture(BitmapHandler::loadBitmap(rand()%2 ? "ZPIC1000.bmp" : "ZPIC1001.bmp"), 0, 0, true);
+		//load random background
+		const JsonVector & bgNames = (*CGP->pregameConfig)["game-select"].Vector();
+		bg = new CPicture(bgNames[rand() % bgNames.size()].String(), 0, 0);
 		pos = bg->center();
 	}
 

+ 2 - 2
client/CPreGame.h

@@ -489,14 +489,14 @@ public:
 /// Handles background screen, loads graphics for victory/loss condition and random town or hero selection
 class CGPreGame : public CIntObject, public IUpdateable
 {
-	const JsonNode * const pregameConfig;
-
 	void loadGraphics();
 	void disposeGraphics();
 
 	CGPreGame(); //Use createIfNotPresent
 
 public:
+	const JsonNode * const pregameConfig;
+
 	CMenuScreen* menu;
 
 	SDL_Surface *nHero, *rHero, *nTown, *rTown; // none/random hero/town imgs

+ 15 - 10
client/GUIClasses.cpp

@@ -431,15 +431,27 @@ void CGarrisonSlot::clickLeft(tribool down, bool previousState)
 
 CGarrisonSlot::CGarrisonSlot(CGarrisonInt *Owner, int x, int y, int IID, int Upg, const CStackInstance * Creature)
 {
+	OBJ_CONSTRUCTION_CAPTURING_ALL;
 	addUsedEvents(LCLICK | RCLICK | HOVER);
+	pos.x += x;
+	pos.y += y;
+	owner = Owner;
+
 	//assert(Creature == CGI->creh->creatures[Creature->idNumber]);
 	upg = Upg;
 	ID = IID;
 	myStack = Creature;
 	creature = Creature ? Creature->type : NULL;
+	if (creature)
+	{
+		std::string imgName = owner->smallIcons ? "cprsmall" : "TWCRPORT";
+		creatureImage = new CAnimImage(imgName, creature->idNumber + 2);
+	}
+	else
+		creatureImage = nullptr;
+
 	count = Creature ? Creature->count : 0;
-	pos.x += x;
-	pos.y += y;
+
 	if(Owner->smallIcons)
 	{
 		pos.w = 32;
@@ -450,7 +462,6 @@ CGarrisonSlot::CGarrisonSlot(CGarrisonInt *Owner, int x, int y, int IID, int Upg
 		pos.w = 58;
 		pos.h = 64;
 	}
-	owner = Owner;
 }
 
 void CGarrisonSlot::showAll(SDL_Surface * to)
@@ -458,9 +469,9 @@ void CGarrisonSlot::showAll(SDL_Surface * to)
 	std::map<int,SDL_Surface*> &imgs = (owner->smallIcons ? graphics->smallImgs : graphics->bigImgs);
 	if(creature)
 	{
+		creatureImage->showAll(to);
 		char buf[15];
 		SDL_itoa(count,buf,10);
-		blitAt(imgs[creature->idNumber],pos,to);
 		printTo(buf, pos.x+pos.w, pos.y+pos.h+1, owner->smallIcons ? FONT_TINY : FONT_MEDIUM, Colors::Cornsilk, to);
 
 		if((owner->highlighted==this)
@@ -476,12 +487,6 @@ void CGarrisonSlot::showAll(SDL_Surface * to)
 	}
 }
 
-CGarrisonInt::~CGarrisonInt()
-{/*
-	for(size_t i = 0; i<splitButtons.size(); i++)
-		delete splitButtons[i];*/
-}
-
 void CGarrisonInt::addSplitBtn(CAdventureMapButton * button)
 {
 	addChild(button);

+ 4 - 2
client/GUIClasses.h

@@ -306,7 +306,6 @@ class CGarrisonInt;
 /// A single garrison slot which holds one creature of a specific amount
 class CGarrisonSlot : public CIntObject
 {
-public:
 	int ID; //for identification
 	CGarrisonInt *owner;
 	const CStackInstance *myStack; //NULL if slot is empty
@@ -315,6 +314,8 @@ public:
 	int upg; //0 - up garrison, 1 - down garrison
 	bool highlight;
 
+	CAnimImage * creatureImage;
+public:
 	virtual void hover (bool on); //call-in
 	const CArmedInstance * getObj();
 	bool our();
@@ -322,6 +323,8 @@ public:
 	void clickLeft(tribool down, bool previousState);
 	void showAll(SDL_Surface * to);
 	CGarrisonSlot(CGarrisonInt *Owner, int x, int y, int IID, int Upg=0, const CStackInstance * Creature=NULL);
+
+	friend class CGarrisonInt;
 };
 
 /// Class which manages slots of upper and lower garrison, splitting of units
@@ -367,7 +370,6 @@ public:
 	//smallImgs - units images size 64x58 or 32x32;
 	//twoRows - display slots in 2 row (1st row = 4 slots, 2nd = 3 slots)
 	CGarrisonInt(int x, int y, int inx, const Point &garsOffset, SDL_Surface *pomsur, const Point &SurOffset, const CArmedInstance *s1, const CArmedInstance *s2=NULL, bool _removableUnits = true, bool smallImgs = false, bool _twoRows=false); //c-tor
-	~CGarrisonInt(); //d-tor
 };
 
 /// draws picture with creature on background, use Animated=true to get animation

+ 1 - 2
client/Graphics.cpp

@@ -64,8 +64,7 @@ void Graphics::loadPaletteAndColors()
 
 	neutralColorPalette = new SDL_Color[32];
 	std::ifstream ncp;
-	std::string neutralFile = GameConstants::DATA_DIR + "/config/NEUTRAL.PAL";
-	ncp.open(neutralFile.c_str(), std::ios::binary);
+	ncp.open(CResourceHandler::get()->getResourceName(ResourceID("config/NEUTRAL.PAL")), std::ios::binary);
 	for(int i=0; i<32; ++i)
 	{
 		ncp.read((char*)&neutralColorPalette[i].r,1);

+ 0 - 1
client/UIFramework/CIntObjectClasses.cpp

@@ -1303,7 +1303,6 @@ void CBoundedLabel::recalculateLines(const std::string &Txt)
 
 	const Font &f = *graphics->fonts[font];
 	int lineHeight =  f.height; 
-	int lineCapacity = pos.h / lineHeight;
 
 	lines = CMessage::breakText(Txt, pos.w, font);
 

+ 1 - 1
client/UIFramework/SDL_Extensions.cpp

@@ -45,7 +45,7 @@ SDL_Surface * CSDL_Ext::copySurface(SDL_Surface * mod) //returns copy of given s
 template<int bpp>
 SDL_Surface * CSDL_Ext::createSurfaceWithBpp(int width, int height)
 {
-	int rMask = 0, gMask = 0, bMask = 0, aMask = 0;
+	Uint32 rMask = 0, gMask = 0, bMask = 0, aMask = 0;
 
 	Channels::px<bpp>::r.set((Uint8*)&rMask, 255);
 	Channels::px<bpp>::g.set((Uint8*)&gMask, 255);

+ 1 - 0
config/filesystem.json

@@ -26,6 +26,7 @@
 		"SOUNDS/":
 		[
 			{"type" : "file", "path" : "ALL/Data/H3ab_ahd.snd"},
+			{"type" : "file", "path" : "ALL/Data/Heroes3-cd2.snd"},
 			{"type" : "file", "path" : "ALL/Data/Heroes3.snd"},
 			//WoG have overriden sounds with .82m extension in Data
 			{"type" : "dir",  "path" : "GLOBAL/Data"},

+ 3 - 1
config/mainmenu.json

@@ -1,4 +1,6 @@
 {
+	//images used in game selection screen
+	"game-select" : ["ZPIC1000", "ZPIC1001"],
 	//Main menu window, consists of several sub-menus aka items
 	"window":
 	{
@@ -68,7 +70,7 @@
 				{ "x":90,  "y":72,  "file":"DATA/GOOD1.H3C",    "image":"CAMPGD1S", "video":"CGOOD1",   "open": true },
 				{ "x":539, "y":72,  "file":"DATA/EVIL1.H3C",    "image":"CAMPEV1S", "video":"CEVIL1",   "open": true },
 				{ "x":43,  "y":245, "file":"DATA/GOOD2.H3C",    "image":"CAMPGD2S", "video":"CGOOD2",   "open": true },
-				{ "x":313, "y":244, "file":"DATA/NEUTRAL.H3C",  "image":"CAMPNEUS", "video":"CNEUTRAL", "open": true },
+				{ "x":313, "y":244, "file":"DATA/NEUTRAL1.H3C",  "image":"CAMPNEUS", "video":"CNEUTRAL", "open": true },
 				{ "x":586, "y":246, "file":"DATA/EVIL2.H3C",    "image":"CAMPEV2S", "video":"CEVIL2",   "open": true },
 				{ "x":34,  "y":417, "file":"DATA/GOOD3.H3C",    "image":"CAMPGD3S", "video":"CGOOD3",   "open": true },
 				{ "x":404, "y":414, "file":"DATA/SECRET.H3C",   "image":"CAMPSCTS", "video":"CSECRET",  "open": true }

+ 1 - 2
lib/CGeneralTextHandler.cpp

@@ -560,8 +560,7 @@ void CGeneralTextHandler::load()
 		zcrexp.push_back(nameBuf);
 	}
 
-	std::string threatLevelDir = GameConstants::DATA_DIR + "/config/threatlevel.txt";
-	std::ifstream ifs(threatLevelDir.c_str(), std::ios::in | std::ios::binary);
+	std::ifstream ifs(CResourceHandler::get()->getResourceName(ResourceID("config/threatlevel.txt")), std::ios::binary);
 	getline(ifs, buf); //skip 1st line
 	for (int i = 0; i < 13; ++i)
 	{

+ 26 - 16
lib/Filesystem/CResourceLoader.cpp

@@ -118,6 +118,17 @@ ResourceLocator CResourceLoader::getResource(const ResourceID & resourceIdent) c
 	return resource->second.back();
 }
 
+const std::list<ResourceLocator> & CResourceLoader::getResourcesWithName(const ResourceID & resourceIdent) const
+{
+	static const std::list<ResourceLocator> emptyList;
+	auto resource = resources.find(resourceIdent);
+
+	if (resource == resources.end())
+		return emptyList;
+	return resource->second;
+}
+
+
 std::string CResourceLoader::getResourceName(const ResourceID & resourceIdent) const
 {
 	auto locator = getResource(resourceIdent);
@@ -147,6 +158,11 @@ void CResourceLoader::addLoader(std::string mountPoint, ISimpleResourceLoader *
 
 		// Create identifier and locator and add them to the resources list
 		ResourceID ident(mountPoint, file.getStem(), file.getType());
+
+		//check if entry can be directory. Will only work for filesystem loader but H3 archives don't have dirs anyway.
+		if (boost::filesystem::is_directory(loader->getOrigin() + '/' + entry))
+			ident.setType(EResType::DIRECTORY);
+
 		ResourceLocator locator(loader, entry);
 		resources[ident].push_back(locator);
 	}
@@ -247,6 +263,7 @@ std::string EResTypeHelper::getEResTypeAsString(EResType::Type type)
 		MAP_ENUM(CLIENT_SAVEGAME)
 		MAP_ENUM(LIB_SAVEGAME)
 		MAP_ENUM(SERVER_SAVEGAME)
+		MAP_ENUM(DIRECTORY)
 		MAP_ENUM(OTHER);
 
 #undef MAP_ENUM
@@ -279,17 +296,10 @@ void CResourceHandler::initialize()
 
 	//create "LOCAL" dir with current userDir (may be same as rootDir)
 	initialLoader->addLoader("LOCAL/", userDir);
-
-	//check for presence of "VCMI" mod. If found - add it to our initial FS
-	std::string filename = initialLoader->getResourceName(ResourceID("ALL/MODS/VCMI"));
-	if (!filename.empty())
-		initialLoader->addLoader("ALL/", new CFilesystemLoader(filename, 2));
 }
 
 void CResourceHandler::loadFileSystem(const std::string fsConfigURI)
 {
-	//TODO: better way to detect fs config.
-	// right now it can be: global_dir/config/, local_dir/config, global/mods/vcmi/config, local/mods/vcmi/config
 	auto fsConfigData = initialLoader->loadData(ResourceID(fsConfigURI, EResType::TEXT));
 
 	const JsonNode fsConfig((char*)fsConfigData.first.get(), fsConfigData.second);
@@ -298,26 +308,26 @@ void CResourceHandler::loadFileSystem(const std::string fsConfigURI)
 	{
 		BOOST_FOREACH(auto & entry, mountPoint.second.Vector())
 		{
-			tlog5 << "loading resource at " << entry["path"].String() << ": ";
-			std::string filename = initialLoader->getResourceName(ResourceID(entry["path"].String()));
+			tlog5 << "loading resource at " << entry["path"].String() << "\n";
 
-			if (!filename.empty())
+			if (entry["type"].String() == "dir")
 			{
-				if (entry["type"].String() == "dir")
+				std::string filename = initialLoader->getResourceName(ResourceID(entry["path"].String(), EResType::DIRECTORY));
+				if (!filename.empty())
 				{
 					int depth = 16;
 					if (!entry["depth"].isNull())
 						depth = entry["depth"].Float();
 					resourceLoader->addLoader(mountPoint.first, new CFilesystemLoader(filename, depth));
 				}
+			}
 
-				if (entry["type"].String() == "file")
+			if (entry["type"].String() == "file")
+			{
+				std::string filename = initialLoader->getResourceName(ResourceID(entry["path"].String(), EResType::ARCHIVE));
+				if (!filename.empty())
 					resourceLoader->addLoader(mountPoint.first, new CLodArchiveLoader(filename));
-
-				tlog5 << "OK\n";
 			}
-			else
-				tlog5 << "Not found\n";
 		}
 	}
 }

+ 5 - 1
lib/Filesystem/CResourceLoader.h

@@ -53,6 +53,7 @@ namespace EResType
 		CLIENT_SAVEGAME,
 		LIB_SAVEGAME,
 		SERVER_SAVEGAME,
+		DIRECTORY,
 		OTHER
 	};
 }
@@ -259,9 +260,12 @@ public:
 	 * @return resource locator for this resource or empty one if resource was not found
 	 */
 	ResourceLocator getResource(const ResourceID & resourceIdent) const;
+
+	/// returns ALL overriden resources with same name, including last one acessible via getResource
+	const std::list<ResourceLocator> & getResourcesWithName(const ResourceID & resourceIdent) const;
+
 	/// returns real name of file in filesystem. Not usable for archives
 	std::string getResourceName(const ResourceID & resourceIdent) const;
-	/// return size of file or 0 if not found
 
 	/**
 	 * Get iterator for looping all files matching filter