Sfoglia il codice sorgente

Refactoring of campaign handler: rename types and use strong typing

Ivan Savenko 2 anni fa
parent
commit
d1e5a347ff

+ 3 - 3
client/CServerHandler.cpp

@@ -442,7 +442,7 @@ void CServerHandler::sendClientDisconnecting()
 	sendLobbyPack(lcd);
 }
 
-void CServerHandler::setCampaignState(std::shared_ptr<CCampaignState> newCampaign)
+void CServerHandler::setCampaignState(std::shared_ptr<CampaignState> newCampaign)
 {
 	state = EClientState::LOBBY_CAMPAIGN;
 	LobbySetCampaign lsc;
@@ -450,7 +450,7 @@ void CServerHandler::setCampaignState(std::shared_ptr<CCampaignState> newCampaig
 	sendLobbyPack(lsc);
 }
 
-void CServerHandler::setCampaignMap(int mapId) const
+void CServerHandler::setCampaignMap(CampaignScenarioID mapId) const
 {
 	if(state == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place
 		return;
@@ -660,7 +660,7 @@ void CServerHandler::endGameplay(bool closeConnection, bool restart)
 	saveSession->Bool() = false;
 }
 
-void CServerHandler::startCampaignScenario(std::shared_ptr<CCampaignState> cs)
+void CServerHandler::startCampaignScenario(std::shared_ptr<CampaignState> cs)
 {
 	if(cs)
 		GH.pushUserEvent(EUserEvent::CAMPAIGN_START_SCENARIO, CMemorySerializer::deepCopy(*cs.get()).release());

+ 6 - 6
client/CServerHandler.h

@@ -57,8 +57,8 @@ public:
 
 	virtual void sendClientConnecting() const = 0;
 	virtual void sendClientDisconnecting() = 0;
-	virtual void setCampaignState(std::shared_ptr<CCampaignState> newCampaign) = 0;
-	virtual void setCampaignMap(int mapId) const = 0;
+	virtual void setCampaignState(std::shared_ptr<CampaignState> newCampaign) = 0;
+	virtual void setCampaignMap(CampaignScenarioID mapId) const = 0;
 	virtual void setCampaignBonus(int bonusId) const = 0;
 	virtual void setMapInfo(std::shared_ptr<CMapInfo> to, std::shared_ptr<CMapGenOptions> mapGenOpts = {}) const = 0;
 	virtual void setPlayer(PlayerColor color) const = 0;
@@ -92,7 +92,7 @@ public:
 	// FIXME: Bunch of crutches to glue it all together
 
 	// For starting non-custom campaign and continue to next mission
-	std::shared_ptr<CCampaignState> campaignStateToSend;
+	std::shared_ptr<CampaignState> campaignStateToSend;
 
 	ui8 screenType; // To create lobby UI only after server is setup
 	ui8 loadMode; // For saves filtering in SelectionTab
@@ -135,8 +135,8 @@ public:
 	// Lobby server API for UI
 	void sendClientConnecting() const override;
 	void sendClientDisconnecting() override;
-	void setCampaignState(std::shared_ptr<CCampaignState> newCampaign) override;
-	void setCampaignMap(int mapId) const override;
+	void setCampaignState(std::shared_ptr<CampaignState> newCampaign) override;
+	void setCampaignMap(CampaignScenarioID mapId) const override;
 	void setCampaignBonus(int bonusId) const override;
 	void setMapInfo(std::shared_ptr<CMapInfo> to, std::shared_ptr<CMapGenOptions> mapGenOpts = {}) const override;
 	void setPlayer(PlayerColor color) const override;
@@ -150,7 +150,7 @@ public:
 
 	void startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameState = nullptr);
 	void endGameplay(bool closeConnection = true, bool restart = false);
-	void startCampaignScenario(std::shared_ptr<CCampaignState> cs = {});
+	void startCampaignScenario(std::shared_ptr<CampaignState> cs = {});
 	void showServerError(std::string txt);
 
 	// TODO: LobbyState must be updated within game so we should always know how many player interfaces our client handle

+ 1 - 1
client/Client.h

@@ -23,7 +23,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 struct CPack;
 struct CPackForServer;
-class CCampaignState;
+class CampaignState;
 class IGameEventsReceiver;
 class IBattleEventsReceiver;
 class CBattleGameInterface;

+ 3 - 3
client/ClientCommandManager.cpp

@@ -209,9 +209,9 @@ void ClientCommandManager::handleConvertTextCommand()
 	logGlobal->info("Loading campaigns for export");
 	for (auto const & campaignName : campaignList)
 	{
-		CCampaignState state(CCampaignHandler::getCampaign(campaignName.getName()));
-		for (auto const & part : state.camp->mapPieces)
-			delete state.getMap(part.first);
+		auto state = CampaignHandler::getCampaign(campaignName.getName());
+		for (auto const & part : state->mapPieces)
+			state->getMap(part.first);
 	}
 
 	VLC->generaltexth->dumpAllTexts();

+ 2 - 2
client/eventsSDL/UserEventHandler.cpp

@@ -48,8 +48,8 @@ void UserEventHandler::handleUserEvent(const SDL_UserEvent & user)
 		{
 			CSH->campaignServerRestartLock.set(true);
 			CSH->endGameplay();
-			auto ourCampaign = std::shared_ptr<CCampaignState>(reinterpret_cast<CCampaignState *>(user.data1));
-			auto & epilogue = ourCampaign->camp->scenarios[ourCampaign->mapsConquered.back()].epilog;
+			auto ourCampaign = std::shared_ptr<CampaignState>(reinterpret_cast<CampaignState *>(user.data1));
+			auto & epilogue = ourCampaign->scenarios[ourCampaign->mapsConquered.back()].epilog;
 			auto finisher = [=]()
 			{
 				if(!ourCampaign->mapsRemaining.empty())

+ 35 - 32
client/lobby/CBonusSelection.cpp

@@ -54,7 +54,7 @@
 #include "../../lib/mapObjects/CGHeroInstance.h"
 
 
-std::shared_ptr<CCampaignState> CBonusSelection::getCampaign()
+std::shared_ptr<CampaignState> CBonusSelection::getCampaign()
 {
 	return CSH->si->campState;
 }
@@ -64,7 +64,7 @@ CBonusSelection::CBonusSelection()
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 
-	std::string bgName = getCampaign()->camp->header.campaignRegions.campPrefix + "_BG.BMP";
+	std::string bgName = getCampaign()->header.campaignRegions.campPrefix + "_BG.BMP";
 	setBackground(bgName);
 
 	panelBackground = std::make_shared<CPicture>("CAMPBRF.BMP", 456, 6);
@@ -78,7 +78,7 @@ CBonusSelection::CBonusSelection()
 	iconsMapSizes = std::make_shared<CAnimImage>("SCNRMPSZ", 4, 0, 735, 26);
 
 	labelCampaignDescription = std::make_shared<CLabel>(481, 63, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[38]);
-	campaignDescription = std::make_shared<CTextBox>(getCampaign()->camp->header.description, Rect(480, 86, 286, 117), 1);
+	campaignDescription = std::make_shared<CTextBox>(getCampaign()->header.description, Rect(480, 86, 286, 117), 1);
 
 	mapName = std::make_shared<CLabel>(481, 219, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->mi->getName());
 	labelMapDescription = std::make_shared<CLabel>(481, 253, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[496]);
@@ -99,26 +99,28 @@ CBonusSelection::CBonusSelection()
 		difficultyIcons[b] = std::make_shared<CAnimImage>("GSPBUT" + std::to_string(b + 3) + ".DEF", 0, 0, 709, 455);
 	}
 
-	if(getCampaign()->camp->header.difficultyChoosenByPlayer)
+	if(getCampaign()->header.difficultyChoosenByPlayer)
 	{
 		buttonDifficultyLeft = std::make_shared<CButton>(Point(694, 508), "SCNRBLF.DEF", CButton::tooltip(), std::bind(&CBonusSelection::decreaseDifficulty, this));
 		buttonDifficultyRight = std::make_shared<CButton>(Point(738, 508), "SCNRBRT.DEF", CButton::tooltip(), std::bind(&CBonusSelection::increaseDifficulty, this));
 	}
 
-	for(int g = 0; g < getCampaign()->camp->scenarios.size(); ++g)
+	for(int g = 0; g < getCampaign()->scenarios.size(); ++g)
 	{
-		if(getCampaign()->camp->conquerable(g))
-			regions.push_back(std::make_shared<CRegion>(g, true, true, getCampaign()->camp->header.campaignRegions));
-		else if(getCampaign()->camp->scenarios[g].conquered) //display as striped
-			regions.push_back(std::make_shared<CRegion>(g, false, false, getCampaign()->camp->header.campaignRegions));
+		auto scenarioID = static_cast<CampaignScenarioID>(g);
+
+		if(getCampaign()->conquerable(scenarioID))
+			regions.push_back(std::make_shared<CRegion>(scenarioID, true, true, getCampaign()->header.campaignRegions));
+		else if(getCampaign()->scenarios[scenarioID].conquered) //display as striped
+			regions.push_back(std::make_shared<CRegion>(scenarioID, false, false, getCampaign()->header.campaignRegions));
 	}
 }
 
 void CBonusSelection::createBonusesIcons()
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
-	const CCampaignScenario & scenario = getCampaign()->camp->scenarios[CSH->campaignMap];
-	const std::vector<CScenarioTravel::STravelBonus> & bonDescs = scenario.travelOptions.bonusesToChoose;
+	const CampaignScenario & scenario = getCampaign()->scenarios[CSH->campaignMap];
+	const std::vector<CampaignBonus> & bonDescs = scenario.travelOptions.bonusesToChoose;
 	groupBonuses = std::make_shared<CToggleGroup>(std::bind(&IServerAPI::setCampaignBonus, CSH, _1));
 
 	static const char * bonusPics[] =
@@ -138,23 +140,24 @@ void CBonusSelection::createBonusesIcons()
 
 	for(int i = 0; i < bonDescs.size(); i++)
 	{
-		std::string picName = bonusPics[bonDescs[i].type];
+		int bonusType = static_cast<size_t>(bonDescs[i].type);
+		std::string picName = bonusPics[bonusType];
 		size_t picNumber = bonDescs[i].info2;
 
 		std::string desc;
 		switch(bonDescs[i].type)
 		{
-		case CScenarioTravel::STravelBonus::SPELL:
+		case CampaignBonusType::SPELL:
 			desc = CGI->generaltexth->allTexts[715];
 			boost::algorithm::replace_first(desc, "%s", CGI->spells()->getByIndex(bonDescs[i].info2)->getNameTranslated());
 			break;
-		case CScenarioTravel::STravelBonus::MONSTER:
+		case CampaignBonusType::MONSTER:
 			picNumber = bonDescs[i].info2 + 2;
 			desc = CGI->generaltexth->allTexts[717];
 			boost::algorithm::replace_first(desc, "%d", std::to_string(bonDescs[i].info3));
 			boost::algorithm::replace_first(desc, "%s", CGI->creatures()->getByIndex(bonDescs[i].info2)->getNamePluralTranslated());
 			break;
-		case CScenarioTravel::STravelBonus::BUILDING:
+		case CampaignBonusType::BUILDING:
 		{
 			int faction = -1;
 			for(auto & elem : CSH->si->playerInfos)
@@ -169,7 +172,7 @@ void CBonusSelection::createBonusesIcons()
 			assert(faction != -1);
 
 			BuildingID buildID;
-			if(getCampaign()->camp->header.version == CampaignVersion::VCMI)
+			if(getCampaign()->header.version == CampaignVersion::VCMI)
 				buildID = BuildingID(bonDescs[i].info1);
 			else
 				buildID = CBuildingHandler::campToERMU(bonDescs[i].info1, faction, std::set<BuildingID>());
@@ -181,15 +184,15 @@ void CBonusSelection::createBonusesIcons()
 
 			break;
 		}
-		case CScenarioTravel::STravelBonus::ARTIFACT:
+		case CampaignBonusType::ARTIFACT:
 			desc = CGI->generaltexth->allTexts[715];
 			boost::algorithm::replace_first(desc, "%s", CGI->artifacts()->getByIndex(bonDescs[i].info2)->getNameTranslated());
 			break;
-		case CScenarioTravel::STravelBonus::SPELL_SCROLL:
+		case CampaignBonusType::SPELL_SCROLL:
 			desc = CGI->generaltexth->allTexts[716];
 			boost::algorithm::replace_first(desc, "%s", CGI->spells()->getByIndex(bonDescs[i].info2)->getNameTranslated());
 			break;
-		case CScenarioTravel::STravelBonus::PRIMARY_SKILL:
+		case CampaignBonusType::PRIMARY_SKILL:
 		{
 			int leadingSkill = -1;
 			std::vector<std::pair<int, int>> toPrint; //primary skills to be listed <num, val>
@@ -222,7 +225,7 @@ void CBonusSelection::createBonusesIcons()
 			boost::algorithm::replace_first(desc, "%s", substitute);
 			break;
 		}
-		case CScenarioTravel::STravelBonus::SECONDARY_SKILL:
+		case CampaignBonusType::SECONDARY_SKILL:
 			desc = CGI->generaltexth->allTexts[718];
 
 			boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->levels[bonDescs[i].info3 - 1]); //skill level
@@ -230,7 +233,7 @@ void CBonusSelection::createBonusesIcons()
 			picNumber = bonDescs[i].info2 * 3 + bonDescs[i].info3 - 1;
 
 			break;
-		case CScenarioTravel::STravelBonus::RESOURCE:
+		case CampaignBonusType::RESOURCE:
 		{
 			int serialResID = 0;
 			switch(bonDescs[i].info1)
@@ -267,19 +270,19 @@ void CBonusSelection::createBonusesIcons()
 			boost::algorithm::replace_first(desc, "%s", replacement);
 			break;
 		}
-		case CScenarioTravel::STravelBonus::HEROES_FROM_PREVIOUS_SCENARIO:
+		case CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO:
 		{
-			auto superhero = getCampaign()->camp->scenarios[bonDescs[i].info2].strongestHero(PlayerColor(bonDescs[i].info1));
+			auto superhero = getCampaign()->scenarios[static_cast<CampaignScenarioID>(bonDescs[i].info2)].strongestHero(PlayerColor(bonDescs[i].info1));
 			if(!superhero)
 				logGlobal->warn("No superhero! How could it be transferred?");
 			picNumber = superhero ? superhero->portrait : 0;
 			desc = CGI->generaltexth->allTexts[719];
 
-			boost::algorithm::replace_first(desc, "%s", getCampaign()->camp->scenarios[bonDescs[i].info2].scenarioName);
+			boost::algorithm::replace_first(desc, "%s", getCampaign()->scenarios[static_cast<CampaignScenarioID>(bonDescs[i].info2)].scenarioName);
 			break;
 		}
 
-		case CScenarioTravel::STravelBonus::HERO:
+		case CampaignBonusType::HERO:
 
 			desc = CGI->generaltexth->allTexts[718];
 			boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->capColors[bonDescs[i].info1]); //hero's color
@@ -339,7 +342,7 @@ void CBonusSelection::updateAfterStateChange()
 	}
 	if(CSH->campaignBonus == -1)
 	{
-		buttonStart->block(getCampaign()->camp->scenarios[CSH->campaignMap].travelOptions.bonusesToChoose.size());
+		buttonStart->block(getCampaign()->scenarios[CSH->campaignMap].travelOptions.bonusesToChoose.size());
 	}
 	else if(buttonStart->isBlocked())
 	{
@@ -391,11 +394,11 @@ void CBonusSelection::startMap()
 	{
 		auto exitCb = [=]()
 		{
-			logGlobal->info("Starting scenario %d", CSH->campaignMap);
+			logGlobal->info("Starting scenario %d", static_cast<int>(CSH->campaignMap));
 			CSH->sendStartGame();
 		};
 
-		const CCampaignScenario & scenario = getCampaign()->camp->scenarios[CSH->campaignMap];
+		const CampaignScenario & scenario = getCampaign()->scenarios[CSH->campaignMap];
 		if(scenario.prolog.hasPrologEpilog)
 		{
 			GH.windows().createAndPushWindow<CPrologEpilogVideo>(scenario.prolog, exitCb);
@@ -446,7 +449,7 @@ void CBonusSelection::decreaseDifficulty()
 		CSH->setDifficulty(CSH->si->difficulty - 1);
 }
 
-CBonusSelection::CRegion::CRegion(int id, bool accessible, bool selectable, const CampaignRegions & campDsc)
+CBonusSelection::CRegion::CRegion(CampaignScenarioID id, bool accessible, bool selectable, const CampaignRegions & campDsc)
 	: CIntObject(LCLICK | SHOW_POPUP), idOfMapAndRegion(id), accessible(accessible), selectable(selectable)
 {
 	OBJ_CONSTRUCTION;
@@ -456,12 +459,12 @@ CBonusSelection::CRegion::CRegion(int id, bool accessible, bool selectable, cons
 		{"Re", "Bl", "Br", "Gr", "Or", "Vi", "Te", "Pi"}
 	};
 
-	const CampaignRegions::RegionDescription & desc = campDsc.regions[idOfMapAndRegion];
+	const CampaignRegions::RegionDescription & desc = campDsc.regions[static_cast<int>(idOfMapAndRegion)];
 	pos.x += desc.xpos;
 	pos.y += desc.ypos;
 
 	std::string prefix = campDsc.campPrefix + desc.infix + "_";
-	std::string suffix = colors[campDsc.colorSuffixLength - 1][CSH->si->campState->camp->scenarios[idOfMapAndRegion].regionColor];
+	std::string suffix = colors[campDsc.colorSuffixLength - 1][CSH->si->campState->scenarios[idOfMapAndRegion].regionColor];
 	graphicsNotSelected = std::make_shared<CPicture>(prefix + "En" + suffix + ".BMP");
 	graphicsNotSelected->disable();
 	graphicsSelected = std::make_shared<CPicture>(prefix + "Se" + suffix + ".BMP");
@@ -510,7 +513,7 @@ void CBonusSelection::CRegion::clickLeft(tribool down, bool previousState)
 void CBonusSelection::CRegion::showPopupWindow()
 {
 	// FIXME: For some reason "down" is only ever contain indeterminate_value
-	auto text = CSH->si->campState->camp->scenarios[idOfMapAndRegion].regionText;
+	auto text = CSH->si->campState->scenarios[idOfMapAndRegion].regionText;
 	if(!graphicsNotSelected->getSurface()->isTransparent(GH.getCursorPosition() - pos.topLeft()) && text.size())
 	{
 		CRClickPopup::createAndPush(text);

+ 5 - 4
client/lobby/CBonusSelection.h

@@ -10,10 +10,11 @@
 #pragma once
 
 #include "../windows/CWindowObject.h"
+#include "../lib/mapping/CCampaignHandler.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class CCampaignState;
+class CampaignState;
 struct CampaignRegions;
 
 VCMI_LIB_NAMESPACE_END
@@ -30,7 +31,7 @@ class ISelectionScreenInfo;
 class CBonusSelection : public CWindowObject
 {
 public:
-	std::shared_ptr<CCampaignState> getCampaign();
+	std::shared_ptr<CampaignState> getCampaign();
 	CBonusSelection();
 
 	class CRegion
@@ -39,11 +40,11 @@ public:
 		std::shared_ptr<CPicture> graphicsNotSelected;
 		std::shared_ptr<CPicture> graphicsSelected;
 		std::shared_ptr<CPicture> graphicsStriped;
-		int idOfMapAndRegion;
+		CampaignScenarioID idOfMapAndRegion;
 		bool accessible; // false if region should be striped
 		bool selectable; // true if region should be selectable
 	public:
-		CRegion(int id, bool accessible, bool selectable, const CampaignRegions & campDsc);
+		CRegion(CampaignScenarioID id, bool accessible, bool selectable, const CampaignRegions & campDsc);
 		void updateState();
 		void clickLeft(tribool down, bool previousState) override;
 		void showPopupWindow() override;

+ 1 - 1
client/lobby/CLobbyScreen.cpp

@@ -119,7 +119,7 @@ void CLobbyScreen::startCampaign()
 {
 	if(CSH->mi)
 	{
-		auto ourCampaign = std::make_shared<CCampaignState>(CCampaignHandler::getCampaign(CSH->mi->fileURI));
+		auto ourCampaign = CampaignHandler::getCampaign(CSH->mi->fileURI);
 		CSH->setCampaignState(ourCampaign);
 	}
 }

+ 1 - 1
client/mainmenu/CCampaignScreen.cpp

@@ -96,7 +96,7 @@ CCampaignScreen::CCampaignButton::CCampaignButton(const JsonNode & config)
 
 	status = config["open"].Bool() ? CCampaignScreen::ENABLED : CCampaignScreen::DISABLED;
 
-	CCampaignHeader header = CCampaignHandler::getHeader(campFile);
+	CampaignHeader header = CampaignHandler::getHeader(campFile);
 	hoverText = header.name;
 
 	if(status != CCampaignScreen::DISABLED)

+ 2 - 2
client/mainmenu/CMainMenu.cpp

@@ -354,11 +354,11 @@ void CMainMenu::openLobby(ESelectionScreen screenType, bool host, const std::vec
 
 void CMainMenu::openCampaignLobby(const std::string & campaignFileName)
 {
-	auto ourCampaign = std::make_shared<CCampaignState>(CCampaignHandler::getCampaign(campaignFileName));
+	auto ourCampaign = CampaignHandler::getCampaign(campaignFileName);
 	openCampaignLobby(ourCampaign);
 }
 
-void CMainMenu::openCampaignLobby(std::shared_ptr<CCampaignState> campaign)
+void CMainMenu::openCampaignLobby(std::shared_ptr<CampaignState> campaign)
 {
 	CSH->resetStateForLobby(StartInfo::CAMPAIGN);
 	CSH->screenType = ESelectionScreen::campaignList;

+ 2 - 2
client/mainmenu/CMainMenu.h

@@ -14,7 +14,7 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class CCampaignState;
+class CampaignState;
 
 VCMI_LIB_NAMESPACE_END
 
@@ -147,7 +147,7 @@ public:
 	void update() override;
 	static void openLobby(ESelectionScreen screenType, bool host, const std::vector<std::string> * names, ELoadMode loadMode);
 	static void openCampaignLobby(const std::string & campaignFileName);
-	static void openCampaignLobby(std::shared_ptr<CCampaignState> campaign);
+	static void openCampaignLobby(std::shared_ptr<CampaignState> campaign);
 	void openCampaignScreen(std::string name);
 
 	static std::shared_ptr<CMainMenu> create();

+ 1 - 1
client/mainmenu/CPrologEpilogVideo.cpp

@@ -21,7 +21,7 @@
 #include "../../lib/mapping/CCampaignHandler.h"
 
 
-CPrologEpilogVideo::CPrologEpilogVideo(CCampaignScenario::SScenarioPrologEpilog _spe, std::function<void()> callback)
+CPrologEpilogVideo::CPrologEpilogVideo(CampaignScenarioPrologEpilog _spe, std::function<void()> callback)
 	: CWindowObject(BORDERED), spe(_spe), positionCounter(0), voiceSoundHandle(-1), exitCb(callback)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;

+ 2 - 2
client/mainmenu/CPrologEpilogVideo.h

@@ -15,7 +15,7 @@ class CMultiLineLabel;
 
 class CPrologEpilogVideo : public CWindowObject
 {
-	CCampaignScenario::SScenarioPrologEpilog spe;
+	CampaignScenarioPrologEpilog spe;
 	int positionCounter;
 	int voiceSoundHandle;
 	std::function<void()> exitCb;
@@ -23,7 +23,7 @@ class CPrologEpilogVideo : public CWindowObject
 	std::shared_ptr<CMultiLineLabel> text;
 
 public:
-	CPrologEpilogVideo(CCampaignScenario::SScenarioPrologEpilog _spe, std::function<void()> callback);
+	CPrologEpilogVideo(CampaignScenarioPrologEpilog _spe, std::function<void()> callback);
 
 	void clickLeft(tribool down, bool previousState) override;
 	void show(Canvas & to) override;

+ 2 - 2
client/windows/CCastleInterface.cpp

@@ -922,8 +922,8 @@ void CCastleBuildings::enterMagesGuild()
 	{
 		const StartInfo *si = LOCPLINT->cb->getStartInfo();
 		// it would be nice to find a way to move this hack to config/mapOverrides.json
-		if(si && si->campState && si->campState->camp &&                // We're in campaign,
-			(si->campState->camp->header.filename == "DATA/YOG.H3C") && // which is "Birth of a Barbarian",
+		if(si && si->campState && si->campState &&                // We're in campaign,
+			(si->campState->header.filename == "DATA/YOG.H3C") && // which is "Birth of a Barbarian",
 			(hero->subID == 45))                                        // and the hero is Yog (based on Solmyr)
 		{
 			// "Yog has given up magic in all its forms..."

+ 3 - 3
lib/NetPacksLobby.h

@@ -18,7 +18,7 @@ class CVCMIServer;
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class CCampaignState;
+class CampaignState;
 class CMapInfo;
 struct StartInfo;
 class CMapGenOptions;
@@ -175,7 +175,7 @@ struct DLL_LINKAGE LobbySetMap : public CLobbyPackToServer
 
 struct DLL_LINKAGE LobbySetCampaign : public CLobbyPackToServer
 {
-	std::shared_ptr<CCampaignState> ourCampaign;
+	std::shared_ptr<CampaignState> ourCampaign;
 
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 
@@ -187,7 +187,7 @@ struct DLL_LINKAGE LobbySetCampaign : public CLobbyPackToServer
 
 struct DLL_LINKAGE LobbySetCampaignMap : public CLobbyPackToServer
 {
-	int mapId = -1;
+	CampaignScenarioID mapId = CampaignScenarioID::NONE;
 
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 

+ 2 - 2
lib/StartInfo.cpp

@@ -62,8 +62,8 @@ PlayerSettings * StartInfo::getPlayersSettings(const ui8 connectedPlayerId)
 
 std::string StartInfo::getCampaignName() const
 {
-	if(campState->camp->header.name.length())
-		return campState->camp->header.name;
+	if(campState->header.name.length())
+		return campState->header.name;
 	else
 		return VLC->generaltexth->allTexts[508];
 }

+ 5 - 4
lib/StartInfo.h

@@ -10,11 +10,12 @@
 #pragma once
 
 #include "GameConstants.h"
+#include "../lib/mapping/CCampaignHandler.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
 class CMapGenOptions;
-class CCampaignState;
+class CampaignState;
 class CMapInfo;
 struct PlayerInfo;
 class PlayerColor;
@@ -87,7 +88,7 @@ struct DLL_LINKAGE StartInfo
 	bool createRandomMap() const { return mapGenOptions != nullptr; }
 	std::shared_ptr<CMapGenOptions> mapGenOptions;
 
-	std::shared_ptr<CCampaignState> campState;
+	std::shared_ptr<CampaignState> campState;
 
 	PlayerSettings & getIthPlayersSettings(const PlayerColor & no);
 	const PlayerSettings & getIthPlayersSettings(const PlayerColor & no) const;
@@ -138,10 +139,10 @@ struct DLL_LINKAGE LobbyState
 	int hostClientId;
 	// TODO: Campaign-only and we don't really need either of them.
 	// Before start both go into CCampaignState that is part of StartInfo
-	int campaignMap;
+	CampaignScenarioID campaignMap;
 	int campaignBonus;
 
-	LobbyState() : si(new StartInfo()), hostClientId(-1), campaignMap(-1), campaignBonus(-1) {}
+	LobbyState() : si(new StartInfo()), hostClientId(-1), campaignMap(CampaignScenarioID::NONE), campaignBonus(-1) {}
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{

+ 1 - 1
lib/gameState/CGameState.cpp

@@ -684,7 +684,7 @@ void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRan
 void CGameState::initCampaign()
 {
 	campaign = std::make_unique<CGameStateCampaign>(this);
-	map = campaign->getCurrentMap();
+	map = campaign->getCurrentMap().release();
 }
 
 void CGameState::checkMapChecksum()

+ 1 - 1
lib/gameState/CGameState.h

@@ -26,7 +26,7 @@ class CMap;
 struct CPack;
 class CHeroClass;
 struct EventCondition;
-class CScenarioTravel;
+class CampaignTravel;
 class CStackInstance;
 class CGameStateCampaign;
 

+ 24 - 22
lib/gameState/CGameStateCampaign.cpp

@@ -60,12 +60,14 @@ CrossoverHeroesList CGameStateCampaign::getCrossoverHeroesFromPreviousScenarios(
 
 	auto campaignState = gameState->scenarioOps->campState;
 	auto bonus = campaignState->getBonusForCurrentMap();
-	if(bonus && bonus->type == CScenarioTravel::STravelBonus::HEROES_FROM_PREVIOUS_SCENARIO)
+	if(bonus && bonus->type == CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO)
 	{
+		auto scenarioID = static_cast<CampaignScenarioID>(bonus->info2);
+
 		std::vector<CGHeroInstance *> heroes;
-		for(auto & node : campaignState->camp->scenarios[bonus->info2].crossoverHeroes)
+		for(auto & node : campaignState->scenarios.at(scenarioID).crossoverHeroes)
 		{
-			auto * h = CCampaignState::crossoverDeserialize(node);
+			auto * h = CampaignState::crossoverDeserialize(node);
 			heroes.push_back(h);
 		}
 		crossoverHeroes.heroesFromAnyPreviousScenarios = heroes;
@@ -80,7 +82,7 @@ CrossoverHeroesList CGameStateCampaign::getCrossoverHeroesFromPreviousScenarios(
 	for(auto mapNr : campaignState->mapsConquered)
 	{
 		// create a list of deleted heroes
-		auto & scenario = campaignState->camp->scenarios[mapNr];
+		auto & scenario = campaignState->scenarios[mapNr];
 		auto lostCrossoverHeroes = scenario.getLostCrossoverHeroes();
 
 		// remove heroes which didn't reached the end of the scenario, but were available at the start
@@ -96,7 +98,7 @@ CrossoverHeroesList CGameStateCampaign::getCrossoverHeroesFromPreviousScenarios(
 		// now add heroes which completed the scenario
 		for(auto node : scenario.crossoverHeroes)
 		{
-			auto * hero = CCampaignState::crossoverDeserialize(node);
+			auto * hero = CampaignState::crossoverDeserialize(node);
 			// add new heroes and replace old heroes with newer ones
 			auto it = range::find_if(crossoverHeroes.heroesFromAnyPreviousScenarios, [hero](CGHeroInstance * h)
 			{
@@ -123,7 +125,7 @@ CrossoverHeroesList CGameStateCampaign::getCrossoverHeroesFromPreviousScenarios(
 	return crossoverHeroes;
 }
 
-void CGameStateCampaign::trimCrossoverHeroesParameters(std::vector<CampaignHeroReplacement> & campaignHeroReplacements, const CScenarioTravel & travelOptions)
+void CGameStateCampaign::trimCrossoverHeroesParameters(std::vector<CampaignHeroReplacement> & campaignHeroReplacements, const CampaignTravel & travelOptions)
 {
 	// create heroes list for convenience iterating
 	std::vector<CGHeroInstance *> crossoverHeroes;
@@ -253,7 +255,7 @@ void CGameStateCampaign::placeCampaignHeroes()
 
 	// place bonus hero
 	auto campaignBonus = gameState->scenarioOps->campState->getBonusForCurrentMap();
-	bool campaignGiveHero = campaignBonus && campaignBonus->type == CScenarioTravel::STravelBonus::HERO;
+	bool campaignGiveHero = campaignBonus && campaignBonus->type == CampaignBonusType::HERO;
 
 	if(campaignGiveHero)
 	{
@@ -337,7 +339,7 @@ void CGameStateCampaign::placeCampaignHeroes()
 
 void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero)
 {
-	const std::optional<CScenarioTravel::STravelBonus> & curBonus = gameState->scenarioOps->campState->getBonusForCurrentMap();
+	const std::optional<CampaignBonus> & curBonus = gameState->scenarioOps->campState->getBonusForCurrentMap();
 	if(!curBonus)
 		return;
 
@@ -346,12 +348,12 @@ void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero)
 	//apply bonus
 	switch(curBonus->type)
 	{
-		case CScenarioTravel::STravelBonus::SPELL:
+		case CampaignBonusType::SPELL:
 		{
 			hero->addSpellToSpellbook(SpellID(curBonus->info2));
 			break;
 		}
-		case CScenarioTravel::STravelBonus::MONSTER:
+		case CampaignBonusType::MONSTER:
 		{
 			for(int i = 0; i < GameConstants::ARMY_SIZE; i++)
 			{
@@ -363,13 +365,13 @@ void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero)
 			}
 			break;
 		}
-		case CScenarioTravel::STravelBonus::ARTIFACT:
+		case CampaignBonusType::ARTIFACT:
 		{
 			if(!gameState->giveHeroArtifact(hero, static_cast<ArtifactID>(curBonus->info2)))
 				logGlobal->error("Cannot give starting artifact - no free slots!");
 			break;
 		}
-		case CScenarioTravel::STravelBonus::SPELL_SCROLL:
+		case CampaignBonusType::SPELL_SCROLL:
 		{
 			CArtifactInstance * scroll = ArtifactUtils::createScroll(SpellID(curBonus->info2));
 			const auto slot = ArtifactUtils::getArtAnyPosition(hero, scroll->getTypeId());
@@ -379,7 +381,7 @@ void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero)
 				logGlobal->error("Cannot give starting scroll - no free slots!");
 			break;
 		}
-		case CScenarioTravel::STravelBonus::PRIMARY_SKILL:
+		case CampaignBonusType::PRIMARY_SKILL:
 		{
 			const ui8 * ptr = reinterpret_cast<const ui8 *>(&curBonus->info2);
 			for(int g = 0; g < GameConstants::PRIMARY_SKILLS; ++g)
@@ -390,13 +392,13 @@ void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero)
 					continue;
 				}
 				auto bb = std::make_shared<Bonus>(
-					BonusDuration::PERMANENT, BonusType::PRIMARY_SKILL, BonusSource::CAMPAIGN_BONUS, val, *gameState->scenarioOps->campState->currentMap, g
+					BonusDuration::PERMANENT, BonusType::PRIMARY_SKILL, BonusSource::CAMPAIGN_BONUS, val, static_cast<int>(*gameState->scenarioOps->campState->currentMap), g
 				);
 				hero->addNewBonus(bb);
 			}
 			break;
 		}
-		case CScenarioTravel::STravelBonus::SECONDARY_SKILL:
+		case CampaignBonusType::SECONDARY_SKILL:
 		{
 			hero->setSecSkillLevel(SecondarySkill(curBonus->info2), curBonus->info3, true);
 			break;
@@ -444,7 +446,7 @@ void CGameStateCampaign::replaceHeroesPlaceholders(const std::vector<CampaignHer
 
 		delete heroPlaceholder;
 
-		gameState->scenarioOps->campState->getCurrentScenario().placedCrossoverHeroes.push_back(CCampaignState::crossoverSerialize(heroToPlace));
+		gameState->scenarioOps->campState->getCurrentScenario().placedCrossoverHeroes.push_back(CampaignState::crossoverSerialize(heroToPlace));
 	}
 }
 
@@ -575,7 +577,7 @@ void CGameStateCampaign::initStartingResources()
 	};
 
 	auto chosenBonus = gameState->scenarioOps->campState->getBonusForCurrentMap();
-	if(chosenBonus && chosenBonus->type == CScenarioTravel::STravelBonus::RESOURCE)
+	if(chosenBonus && chosenBonus->type == CampaignBonusType::RESOURCE)
 	{
 		std::vector<const PlayerSettings *> people = getHumanPlayerInfo(); //players we will give resource bonus
 		for(const PlayerSettings *ps : people)
@@ -613,7 +615,7 @@ void CGameStateCampaign::initTowns()
 {
 	auto chosenBonus = gameState->scenarioOps->campState->getBonusForCurrentMap();
 
-	if (chosenBonus && chosenBonus->type == CScenarioTravel::STravelBonus::BUILDING)
+	if (chosenBonus && chosenBonus->type == CampaignBonusType::BUILDING)
 	{
 		for (int g=0; g<gameState->map->towns.size(); ++g)
 		{
@@ -626,7 +628,7 @@ void CGameStateCampaign::initTowns()
 					gameState->map->towns[g]->pos == pi.posOfMainTown)
 				{
 					BuildingID buildingId;
-					if(gameState->scenarioOps->campState->camp->header.version == CampaignVersion::VCMI)
+					if(gameState->scenarioOps->campState->header.version == CampaignVersion::VCMI)
 						buildingId = BuildingID(chosenBonus->info1);
 					else
 						buildingId = CBuildingHandler::campToERMU(chosenBonus->info1, gameState->map->towns[g]->subID, gameState->map->towns[g]->builtBuildings);
@@ -646,14 +648,14 @@ bool CGameStateCampaign::playerHasStartingHero(PlayerColor playerColor) const
 	if (!campaignBonus)
 		return false;
 
-	if(campaignBonus->type == CScenarioTravel::STravelBonus::HERO && playerColor == PlayerColor(campaignBonus->info1))
+	if(campaignBonus->type == CampaignBonusType::HERO && playerColor == PlayerColor(campaignBonus->info1))
 		return true;
 	return false;
 }
 
-CMap * CGameStateCampaign::getCurrentMap() const
+std::unique_ptr<CMap> CGameStateCampaign::getCurrentMap() const
 {
-	return gameState->scenarioOps->campState->getMap();
+	return gameState->scenarioOps->campState->getMap(CampaignScenarioID::NONE);
 }
 
 VCMI_LIB_NAMESPACE_END

+ 3 - 3
lib/gameState/CGameStateCampaign.h

@@ -13,7 +13,7 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class CScenarioTravel;
+class CampaignTravel;
 class CGHeroInstance;
 class CGameState;
 class CMap;
@@ -43,7 +43,7 @@ class CGameStateCampaign
 	std::vector<CampaignHeroReplacement> generateCampaignHeroesToReplace(CrossoverHeroesList & crossoverHeroes);
 
 	/// Trims hero parameters that should not transfer between scenarios according to travelOptions flags
-	void trimCrossoverHeroesParameters(std::vector<CampaignHeroReplacement> & campaignHeroReplacements, const CScenarioTravel & travelOptions);
+	void trimCrossoverHeroesParameters(std::vector<CampaignHeroReplacement> & campaignHeroReplacements, const CampaignTravel & travelOptions);
 
 	void replaceHeroesPlaceholders(const std::vector<CampaignHeroReplacement> & campaignHeroReplacements);
 
@@ -59,7 +59,7 @@ public:
 	void initTowns();
 
 	bool playerHasStartingHero(PlayerColor player) const;
-	CMap * getCurrentMap() const;
+	std::unique_ptr<CMap> getCurrentMap() const;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{

+ 1 - 1
lib/mapObjects/CGHeroInstance.h

@@ -42,7 +42,7 @@ public:
 class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator, public CArtifactSet, public spells::Caster, public AFactionMember, public ICreatureUpgrader
 {
 	// We serialize heroes into JSON for crossover
-	friend class CCampaignState;
+	friend class CampaignState;
 	friend class CMapLoaderH3M;
 	friend class CMapFormatJson;
 

+ 168 - 170
lib/mapping/CCampaignHandler.cpp

@@ -71,19 +71,23 @@ CampaignRegions CampaignRegions::getLegacy(int campId)
 }
 
 
-bool CScenarioTravel::STravelBonus::isBonusForHero() const
+bool CampaignBonus::isBonusForHero() const
 {
-	return type == SPELL || type == MONSTER || type == ARTIFACT || type == SPELL_SCROLL || type == PRIMARY_SKILL
-		|| type == SECONDARY_SKILL;
+	return type == CampaignBonusType::SPELL ||
+		   type == CampaignBonusType::MONSTER ||
+		   type == CampaignBonusType::ARTIFACT ||
+		   type == CampaignBonusType::SPELL_SCROLL ||
+		   type == CampaignBonusType::PRIMARY_SKILL ||
+		   type == CampaignBonusType::SECONDARY_SKILL;
 }
 
-void CCampaignHeader::loadLegacyData(ui8 campId)
+void CampaignHeader::loadLegacyData(ui8 campId)
 {
 	campaignRegions = CampaignRegions::getLegacy(campId);
 	numberOfScenarios = VLC->generaltexth->getCampaignLength(campId);
 }
 
-CCampaignHeader CCampaignHandler::getHeader( const std::string & name)
+CampaignHeader CampaignHandler::getHeader( const std::string & name)
 {
 	ResourceID resourceID(name, EResType::CAMPAIGN);
 	std::string modName = VLC->modh->findResourceOrigin(resourceID);
@@ -105,14 +109,14 @@ CCampaignHeader CCampaignHandler::getHeader( const std::string & name)
 	return readHeaderFromJson(jsonCampaign, resourceID.getName(), modName, encoding);
 }
 
-std::unique_ptr<CCampaign> CCampaignHandler::getCampaign( const std::string & name )
+std::shared_ptr<CampaignState> CampaignHandler::getCampaign( const std::string & name )
 {
 	ResourceID resourceID(name, EResType::CAMPAIGN);
 	std::string modName = VLC->modh->findResourceOrigin(resourceID);
 	std::string language = VLC->modh->getModLanguage(modName);
 	std::string encoding = Languages::getLanguageOptions(language).encoding;
 	
-	auto ret = std::make_unique<CCampaign>();
+	auto ret = std::make_unique<CampaignState>();
 	
 	auto fileStream = CResourceHandler::get(modName)->load(resourceID);
 
@@ -125,20 +129,30 @@ std::unique_ptr<CCampaign> CCampaignHandler::getCampaign( const std::string & na
 		ret->header = readHeaderFromMemory(reader, resourceID.getName(), modName, encoding);
 		
 		for(int g = 0; g < ret->header.numberOfScenarios; ++g)
-			ret->scenarios.emplace_back(readScenarioFromMemory(reader, ret->header));
+		{
+			auto scenarioID = static_cast<CampaignScenarioID>(ret->scenarios.size());
+			ret->scenarios[scenarioID] = readScenarioFromMemory(reader, ret->header);
+		}
 	}
 	else // text format (json)
 	{
 		JsonNode jsonCampaign((const char*)files[0].data(), files[0].size());
 		ret->header = readHeaderFromJson(jsonCampaign, resourceID.getName(), modName, encoding);
+
+
 		for(auto & scenario : jsonCampaign["scenarios"].Vector())
-			ret->scenarios.emplace_back(readScenarioFromJson(scenario));
+		{
+			auto scenarioID = static_cast<CampaignScenarioID>(ret->scenarios.size());
+			ret->scenarios[scenarioID] = readScenarioFromJson(scenario);
+		}
 	}
 	
 	//first entry is campaign header. start loop from 1
 	for(int scenarioID = 0, g = 1; g < files.size() && scenarioID < ret->header.numberOfScenarios; ++g)
 	{
-		while(!ret->scenarios[scenarioID].isNotVoid()) //skip void scenarios
+		auto id = static_cast<CampaignScenarioID>(scenarioID);
+
+		while(!ret->scenarios[id].isNotVoid()) //skip void scenarios
 			scenarioID++;
 
 		std::string scenarioName = resourceID.getName();
@@ -146,30 +160,24 @@ std::unique_ptr<CCampaign> CCampaignHandler::getCampaign( const std::string & na
 		scenarioName += ':' + std::to_string(g - 1);
 
 		//set map piece appropriately, convert vector to string
-		ret->mapPieces[scenarioID].assign(reinterpret_cast<const char*>(files[g].data()), files[g].size());
+		ret->mapPieces[id].assign(reinterpret_cast<const char*>(files[g].data()), files[g].size());
 		CMapService mapService;
 		auto hdr = mapService.loadMapHeader(
-			reinterpret_cast<const ui8 *>(ret->mapPieces[scenarioID].c_str()),
-			static_cast<int>(ret->mapPieces[scenarioID].size()),
+			reinterpret_cast<const ui8 *>(ret->mapPieces[id].c_str()),
+			static_cast<int>(ret->mapPieces[id].size()),
 			scenarioName,
 			modName,
 			encoding);
-		ret->scenarios[scenarioID].scenarioName = hdr->name;
+		ret->scenarios[id].scenarioName = hdr->name;
 		scenarioID++;
 	}
 
-	// handle campaign specific discrepancies
-	if(resourceID.getName() == "DATA/AB")
+	for(int i = 0; i < ret->scenarios.size(); i++)
 	{
-		ret->scenarios[6].keepHeroes.emplace_back(155); // keep hero Xeron for map 7 To Kill A Hero
-	}
-	else if(resourceID.getName() == "DATA/FINAL")
-	{
-		// keep following heroes for map 8 Final H
-		ret->scenarios[7].keepHeroes.emplace_back(148); // Gelu
-		ret->scenarios[7].keepHeroes.emplace_back(27); // Gem
-		ret->scenarios[7].keepHeroes.emplace_back(102); // Crag Hack
-		ret->scenarios[7].keepHeroes.emplace_back(96); // Yog
+		auto scenarioID = static_cast<CampaignScenarioID>(i);
+
+		if(vstd::contains(ret->mapPieces, scenarioID)) //not all maps must be present in a campaign
+			ret->mapsRemaining.push_back(scenarioID);
 	}
 
 	return ret;
@@ -188,7 +196,7 @@ static std::string convertMapName(std::string input)
 	return input;
 }
 
-std::string CCampaignHandler::readLocalizedString(CBinaryReader & reader, std::string filename, std::string modName, std::string encoding, std::string identifier)
+std::string CampaignHandler::readLocalizedString(CBinaryReader & reader, std::string filename, std::string modName, std::string encoding, std::string identifier)
 {
 	TextIdentifier stringID( "campaign", convertMapName(filename), identifier);
 
@@ -201,14 +209,14 @@ std::string CCampaignHandler::readLocalizedString(CBinaryReader & reader, std::s
 	return VLC->generaltexth->translate(stringID.get());
 }
 
-CCampaignHeader CCampaignHandler::readHeaderFromJson(JsonNode & reader, std::string filename, std::string modName, std::string encoding)
+CampaignHeader CampaignHandler::readHeaderFromJson(JsonNode & reader, std::string filename, std::string modName, std::string encoding)
 {
-	CCampaignHeader ret;
+	CampaignHeader ret;
 
-	ret.version = reader["version"].Integer();
+	ret.version = static_cast<CampaignVersion>(reader["version"].Integer());
 	if(ret.version < CampaignVersion::VCMI_MIN || ret.version > CampaignVersion::VCMI_MAX)
 	{
-		logGlobal->info("VCMP Loading: Unsupported campaign %s version %d", filename, ret.version);
+		logGlobal->info("VCMP Loading: Unsupported campaign %s version %d", filename, static_cast<int>(ret.version));
 		return ret;
 	}
 	
@@ -226,11 +234,11 @@ CCampaignHeader CCampaignHandler::readHeaderFromJson(JsonNode & reader, std::str
 	return ret;
 }
 
-CCampaignScenario CCampaignHandler::readScenarioFromJson(JsonNode & reader)
+CampaignScenario CampaignHandler::readScenarioFromJson(JsonNode & reader)
 {
-	auto prologEpilogReader = [](JsonNode & identifier) -> CCampaignScenario::SScenarioPrologEpilog
+	auto prologEpilogReader = [](JsonNode & identifier) -> CampaignScenarioPrologEpilog
 	{
-		CCampaignScenario::SScenarioPrologEpilog ret;
+		CampaignScenarioPrologEpilog ret;
 		ret.hasPrologEpilog = !identifier.isNull();
 		if(ret.hasPrologEpilog)
 		{
@@ -241,11 +249,11 @@ CCampaignScenario CCampaignHandler::readScenarioFromJson(JsonNode & reader)
 		return ret;
 	};
 
-	CCampaignScenario ret;
+	CampaignScenario ret;
 	ret.conquered = false;
 	ret.mapName = reader["map"].String();
 	for(auto & g : reader["preconditions"].Vector())
-		ret.preconditionRegions.insert(g.Integer());
+		ret.preconditionRegions.insert(static_cast<CampaignScenarioID>(g.Integer()));
 
 	ret.regionColor = reader["color"].Integer();
 	ret.difficulty = reader["difficulty"].Integer();
@@ -258,26 +266,26 @@ CCampaignScenario CCampaignHandler::readScenarioFromJson(JsonNode & reader)
 	return ret;
 }
 
-CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
+CampaignTravel CampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
 {
-	CScenarioTravel ret;
+	CampaignTravel ret;
 
-	std::map<std::string, ui8> startOptionsMap = {
-		{"none", 0},
-		{"bonus", 1},
-		{"crossover", 2},
-		{"hero", 3}
+	std::map<std::string, CampaignStartOptions> startOptionsMap = {
+		{"none", CampaignStartOptions::NONE},
+		{"bonus", CampaignStartOptions::START_BONUS},
+		{"crossover", CampaignStartOptions::HERO_CROSSOVER},
+		{"hero", CampaignStartOptions::HERO_OPTIONS}
 	};
 	
-	std::map<std::string, CScenarioTravel::STravelBonus::EBonusType> bonusTypeMap = {
-		{"spell", CScenarioTravel::STravelBonus::EBonusType::SPELL},
-		{"creature", CScenarioTravel::STravelBonus::EBonusType::MONSTER},
-		{"building", CScenarioTravel::STravelBonus::EBonusType::BUILDING},
-		{"artifact", CScenarioTravel::STravelBonus::EBonusType::ARTIFACT},
-		{"scroll", CScenarioTravel::STravelBonus::EBonusType::SPELL_SCROLL},
-		{"primarySkill", CScenarioTravel::STravelBonus::EBonusType::PRIMARY_SKILL},
-		{"secondarySkill", CScenarioTravel::STravelBonus::EBonusType::SECONDARY_SKILL},
-		{"resource", CScenarioTravel::STravelBonus::EBonusType::RESOURCE},
+	std::map<std::string, CampaignBonusType> bonusTypeMap = {
+		{"spell", CampaignBonusType::SPELL},
+		{"creature", CampaignBonusType::MONSTER},
+		{"building", CampaignBonusType::BUILDING},
+		{"artifact", CampaignBonusType::ARTIFACT},
+		{"scroll", CampaignBonusType::SPELL_SCROLL},
+		{"primarySkill", CampaignBonusType::PRIMARY_SKILL},
+		{"secondarySkill", CampaignBonusType::SECONDARY_SKILL},
+		{"resource", CampaignBonusType::RESOURCE},
 		//{"prevHero", CScenarioTravel::STravelBonus::EBonusType::HEROES_FROM_PREVIOUS_SCENARIO},
 		//{"hero", CScenarioTravel::STravelBonus::EBonusType::HERO},
 	};
@@ -336,24 +344,24 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
 	ret.startOptions = startOptionsMap[reader["startOptions"].String()];
 	switch(ret.startOptions)
 	{
-	case 0:
+	case CampaignStartOptions::NONE:
 		//no bonuses. Seems to be OK
 		break;
-	case 1: //reading of bonuses player can choose
+	case CampaignStartOptions::START_BONUS: //reading of bonuses player can choose
 		{
 			ret.playerColor = reader["playerColor"].Integer();
 			for(auto & bjson : reader["bonuses"].Vector())
 			{
-				CScenarioTravel::STravelBonus bonus;
+				CampaignBonus bonus;
 				bonus.type = bonusTypeMap[bjson["what"].String()];
 				switch (bonus.type)
 				{
-					case CScenarioTravel::STravelBonus::EBonusType::RESOURCE:
+					case CampaignBonusType::RESOURCE:
 						bonus.info1 = resourceTypeMap[bjson["type"].String()];
 						bonus.info2 = bjson["amount"].Integer();
 						break;
 						
-					case CScenarioTravel::STravelBonus::EBonusType::BUILDING:
+					case CampaignBonusType::BUILDING:
 						bonus.info1 = vstd::find_pos(EBuildingType::names, bjson["type"].String());
 						if(bonus.info1 == -1)
 							logGlobal->warn("VCMP Loading: unresolved building identifier %s", bjson["type"].String());
@@ -372,24 +380,24 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
 						
 						switch(bonus.type)
 						{
-							case CScenarioTravel::STravelBonus::EBonusType::SPELL:
-							case CScenarioTravel::STravelBonus::EBonusType::MONSTER:
-							case CScenarioTravel::STravelBonus::EBonusType::SECONDARY_SKILL:
-							case CScenarioTravel::STravelBonus::EBonusType::ARTIFACT:
+							case CampaignBonusType::SPELL:
+							case CampaignBonusType::MONSTER:
+							case CampaignBonusType::SECONDARY_SKILL:
+							case CampaignBonusType::ARTIFACT:
 								if(auto identifier  = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), bjson["what"].String(), bjson["type"].String()))
 									bonus.info2 = identifier.value();
 								else
 									logGlobal->warn("VCMP Loading: unresolved %s identifier %s", bjson["what"].String(), bjson["type"].String());
 								break;
 								
-							case CScenarioTravel::STravelBonus::EBonusType::SPELL_SCROLL:
+							case CampaignBonusType::SPELL_SCROLL:
 								if(auto Identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "spell", bjson["type"].String()))
 									bonus.info2 = Identifier.value();
 								else
 									logGlobal->warn("VCMP Loading: unresolved spell scroll identifier %s", bjson["type"].String());
 								break;
 								
-							case CScenarioTravel::STravelBonus::EBonusType::PRIMARY_SKILL:
+							case CampaignBonusType::PRIMARY_SKILL:
 								for(auto & ps : primarySkillsMap)
 									bonus.info2 |= bjson[ps.first].Integer() << ps.second;
 								break;
@@ -403,24 +411,24 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
 			}
 			break;
 		}
-	case 2: //reading of players (colors / scenarios ?) player can choose
+	case CampaignStartOptions::HERO_CROSSOVER: //reading of players (colors / scenarios ?) player can choose
 		{
 			for(auto & bjson : reader["bonuses"].Vector())
 			{
-				CScenarioTravel::STravelBonus bonus;
-				bonus.type = CScenarioTravel::STravelBonus::HEROES_FROM_PREVIOUS_SCENARIO;
+				CampaignBonus bonus;
+				bonus.type = CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO;
 				bonus.info1 = bjson["playerColor"].Integer(); //player color
 				bonus.info2 = bjson["scenario"].Integer(); //from what scenario
 				ret.bonusesToChoose.push_back(bonus);
 			}
 			break;
 		}
-	case 3: //heroes player can choose between
+	case CampaignStartOptions::HERO_OPTIONS: //heroes player can choose between
 		{
 			for(auto & bjson : reader["bonuses"].Vector())
 			{
-				CScenarioTravel::STravelBonus bonus;
-				bonus.type = CScenarioTravel::STravelBonus::HERO;
+				CampaignBonus bonus;
+				bonus.type = CampaignBonusType::HERO;
 				bonus.info1 = bjson["playerColor"].Integer(); //player color
 				
 				if(int heroId = heroSpecialMap[bjson["hero"].String()])
@@ -446,11 +454,11 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
 }
 
 
-CCampaignHeader CCampaignHandler::readHeaderFromMemory( CBinaryReader & reader, std::string filename, std::string modName, std::string encoding )
+CampaignHeader CampaignHandler::readHeaderFromMemory( CBinaryReader & reader, std::string filename, std::string modName, std::string encoding )
 {
-	CCampaignHeader ret;
+	CampaignHeader ret;
 
-	ret.version = reader.readUInt32();
+	ret.version = static_cast<CampaignVersion>(reader.readUInt32());
 	ui8 campId = reader.readUInt8() - 1;//change range of it from [1, 20] to [0, 19]
 	ret.loadLegacyData(campId);
 	ret.name = readLocalizedString(reader, filename, modName, encoding, "name");
@@ -467,22 +475,22 @@ CCampaignHeader CCampaignHandler::readHeaderFromMemory( CBinaryReader & reader,
 	return ret;
 }
 
-CCampaignScenario CCampaignHandler::readScenarioFromMemory( CBinaryReader & reader, const CCampaignHeader & header)
+CampaignScenario CampaignHandler::readScenarioFromMemory( CBinaryReader & reader, const CampaignHeader & header)
 {
-	auto prologEpilogReader = [&](const std::string & identifier) -> CCampaignScenario::SScenarioPrologEpilog
+	auto prologEpilogReader = [&](const std::string & identifier) -> CampaignScenarioPrologEpilog
 	{
-		CCampaignScenario::SScenarioPrologEpilog ret;
+		CampaignScenarioPrologEpilog ret;
 		ret.hasPrologEpilog = reader.readUInt8();
 		if(ret.hasPrologEpilog)
 		{
-			ret.prologVideo = CCampaignHandler::prologVideoName(reader.readUInt8());
-			ret.prologMusic = CCampaignHandler::prologMusicName(reader.readUInt8());
+			ret.prologVideo = CampaignHandler::prologVideoName(reader.readUInt8());
+			ret.prologMusic = CampaignHandler::prologMusicName(reader.readUInt8());
 			ret.prologText = readLocalizedString(reader, header.filename, header.modName, header.encoding, identifier);
 		}
 		return ret;
 	};
 
-	CCampaignScenario ret;
+	CampaignScenario ret;
 	ret.conquered = false;
 	ret.mapName = reader.readBaseString();
 	reader.readUInt32(); //packedMapSize - not used
@@ -505,18 +513,30 @@ CCampaignScenario CCampaignHandler::readScenarioFromMemory( CBinaryReader & read
 	return ret;
 }
 
-void CCampaignScenario::loadPreconditionRegions(ui32 regions)
+void CampaignScenario::loadPreconditionRegions(ui32 regions)
 {
 	for (int i=0; i<32; i++) //for each bit in region. h3c however can only hold up to 16
 	{
 		if ( (1 << i) & regions)
-			preconditionRegions.insert(i);
+			preconditionRegions.insert(static_cast<CampaignScenarioID>(i));
+	}
+}
+
+template<typename Identifier>
+static void readContainer(std::set<Identifier> container, CBinaryReader & reader, int sizeBytes)
+{
+	for(int iId = 0, byte = 0; iId < sizeBytes * 8; ++iId)
+	{
+		if(iId % 8 == 0)
+			byte = reader.readUInt8();
+		if(byte & (1 << iId % 8))
+			container.insert(Identifier(iId));
 	}
 }
 
-CScenarioTravel CCampaignHandler::readScenarioTravelFromMemory(CBinaryReader & reader, int version )
+CampaignTravel CampaignHandler::readScenarioTravelFromMemory(CBinaryReader & reader, CampaignVersion version )
 {
-	CScenarioTravel ret;
+	CampaignTravel ret;
 
 	ui8 whatHeroKeeps = reader.readUInt8();
 	ret.whatHeroKeeps.experience = whatHeroKeeps & 1;
@@ -525,83 +545,71 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromMemory(CBinaryReader & r
 	ret.whatHeroKeeps.spells = whatHeroKeeps & 8;
 	ret.whatHeroKeeps.artifacts = whatHeroKeeps & 16;
 	
-	//TODO: replace with template lambda form C++20 and make typed containers
-	auto bitMaskToId = [&reader](std::set<int> & container, int size)
-	{
-		for(int iId = 0, byte = 0; iId < size * 8; ++iId)
-		{
-			if(iId % 8 == 0)
-				byte = reader.readUInt8();
-			if(byte & (1 << iId % 8))
-				container.insert(iId);
-		}
-	};
-	
-	bitMaskToId(ret.monstersKeptByHero, 19);
-	bitMaskToId(ret.artifactsKeptByHero, version < CampaignVersion::SoD ? 17 : 18);
+	readContainer(ret.monstersKeptByHero, reader, 19);
+	readContainer(ret.artifactsKeptByHero, reader, version < CampaignVersion::SoD ? 17 : 18);
 
-	ret.startOptions = reader.readUInt8();
+	ret.startOptions = static_cast<CampaignStartOptions>(reader.readUInt8());
 
 	switch(ret.startOptions)
 	{
-	case 0:
+	case CampaignStartOptions::NONE:
 		//no bonuses. Seems to be OK
 		break;
-	case 1: //reading of bonuses player can choose
+	case CampaignStartOptions::START_BONUS: //reading of bonuses player can choose
 		{
 			ret.playerColor = reader.readUInt8();
 			ui8 numOfBonuses = reader.readUInt8();
 			for (int g=0; g<numOfBonuses; ++g)
 			{
-				CScenarioTravel::STravelBonus bonus;
-				bonus.type = static_cast<CScenarioTravel::STravelBonus::EBonusType>(reader.readUInt8());
+				CampaignBonus bonus;
+				bonus.type = static_cast<CampaignBonusType>(reader.readUInt8());
 				//hero: FFFD means 'most powerful' and FFFE means 'generated'
 				switch(bonus.type)
 				{
-				case CScenarioTravel::STravelBonus::SPELL:
+				case CampaignBonusType::SPELL:
 					{
 						bonus.info1 = reader.readUInt16(); //hero
 						bonus.info2 = reader.readUInt8(); //spell ID
 						break;
 					}
-				case CScenarioTravel::STravelBonus::MONSTER:
+				case CampaignBonusType::MONSTER:
 					{
 						bonus.info1 = reader.readUInt16(); //hero
 						bonus.info2 = reader.readUInt16(); //monster type
 						bonus.info3 = reader.readUInt16(); //monster count
 						break;
 					}
-				case CScenarioTravel::STravelBonus::BUILDING:
+				case CampaignBonusType::BUILDING:
 					{
 						bonus.info1 = reader.readUInt8(); //building ID (0 - town hall, 1 - city hall, 2 - capitol, etc)
 						break;
 					}
-				case CScenarioTravel::STravelBonus::ARTIFACT:
+				case CampaignBonusType::ARTIFACT:
 					{
 						bonus.info1 = reader.readUInt16(); //hero
 						bonus.info2 = reader.readUInt16(); //artifact ID
 						break;
 					}
-				case CScenarioTravel::STravelBonus::SPELL_SCROLL:
+				case CampaignBonusType::SPELL_SCROLL:
 					{
 						bonus.info1 = reader.readUInt16(); //hero
 						bonus.info2 = reader.readUInt8(); //spell ID
 						break;
 					}
-				case CScenarioTravel::STravelBonus::PRIMARY_SKILL:
+				case CampaignBonusType::PRIMARY_SKILL:
 					{
 						bonus.info1 = reader.readUInt16(); //hero
 						bonus.info2 = reader.readUInt32(); //bonuses (4 bytes for 4 skills)
 						break;
 					}
-				case CScenarioTravel::STravelBonus::SECONDARY_SKILL:
+				case CampaignBonusType::SECONDARY_SKILL:
 					{
 						bonus.info1 = reader.readUInt16(); //hero
 						bonus.info2 = reader.readUInt8(); //skill ID
 						bonus.info3 = reader.readUInt8(); //skill level
 						break;
 					}
-				case CScenarioTravel::STravelBonus::RESOURCE:
+				case CampaignBonusType::RESOURCE:
 					{
 						bonus.info1 = reader.readUInt8(); //type
 						//FD - wood+ore
@@ -617,13 +625,13 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromMemory(CBinaryReader & r
 			}
 			break;
 		}
-	case 2: //reading of players (colors / scenarios ?) player can choose
+	case CampaignStartOptions::HERO_CROSSOVER: //reading of players (colors / scenarios ?) player can choose
 		{
 			ui8 numOfBonuses = reader.readUInt8();
 			for (int g=0; g<numOfBonuses; ++g)
 			{
-				CScenarioTravel::STravelBonus bonus;
-				bonus.type = CScenarioTravel::STravelBonus::HEROES_FROM_PREVIOUS_SCENARIO;
+				CampaignBonus bonus;
+				bonus.type = CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO;
 				bonus.info1 = reader.readUInt8(); //player color
 				bonus.info2 = reader.readUInt8(); //from what scenario
 
@@ -631,13 +639,13 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromMemory(CBinaryReader & r
 			}
 			break;
 		}
-	case 3: //heroes player can choose between
+	case CampaignStartOptions::HERO_OPTIONS: //heroes player can choose between
 		{
 			ui8 numOfBonuses = reader.readUInt8();
 			for (int g=0; g<numOfBonuses; ++g)
 			{
-				CScenarioTravel::STravelBonus bonus;
-				bonus.type = CScenarioTravel::STravelBonus::HERO;
+				CampaignBonus bonus;
+				bonus.type = CampaignBonusType::HERO;
 				bonus.info1 = reader.readUInt8(); //player color
 				bonus.info2 = reader.readUInt16(); //hero, FF FF - random
 
@@ -655,7 +663,7 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromMemory(CBinaryReader & r
 	return ret;
 }
 
-std::vector< std::vector<ui8> > CCampaignHandler::getFile(std::unique_ptr<CInputStream> file, bool headerOnly)
+std::vector< std::vector<ui8> > CampaignHandler::getFile(std::unique_ptr<CInputStream> file, bool headerOnly)
 {
 	CCompressedStream stream(std::move(file), true);
 
@@ -671,38 +679,37 @@ std::vector< std::vector<ui8> > CCampaignHandler::getFile(std::unique_ptr<CInput
 	return ret;
 }
 
-bool CCampaign::conquerable( int whichScenario ) const
+bool CampaignState::conquerable(CampaignScenarioID whichScenario) const
 {
 	//check for void scenraio
-	if (!scenarios[whichScenario].isNotVoid())
+	if (!scenarios.at(whichScenario).isNotVoid())
 	{
 		return false;
 	}
 
-	if (scenarios[whichScenario].conquered)
+	if (scenarios.at(whichScenario).conquered)
 	{
 		return false;
 	}
 	//check preconditioned regions
-	for (int g=0; g<scenarios.size(); ++g)
+	for (auto const & it : scenarios.at(whichScenario).preconditionRegions)
 	{
-		if( vstd::contains(scenarios[whichScenario].preconditionRegions, g) && !scenarios[g].conquered)
-			return false; //prerequisite does not met
-
+		if (!scenarios.at(it).conquered)
+			return false;
 	}
 	return true;
 }
 
-bool CCampaignScenario::isNotVoid() const
+bool CampaignScenario::isNotVoid() const
 {
 	return !mapName.empty();
 }
 
-const CGHeroInstance * CCampaignScenario::strongestHero(const PlayerColor & owner)
+const CGHeroInstance * CampaignScenario::strongestHero(const PlayerColor & owner)
 {
 	std::function<bool(JsonNode & node)> isOwned = [owner](JsonNode & node)
 	{
-		auto * h = CCampaignState::crossoverDeserialize(node);
+		auto * h = CampaignState::crossoverDeserialize(node);
 		bool result = h->tempOwner == owner;
 		vstd::clear_pointer(h);
 		return result;
@@ -711,25 +718,25 @@ const CGHeroInstance * CCampaignScenario::strongestHero(const PlayerColor & owne
 
 	auto i = vstd::maxElementByFun(ownedHeroes, [](JsonNode & node)
 	{
-		auto * h = CCampaignState::crossoverDeserialize(node);
+		auto * h = CampaignState::crossoverDeserialize(node);
 		double result = h->getHeroStrength();
 		vstd::clear_pointer(h);
 		return result;
 	});
-	return i == ownedHeroes.end() ? nullptr : CCampaignState::crossoverDeserialize(*i);
+	return i == ownedHeroes.end() ? nullptr : CampaignState::crossoverDeserialize(*i);
 }
 
-std::vector<CGHeroInstance *> CCampaignScenario::getLostCrossoverHeroes()
+std::vector<CGHeroInstance *> CampaignScenario::getLostCrossoverHeroes()
 {
 	std::vector<CGHeroInstance *> lostCrossoverHeroes;
 	if(conquered)
 	{
 		for(auto node2 : placedCrossoverHeroes)
 		{
-			auto * hero = CCampaignState::crossoverDeserialize(node2);
+			auto * hero = CampaignState::crossoverDeserialize(node2);
 			auto it = range::find_if(crossoverHeroes, [hero](JsonNode node)
 			{
-				auto * h = CCampaignState::crossoverDeserialize(node);
+				auto * h = CampaignState::crossoverDeserialize(node);
 				bool result = hero->subID == h->subID;
 				vstd::clear_pointer(h);
 				return result;
@@ -743,96 +750,87 @@ std::vector<CGHeroInstance *> CCampaignScenario::getLostCrossoverHeroes()
 	return lostCrossoverHeroes;
 }
 
-void CCampaignState::setCurrentMapAsConquered(const std::vector<CGHeroInstance *> & heroes)
+void CampaignState::setCurrentMapAsConquered(const std::vector<CGHeroInstance *> & heroes)
 {
-	camp->scenarios[*currentMap].crossoverHeroes.clear();
+	scenarios.at(*currentMap).crossoverHeroes.clear();
 	for(CGHeroInstance * hero : heroes)
 	{
-		camp->scenarios[*currentMap].crossoverHeroes.push_back(crossoverSerialize(hero));
+		scenarios.at(*currentMap).crossoverHeroes.push_back(crossoverSerialize(hero));
 	}
 
 	mapsConquered.push_back(*currentMap);
 	mapsRemaining -= *currentMap;
-	camp->scenarios[*currentMap].conquered = true;
+	scenarios.at(*currentMap).conquered = true;
 }
 
-std::optional<CScenarioTravel::STravelBonus> CCampaignState::getBonusForCurrentMap() const
+std::optional<CampaignBonus> CampaignState::getBonusForCurrentMap() const
 {
 	auto bonuses = getCurrentScenario().travelOptions.bonusesToChoose;
 	assert(chosenCampaignBonuses.count(*currentMap) || bonuses.size() == 0);
 
 	if(bonuses.empty())
-		return std::optional<CScenarioTravel::STravelBonus>();
+		return std::optional<CampaignBonus>();
 	else
 		return bonuses[currentBonusID()];
 }
 
-const CCampaignScenario & CCampaignState::getCurrentScenario() const
+const CampaignScenario & CampaignState::getCurrentScenario() const
 {
-	return camp->scenarios[*currentMap];
+	return scenarios.at(*currentMap);
 }
 
-CCampaignScenario & CCampaignState::getCurrentScenario()
+CampaignScenario & CampaignState::getCurrentScenario()
 {
-	return camp->scenarios[*currentMap];
+	return scenarios.at(*currentMap);
 }
 
-ui8 CCampaignState::currentBonusID() const
+ui8 CampaignState::currentBonusID() const
 {
 	return chosenCampaignBonuses.at(*currentMap);
 }
 
-CCampaignState::CCampaignState( std::unique_ptr<CCampaign> _camp ) : camp(std::move(_camp))
-{
-	for(int i = 0; i < camp->scenarios.size(); i++)
-	{
-		if(vstd::contains(camp->mapPieces, i)) //not all maps must be present in a campaign
-			mapsRemaining.push_back(i);
-	}
-}
-
-CMap * CCampaignState::getMap(int scenarioId) const
+std::unique_ptr<CMap> CampaignState::getMap(CampaignScenarioID scenarioId) const
 {
 	// FIXME: there is certainly better way to handle maps inside campaigns
-	if(scenarioId == -1)
+	if(scenarioId == CampaignScenarioID::NONE)
 		scenarioId = currentMap.value();
 	
 	CMapService mapService;
-	std::string scenarioName = camp->header.filename.substr(0, camp->header.filename.find('.'));
+	std::string scenarioName = header.filename.substr(0, header.filename.find('.'));
 	boost::to_lower(scenarioName);
-	scenarioName += ':' + std::to_string(scenarioId);
-	std::string & mapContent = camp->mapPieces.find(scenarioId)->second;
+	scenarioName += ':' + std::to_string(static_cast<int>(scenarioId));
+	const std::string & mapContent = mapPieces.find(scenarioId)->second;
 	const auto * buffer = reinterpret_cast<const ui8 *>(mapContent.data());
-	return mapService.loadMap(buffer, static_cast<int>(mapContent.size()), scenarioName, camp->header.modName, camp->header.encoding).release();
+	return mapService.loadMap(buffer, static_cast<int>(mapContent.size()), scenarioName, header.modName, header.encoding);
 }
 
-std::unique_ptr<CMapHeader> CCampaignState::getHeader(int scenarioId) const
+std::unique_ptr<CMapHeader> CampaignState::getHeader(CampaignScenarioID scenarioId) const
 {
-	if(scenarioId == -1)
+	if(scenarioId == CampaignScenarioID::NONE)
 		scenarioId = currentMap.value();
 	
 	CMapService mapService;
-	std::string scenarioName = camp->header.filename.substr(0, camp->header.filename.find('.'));
+	std::string scenarioName = header.filename.substr(0, header.filename.find('.'));
 	boost::to_lower(scenarioName);
-	scenarioName += ':' + std::to_string(scenarioId);
-	std::string & mapContent = camp->mapPieces.find(scenarioId)->second;
+	scenarioName += ':' + std::to_string(static_cast<int>(scenarioId));
+	const std::string & mapContent = mapPieces.find(scenarioId)->second;
 	const auto * buffer = reinterpret_cast<const ui8 *>(mapContent.data());
-	return mapService.loadMapHeader(buffer, static_cast<int>(mapContent.size()), scenarioName, camp->header.modName, camp->header.encoding);
+	return mapService.loadMapHeader(buffer, static_cast<int>(mapContent.size()), scenarioName, header.modName, header.encoding);
 }
 
-std::shared_ptr<CMapInfo> CCampaignState::getMapInfo(int scenarioId) const
+std::shared_ptr<CMapInfo> CampaignState::getMapInfo(CampaignScenarioID scenarioId) const
 {
-	if(scenarioId == -1)
+	if(scenarioId == CampaignScenarioID::NONE)
 		scenarioId = currentMap.value();
 
 	auto mapInfo = std::make_shared<CMapInfo>();
-	mapInfo->fileURI = camp->header.filename;
+	mapInfo->fileURI = header.filename;
 	mapInfo->mapHeader = getHeader(scenarioId);
 	mapInfo->countPlayers();
 	return mapInfo;
 }
 
-JsonNode CCampaignState::crossoverSerialize(CGHeroInstance * hero)
+JsonNode CampaignState::crossoverSerialize(CGHeroInstance * hero)
 {
 	JsonNode node;
 	JsonSerializer handler(nullptr, node);
@@ -840,7 +838,7 @@ JsonNode CCampaignState::crossoverSerialize(CGHeroInstance * hero)
 	return node;
 }
 
-CGHeroInstance * CCampaignState::crossoverDeserialize(JsonNode & node)
+CGHeroInstance * CampaignState::crossoverDeserialize(JsonNode & node)
 {
 	JsonDeserializer handler(nullptr, node);
 	auto * hero = new CGHeroInstance();
@@ -849,7 +847,7 @@ CGHeroInstance * CCampaignState::crossoverDeserialize(JsonNode & node)
 	return hero;
 }
 
-std::string CCampaignHandler::prologVideoName(ui8 index)
+std::string CampaignHandler::prologVideoName(ui8 index)
 {
 	JsonNode config(ResourceID(std::string("CONFIG/campaignMedia"), EResType::TEXT));
 	auto vids = config["videos"].Vector();
@@ -858,13 +856,13 @@ std::string CCampaignHandler::prologVideoName(ui8 index)
 	return "";
 }
 
-std::string CCampaignHandler::prologMusicName(ui8 index)
+std::string CampaignHandler::prologMusicName(ui8 index)
 {
 	std::vector<std::string> music;
 	return VLC->generaltexth->translate("core.cmpmusic." + std::to_string(static_cast<int>(index)));
 }
 
-std::string CCampaignHandler::prologVoiceName(ui8 index)
+std::string CampaignHandler::prologVoiceName(ui8 index)
 {
 	JsonNode config(ResourceID(std::string("CONFIG/campaignMedia"), EResType::TEXT));
 	auto audio = config["voice"].Vector();

+ 120 - 105
lib/mapping/CCampaignHandler.h

@@ -22,21 +22,19 @@ class CMapHeader;
 class CMapInfo;
 class JsonNode;
 
-namespace CampaignVersion
+enum class CampaignVersion : uint8_t
 {
-	enum Version
-	{
-		RoE = 4,
-		AB = 5,
-		SoD = 6,
-		WoG = 6,
-//		Chr = 7, // Heroes Chronicles, likely identical to SoD, untested
-		VCMI = 1
-	};
-
-	const int VCMI_MIN = 1;
-	const int VCMI_MAX = 1;
-}
+	NONE = 0,
+	RoE = 4,
+	AB = 5,
+	SoD = 6,
+	WoG = 6,
+	//		Chr = 7, // Heroes Chronicles, likely identical to SoD, untested
+
+	VCMI = 1,
+	VCMI_MIN = 1,
+	VCMI_MAX = 1,
+};
 
 struct DLL_LINKAGE CampaignRegions
 {
@@ -71,10 +69,10 @@ struct DLL_LINKAGE CampaignRegions
 	static CampaignRegions getLegacy(int campId);
 };
 
-class DLL_LINKAGE CCampaignHeader
+class DLL_LINKAGE CampaignHeader
 {
 public:
-	si32 version = 0; //4 - RoE, 5 - AB, 6 - SoD, WoG and HotA
+	CampaignVersion version = CampaignVersion::NONE;
 	CampaignRegions campaignRegions;
 	int numberOfScenarios = 0;
 	std::string name, description;
@@ -102,7 +100,56 @@ public:
 	}
 };
 
-class DLL_LINKAGE CScenarioTravel
+enum class CampaignStartOptions: int8_t
+{
+	NONE = 0,
+	START_BONUS,
+	HERO_CROSSOVER,
+	HERO_OPTIONS
+};
+
+enum class CampaignBonusType : int8_t
+{
+	NONE = -1,
+	SPELL,
+	MONSTER,
+	BUILDING,
+	ARTIFACT,
+	SPELL_SCROLL,
+	PRIMARY_SKILL,
+	SECONDARY_SKILL,
+	RESOURCE,
+	HEROES_FROM_PREVIOUS_SCENARIO,
+	HERO
+};
+
+enum class CampaignScenarioID : int8_t
+{
+	NONE = -1,
+	// no members - fake enum to create integer type that is not implicitly convertible to int
+};
+
+struct DLL_LINKAGE CampaignBonus
+{
+	CampaignBonusType type = CampaignBonusType::NONE; //uses EBonusType
+
+	//purpose depends on type
+	int32_t info1 = 0;
+	int32_t info2 = 0;
+	int32_t info3 = 0;
+
+	bool isBonusForHero() const;
+
+	template <typename Handler> void serialize(Handler &h, const int formatVersion)
+	{
+		h & type;
+		h & info1;
+		h & info2;
+		h & info3;
+	}
+};
+
+class DLL_LINKAGE CampaignTravel
 {
 public:
 	
@@ -124,35 +171,13 @@ public:
 		}
 	};
 	
-	WhatHeroKeeps whatHeroKeeps;
-	
-	//TODO: use typed containers
-	std::set<int> monstersKeptByHero;
-	std::set<int> artifactsKeptByHero;
-
-	ui8 startOptions = 0; //1 - start bonus, 2 - traveling hero, 3 - hero options
+	std::set<CreatureID> monstersKeptByHero;
+	std::set<ArtifactID> artifactsKeptByHero;
+	std::vector<CampaignBonus> bonusesToChoose;
 
-	ui8 playerColor = 0; //only for startOptions == 1
-
-	struct DLL_LINKAGE STravelBonus
-	{
-		enum EBonusType {SPELL, MONSTER, BUILDING, ARTIFACT, SPELL_SCROLL, PRIMARY_SKILL, SECONDARY_SKILL, RESOURCE,
-			HEROES_FROM_PREVIOUS_SCENARIO, HERO};
-		EBonusType type = EBonusType::SPELL; //uses EBonusType
-		si32 info1 = 0, info2 = 0, info3 = 0; //purpose depends on type
-
-		bool isBonusForHero() const;
-
-		template <typename Handler> void serialize(Handler &h, const int formatVersion)
-		{
-			h & type;
-			h & info1;
-			h & info2;
-			h & info3;
-		}
-	};
-
-	std::vector<STravelBonus> bonusesToChoose;
+	WhatHeroKeeps whatHeroKeeps;
+	CampaignStartOptions startOptions = CampaignStartOptions::NONE; //1 - start bonus, 2 - traveling hero, 3 - hero options
+	PlayerColor playerColor = PlayerColor::NEUTRAL; //only for startOptions == 1
 
 	template <typename Handler> void serialize(Handler &h, const int formatVersion)
 	{
@@ -163,39 +188,39 @@ public:
 		h & playerColor;
 		h & bonusesToChoose;
 	}
-
 };
 
-class DLL_LINKAGE CCampaignScenario
+struct DLL_LINKAGE CampaignScenarioPrologEpilog
 {
-public:
-	struct DLL_LINKAGE SScenarioPrologEpilog
-	{
-		bool hasPrologEpilog = false;
-		std::string prologVideo; // from CmpMovie.txt
-		std::string prologMusic; // from CmpMusic.txt
-		std::string prologText;
+	bool hasPrologEpilog = false;
+	std::string prologVideo; // from CmpMovie.txt
+	std::string prologMusic; // from CmpMusic.txt
+	std::string prologText;
 
-		template <typename Handler> void serialize(Handler &h, const int formatVersion)
-		{
-			h & hasPrologEpilog;
-			h & prologVideo;
-			h & prologMusic;
-			h & prologText;
-		}
-	};
+	template <typename Handler> void serialize(Handler &h, const int formatVersion)
+	{
+		h & hasPrologEpilog;
+		h & prologVideo;
+		h & prologMusic;
+		h & prologText;
+	}
+};
 
+class DLL_LINKAGE CampaignScenario
+{
+public:
 	std::string mapName; //*.h3m
 	std::string scenarioName; //from header. human-readble
-	std::set<ui8> preconditionRegions; //what we need to conquer to conquer this one (stored as bitfield in h3c)
+	std::set<CampaignScenarioID> preconditionRegions; //what we need to conquer to conquer this one (stored as bitfield in h3c)
 	ui8 regionColor = 0;
 	ui8 difficulty = 0;
 	bool conquered = false;
 
 	std::string regionText;
-	SScenarioPrologEpilog prolog, epilog;
+	CampaignScenarioPrologEpilog prolog;
+	CampaignScenarioPrologEpilog epilog;
 
-	CScenarioTravel travelOptions;
+	CampaignTravel travelOptions;
 	std::vector<HeroTypeID> keepHeroes; // contains list of heroes which should be kept for next scenario (doesn't matter if they lost)
 	std::vector<JsonNode> crossoverHeroes; // contains all heroes with the same state when the campaign scenario was finished
 	std::vector<JsonNode> placedCrossoverHeroes; // contains all placed crossover heroes defined by hero placeholders when the scenario was started
@@ -224,52 +249,42 @@ public:
 	}
 };
 
-class DLL_LINKAGE CCampaign
+class DLL_LINKAGE CampaignState
 {
 public:
-	CCampaignHeader header;
-	std::vector<CCampaignScenario> scenarios;
-	std::map<int, std::string > mapPieces; //binary h3ms, scenario number -> map data
-
-	template <typename Handler> void serialize(Handler &h, const int formatVersion)
-	{
-		h & header;
-		h & scenarios;
-		h & mapPieces;
-	}
-
-	bool conquerable(int whichScenario) const;
-};
+	CampaignHeader header;
+	std::map<CampaignScenarioID, CampaignScenario> scenarios;
+	std::map<CampaignScenarioID, std::string > mapPieces; //binary h3ms, scenario number -> map data
 
-class DLL_LINKAGE CCampaignState
-{
-public:
-	std::unique_ptr<CCampaign> camp;
 	std::string fileEncoding;
-	std::vector<ui8> mapsConquered, mapsRemaining;
-	std::optional<si32> currentMap;
+	std::vector<CampaignScenarioID> mapsConquered;
+	std::vector<CampaignScenarioID> mapsRemaining;
+	std::optional<CampaignScenarioID> currentMap;
 
-	std::map<ui8, ui8> chosenCampaignBonuses;
+	std::map<CampaignScenarioID, ui8> chosenCampaignBonuses;
 
-	void setCurrentMapAsConquered(const std::vector<CGHeroInstance*> & heroes);
-	std::optional<CScenarioTravel::STravelBonus> getBonusForCurrentMap() const;
-	const CCampaignScenario & getCurrentScenario() const;
-	CCampaignScenario & getCurrentScenario();
+public:
+	std::optional<CampaignBonus> getBonusForCurrentMap() const;
+	const CampaignScenario & getCurrentScenario() const;
 	ui8 currentBonusID() const;
+	bool conquerable(CampaignScenarioID whichScenario) const;
 
-	CMap * getMap(int scenarioId = -1) const;
-	std::unique_ptr<CMapHeader> getHeader(int scenarioId = -1) const;
-	std::shared_ptr<CMapInfo> getMapInfo(int scenarioId = -1) const;
+	std::unique_ptr<CMap> getMap(CampaignScenarioID scenarioId) const;
+	std::unique_ptr<CMapHeader> getHeader(CampaignScenarioID scenarioId) const;
+	std::shared_ptr<CMapInfo> getMapInfo(CampaignScenarioID scenarioId) const;
 
+	CampaignScenario & getCurrentScenario();
+	void setCurrentMapAsConquered(const std::vector<CGHeroInstance*> & heroes);
 	static JsonNode crossoverSerialize(CGHeroInstance * hero);
 	static CGHeroInstance * crossoverDeserialize(JsonNode & node);
 
-	CCampaignState() = default;
-	CCampaignState(std::unique_ptr<CCampaign> _camp);
+	CampaignState() = default;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & camp;
+		h & header;
+		h & scenarios;
+		h & mapPieces;
 		h & mapsRemaining;
 		h & mapsConquered;
 		h & currentMap;
@@ -277,19 +292,19 @@ public:
 	}
 };
 
-class DLL_LINKAGE CCampaignHandler
+class DLL_LINKAGE CampaignHandler
 {
 	static std::string readLocalizedString(CBinaryReader & reader, std::string filename, std::string modName, std::string encoding, std::string identifier);
 	
 	//parsers for VCMI campaigns (*.vcmp)
-	static CCampaignHeader readHeaderFromJson(JsonNode & reader, std::string filename, std::string modName, std::string encoding);
-	static CCampaignScenario readScenarioFromJson(JsonNode & reader);
-	static CScenarioTravel readScenarioTravelFromJson(JsonNode & reader);
+	static CampaignHeader readHeaderFromJson(JsonNode & reader, std::string filename, std::string modName, std::string encoding);
+	static CampaignScenario readScenarioFromJson(JsonNode & reader);
+	static CampaignTravel readScenarioTravelFromJson(JsonNode & reader);
 
 	//parsers for original H3C campaigns
-	static CCampaignHeader readHeaderFromMemory(CBinaryReader & reader, std::string filename, std::string modName, std::string encoding);
-	static CCampaignScenario readScenarioFromMemory(CBinaryReader & reader, const CCampaignHeader & header);
-	static CScenarioTravel readScenarioTravelFromMemory(CBinaryReader & reader, int version);
+	static CampaignHeader readHeaderFromMemory(CBinaryReader & reader, std::string filename, std::string modName, std::string encoding);
+	static CampaignScenario readScenarioFromMemory(CBinaryReader & reader, const CampaignHeader & header);
+	static CampaignTravel readScenarioTravelFromMemory(CBinaryReader & reader, CampaignVersion version);
 	/// returns h3c split in parts. 0 = h3c header, 1-end - maps (binary h3m)
 	/// headerOnly - only header will be decompressed, returned vector wont have any maps
 	static std::vector<std::vector<ui8>> getFile(std::unique_ptr<CInputStream> file, bool headerOnly);
@@ -299,9 +314,9 @@ class DLL_LINKAGE CCampaignHandler
 	static std::string prologVoiceName(ui8 index);
 
 public:
-	static CCampaignHeader getHeader( const std::string & name); //name - name of appropriate file
+	static CampaignHeader getHeader( const std::string & name); //name - name of appropriate file
 
-	static std::unique_ptr<CCampaign> getCampaign(const std::string & name); //name - name of appropriate file
+	static std::shared_ptr<CampaignState> getCampaign(const std::string & name); //name - name of appropriate file
 };
 
 VCMI_LIB_NAMESPACE_END

+ 1 - 1
lib/mapping/CMapInfo.cpp

@@ -66,7 +66,7 @@ void CMapInfo::saveInit(const ResourceID & file)
 
 void CMapInfo::campaignInit()
 {
-	campaignHeader = std::make_unique<CCampaignHeader>(CCampaignHandler::getHeader(fileURI));
+	campaignHeader = std::make_unique<CampaignHeader>(CampaignHandler::getHeader(fileURI));
 	if(!campaignHeader->valid)
 		campaignHeader.reset();
 }

+ 2 - 2
lib/mapping/CMapInfo.h

@@ -14,7 +14,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 struct StartInfo;
 
 class CMapHeader;
-class CCampaignHeader;
+class CampaignHeader;
 class ResourceID;
 
 /**
@@ -25,7 +25,7 @@ class DLL_LINKAGE CMapInfo
 {
 public:
 	std::unique_ptr<CMapHeader> mapHeader; //may be nullptr if campaign
-	std::unique_ptr<CCampaignHeader> campaignHeader; //may be nullptr if scenario
+	std::unique_ptr<CampaignHeader> campaignHeader; //may be nullptr if scenario
 	StartInfo * scenarioOptionsOfSave; // Options with which scenario has been started (used only with saved games)
 	std::string fileURI;
 	std::string date;

+ 5 - 5
server/CVCMIServer.cpp

@@ -870,10 +870,10 @@ void CVCMIServer::optionNextCastle(PlayerColor player, int dir)
 		s.bonus = PlayerSettings::RANDOM;
 }
 
-void CVCMIServer::setCampaignMap(int mapId)
+void CVCMIServer::setCampaignMap(CampaignScenarioID mapId)
 {
 	campaignMap = mapId;
-	si->difficulty = si->campState->camp->scenarios[mapId].difficulty;
+	si->difficulty = si->campState->scenarios[mapId].difficulty;
 	campaignBonus = -1;
 	updateStartInfoOnMapChange(si->campState->getMapInfo(mapId));
 }
@@ -882,9 +882,9 @@ void CVCMIServer::setCampaignBonus(int bonusId)
 {
 	campaignBonus = bonusId;
 
-	const CCampaignScenario & scenario = si->campState->camp->scenarios[campaignMap];
-	const std::vector<CScenarioTravel::STravelBonus> & bonDescs = scenario.travelOptions.bonusesToChoose;
-	if(bonDescs[bonusId].type == CScenarioTravel::STravelBonus::HERO)
+	const CampaignScenario & scenario = si->campState->scenarios[campaignMap];
+	const std::vector<CampaignBonus> & bonDescs = scenario.travelOptions.bonusesToChoose;
+	if(bonDescs[bonusId].type == CampaignBonusType::HERO)
 	{
 		for(auto & elem : si->playerInfos)
 		{

+ 1 - 1
server/CVCMIServer.h

@@ -112,7 +112,7 @@ public:
 	void optionNextCastle(PlayerColor player, int dir); //dir == -1 or +
 
 	// Campaigns
-	void setCampaignMap(int mapId);
+	void setCampaignMap(CampaignScenarioID mapId);
 	void setCampaignBonus(int bonusId);
 
 	ui8 getIdOfFirstUnallocatedPlayer() const;

+ 8 - 6
server/NetPacksLobbyServer.cpp

@@ -213,18 +213,20 @@ void ApplyOnServerNetPackVisitor::visitLobbySetMap(LobbySetMap & pack)
 
 void ApplyOnServerNetPackVisitor::visitLobbySetCampaign(LobbySetCampaign & pack)
 {
-	srv.si->mapname = pack.ourCampaign->camp->header.filename;
+	srv.si->mapname = pack.ourCampaign->header.filename;
 	srv.si->mode = StartInfo::CAMPAIGN;
 	srv.si->campState = pack.ourCampaign;
 	srv.si->turnTime = 0;
-	bool isCurrentMapConquerable = pack.ourCampaign->currentMap && pack.ourCampaign->camp->conquerable(*pack.ourCampaign->currentMap);
-	for(int i = 0; i < pack.ourCampaign->camp->scenarios.size(); i++)
+	bool isCurrentMapConquerable = pack.ourCampaign->currentMap && pack.ourCampaign->conquerable(*pack.ourCampaign->currentMap);
+	for(int i = 0; i < pack.ourCampaign->scenarios.size(); i++)
 	{
-		if(pack.ourCampaign->camp->conquerable(i))
+		auto scenarioID = static_cast<CampaignScenarioID>(i);
+
+		if(pack.ourCampaign->conquerable(scenarioID))
 		{
-			if(!isCurrentMapConquerable || (isCurrentMapConquerable && i == *pack.ourCampaign->currentMap))
+			if(!isCurrentMapConquerable || (isCurrentMapConquerable && scenarioID == *pack.ourCampaign->currentMap))
 			{
-				srv.setCampaignMap(i);
+				srv.setCampaignMap(scenarioID);
 			}
 		}
 	}