Browse Source

- handling of incorrect or missing configuration files (settings.json\defaultSettings.json)
- better code for recruitment window, fixes #1013
- minor tweak to adv.map classes

Ivan Savenko 13 years ago
parent
commit
770a6e077c

+ 5 - 5
client/AdventureMapClasses.cpp

@@ -247,7 +247,7 @@ void CHeroList::update(const CGHeroInstance * hero)
 	for (auto iter = list->getItems().begin(); iter != list->getItems().end(); iter++)
 	{
 		auto item = dynamic_cast<CHeroItem*>(*iter);
-		if (item && item->hero == hero)
+		if (item && item->hero == hero && vstd::contains(LOCPLINT->wanderingHeroes, hero))
 		{
 			item->update();
 			return;
@@ -817,13 +817,13 @@ void CInfoBar::showSelection()
 		auto hero = dynamic_cast<const CGHeroInstance *>(adventureInt->selection);
 		if (hero)
 		{
-			showHeroSelection(hero, false);
+			showHeroSelection(hero);
 			return;
 		}
 		auto town = dynamic_cast<const CGTownInstance *>(adventureInt->selection);
 		if (town)
 		{
-			showTownSelection(town, false);
+			showTownSelection(town);
 			return;
 		}
 	}
@@ -903,7 +903,7 @@ void CInfoBar::updateEnemyTurn(double progress)
 	redraw();
 }
 
-void CInfoBar::showHeroSelection(const CGHeroInstance * hero, bool onlyUpdate)
+void CInfoBar::showHeroSelection(const CGHeroInstance * hero)
 {
 	if (!hero)
 		return;
@@ -914,7 +914,7 @@ void CInfoBar::showHeroSelection(const CGHeroInstance * hero, bool onlyUpdate)
 	redraw();
 }
 
-void CInfoBar::showTownSelection(const CGTownInstance * town, bool onlyUpdate)
+void CInfoBar::showTownSelection(const CGTownInstance * town)
 {
 	if (!town)
 		return;

+ 5 - 5
client/AdventureMapClasses.h

@@ -277,8 +277,6 @@ class CInfoBar : public CIntObject
 
 	//removes all information about current state, deactivates timer (if any)
 	void reset(EState newState);
-	//reset to default view - selected object
-	void showSelection();
 
 	void tick();
 
@@ -301,10 +299,12 @@ public:
 	/// NOTE: currently DISABLED. Check comments in CInfoBar::CVisibleInfo::loadEnemyTurn()
 	void updateEnemyTurn(double progress);
 
+	/// reset to default view - selected object
+	void showSelection();
+
 	/// show hero\town information
-	/// if onlyUpdate set to true this call won't switch to town\hero but only update current view
-	void showHeroSelection(const CGHeroInstance * hero, bool onlyUpdate);
-	void showTownSelection(const CGTownInstance * town, bool onlyUpdate);
+	void showHeroSelection(const CGHeroInstance * hero);
+	void showTownSelection(const CGTownInstance * town);
 
 	/// for 3 seconds shows amount of town halls and players status
 	void showGameStatus();

+ 3 - 3
client/CAdvmapInterface.cpp

@@ -981,7 +981,7 @@ void CAdvMapInt::select(const CArmedInstance *sel, bool centerView /*= true*/)
 	assert(sel);
 	LOCPLINT->cb->setSelection(sel);
 	selection = sel;
-	if (LOCPLINT->battleInt == NULL)
+	if (LOCPLINT->battleInt == NULL && active & GENERAL)
 		CCS->musich->playMusic(CCS->musich->terrainMusics[LOCPLINT->cb->getTile(sel->visitablePos())->tertype], -1);
 	if(centerView)
 		centerOn(sel);
@@ -991,7 +991,7 @@ void CAdvMapInt::select(const CArmedInstance *sel, bool centerView /*= true*/)
 	{
 		auto town = dynamic_cast<const CGTownInstance*>(sel);
 
-		infoBar.showTownSelection(town, false);
+		infoBar.showTownSelection(town);
 		townList.select(town);
 		heroList.select(nullptr);
 
@@ -1002,7 +1002,7 @@ void CAdvMapInt::select(const CArmedInstance *sel, bool centerView /*= true*/)
 	{
 		auto hero = dynamic_cast<const CGHeroInstance*>(sel);
 
-		infoBar.showHeroSelection(hero, false);
+		infoBar.showHeroSelection(hero);
 		heroList.select(hero);
 		townList.select(nullptr);
 

+ 3 - 1
client/CCastleInterface.h

@@ -198,7 +198,6 @@ class CCastleInterface : public CWindowObject, public CWindowWithGarrison
 	CGStatusBar * statusbar;
 
 	CTownInfo *hall, *fort;
-	CTownList * townlist;
 
 	CAdventureMapButton *exit;
 	CAdventureMapButton *split;
@@ -206,6 +205,8 @@ class CCastleInterface : public CWindowObject, public CWindowWithGarrison
 	std::vector<CCreaInfo*> creainfo;//small icons of creatures (bottom-left corner);
 
 public:
+	CTownList * townlist;
+
 	//TODO: remove - currently used only in dialog messages
 	CDefEssential* bicons; //150x70 buildings imgs
 
@@ -214,6 +215,7 @@ public:
 	HeroSlots *heroes;
 	CCastleBuildings *builds;
 
+	//from - previously selected castle (if any)
 	CCastleInterface(const CGTownInstance * Town, const CGTownInstance * from = nullptr); //c-tor
 	~CCastleInterface();
 

+ 11 - 0
client/CMT.cpp

@@ -251,6 +251,17 @@ int main(int argc, char** argv)
 	const JsonNode& video = settings["video"];
 	const JsonNode& res = video["screenRes"];
 
+	//something is really wrong...
+	if (res["width"].Float() < 100 || res["height"].Float() < 100)
+	{
+		tlog0 << "Fatal error: failed to load settings!\n";
+		tlog0 << "Possible reasons:\n";
+		tlog0 << "\tCorrupted local configuration file at " << GVCMIDirs.UserPath << "/config/settings.json\n";
+		tlog0 << "\tMissing or corrupted global configuration file at " << GameConstants::DATA_DIR << "/config/defaultSettings.json\n";
+		tlog0 << "VCMI will now exit...\n";
+		exit(EXIT_FAILURE);
+	}
+
 	setScreenRes(res["width"].Float(), res["height"].Float(), video["bitsPerPixel"].Float(), video["fullscreen"].Bool());
 
 	tlog0 <<"\tInitializing screen: "<<pomtime.getDiff() << std::endl;

+ 28 - 6
client/CPlayerInterface.cpp

@@ -367,12 +367,36 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details)
 void CPlayerInterface::heroKilled(const CGHeroInstance* hero)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
+
+	const CArmedInstance *newSelection = nullptr;
+	if (makingTurn)
+	{
+		//find new object for selection: either hero
+		int next = adventureInt->getNextHeroIndex(vstd::find_pos(wanderingHeroes, hero));
+		if (next >= 0)
+			newSelection = wanderingHeroes[next];
+
+		//or town
+		if (!newSelection || newSelection == hero)
+		{
+			if (towns.empty())
+				newSelection = nullptr;
+			else
+				newSelection = towns.front();
+		}
+	}
+
 	wanderingHeroes -= hero;
 	if(vstd::contains(paths, hero))
 		paths.erase(hero);
 
 	adventureInt->heroList.update(hero);
+	if (makingTurn)
+		adventureInt->select(newSelection, true);
+	else
+		adventureInt->selection = nullptr;
 }
+
 void CPlayerInterface::heroCreated(const CGHeroInstance * hero)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
@@ -560,6 +584,7 @@ void CPlayerInterface::buildChanged(const CGTownInstance *town, int buildingID,
 		break;
 	}
 	adventureInt->townList.update(town);
+	castleInt->townlist->update(town);
 }
 
 void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side)
@@ -1076,8 +1101,8 @@ void CPlayerInterface::availableCreaturesChanged( const CGDwelling *town )
 	else if(GH.listInt.size() && (town->ID == 17  ||  town->ID == 20  ||  town->ID == 106)) //external dwelling
 	{
 		CRecruitmentWindow *crw = dynamic_cast<CRecruitmentWindow*>(GH.topInt());
-		if(crw)
-			crw->initCres();
+		if(crw && crw->dwelling == town)
+			crw->availableCreaturesChanged();
 	}
 }
 
@@ -2159,10 +2184,7 @@ void CPlayerInterface::tryDiggging(const CGHeroInstance *h)
 
 void CPlayerInterface::updateInfo(const CGObjectInstance * specific)
 {
-	if (specific->ID == GameConstants::TOWNI_TYPE)
-		adventureInt->infoBar.showTownSelection(dynamic_cast<const CGTownInstance *>(specific), true);
-	else
-		adventureInt->infoBar.showHeroSelection(dynamic_cast<const CGHeroInstance *>(specific), true);
+	adventureInt->infoBar.showSelection();
 }
 
 void CPlayerInterface::battleNewRoundFirst( int round )

+ 210 - 128
client/GUIClasses.cpp

@@ -1039,15 +1039,146 @@ CCreaturePic::CCreaturePic(int x, int y, const CCreature *cre, bool Big, bool An
 	anim = new CCreatureAnim(0, 0, cre->animDefName, Rect());
 	anim->clipRect(cre->doubleWide?170:150, 155, bg->pos.w, bg->pos.h);
 	anim->startPreview();
+
+	pos.w = bg->pos.w;
+	pos.h = bg->pos.h;
 }
 
-void CRecruitmentWindow::Max()
+CRecruitmentWindow::CCreatureCard::CCreatureCard(CRecruitmentWindow *window, const CCreature *crea, int totalAmount):
+    CIntObject(LCLICK | RCLICK),
+    parent(window),
+    selected(false),
+    creature(crea),
+    amount(totalAmount)
 {
-	slider->moveToMax();
+	OBJ_CONSTRUCTION_CAPTURING_ALL;
+	pic = new CCreaturePic(1,1, creature, true, true);
+	// 1 + 1 px for borders
+	pos.w = pic->pos.w + 2;
+	pos.h = pic->pos.h + 2;
+}
+
+void CRecruitmentWindow::CCreatureCard::select(bool on)
+{
+	selected = on;
+	redraw();
+}
+
+void CRecruitmentWindow::CCreatureCard::clickLeft(tribool down, bool previousState)
+{
+	if (down)
+		parent->select(this);
+}
+
+void CRecruitmentWindow::CCreatureCard::clickRight(tribool down, bool previousState)
+{
+	if (down)
+		GH.pushInt(createCreWindow(creature->idNumber, 0, 0));
+}
+
+void CRecruitmentWindow::CCreatureCard::showAll(SDL_Surface * to)
+{
+	CIntObject::showAll(to);
+	if (selected)
+		drawBorder(to, pos, int3(248, 0, 0));
+	else
+		drawBorder(to, pos, int3(232, 212, 120));
+}
+
+CRecruitmentWindow::CCostBox::CCostBox(Rect position, std::string title)
+{
+	type |= REDRAW_PARENT;
+	pos = position + pos;
+	OBJ_CONSTRUCTION_CAPTURING_ALL;
+	new CLabel(pos.w/2, 10, FONT_SMALL, CENTER, Colors::Cornsilk, title);
+}
+
+void CRecruitmentWindow::CCostBox::set(TResources res)
+{
+	//just update values
+	BOOST_FOREACH(auto & item, resources)
+	{
+		item.second.first->setTxt(boost::lexical_cast<std::string>(res[item.first]));
+	}
+}
+
+void CRecruitmentWindow::CCostBox::createItems(TResources res)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL;
+
+	BOOST_FOREACH(auto & curr, resources)
+	{
+		delete curr.second.first;
+		delete curr.second.second;
+	}
+	resources.clear();
+
+	TResources::nziterator iter(res);
+	while (iter.valid())
+	{
+		CAnimImage * image = new CAnimImage("RESOURCE", iter->resType);
+		CLabel * text = new CLabel(15, 43, FONT_SMALL, CENTER, Colors::Cornsilk, "0");
+
+		resources.insert(std::make_pair(iter->resType, std::make_pair(text, image)));
+		iter++;
+	}
+
+	if (!resources.empty())
+	{
+		int curx = pos.w / 2 - (16 * resources.size()) - (8 * (resources.size() - 1));
+		//reverse to display gold as first resource
+		BOOST_REVERSE_FOREACH(auto & res, resources)
+		{
+			res.second.first->moveBy(Point(curx, 22));
+			res.second.second->moveBy(Point(curx, 22));
+			curx += 48;
+		}
+	}
+	redraw();
 }
-void CRecruitmentWindow::Buy()
+
+void CRecruitmentWindow::select(CCreatureCard *card)
 {
-	int crid = creatures[which].ID,
+	if (card == selected)
+		return;
+
+	if (selected)
+		selected->select(false);
+
+	selected = card;
+
+	if (selected)
+		selected->select(true);
+
+	if (card)
+	{
+		si32 maxAmount = card->creature->maxAmount(LOCPLINT->cb->getResourceAmount());
+
+		vstd::amin(maxAmount, card->amount);
+
+		slider->setAmount(maxAmount);
+
+		if(slider->value)
+			slider->moveTo(0);
+		else // if slider already at 0 - emulate call to sliderMoved()
+			sliderMoved(0);
+
+		costPerTroopValue->createItems(card->creature->cost);
+		totalCostValue->createItems(card->creature->cost);
+
+		costPerTroopValue->set(card->creature->cost);
+
+		//Recruit %s
+		title->setTxt(boost::str(boost::format(CGI->generaltexth->tcommands[21]) % card->creature->namePl));
+
+		maxButton->block(maxAmount == 0);
+		slider->block(maxAmount == 0);
+	}
+}
+
+void CRecruitmentWindow::buy()
+{
+	int crid =  selected->creature->idNumber,
 		dstslot = dst-> getSlotFor(crid);
 
 	if(dstslot < 0 && !vstd::contains(CGI->arth->bigArtifacts,CGI->arth->convertMachineID(crid, true))) //no available slot
@@ -1067,175 +1198,122 @@ void CRecruitmentWindow::Buy()
 		return;
 	}
 
-	recruit(crid, slider->value);
+	onRecruit(crid, slider->value);
 	if(level >= 0)
 		close();
 	else
 		slider->moveTo(0);
 
 }
-void CRecruitmentWindow::Cancel()
-{
-	close();
-}
-void CRecruitmentWindow::sliderMoved(int to)
-{
-	buy->block(!to);
-	redraw();
-}
-void CRecruitmentWindow::clickLeft(tribool down, bool previousState)
-{
-	for(int i=0;i<creatures.size();i++)
-	{
-		Rect creaPos = Rect(creatures[i].pos) + pos;
-		if(isItIn(&creaPos, GH.current->motion.x, GH.current->motion.y))
-		{
-			which = i;
-			int newAmount = std::min(amounts[i],creatures[i].amount);
-			slider->setAmount(newAmount);
-			max->block(!newAmount);
-
-			if(slider->value > newAmount)
-				slider->moveTo(newAmount);
-			else
-				slider->moveTo(slider->value);
-			redraw();
-			break;
-		}
-	}
-}
-void CRecruitmentWindow::clickRight(tribool down, bool previousState)
-{
-	if(down)
-	{
-		int curx = 192 + 51 - (CREATURE_WIDTH*creatures.size()/2) - (SPACE_BETWEEN*(creatures.size()-1)/2);
-		for(int i=0;i<creatures.size();i++)
-		{
-			const int sCREATURE_WIDTH = CREATURE_WIDTH; // gcc -O0 workaround
-			Rect creatureRect = genRect(132, sCREATURE_WIDTH, pos.x+curx, pos.y+64);
-			if(isItIn(&creatureRect, GH.current->motion.x, GH.current->motion.y))
-			{
-				CIntObject *popup = createCreWindow(creatures[i].ID, 0, 0);
-				GH.pushInt(popup);
-				break;
-			}
-			curx += TOTAL_CREATURE_WIDTH;
-		}
-	}
-}
 
 void CRecruitmentWindow::showAll(SDL_Surface * to)
 {
 	CWindowObject::showAll(to);
+
+	// recruit\total values
 	drawBorder(to, pos.x + 172, pos.y + 222, 67, 42, int3(239,215,123));
 	drawBorder(to, pos.x + 246, pos.y + 222, 67, 42, int3(239,215,123));
+
+	//cost boxes
 	drawBorder(to, pos.x + 64,  pos.y + 222, 99, 76, int3(239,215,123));
 	drawBorder(to, pos.x + 322, pos.y + 222, 99, 76, int3(239,215,123));
+
+	//buttons borders
 	drawBorder(to, pos.x + 133, pos.y + 312, 66, 34, int3(173,142,66));
 	drawBorder(to, pos.x + 211, pos.y + 312, 66, 34, int3(173,142,66));
 	drawBorder(to, pos.x + 289, pos.y + 312, 66, 34, int3(173,142,66));
-
-	char pom[15];
-	SDL_itoa(creatures[which].amount-slider->value,pom,10); //available
-	printAtMiddleLoc(pom,205,253,FONT_SMALL,Colors::Cornsilk,to);
-	SDL_itoa(slider->value,pom,10); //recruit
-	printAtMiddleLoc(pom,279,253,FONT_SMALL,Colors::Cornsilk,to);
-	printAtMiddleLoc(CGI->generaltexth->allTexts[16] + " " + CGI->creh->creatures[creatures[which].ID]->namePl,243,32,FONT_BIG,Colors::Jasmine,to); //eg "Recruit Dragon flies"
-
-	int curx = 122-creatures[which].res.size()*24;
-	for(int i=creatures[which].res.size()-1; i>=0; i--)// decrement used to make gold displayed as first res
-	{
-		blitAtLoc(graphics->resources32->ourImages[creatures[which].res[i].first].bitmap,curx,243,to);
-		blitAtLoc(graphics->resources32->ourImages[creatures[which].res[i].first].bitmap,curx+258,243,to);
-		SDL_itoa(creatures[which].res[i].second,pom,10);
-		printAtMiddleLoc(pom,curx+15,287,FONT_SMALL,Colors::Cornsilk,to);
-		SDL_itoa(creatures[which].res[i].second * slider->value,pom,10);
-		printAtMiddleLoc(pom,curx+15+258,287,FONT_SMALL,Colors::Cornsilk,to);
-		curx+=32+16;//size of bitmap + distance between them
-	}
-
-	for(int j=0;j<creatures.size();j++)
-	{
-		if(which==j)
-			drawBorderLoc(to,creatures[j].pos,int3(255,0,0));
-		else
-			drawBorderLoc(to,creatures[j].pos,int3(239,215,123));
-	}
 }
 
 CRecruitmentWindow::CRecruitmentWindow(const CGDwelling *Dwelling, int Level, const CArmedInstance *Dst, const boost::function<void(int,int)> &Recruit, int y_offset):
     CWindowObject(PLAYER_COLORED, "TPRCRT"),
-	recruit(Recruit),
-    dwelling(Dwelling),
+	onRecruit(Recruit),
     level(Level),
-    dst(Dst)
+    dst(Dst),
+    selected(nullptr),
+    dwelling(Dwelling)
 {
-	addUsedEvents(LCLICK | RCLICK);
+	moveBy(Point(0, y_offset));
+
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
+	new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26));
 
-	which = 0;
-	bar = new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26));
-	max = new CAdventureMapButton(CGI->generaltexth->zelp[553],boost::bind(&CRecruitmentWindow::Max,this),134,313,"IRCBTNS.DEF",SDLK_m);
-	buy = new CAdventureMapButton(CGI->generaltexth->zelp[554],boost::bind(&CRecruitmentWindow::Buy,this),212,313,"IBY6432.DEF",SDLK_RETURN);
-	cancel = new CAdventureMapButton(CGI->generaltexth->zelp[555],boost::bind(&CRecruitmentWindow::Cancel,this),290,313,"ICN6432.DEF",SDLK_ESCAPE);
 	slider = new CSlider(176,279,135,0,0,0,0,true);
 	slider->moved = boost::bind(&CRecruitmentWindow::sliderMoved,this, _1);
 
-	initCres();
+	maxButton = new CAdventureMapButton(CGI->generaltexth->zelp[553],boost::bind(&CSlider::moveToMax,slider),134,313,"IRCBTNS.DEF",SDLK_m);
+	buyButton = new CAdventureMapButton(CGI->generaltexth->zelp[554],boost::bind(&CRecruitmentWindow::buy,this),212,313,"IBY6432.DEF",SDLK_RETURN);
+	cancelButton = new CAdventureMapButton(CGI->generaltexth->zelp[555],boost::bind(&CRecruitmentWindow::close,this),290,313,"ICN6432.DEF",SDLK_ESCAPE);
 
-	new CLabel(113, 232, FONT_SMALL, CENTER, Colors::Cornsilk, CGI->generaltexth->allTexts[346]); //cost per troop t
-	new CLabel(205, 233, FONT_SMALL, CENTER, Colors::Cornsilk, CGI->generaltexth->allTexts[465]); //available t
-	new CLabel(279, 233, FONT_SMALL, CENTER, Colors::Cornsilk, CGI->generaltexth->allTexts[16]); //recruit t
-	new CLabel(371, 232, FONT_SMALL, CENTER, Colors::Cornsilk, CGI->generaltexth->allTexts[466]); //total cost t
+	title = new CLabel(243, 32, FONT_BIG, CENTER, Colors::Jasmine);
+	availableValue = new CLabel(205, 253, FONT_SMALL, CENTER, Colors::Cornsilk);
+	toRecruitValue = new CLabel(279, 253, FONT_SMALL, CENTER, Colors::Cornsilk);
 
-	//border for creatures
-	int curx = 192 + 50 - (CREATURE_WIDTH*creatures.size()/2) - (SPACE_BETWEEN*(creatures.size()-1)/2);
-	for(int i=0;i<creatures.size();i++)
-	{
-		creatures[i].pos.x = curx-1;
-		creatures[i].pos.y = 65 - 1;
-		creatures[i].pos.w = 100 + 2;
-		creatures[i].pos.h = 130 + 2;
+	costPerTroopValue =  new CCostBox(Rect(65, 222, 97, 74), CGI->generaltexth->allTexts[346]);
+	totalCostValue = new CCostBox(Rect(323, 222, 97, 74), CGI->generaltexth->allTexts[466]);
 
-		creatures[i].pic = new CCreaturePic(curx, 65, CGI->creh->creatures[creatures[i].ID]);
-		curx += TOTAL_CREATURE_WIDTH;
-	}
+	new CLabel(205, 233, FONT_SMALL, CENTER, Colors::Cornsilk, CGI->generaltexth->allTexts[465]); //available t
+	new CLabel(279, 233, FONT_SMALL, CENTER, Colors::Cornsilk, CGI->generaltexth->allTexts[16]); //recruit t
 
-	if(!creatures[0].amount ||  !amounts[0])
-	{
-		max->block(true);
-		slider->block(true);
-	}
-	buy->block(true);
+	availableCreaturesChanged();
 }
 
-void CRecruitmentWindow::initCres()
+void CRecruitmentWindow::availableCreaturesChanged()
 {
-	creatures.clear();
-	amounts.clear();
+	OBJ_CONSTRUCTION_CAPTURING_ALL;
+
+	//deselect card
+	select(nullptr);
+
+	static const int SPACE_BETWEEN = 18;
+	static const int CREATURE_WIDTH = 102;
+	static const int TOTAL_CREATURE_WIDTH = SPACE_BETWEEN + CREATURE_WIDTH;
+
+	//delete old cards
+	BOOST_FOREACH(auto & card, cards)
+		delete card;
+	cards.clear();
 
 	for(int i=0; i<dwelling->creatures.size(); i++)
 	{
+		//find appropriate level
 		if(level >= 0 && i != level)
 			continue;
 
-		for(int j = dwelling->creatures[i].second.size() - 1; j >= 0 ; j--)
-		{
-			creatures.resize(creatures.size()+1);
-			creinfo &cur = creatures.back();
+		int amount = dwelling->creatures[i].first;
 
-			cur.amount = dwelling->creatures[i].first;
-			cur.ID = dwelling->creatures[i].second[j];
-			const CCreature * cre= CGI->creh->creatures[cur.ID];
+		//create new cards
+		BOOST_REVERSE_FOREACH(auto & creature, dwelling->creatures[i].second)
+			cards.push_back(new CCreatureCard(this, CGI->creh->creatures[creature], amount));
+	}
 
-			for(int k=0; k<cre->cost.size(); k++)
-				if(cre->cost[k])
-					cur.res.push_back(std::make_pair(k,cre->cost[k]));
-			amounts.push_back(cre->maxAmount(LOCPLINT->cb->getResourceAmount()));
-		}
+	assert(!cards.empty());
+
+	//now we know total amount of cards and can move them to correct position
+	int curx = 192 + 50 - (CREATURE_WIDTH*cards.size()/2) - (SPACE_BETWEEN*(cards.size()-1)/2);
+	BOOST_FOREACH(auto & card, cards)
+	{
+		card->moveBy(Point(curx, 64));
+		curx += TOTAL_CREATURE_WIDTH;
 	}
 
-	slider->setAmount(std::min(amounts[which],creatures[which].amount));
+	select(cards.front());
+
+	if(slider->value)
+		slider->moveTo(0);
+	else // if slider already at 0 - emulate call to sliderMoved()
+		sliderMoved(0);
+}
+
+void CRecruitmentWindow::sliderMoved(int to)
+{
+	if (!selected)
+		return;
+
+	buyButton->block(!to);
+	availableValue->setTxt(boost::lexical_cast<std::string>(selected->amount - to));
+	toRecruitValue->setTxt(boost::lexical_cast<std::string>(to));
+
+	totalCostValue->set(selected->creature->cost * to);
 }
 
 CSplitWindow::CSplitWindow(const CCreature * creature, boost::function<void(int, int)> callback_,
@@ -2202,6 +2280,7 @@ CMarketplaceWindow::CMarketplaceWindow(const IMarket *Market, const CGHeroInstan
 	case EMarketMode::RESOURCE_PLAYER:
 	case EMarketMode::RESOURCE_ARTIFACT:
 		new CLabel(154, 148, FONT_SMALL, CENTER, Colors::Cornsilk, CGI->generaltexth->allTexts[270]);
+		break;
 
 	case EMarketMode::CREATURE_RESOURCE:
 		//%s's Creatures
@@ -3138,6 +3217,9 @@ void CSystemOptionsWindow::setGameRes(int index)
 	while (index--)
 		iter++;
 
+	//do not set resolution to illegal one (0x0)
+	assert(iter!=conf.guiOptions.end() && iter->first.first > 0 && iter->first.second > 0);
+
 	Settings gameRes = settings.write["video"]["screenRes"];
 	gameRes["width"].Float() = iter->first.first;
 	gameRes["height"].Float() = iter->first.second;

+ 47 - 24
client/GUIClasses.h

@@ -333,39 +333,62 @@ public:
 /// Recruitment window where you can recruit creatures
 class CRecruitmentWindow : public CWindowObject
 {
-public:
-	static const int SPACE_BETWEEN = 18;
-	static const int CREATURE_WIDTH = 102;
-	static const int TOTAL_CREATURE_WIDTH = SPACE_BETWEEN + CREATURE_WIDTH;
-
-	struct creinfo
+	class CCreatureCard : public CIntObject
 	{
-		SDL_Rect pos;
+		CRecruitmentWindow * parent;
 		CCreaturePic *pic; //creature's animation
-		int ID, amount; //creature ID and available amount
-		std::vector<std::pair<int,int> > res; //res_id - cost_per_unit
+		bool selected;
+
+		void clickLeft(tribool down, bool previousState);
+		void clickRight(tribool down, bool previousState);
+		void showAll(SDL_Surface *to);
+	public:
+		const CCreature * creature;
+		si32 amount;
+
+		void select(bool on);
+
+		CCreatureCard(CRecruitmentWindow * window, const CCreature *crea, int totalAmount);
 	};
-	std::vector<int> amounts; //how many creatures we can afford
-	std::vector<creinfo> creatures; //recruitable creatures
-	boost::function<void(int,int)> recruit; //void (int ID, int amount) <-- call to recruit creatures
-	CSlider *slider; //for selecting amount
-	CAdventureMapButton *max, *buy, *cancel;
-	CGStatusBar *bar;
-	int which; //which creature is active
 
-	const CGDwelling *dwelling;
+	/// small class to display creature costs
+	class CCostBox : public CIntObject
+	{
+		std::map<int, std::pair<CLabel *, CAnimImage * > > resources;
+	public:
+		//res - resources to show
+		void set(TResources res);
+		//res - visible resources
+		CCostBox(Rect position, std::string title);
+		void createItems(TResources res);
+	};
+
+	boost::function<void(int,int)> onRecruit; //void (int ID, int amount) <-- call to recruit creatures
+
 	int level;
 	const CArmedInstance *dst;
 
-	void Max();
-	void Buy();
-	void Cancel();
+	CCreatureCard * selected;
+	std::vector<CCreatureCard *> cards;
+
+	CSlider *slider; //for selecting amount
+	CAdventureMapButton *maxButton, *buyButton, *cancelButton;
+	//labels for visible values
+	CLabel * title;
+	CLabel * availableValue;
+	CLabel * toRecruitValue;
+	CCostBox * costPerTroopValue;
+	CCostBox * totalCostValue;
+
+	void select(CCreatureCard * card);
+	void buy();
 	void sliderMoved(int to);
-	void clickLeft(tribool down, bool previousState);
-	void clickRight(tribool down, bool previousState);
-	void showAll(SDL_Surface * to);
-	void initCres();
+
+	void showAll(SDL_Surface *to);
+public:
+	const CGDwelling * const dwelling;
 	CRecruitmentWindow(const CGDwelling *Dwelling, int Level, const CArmedInstance *Dst, const boost::function<void(int,int)> & Recruit, int y_offset = 0); //creatures - pairs<creature_ID,amount> //c-tor
+	void availableCreaturesChanged();
 };
 
 /// Split window where creatures can be splitted up into two single unit stacks

+ 12 - 7
client/UIFramework/CIntObjectClasses.cpp

@@ -751,17 +751,22 @@ CSlider::CSlider(int x, int y, int totalw, boost::function<void(int)> Moved, int
 	if(style == 0)
 	{
 		std::string name = horizontal?"IGPCRDIV.DEF":"OVBUTN2.DEF";
-		CAnimation *animLeft = new CAnimation(name);
+		//NOTE: this images do not have "blocked" frames. They should be implemented somehow (e.g. palette transform or something...)
+
+		//use source def to create custom animations. Format "name.def:123" will load this frame from def file
+		CAnimation *animLeft = new CAnimation();
+		animLeft->setCustom(name + ":0", 0);
+		animLeft->setCustom(name + ":1", 1);
 		left->setImage(animLeft);
-		left->setOffset(0);
 
-		CAnimation *animRight = new CAnimation(name);
+		CAnimation *animRight = new CAnimation();
+		animRight->setCustom(name + ":2", 0);
+		animRight->setCustom(name + ":3", 1);
 		right->setImage(animRight);
-		right->setOffset(2);
 
-		CAnimation *animSlider = new CAnimation(name);
+		CAnimation *animSlider = new CAnimation();
+		animSlider->setCustom(name + ":4", 0);
 		slider->setImage(animSlider);
-		slider->setOffset(4);
 	}
 	else
 	{
@@ -1512,7 +1517,7 @@ bool CTextInput::captureThisEvent(const SDL_KeyboardEvent & key)
 
 void CTextInput::filenameFilter(std::string & text, const std::string &)
 {
-	static const std::string forbiddenChars = "<>:\"/\\|?*"; //if we are entering a filename, some special characters won't be allowed
+	static const std::string forbiddenChars = "<>:\"/\\|?*\r\n"; //if we are entering a filename, some special characters won't be allowed
 	size_t pos;
 	while ((pos = text.find_first_of(forbiddenChars)) != std::string::npos)
 		text.erase(pos, 1);

+ 1 - 1
lib/IGameCallback.cpp

@@ -995,7 +995,7 @@ int CGameInfoCallback::canBuildStructure( const CGTownInstance *t, int ID )
 	}
 	else if(ID == 6) //shipyard
 	{
-		const TerrainTile *tile = getTile(t->bestLocation());
+		const TerrainTile *tile = getTile(t->bestLocation(), false);
 		
 		if(!tile || tile->tertype != TerrainTile::water )
 			ret = EBuildingState::NO_WATER; //lack of water