Преглед изворни кода

- Fixed mantis #1422 (starting another scenario crashes VCMI for now)
- Refactoring

beegee1 пре 12 година
родитељ
комит
b9b25ef552

+ 9 - 1
client/CAdvmapInterface.cpp

@@ -1536,5 +1536,13 @@ CAdventureOptions::CAdventureOptions():
 
 void CAdventureOptions::showScenarioInfo()
 {
-	GH.pushInt(new CScenarioInfo(LOCPLINT->cb->getMapHeader(), LOCPLINT->cb->getStartInfo()));
+	auto campState = LOCPLINT->cb->getStartInfo()->campState;
+	if(campState)
+	{
+		GH.pushInt(new CBonusSelection(campState));
+	}
+	else
+	{
+		GH.pushInt(new CScenarioInfo(LOCPLINT->cb->getMapHeader(), LOCPLINT->cb->getStartInfo()));
+	}
 }

+ 7 - 0
client/CMT.cpp

@@ -886,6 +886,13 @@ static void listenForEvents()
 					startGame(&si);
 				}
 				break;
+			case PREPARE_RESTART_CAMPAIGN:
+				{
+					auto si = reinterpret_cast<StartInfo *>(ev.user.data1);
+					endGame();
+					startGame(si);
+				}
+				break;
 			case RETURN_TO_MENU_LOAD:
 				endGame();
 				CGPreGame::create();

+ 2 - 1
client/CPlayerInterface.h

@@ -78,7 +78,8 @@ enum
 	STOP_CLIENT = 3,
 	RESTART_GAME,
 	RETURN_TO_MENU_LOAD,
-	FULLSCREEN_TOGGLED
+	FULLSCREEN_TOGGLED,
+	PREPARE_RESTART_CAMPAIGN
 };
 
 /// Central class for managing user interface logic

+ 224 - 188
client/CPreGame.cpp

@@ -3119,6 +3119,13 @@ void CBonusSelection::init()
 	diffLb = nullptr;
 	diffRb = nullptr;
 	bonuses = nullptr;
+	selectedMap = 0;
+
+	// Initialize start info
+	startInfo.mapname = ourCampaign->camp->header.filename;
+	startInfo.mode = StartInfo::CAMPAIGN;
+	startInfo.campState = ourCampaign;
+	startInfo.turnTime = 0;
 
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
 	static const std::string bgNames [] = {"E1_BG.BMP", "G2_BG.BMP", "E2_BG.BMP", "G1_BG.BMP", "G3_BG.BMP", "N1_BG.BMP",
@@ -3137,6 +3144,7 @@ void CBonusSelection::init()
 	blitAt(panel, 456, 6, background);
 
 	startB = new CAdventureMapButton("", "", boost::bind(&CBonusSelection::startMap, this), 475, 536, "CBBEGIB.DEF", SDLK_RETURN);
+	restartB = new CAdventureMapButton("", "", boost::bind(&CBonusSelection::restartMap, this), 475, 536, "CBRESTB.DEF", SDLK_RETURN);
 	backB = new CAdventureMapButton("", "", boost::bind(&CBonusSelection::goBack, this), 624, 536, "CBCANCB.DEF", SDLK_ESCAPE);
 
 	//campaign name
@@ -3151,11 +3159,11 @@ void CBonusSelection::init()
 	//campaign description
 	graphics->fonts[FONT_SMALL]->renderTextLeft(background, CGI->generaltexth->allTexts[38], Colors::YELLOW, Point(481, 63));
 
-	cmpgDesc = new CTextBox(ourCampaign->camp->header.description, Rect(480, 86, 286, 117), 1);
-	//cmpgDesc->showAll(background);
+	campaignDescription = new CTextBox(ourCampaign->camp->header.description, Rect(480, 86, 286, 117), 1);
+	//campaignDescription->showAll(background);
 
 	//map description
-	mapDesc = new CTextBox("", Rect(480, 280, 286, 117), 1);
+	mapDescription = new CTextBox("", Rect(480, 280, 286, 117), 1);
 
 	//bonus choosing
 	graphics->fonts[FONT_MEDIUM]->renderTextLeft(background, CGI->generaltexth->allTexts[71], Colors::WHITE, Point(511, 432));
@@ -3181,15 +3189,6 @@ void CBonusSelection::init()
 		}
 	}
 
-	//unlock if no bonuses -- it's acceptable
-
-
-// 	//init campaign state if necessary
-// 	if (ourCampaign->campaignName.size() == 0)
-// 	{
-// 		ourCampaign->initNewCampaign(sInfo);
-// 	}
-
 	//allies / enemies
 	graphics->fonts[FONT_SMALL]->renderTextLeft(background, CGI->generaltexth->allTexts[390] + ":", Colors::WHITE, Point(486, 407));
 	graphics->fonts[FONT_SMALL]->renderTextLeft(background, CGI->generaltexth->allTexts[391] + ":", Colors::WHITE, Point(619, 407));
@@ -3215,26 +3214,22 @@ void CBonusSelection::init()
 	//difficulty selection buttons
 	if (ourCampaign->camp->header.difficultyChoosenByPlayer)
 	{
-		diffLb = new CAdventureMapButton("", "", boost::bind(&CBonusSelection::changeDiff, this, false), 694, 508, "SCNRBLF.DEF");
-		diffRb = new CAdventureMapButton("", "", boost::bind(&CBonusSelection::changeDiff, this, true), 738, 508, "SCNRBRT.DEF");
+		diffLb = new CAdventureMapButton("", "", boost::bind(&CBonusSelection::decreaseDifficulty, this), 694, 508, "SCNRBLF.DEF");
+		diffRb = new CAdventureMapButton("", "", boost::bind(&CBonusSelection::increaseDifficulty, this), 738, 508, "SCNRBRT.DEF");
 	}
 
 	//load miniflags
 	sFlags = CDefHandler::giveDef("ITGFLAGS.DEF");
 }
 
-
-CBonusSelection::CBonusSelection(shared_ptr<CCampaignState> _ourCampaign)
-	: ourCampaign(std::move(_ourCampaign))
+CBonusSelection::CBonusSelection(shared_ptr<CCampaignState> _ourCampaign) : ourCampaign(_ourCampaign)
 {
 	init();
 }
 
-CBonusSelection::CBonusSelection( std::string campaignFName )
+CBonusSelection::CBonusSelection(const std::string & campaignFName)
 {
 	ourCampaign = make_shared<CCampaignState>(CCampaignHandler::getCampaign(campaignFName));
-
-	sInfo.campState = ourCampaign;
 	init();
 }
 
@@ -3295,31 +3290,43 @@ void CBonusSelection::loadPositionsOfGraphics()
 	assert(idx == CGI->generaltexth->campaignMapNames.size());
 }
 
-void CBonusSelection::selectMap( int whichOne, bool initialSelect )
+void CBonusSelection::selectMap(int mapNr, bool initialSelect)
 {
-	if(initialSelect || ourCampaign->currentMap != whichOne)
+	if(initialSelect || selectedMap != mapNr)
 	{
-		sInfo.difficulty = ourCampaign->camp->scenarios[whichOne].difficulty;
-		sInfo.mapname = ourCampaign->camp->header.filename;
-		sInfo.mode = StartInfo::CAMPAIGN;
-		sInfo.campState = ourCampaign;
-		ourCampaign->currentMap = whichOne;
+		// initialize restart / start button
+		if(!ourCampaign->currentMap || *ourCampaign->currentMap != mapNr)
+		{
+			// draw start button
+			restartB->disable();
+			startB->enable();
+		}
+		else
+		{
+			// draw restart button
+			startB->disable();
+			restartB->enable();
+		}
+
+		startInfo.difficulty = ourCampaign->camp->scenarios[mapNr].difficulty;
+		selectedMap = mapNr;
+		selectedBonus = boost::none;
 
 		//get header
 		delete ourHeader;
-		std::string & headerStr = ourCampaign->camp->mapPieces.find(whichOne)->second;
+		std::string & headerStr = ourCampaign->camp->mapPieces.find(mapNr)->second;
 		auto buffer = reinterpret_cast<const ui8 *>(headerStr.data());
 		ourHeader = CMapService::loadMapHeader(buffer, headerStr.size()).release();
 
 		std::map<ui8, std::string> names;
 		names[1] = settings["general"]["playerName"].String();
-		updateStartInfo(ourCampaign->camp->header.filename, sInfo, ourHeader, names);
-		sInfo.turnTime = 0;
-		sInfo.difficulty = ourCampaign->camp->scenarios[whichOne].difficulty;
+		updateStartInfo(ourCampaign->camp->header.filename, startInfo, ourHeader, names);
 
-		mapDesc->setText(ourHeader->description);
+		mapDescription->setText(ourHeader->description);
 
 		updateBonusSelection();
+
+		GH.totalRedraw();
 	}
 }
 
@@ -3338,7 +3345,7 @@ void CBonusSelection::show(SDL_Surface * to)
 	//map description
 	printAtLoc(CGI->generaltexth->allTexts[496], 481, 253, FONT_SMALL, Colors::YELLOW, to);
 
-	mapDesc->showAll(to); //showAll because CTextBox has no show()
+	mapDescription->showAll(to); //showAll because CTextBox has no show()
 
 	//map size icon
 	int temp;
@@ -3367,7 +3374,7 @@ void CBonusSelection::show(SDL_Surface * to)
 	int ex = 629 + graphics->fonts[FONT_SMALL]->getStringWidth(CGI->generaltexth->allTexts[391]);
 	TeamID myT;
 	myT = ourHeader->players[playerColor.getNum()].team;
-	for (auto i = sInfo.playerInfos.cbegin(); i != sInfo.playerInfos.cend(); i++)
+	for (auto i = startInfo.playerInfos.cbegin(); i != startInfo.playerInfos.cend(); i++)
 	{
 		int *myx = ((i->first == playerColor  ||  ourHeader->players[i->first.getNum()].team == myT) ? &fx : &ex);
 		blitAtLoc(sFlags->ourImages[i->first.getNum()].bitmap, *myx, 405, to);
@@ -3375,7 +3382,7 @@ void CBonusSelection::show(SDL_Surface * to)
 	}
 
 	//difficulty
-	blitAtLoc(diffPics[sInfo.difficulty], 709, 455, to);
+	blitAtLoc(diffPics[startInfo.difficulty], 709, 455, to);
 
 	CIntObject::show(to);
 }
@@ -3394,7 +3401,7 @@ void CBonusSelection::updateBonusSelection()
 	//resource - BORES.DEF
 	//player - CREST58.DEF
 	//hero - PORTRAITSLARGE (HPL###.BMPs)
-	const CCampaignScenario &scenario = ourCampaign->camp->scenarios[sInfo.campState->currentMap];
+	const CCampaignScenario &scenario = ourCampaign->camp->scenarios[selectedMap];
 	const std::vector<CScenarioTravel::STravelBonus> & bonDescs = scenario.travelOptions.bonusesToChoose;
 
 	updateStartButtonState(-1);
@@ -3407,180 +3414,192 @@ void CBonusSelection::updateBonusSelection()
 	}
 	bonuses->buttons.clear();
 
-		static const char *bonusPics[] = {"SPELLBON.DEF", "TWCRPORT.DEF", "", "ARTIFBON.DEF", "SPELLBON.DEF",
-			"PSKILBON.DEF", "SSKILBON.DEF", "BORES.DEF", "PORTRAITSLARGE", "PORTRAITSLARGE"};
+	static const char *bonusPics[] = {"SPELLBON.DEF", "TWCRPORT.DEF", "", "ARTIFBON.DEF", "SPELLBON.DEF",
+		"PSKILBON.DEF", "SSKILBON.DEF", "BORES.DEF", "PORTRAITSLARGE", "PORTRAITSLARGE"};
 
-		for(int i = 0; i < bonDescs.size(); i++)
-		{
-			std::string picName=bonusPics[bonDescs[i].type];
-			size_t picNumber=bonDescs[i].info2;
+	for(int i = 0; i < bonDescs.size(); i++)
+	{
+		std::string picName=bonusPics[bonDescs[i].type];
+		size_t picNumber=bonDescs[i].info2;
 
-			std::string desc;
-			switch(bonDescs[i].type)
+		std::string desc;
+		switch(bonDescs[i].type)
+		{
+		case CScenarioTravel::STravelBonus::SPELL:
+			desc = CGI->generaltexth->allTexts[715];
+			boost::algorithm::replace_first(desc, "%s", CGI->spellh->spells[bonDescs[i].info2]->name);
+			break;
+		case CScenarioTravel::STravelBonus::MONSTER:
+			picNumber = bonDescs[i].info2 + 2;
+			desc = CGI->generaltexth->allTexts[717];
+			boost::algorithm::replace_first(desc, "%d", boost::lexical_cast<std::string>(bonDescs[i].info3));
+			boost::algorithm::replace_first(desc, "%s", CGI->creh->creatures[bonDescs[i].info2]->namePl);
+			break;
+		case CScenarioTravel::STravelBonus::BUILDING:
 			{
-			case CScenarioTravel::STravelBonus::SPELL:
-				desc = CGI->generaltexth->allTexts[715];
-				boost::algorithm::replace_first(desc, "%s", CGI->spellh->spells[bonDescs[i].info2]->name);
-				break;
-			case CScenarioTravel::STravelBonus::MONSTER:
-				picNumber = bonDescs[i].info2 + 2;
-				desc = CGI->generaltexth->allTexts[717];
-				boost::algorithm::replace_first(desc, "%d", boost::lexical_cast<std::string>(bonDescs[i].info3));
-				boost::algorithm::replace_first(desc, "%s", CGI->creh->creatures[bonDescs[i].info2]->namePl);
-				break;
-			case CScenarioTravel::STravelBonus::BUILDING:
+				int faction = -1;
+				for(auto & elem : startInfo.playerInfos)
 				{
-					int faction = -1;
-					for(auto & elem : sInfo.playerInfos)
+					if (elem.second.playerID)
 					{
-						if (elem.second.playerID)
-						{
-							faction = elem.second.castle;
-							break;
-						}
-
+						faction = elem.second.castle;
+						break;
 					}
-					assert(faction != -1);
-
-					BuildingID buildID = CBuildingHandler::campToERMU(bonDescs[i].info1, faction, std::set<BuildingID>());
-					picName = graphics->ERMUtoPicture[faction][buildID];
-					picNumber = -1;
 
-					if (vstd::contains(CGI->townh->factions[faction]->town->buildings, buildID))
-						desc = CGI->townh->factions[faction]->town->buildings.find(buildID)->second->Name();
 				}
-				break;
-			case CScenarioTravel::STravelBonus::ARTIFACT:
-				desc = CGI->generaltexth->allTexts[715];
-				boost::algorithm::replace_first(desc, "%s", CGI->arth->artifacts[bonDescs[i].info2]->Name());
-				break;
-			case CScenarioTravel::STravelBonus::SPELL_SCROLL:
-				desc = CGI->generaltexth->allTexts[716];
-				boost::algorithm::replace_first(desc, "%s", CGI->spellh->spells[bonDescs[i].info2]->name);
-				break;
-			case CScenarioTravel::STravelBonus::PRIMARY_SKILL:
+				assert(faction != -1);
+
+				BuildingID buildID = CBuildingHandler::campToERMU(bonDescs[i].info1, faction, std::set<BuildingID>());
+				picName = graphics->ERMUtoPicture[faction][buildID];
+				picNumber = -1;
+
+				if (vstd::contains(CGI->townh->factions[faction]->town->buildings, buildID))
+					desc = CGI->townh->factions[faction]->town->buildings.find(buildID)->second->Name();
+			}
+			break;
+		case CScenarioTravel::STravelBonus::ARTIFACT:
+			desc = CGI->generaltexth->allTexts[715];
+			boost::algorithm::replace_first(desc, "%s", CGI->arth->artifacts[bonDescs[i].info2]->Name());
+			break;
+		case CScenarioTravel::STravelBonus::SPELL_SCROLL:
+			desc = CGI->generaltexth->allTexts[716];
+			boost::algorithm::replace_first(desc, "%s", CGI->spellh->spells[bonDescs[i].info2]->name);
+			break;
+		case CScenarioTravel::STravelBonus::PRIMARY_SKILL:
+			{
+				int leadingSkill = -1;
+				std::vector<std::pair<int, int> > toPrint; //primary skills to be listed <num, val>
+				const ui8* ptr = reinterpret_cast<const ui8*>(&bonDescs[i].info2);
+				for (int g=0; g<GameConstants::PRIMARY_SKILLS; ++g)
 				{
-					int leadingSkill = -1;
-					std::vector<std::pair<int, int> > toPrint; //primary skills to be listed <num, val>
-					const ui8* ptr = reinterpret_cast<const ui8*>(&bonDescs[i].info2);
-					for (int g=0; g<GameConstants::PRIMARY_SKILLS; ++g)
+					if (leadingSkill == -1 || ptr[g] > ptr[leadingSkill])
 					{
-						if (leadingSkill == -1 || ptr[g] > ptr[leadingSkill])
-						{
-							leadingSkill = g;
-						}
-						if (ptr[g] != 0)
-						{
-							toPrint.push_back(std::make_pair(g, ptr[g]));
-						}
+						leadingSkill = g;
 					}
-					picNumber = leadingSkill;
-					desc = CGI->generaltexth->allTexts[715];
-
-					std::string substitute; //text to be printed instead of %s
-					for (int v=0; v<toPrint.size(); ++v)
+					if (ptr[g] != 0)
 					{
-						substitute += boost::lexical_cast<std::string>(toPrint[v].second);
-						substitute += " " + CGI->generaltexth->primarySkillNames[toPrint[v].first];
-						if(v != toPrint.size() - 1)
-						{
-							substitute += ", ";
-						}
+						toPrint.push_back(std::make_pair(g, ptr[g]));
 					}
-
-					boost::algorithm::replace_first(desc, "%s", substitute);
-					break;
 				}
-			case CScenarioTravel::STravelBonus::SECONDARY_SKILL:
-				desc = CGI->generaltexth->allTexts[718];
-
-				boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->levels[bonDescs[i].info3 - 1]); //skill level
-				boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->skillName[bonDescs[i].info2]); //skill name
-				picNumber = bonDescs[i].info2 * 3 + bonDescs[i].info3 - 1;
+				picNumber = leadingSkill;
+				desc = CGI->generaltexth->allTexts[715];
 
-				break;
-			case CScenarioTravel::STravelBonus::RESOURCE:
+				std::string substitute; //text to be printed instead of %s
+				for (int v=0; v<toPrint.size(); ++v)
 				{
-					int serialResID = 0;
-					switch(bonDescs[i].info1)
+					substitute += boost::lexical_cast<std::string>(toPrint[v].second);
+					substitute += " " + CGI->generaltexth->primarySkillNames[toPrint[v].first];
+					if(v != toPrint.size() - 1)
 					{
-					case 0: case 1: case 2: case 3: case 4: case 5: case 6:
-						serialResID = bonDescs[i].info1;
-						break;
-					case 0xFD: //wood + ore
-						serialResID = 7;
-						break;
-					case 0xFE: //rare resources
-						serialResID = 8;
-						break;
+						substitute += ", ";
 					}
-					picNumber = serialResID;
-
-					desc = CGI->generaltexth->allTexts[717];
-					boost::algorithm::replace_first(desc, "%d", boost::lexical_cast<std::string>(bonDescs[i].info2));
-					std::string replacement;
-					if (serialResID <= 6)
-					{
-						replacement = CGI->generaltexth->restypes[serialResID];
-					}
-					else
-					{
-						replacement = CGI->generaltexth->allTexts[714 + serialResID];
-					}
-					boost::algorithm::replace_first(desc, "%s", replacement);
-				}
-				break;
-			case CScenarioTravel::STravelBonus::PLAYER_PREV_SCENARIO:
-				{
-					auto superhero = ourCampaign->camp->scenarios[bonDescs[i].info2].strongestHero(PlayerColor(bonDescs[i].info1));
-                    if (!superhero) logGlobal->warnStream() << "No superhero! How could it be transfered?";
-					picNumber = superhero ? superhero->portrait : 0;
-					desc = CGI->generaltexth->allTexts[719];
-
-					boost::algorithm::replace_first(desc, "%s", ourCampaign->camp->scenarios[bonDescs[i].info2].scenarioName); //scenario
 				}
 
+				boost::algorithm::replace_first(desc, "%s", substitute);
 				break;
-			case CScenarioTravel::STravelBonus::HERO:
+			}
+		case CScenarioTravel::STravelBonus::SECONDARY_SKILL:
+			desc = CGI->generaltexth->allTexts[718];
+
+			boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->levels[bonDescs[i].info3 - 1]); //skill level
+			boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->skillName[bonDescs[i].info2]); //skill name
+			picNumber = bonDescs[i].info2 * 3 + bonDescs[i].info3 - 1;
 
-				desc = CGI->generaltexth->allTexts[718];
-				boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->capColors[bonDescs[i].info1]); //hero's color
+			break;
+		case CScenarioTravel::STravelBonus::RESOURCE:
+			{
+				int serialResID = 0;
+				switch(bonDescs[i].info1)
+				{
+				case 0: case 1: case 2: case 3: case 4: case 5: case 6:
+					serialResID = bonDescs[i].info1;
+					break;
+				case 0xFD: //wood + ore
+					serialResID = 7;
+					break;
+				case 0xFE: //rare resources
+					serialResID = 8;
+					break;
+				}
+				picNumber = serialResID;
 
-				if (bonDescs[i].info2 == 0xFFFF)
+				desc = CGI->generaltexth->allTexts[717];
+				boost::algorithm::replace_first(desc, "%d", boost::lexical_cast<std::string>(bonDescs[i].info2));
+				std::string replacement;
+				if (serialResID <= 6)
 				{
-					boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->allTexts[101]); //hero's name
-					picNumber = -1;
-					picName = "CBONN1A3.BMP";
+					replacement = CGI->generaltexth->restypes[serialResID];
 				}
 				else
 				{
-					boost::algorithm::replace_first(desc, "%s", CGI->heroh->heroes[bonDescs[i].info2]->name); //hero's name
+					replacement = CGI->generaltexth->allTexts[714 + serialResID];
 				}
-				break;
+				boost::algorithm::replace_first(desc, "%s", replacement);
+			}
+			break;
+		case CScenarioTravel::STravelBonus::PLAYER_PREV_SCENARIO:
+			{
+				auto superhero = ourCampaign->camp->scenarios[bonDescs[i].info2].strongestHero(PlayerColor(bonDescs[i].info1));
+				if (!superhero) logGlobal->warnStream() << "No superhero! How could it be transfered?";
+				picNumber = superhero ? superhero->portrait : 0;
+				desc = CGI->generaltexth->allTexts[719];
+
+				boost::algorithm::replace_first(desc, "%s", ourCampaign->camp->scenarios[bonDescs[i].info2].scenarioName); //scenario
 			}
 
-			CHighlightableButton *bonusButton = new CHighlightableButton(desc, desc, 0, 475 + i*68, 455, "", i);
+			break;
+		case CScenarioTravel::STravelBonus::HERO:
 
-			if (picNumber != -1)
-				picName += ":" + boost::lexical_cast<std::string>(picNumber);
+			desc = CGI->generaltexth->allTexts[718];
+			boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->capColors[bonDescs[i].info1]); //hero's color
 
-			auto   anim = new CAnimation();
-			anim->setCustom(picName, 0);
-			bonusButton->setImage(anim);
-			const SDL_Color brightYellow = { 242, 226, 110, 0 };
-			bonusButton->borderColor = brightYellow;
-			bonuses->addButton(bonusButton);
+			if (bonDescs[i].info2 == 0xFFFF)
+			{
+				boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->allTexts[101]); //hero's name
+				picNumber = -1;
+				picName = "CBONN1A3.BMP";
+			}
+			else
+			{
+				boost::algorithm::replace_first(desc, "%s", CGI->heroh->heroes[bonDescs[i].info2]->name); //hero's name
+			}
+			break;
 		}
+
+		CHighlightableButton *bonusButton = new CHighlightableButton(desc, desc, 0, 475 + i*68, 455, "", i);
+
+		if (picNumber != -1)
+			picName += ":" + boost::lexical_cast<std::string>(picNumber);
+
+		auto   anim = new CAnimation();
+		anim->setCustom(picName, 0);
+		bonusButton->setImage(anim);
+		const SDL_Color brightYellow = { 242, 226, 110, 0 };
+		bonusButton->borderColor = brightYellow;
+		bonuses->addButton(bonusButton);
+	}
+
+	// set bonus if already chosen
+	if(vstd::contains(ourCampaign->chosenCampaignBonuses, selectedMap))
+	{
+		bonuses->select(ourCampaign->chosenCampaignBonuses[selectedMap], false);
+	}
 }
 
-void CBonusSelection::startMap()
+void CBonusSelection::updateCampaignState()
 {
-	auto  si = new StartInfo(sInfo);
+	ourCampaign->currentMap = selectedMap;
+	ourCampaign->chosenCampaignBonuses[selectedMap] = *selectedBonus;
+}
 
-	const CCampaignScenario & scenario = ourCampaign->camp->scenarios[ourCampaign->currentMap];
+void CBonusSelection::startMap()
+{
+	updateCampaignState();
 
-    logGlobal->infoStream() << "Starting scenario " << int(ourCampaign->currentMap);
+	logGlobal->infoStream() << "Starting scenario " << selectedMap;
 
+	const CCampaignScenario & scenario = ourCampaign->camp->scenarios[selectedMap];
+	auto si = new StartInfo(startInfo);
 	auto exitCb = [si]()
 	{
 		CGP->showLoadingScreen(boost::bind(&startGame, si, (CConnection *)nullptr));
@@ -3596,26 +3615,41 @@ void CBonusSelection::startMap()
 	}
 }
 
-void CBonusSelection::selectBonus( int id )
+void CBonusSelection::restartMap()
+{
+	updateCampaignState();
+	auto si = new StartInfo(startInfo); // copy before this object will be destroyed
+
+	GH.popIntTotally(this);
+	LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[67], [si]
+	{
+		SDL_Event event;
+		event.type = SDL_USEREVENT;
+		event.user.code = PREPARE_RESTART_CAMPAIGN;
+		event.user.data1 = si;
+		SDL_PushEvent(&event);
+	}, 0);
+}
+
+void CBonusSelection::selectBonus(int id)
 {
 	// Total redraw is needed because the border around the bonus images
 	// have to be undrawn/drawn.
-	if (!vstd::contains(sInfo.campState->chosenCampaignBonuses, sInfo.campState->currentMap)
-		|| id != sInfo.campState->currentBonusID())
+	if (!selectedBonus || *selectedBonus != id)
 	{
-		sInfo.campState->chosenCampaignBonuses[sInfo.campState->currentMap] = id;
+		selectedBonus = id;
 		GH.totalRedraw();
 
 		updateStartButtonState(id);
 	}
 
-	const CCampaignScenario &scenario = ourCampaign->camp->scenarios[sInfo.campState->currentMap];
+	const CCampaignScenario &scenario = ourCampaign->camp->scenarios[selectedMap];
 	const std::vector<CScenarioTravel::STravelBonus> & bonDescs = scenario.travelOptions.bonusesToChoose;
 	if (bonDescs[id].type == CScenarioTravel::STravelBonus::HERO)
 	{
 		std::map<ui8, std::string> names;
 		names[1] = settings["general"]["playerName"].String();
-		for(auto & elem : sInfo.playerInfos)
+		for(auto & elem : startInfo.playerInfos)
 		{
 			if(elem.first == PlayerColor(bonDescs[id].info1))
 				::setPlayer(elem.second, 1, names);
@@ -3625,24 +3659,26 @@ void CBonusSelection::selectBonus( int id )
 	}
 }
 
-void CBonusSelection::changeDiff( bool increase )
+void CBonusSelection::increaseDifficulty()
 {
-	if (increase)
-	{
-		sInfo.difficulty = std::min(sInfo.difficulty + 1, 4);
-	}
-	else
-	{
-		sInfo.difficulty = std::max(sInfo.difficulty - 1, 0);
-	}
+	startInfo.difficulty = std::min(startInfo.difficulty + 1, 4);
 }
 
-void CBonusSelection::updateStartButtonState( int selected /*= -1*/ )
+void CBonusSelection::decreaseDifficulty()
+{
+	startInfo.difficulty = std::max(startInfo.difficulty - 1, 0);
+}
+
+void CBonusSelection::updateStartButtonState(int selected /*= -1*/)
 {
 	if(selected == -1)
-		startB->setState( ourCampaign->getCurrentScenario().travelOptions.bonusesToChoose.size() ? CButtonBase::BLOCKED : CButtonBase::NORMAL);
+	{
+		startB->setState(ourCampaign->camp->scenarios[selectedMap].travelOptions.bonusesToChoose.size() ? CButtonBase::BLOCKED : CButtonBase::NORMAL);
+	}
 	else if(startB->getState() == CButtonBase::BLOCKED)
+	{
 		startB->setState(CButtonBase::NORMAL);
+	}
 }
 
 CBonusSelection::CRegion::CRegion( CBonusSelection * _owner, bool _accessible, bool _selectable, int _myNumber )

+ 35 - 33
client/CPreGame.h

@@ -452,12 +452,15 @@ public:
 /// Campaign screen where you can choose one out of three starting bonuses
 class CBonusSelection : public CIntObject
 {
-	SDL_Surface * background;
-	CAdventureMapButton * startB, * backB;
+public:
+	CBonusSelection(const std::string & campaignFName);
+	CBonusSelection(shared_ptr<CCampaignState> _ourCampaign);
+	~CBonusSelection();
 
-	//campaign & map descriptions:
-	CTextBox * cmpgDesc, * mapDesc;
+	void showAll(SDL_Surface * to) override;
+	void show(SDL_Surface * to) override;
 
+private:
 	struct SCampPositions
 	{
 		std::string campPrefix;
@@ -473,8 +476,6 @@ class CBonusSelection : public CIntObject
 
 	};
 
-	std::vector<SCampPositions> campDescriptions;
-
 	class CRegion : public CIntObject
 	{
 		CBonusSelection * owner;
@@ -482,6 +483,7 @@ class CBonusSelection : public CIntObject
 		bool accessible; //false if region should be striped
 		bool selectable; //true if region should be selectable
 		int myNumber; //number of region
+
 	public:
 		std::string rclickText;
 		CRegion(CBonusSelection * _owner, bool _accessible, bool _selectable, int _myNumber);
@@ -492,40 +494,40 @@ class CBonusSelection : public CIntObject
 		void show(SDL_Surface * to);
 	};
 
-	std::vector<CRegion *> regions;
-	CRegion * highlightedRegion;
-
+	void init();
 	void loadPositionsOfGraphics();
-	shared_ptr<CCampaignState> ourCampaign;
-	CMapHeader *ourHeader;
-	CDefHandler *sizes; //icons of map sizes
-	SDL_Surface* diffPics[5]; //pictures of difficulties, user-selectable (or not if campaign locks this)
-	CAdventureMapButton * diffLb, * diffRb; //buttons for changing difficulty
-	void changeDiff(bool increase); //if false, then decrease
-
-
 	void updateStartButtonState(int selected = -1); //-1 -- no bonus is selected
-	//bonus selection
 	void updateBonusSelection();
-	CHighlightableButtonsGroup * bonuses;
+	void updateCampaignState();
 
-public:
-	StartInfo sInfo;
-	CDefHandler *sFlags;
-
-	void selectMap(int whichOne, bool initialSelect);
+	// Event handlers
+	void goBack();
+	void startMap();
+	void restartMap();
+	void selectMap(int mapNr, bool initialSelect);
 	void selectBonus(int id);
-	void init();
+	void increaseDifficulty();
+	void decreaseDifficulty();
 
-	CBonusSelection(std::string campaignFName);
-	CBonusSelection(shared_ptr<CCampaignState> _ourCampaign);
-	~CBonusSelection();
-
-	void showAll(SDL_Surface * to);
-	void show(SDL_Surface * to);
+	// GUI components
+	SDL_Surface * background;
+	CAdventureMapButton * startB, * restartB, * backB;
+	CTextBox * campaignDescription, * mapDescription;
+	std::vector<SCampPositions> campDescriptions;
+	std::vector<CRegion *> regions;
+	CRegion * highlightedRegion;
+	CHighlightableButtonsGroup * bonuses;
+	SDL_Surface * diffPics[5]; //pictures of difficulties, user-selectable (or not if campaign locks this)
+	CAdventureMapButton * diffLb, * diffRb; //buttons for changing difficulty
+	CDefHandler * sizes; //icons of map sizes
+	CDefHandler * sFlags;
 
-	void goBack();
-	void startMap();
+	// Data
+	shared_ptr<CCampaignState> ourCampaign;
+	int selectedMap;
+	boost::optional<int> selectedBonus;
+	StartInfo startInfo;
+	CMapHeader * ourHeader;
 };
 
 /// Campaign selection screen

+ 3 - 3
lib/CGameState.cpp

@@ -807,7 +807,7 @@ void CGameState::init(StartInfo * si)
 						{
 							continue;
 						}
-						auto bb = new Bonus(Bonus::PERMANENT, Bonus::PRIMARY_SKILL, Bonus::CAMPAIGN_BONUS, val, scenarioOps->campState->currentMap, g);
+						auto bb = new Bonus(Bonus::PERMANENT, Bonus::PRIMARY_SKILL, Bonus::CAMPAIGN_BONUS, val, *scenarioOps->campState->currentMap, g);
 						hero->addNewBonus(bb);
 					}
 				}
@@ -886,9 +886,9 @@ void CGameState::init(StartInfo * si)
 		{
             logGlobal->infoStream() << "Open campaign map file: " << scenarioOps->campState->currentMap;
 			auto campaign = scenarioOps->campState;
-			assert(vstd::contains(campaign->camp->mapPieces, scenarioOps->campState->currentMap));
+			assert(vstd::contains(campaign->camp->mapPieces, *scenarioOps->campState->currentMap));
 
-			std::string & mapContent = campaign->camp->mapPieces[scenarioOps->campState->currentMap];
+			std::string & mapContent = campaign->camp->mapPieces[*scenarioOps->campState->currentMap];
 			auto buffer = reinterpret_cast<const ui8 *>(mapContent.data());
 			map = CMapService::loadMap(buffer, mapContent.size()).release();
 		}

+ 16 - 14
lib/mapping/CCampaignHandler.cpp

@@ -433,16 +433,16 @@ bool CScenarioTravel::STravelBonus::isBonusForHero() const
 
 void CCampaignState::mapConquered( const std::vector<CGHeroInstance*> & heroes )
 {
-	camp->scenarios[currentMap].prepareCrossoverHeroes(heroes);
-	mapsConquered.push_back(currentMap);
-	mapsRemaining -= currentMap;
-	camp->scenarios[currentMap].conquered = true;
+	camp->scenarios[*currentMap].prepareCrossoverHeroes(heroes);
+	mapsConquered.push_back(*currentMap);
+	mapsRemaining -= *currentMap;
+	camp->scenarios[*currentMap].conquered = true;
 }
 
 boost::optional<CScenarioTravel::STravelBonus> CCampaignState::getBonusForCurrentMap() const
 {
 	auto bonuses = getCurrentScenario().travelOptions.bonusesToChoose;
-	assert(chosenCampaignBonuses.count(currentMap) || bonuses.size() == 0);
+	assert(chosenCampaignBonuses.count(*currentMap) || bonuses.size() == 0);
 	if(bonuses.size())
 		return bonuses[currentBonusID()];
 	else
@@ -451,16 +451,18 @@ boost::optional<CScenarioTravel::STravelBonus> CCampaignState::getBonusForCurren
 
 const CCampaignScenario & CCampaignState::getCurrentScenario() const
 {
-	return camp->scenarios[currentMap];
+	return camp->scenarios[*currentMap];
+}
+
+ui8 CCampaignState::currentBonusID() const
+{
+	return chosenCampaignBonuses.at(*currentMap);
+}
+
+CCampaignState::CCampaignState()
+{
+
 }
-
-ui8 CCampaignState::currentBonusID() const
-{
-	return chosenCampaignBonuses.at(currentMap);
-}
-
-CCampaignState::CCampaignState()
-{}
 
 CCampaignState::CCampaignState( unique_ptr<CCampaign> _camp ) : camp(std::move(_camp))
 {

+ 7 - 7
lib/mapping/CCampaignHandler.h

@@ -148,13 +148,13 @@ class DLL_LINKAGE CCampaignState
 public:
 	unique_ptr<CCampaign> camp;
 	std::string campaignName; 
-	std::vector<ui8> mapsConquered, mapsRemaining;
-	ui8 currentMap; 
-
-	std::map<ui8, ui8> chosenCampaignBonuses; //used only for mode CAMPAIGN
-
-	//void initNewCampaign(const StartInfo &si);
-	void mapConquered(const std::vector<CGHeroInstance*> & heroes);
+	std::vector<ui8> mapsConquered, mapsRemaining;
+	boost::optional<si32> currentMap;
+
+	std::map<ui8, ui8> chosenCampaignBonuses;
+
+	//void initNewCampaign(const StartInfo &si);
+	void mapConquered(const std::vector<CGHeroInstance*> & heroes);
 	boost::optional<CScenarioTravel::STravelBonus> getBonusForCurrentMap() const;
 	const CCampaignScenario &getCurrentScenario() const;
 	ui8 currentBonusID() const;