Sfoglia il codice sorgente

Use inheritance instead of composition for campaign header

Ivan Savenko 2 anni fa
parent
commit
f6b2f58da9

+ 6 - 6
client/lobby/CBonusSelection.cpp

@@ -64,7 +64,7 @@ CBonusSelection::CBonusSelection()
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 
-	std::string bgName = getCampaign()->getHeader().campaignRegions.campPrefix + "_BG.BMP";
+	std::string bgName = getCampaign()->getRegions().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()->getHeader().description, Rect(480, 86, 286, 117), 1);
+	campaignDescription = std::make_shared<CTextBox>(getCampaign()->getDescription(), 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,7 +99,7 @@ CBonusSelection::CBonusSelection()
 		difficultyIcons[b] = std::make_shared<CAnimImage>("GSPBUT" + std::to_string(b + 3) + ".DEF", 0, 0, 709, 455);
 	}
 
-	if(getCampaign()->getHeader().difficultyChoosenByPlayer)
+	if(getCampaign()->playerSelectedDifficulty())
 	{
 		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));
@@ -108,9 +108,9 @@ CBonusSelection::CBonusSelection()
 	for(auto scenarioID : getCampaign()->allScenarios())
 	{
 		if(getCampaign()->isAvailable(scenarioID))
-			regions.push_back(std::make_shared<CRegion>(scenarioID, true, true, getCampaign()->getHeader().campaignRegions));
+			regions.push_back(std::make_shared<CRegion>(scenarioID, true, true, getCampaign()->getRegions()));
 		else if(getCampaign()->isConquered(scenarioID)) //display as striped
-			regions.push_back(std::make_shared<CRegion>(scenarioID, false, false, getCampaign()->getHeader().campaignRegions));
+			regions.push_back(std::make_shared<CRegion>(scenarioID, false, false, getCampaign()->getRegions()));
 	}
 }
 
@@ -170,7 +170,7 @@ void CBonusSelection::createBonusesIcons()
 			assert(faction != -1);
 
 			BuildingID buildID;
-			if(getCampaign()->getHeader().version == CampaignVersion::VCMI)
+			if(getCampaign()->formatVCMI())
 				buildID = BuildingID(bonDescs[i].info1);
 			else
 				buildID = CBuildingHandler::campToERMU(bonDescs[i].info1, faction, std::set<BuildingID>());

+ 6 - 8
client/lobby/SelectionTab.cpp

@@ -104,13 +104,11 @@ bool mapSorter::operator()(const std::shared_ptr<CMapInfo> aaa, const std::share
 		switch(sortBy)
 		{
 		case _numOfMaps: //by number of maps in campaign
-			return aaa->campaignHeader->numberOfScenarios <
-				   bbb->campaignHeader->numberOfScenarios;
-			break;
+			return aaa->campaign->scenariosCount() < bbb->campaign->scenariosCount();
 		case _name: //by name
-			return boost::ilexicographical_compare(aaa->campaignHeader->name, bbb->campaignHeader->name);
+			return boost::ilexicographical_compare(aaa->campaign->getName(), bbb->campaign->getName());
 		default:
-			return boost::ilexicographical_compare(aaa->campaignHeader->name, bbb->campaignHeader->name);
+			return boost::ilexicographical_compare(aaa->campaign->getName(), bbb->campaign->getName());
 		}
 	}
 }
@@ -623,7 +621,7 @@ void SelectionTab::parseCampaigns(const std::unordered_set<ResourceID> & files)
 		//allItems[i].date = std::asctime(std::localtime(&files[i].date));
 		info->fileURI = file.getName();
 		info->campaignInit();
-		if(info->campaignHeader)
+		if(info->campaign)
 			allItems.push_back(info);
 	}
 }
@@ -677,7 +675,7 @@ void SelectionTab::ListItem::updateItem(std::shared_ptr<CMapInfo> info, bool sel
 	}
 
 	auto color = selected ? Colors::YELLOW : Colors::WHITE;
-	if(info->campaignHeader)
+	if(info->campaign)
 	{
 		labelAmountOfPlayers->disable();
 		labelMapSizeLetter->disable();
@@ -686,7 +684,7 @@ void SelectionTab::ListItem::updateItem(std::shared_ptr<CMapInfo> info, bool sel
 		iconLossCondition->disable();
 		labelNumberOfCampaignMaps->enable();
 		std::ostringstream ostr(std::ostringstream::out);
-		ostr << info->campaignHeader->numberOfScenarios;
+		ostr << info->campaign->scenariosCount();
 		labelNumberOfCampaignMaps->setText(ostr.str());
 		labelNumberOfCampaignMaps->setColor(color);
 	}

+ 1 - 1
client/mainmenu/CCampaignScreen.cpp

@@ -97,7 +97,7 @@ CCampaignScreen::CCampaignButton::CCampaignButton(const JsonNode & config)
 	status = config["open"].Bool() ? CCampaignScreen::ENABLED : CCampaignScreen::DISABLED;
 
 	auto header = CampaignHandler::getHeader(campFile);
-	hoverText = header->name;
+	hoverText = header->getName();
 
 	if(status != CCampaignScreen::DISABLED)
 	{

+ 1 - 1
client/windows/CCastleInterface.cpp

@@ -923,7 +923,7 @@ 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 &&                // We're in campaign,
-			(si->campState->getHeader().filename == "DATA/YOG.H3C") && // which is "Birth of a Barbarian",
+			(si->campState->getFilename() == "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..."

+ 2 - 2
lib/StartInfo.cpp

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

+ 39 - 44
lib/campaign/CampaignHandler.cpp

@@ -27,26 +27,49 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-std::unique_ptr<CampaignHeader> CampaignHandler::getHeader( const std::string & name)
+void CampaignHandler::readCampaign(Campaign * ret, const std::vector<ui8> & input, std::string filename, std::string modName, std::string encoding)
+{
+	if (input.front() < uint8_t(' ')) // binary format
+	{
+		CMemoryStream stream(input.data(), input.size());
+		CBinaryReader reader(&stream);
+
+		readHeaderFromMemory(*ret, reader, filename, modName, encoding);
+
+		for(int g = 0; g < ret->numberOfScenarios; ++g)
+		{
+			auto scenarioID = static_cast<CampaignScenarioID>(ret->scenarios.size());
+			ret->scenarios[scenarioID] = readScenarioFromMemory(reader, *ret);
+		}
+	}
+	else // text format (json)
+	{
+		JsonNode jsonCampaign((const char*)input.data(), input.size());
+		readHeaderFromJson(*ret, jsonCampaign, filename, modName, encoding);
+
+		for(auto & scenario : jsonCampaign["scenarios"].Vector())
+		{
+			auto scenarioID = static_cast<CampaignScenarioID>(ret->scenarios.size());
+			ret->scenarios[scenarioID] = readScenarioFromJson(scenario);
+		}
+	}
+}
+
+std::unique_ptr<Campaign> CampaignHandler::getHeader( 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<Campaign>();
 	auto fileStream = CResourceHandler::get(modName)->load(resourceID);
 	std::vector<ui8> cmpgn = getFile(std::move(fileStream), true)[0];
 	JsonNode jsonCampaign((const char*)cmpgn.data(), cmpgn.size());
-	if(jsonCampaign.isNull())
-	{
-		//legacy OH3 campaign (*.h3c)
-		CMemoryStream stream(cmpgn.data(), cmpgn.size());
-		CBinaryReader reader(&stream);
-		return std::make_unique<CampaignHeader>(readHeaderFromMemory(reader, resourceID.getName(), modName, encoding));
-	}
-	
-	//VCMI (*.vcmp)
-	return std::make_unique<CampaignHeader>(readHeaderFromJson(jsonCampaign, resourceID.getName(), modName, encoding));
+
+	readCampaign(ret.get(), cmpgn, resourceID.getName(), modName, encoding);
+
+	return ret;
 }
 
 std::shared_ptr<CampaignState> CampaignHandler::getCampaign( const std::string & name )
@@ -62,32 +85,10 @@ std::shared_ptr<CampaignState> CampaignHandler::getCampaign( const std::string &
 
 	std::vector<std::vector<ui8>> files = getFile(std::move(fileStream), false);
 
-	if (files[0].front() < uint8_t(' ')) // binary format
-	{
-		CMemoryStream stream(files[0].data(), files[0].size());
-		CBinaryReader reader(&stream);
-		ret->header = readHeaderFromMemory(reader, resourceID.getName(), modName, encoding);
-		
-		for(int g = 0; g < ret->header.numberOfScenarios; ++g)
-		{
-			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())
-		{
-			auto scenarioID = static_cast<CampaignScenarioID>(ret->scenarios.size());
-			ret->scenarios[scenarioID] = readScenarioFromJson(scenario);
-		}
-	}
+	readCampaign(ret.get(), files[0], resourceID.getName(), modName, encoding);
 	
 	//first entry is campaign header. start loop from 1
-	for(int scenarioID = 0, g = 1; g < files.size() && scenarioID < ret->header.numberOfScenarios; ++g)
+	for(int scenarioID = 0, g = 1; g < files.size() && scenarioID < ret->numberOfScenarios; ++g)
 	{
 		auto id = static_cast<CampaignScenarioID>(scenarioID);
 
@@ -140,15 +141,13 @@ std::string CampaignHandler::readLocalizedString(CBinaryReader & reader, std::st
 	return VLC->generaltexth->translate(stringID.get());
 }
 
-CampaignHeader CampaignHandler::readHeaderFromJson(JsonNode & reader, std::string filename, std::string modName, std::string encoding)
+void CampaignHandler::readHeaderFromJson(CampaignHeader & ret, JsonNode & reader, std::string filename, std::string modName, std::string encoding)
 {
-	CampaignHeader ret;
-
 	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, static_cast<int>(ret.version));
-		return ret;
+		return;
 	}
 	
 	ret.version = CampaignVersion::VCMI;
@@ -161,7 +160,6 @@ CampaignHeader CampaignHandler::readHeaderFromJson(JsonNode & reader, std::strin
 	ret.filename = filename;
 	ret.modName = modName;
 	ret.encoding = encoding;
-	return ret;
 }
 
 CampaignScenario CampaignHandler::readScenarioFromJson(JsonNode & reader)
@@ -383,10 +381,8 @@ CampaignTravel CampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
 }
 
 
-CampaignHeader CampaignHandler::readHeaderFromMemory( CBinaryReader & reader, std::string filename, std::string modName, std::string encoding )
+void CampaignHandler::readHeaderFromMemory( CampaignHeader & ret, CBinaryReader & reader, std::string filename, std::string modName, std::string encoding )
 {
-	CampaignHeader ret;
-
 	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);
@@ -400,7 +396,6 @@ CampaignHeader CampaignHandler::readHeaderFromMemory( CBinaryReader & reader, st
 	ret.filename = filename;
 	ret.modName = modName;
 	ret.encoding = encoding;
-	return ret;
 }
 
 CampaignScenario CampaignHandler::readScenarioFromMemory( CBinaryReader & reader, const CampaignHeader & header)

+ 6 - 4
lib/campaign/CampaignHandler.h

@@ -16,14 +16,16 @@ VCMI_LIB_NAMESPACE_BEGIN
 class DLL_LINKAGE CampaignHandler
 {
 	static std::string readLocalizedString(CBinaryReader & reader, std::string filename, std::string modName, std::string encoding, std::string identifier);
-	
+
+	static void readCampaign(Campaign * target, const std::vector<ui8> & stream, std::string filename, std::string modName, std::string encoding);
+
 	//parsers for VCMI campaigns (*.vcmp)
-	static CampaignHeader readHeaderFromJson(JsonNode & reader, std::string filename, std::string modName, std::string encoding);
+	static void readHeaderFromJson(CampaignHeader & target, 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 CampaignHeader readHeaderFromMemory(CBinaryReader & reader, std::string filename, std::string modName, std::string encoding);
+	static void readHeaderFromMemory(CampaignHeader & target, 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)
@@ -35,7 +37,7 @@ class DLL_LINKAGE CampaignHandler
 	static std::string prologVoiceName(ui8 index);
 
 public:
-	static std::unique_ptr<CampaignHeader> getHeader( const std::string & name); //name - name of appropriate file
+	static std::unique_ptr<Campaign> getHeader( const std::string & name); //name - name of appropriate file
 
 	static std::shared_ptr<CampaignState> getCampaign(const std::string & name); //name - name of appropriate file
 };

+ 52 - 30
lib/campaign/CampaignState.cpp

@@ -83,6 +83,36 @@ void CampaignHeader::loadLegacyData(ui8 campId)
 	numberOfScenarios = VLC->generaltexth->getCampaignLength(campId);
 }
 
+bool CampaignHeader::playerSelectedDifficulty() const
+{
+	return difficultyChoosenByPlayer;
+}
+
+bool CampaignHeader::formatVCMI() const
+{
+	return version == CampaignVersion::VCMI;
+}
+
+std::string CampaignHeader::getDescription() const
+{
+	return description;
+}
+
+std::string CampaignHeader::getName() const
+{
+	return name;
+}
+
+std::string CampaignHeader::getFilename() const
+{
+	return filename;
+}
+
+const CampaignRegions & CampaignHeader::getRegions() const
+{
+	return campaignRegions;
+}
+
 bool CampaignState::isConquered(CampaignScenarioID whichScenario) const
 {
 	return vstd::contains(mapsConquered, whichScenario);
@@ -91,7 +121,7 @@ bool CampaignState::isConquered(CampaignScenarioID whichScenario) const
 bool CampaignState::isAvailable(CampaignScenarioID whichScenario) const
 {
 	//check for void scenraio
-	if (!scenarios.at(whichScenario).isNotVoid())
+	if (!scenario(whichScenario).isNotVoid())
 	{
 		return false;
 	}
@@ -101,7 +131,7 @@ bool CampaignState::isAvailable(CampaignScenarioID whichScenario) const
 		return false;
 	}
 	//check preconditioned regions
-	for (auto const & it : scenarios.at(whichScenario).preconditionRegions)
+	for (auto const & it : scenario(whichScenario).preconditionRegions)
 	{
 		if (!vstd::contains(mapsConquered, it))
 			return false;
@@ -174,23 +204,21 @@ void CampaignState::setCurrentMapAsConquered(const std::vector<CGHeroInstance *>
 	mapsConquered.push_back(*currentMap);
 }
 
-std::optional<CampaignBonus> CampaignState::getBonusForCurrentMap() const
+std::optional<CampaignBonus> CampaignState::getBonus(CampaignScenarioID which) const
 {
-	auto bonuses = getCurrentScenario().travelOptions.bonusesToChoose;
+	auto bonuses = scenario(which).travelOptions.bonusesToChoose;
 	assert(chosenCampaignBonuses.count(*currentMap) || bonuses.size() == 0);
 
 	if(bonuses.empty())
 		return std::optional<CampaignBonus>();
-	else
-		return bonuses[currentBonusID()];
-}
 
-const CampaignScenario & CampaignState::getCurrentScenario() const
-{
-	return scenarios.at(*currentMap);
+	if (!getBonusID(which))
+		return std::optional<CampaignBonus>();
+
+	return bonuses[getBonusID(which).value()];
 }
 
-std::optional<ui8> CampaignState::getBonusID(CampaignScenarioID & which) const
+std::optional<ui8> CampaignState::getBonusID(CampaignScenarioID which) const
 {
 	if (!chosenCampaignBonuses.count(which))
 		return std::nullopt;
@@ -198,11 +226,6 @@ std::optional<ui8> CampaignState::getBonusID(CampaignScenarioID & which) const
 	return chosenCampaignBonuses.at(which);
 }
 
-ui8 CampaignState::currentBonusID() const
-{
-	return chosenCampaignBonuses.at(*currentMap);
-}
-
 std::unique_ptr<CMap> CampaignState::getMap(CampaignScenarioID scenarioId) const
 {
 	// FIXME: there is certainly better way to handle maps inside campaigns
@@ -210,12 +233,12 @@ std::unique_ptr<CMap> CampaignState::getMap(CampaignScenarioID scenarioId) const
 		scenarioId = currentMap.value();
 
 	CMapService mapService;
-	std::string scenarioName = header.filename.substr(0, header.filename.find('.'));
+	std::string scenarioName = filename.substr(0, filename.find('.'));
 	boost::to_lower(scenarioName);
 	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, header.modName, header.encoding);
+	return mapService.loadMap(buffer, static_cast<int>(mapContent.size()), scenarioName, modName, encoding);
 }
 
 std::unique_ptr<CMapHeader> CampaignState::getMapHeader(CampaignScenarioID scenarioId) const
@@ -224,12 +247,12 @@ std::unique_ptr<CMapHeader> CampaignState::getMapHeader(CampaignScenarioID scena
 		scenarioId = currentMap.value();
 
 	CMapService mapService;
-	std::string scenarioName = header.filename.substr(0, header.filename.find('.'));
+	std::string scenarioName = filename.substr(0, filename.find('.'));
 	boost::to_lower(scenarioName);
 	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, header.modName, header.encoding);
+	return mapService.loadMapHeader(buffer, static_cast<int>(mapContent.size()), scenarioName, modName, encoding);
 }
 
 std::shared_ptr<CMapInfo> CampaignState::getMapInfo(CampaignScenarioID scenarioId) const
@@ -238,7 +261,7 @@ std::shared_ptr<CMapInfo> CampaignState::getMapInfo(CampaignScenarioID scenarioI
 		scenarioId = currentMap.value();
 
 	auto mapInfo = std::make_shared<CMapInfo>();
-	mapInfo->fileURI = header.filename;
+	mapInfo->fileURI = filename;
 	mapInfo->mapHeader = getMapHeader(scenarioId);
 	mapInfo->countPlayers();
 	return mapInfo;
@@ -263,8 +286,7 @@ CGHeroInstance * CampaignState::crossoverDeserialize(const JsonNode & node)
 
 void CampaignState::setCurrentMap(CampaignScenarioID which)
 {
-	assert(scenarios.count(which));
-	assert(scenarios.at(which).isNotVoid());
+	assert(scenario(which).isNotVoid());
 
 	currentMap = which;
 }
@@ -293,7 +315,7 @@ std::set<CampaignScenarioID> CampaignState::conqueredScenarios() const
 	return result;
 }
 
-std::set<CampaignScenarioID> CampaignState::allScenarios() const
+std::set<CampaignScenarioID> Campaign::allScenarios() const
 {
 	std::set<CampaignScenarioID> result;
 
@@ -306,7 +328,12 @@ std::set<CampaignScenarioID> CampaignState::allScenarios() const
 	return result;
 }
 
-const CampaignScenario & CampaignState::scenario(CampaignScenarioID which) const
+int Campaign::scenariosCount() const
+{
+	return allScenarios().size();
+}
+
+const CampaignScenario & Campaign::scenario(CampaignScenarioID which) const
 {
 	assert(scenarios.count(which));
 	assert(scenarios.at(which).isNotVoid());
@@ -318,8 +345,3 @@ bool CampaignState::isCampaignFinished() const
 {
 	return conqueredScenarios() == allScenarios();
 }
-
-const CampaignHeader & CampaignState::getHeader() const
-{
-	return header;
-}

+ 39 - 16
lib/campaign/CampaignState.h

@@ -57,9 +57,10 @@ struct DLL_LINKAGE CampaignRegions
 	static CampaignRegions getLegacy(int campId);
 };
 
-class DLL_LINKAGE CampaignHeader
+class DLL_LINKAGE CampaignHeader : public boost::noncopyable
 {
-public:
+	friend class CampaignHandler;
+
 	int numberOfScenarios = 0;
 	CampaignVersion version = CampaignVersion::NONE;
 	CampaignRegions campaignRegions;
@@ -69,10 +70,21 @@ public:
 
 	void loadLegacyData(ui8 campId);
 
+protected:
 	std::string filename;
 	std::string modName;
 	std::string encoding;
 
+public:
+	bool playerSelectedDifficulty() const;
+	bool formatVCMI() const;
+
+	std::string getDescription() const;
+	std::string getName() const;
+	std::string getFilename() const;
+
+	const CampaignRegions & getRegions() const;
+
 	template <typename Handler> void serialize(Handler &h, const int formatVersion)
 	{
 		h & version;
@@ -195,34 +207,48 @@ struct DLL_LINKAGE CampaignHeroes
 	}
 };
 
-class DLL_LINKAGE CampaignState
+/// Class that represents loaded campaign information
+class DLL_LINKAGE Campaign : public CampaignHeader
+{
+	friend class CampaignHandler;
+
+	std::map<CampaignScenarioID, CampaignScenario> scenarios;
+
+public:
+	const CampaignScenario & scenario(CampaignScenarioID which) const;
+	std::set<CampaignScenarioID> allScenarios() const;
+	int scenariosCount() const;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CampaignHeader&>(*this);
+		h & scenarios;
+	}
+};
+
+/// Class that represent campaign that is being played at
+/// Contains campaign itself as well as current state of the campaign
+class DLL_LINKAGE CampaignState : public Campaign
 {
 	friend class CampaignHandler;
 
 	/// List of all maps completed by player, in order of their completion
 	std::vector<CampaignScenarioID> mapsConquered;
 
-	std::map<CampaignScenarioID, CampaignScenario> scenarios;
 	std::map<CampaignScenarioID, std::string > mapPieces; //binary h3ms, scenario number -> map data
 	std::map<CampaignScenarioID, ui8> chosenCampaignBonuses;
 	std::optional<CampaignScenarioID> currentMap;
 
-	CampaignHeader header;
 	CampaignHeroes crossover;
 
 public:
 	std::optional<CampaignScenarioID> lastScenario() const;
 	std::optional<CampaignScenarioID> currentScenario() const;
-	std::set<CampaignScenarioID> allScenarios() const;
 	std::set<CampaignScenarioID> conqueredScenarios() const;
 
-	const CampaignScenario & scenario(CampaignScenarioID which) const;
+	std::optional<CampaignBonus> getBonus(CampaignScenarioID which) const;
 
-	std::optional<CampaignBonus> getBonusForCurrentMap() const;
-	const CampaignScenario & getCurrentScenario() const;
-
-	std::optional<ui8> getBonusID(CampaignScenarioID & which) const;
-	ui8 currentBonusID() const;
+	std::optional<ui8> getBonusID(CampaignScenarioID which) const;
 
 	/// Returns true if selected scenario can be selected and started by player
 	bool isAvailable(CampaignScenarioID whichScenario) const;
@@ -233,8 +259,6 @@ public:
 	/// Returns true if all available scenarios have been completed and campaign is finished
 	bool isCampaignFinished() const;
 
-	const CampaignHeader & getHeader() const;
-
 	std::unique_ptr<CMap> getMap(CampaignScenarioID scenarioId) const;
 	std::unique_ptr<CMapHeader> getMapHeader(CampaignScenarioID scenarioId) const;
 	std::shared_ptr<CMapInfo> getMapInfo(CampaignScenarioID scenarioId) const;
@@ -257,8 +281,7 @@ public:
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & header;
-		h & scenarios;
+		h & static_cast<Campaign&>(*this);
 		h & crossover;
 		h & mapPieces;
 		h & mapsConquered;

+ 17 - 9
lib/gameState/CGameStateCampaign.cpp

@@ -54,12 +54,19 @@ CGameStateCampaign::CGameStateCampaign(CGameState * owner):
 	assert(gameState->scenarioOps->campState != nullptr);
 }
 
+std::optional<CampaignBonus> CGameStateCampaign::currentBonus() const
+{
+	auto campaignState = gameState->scenarioOps->campState;
+	return campaignState->getBonus(*campaignState->currentScenario());
+
+}
+
 CrossoverHeroesList CGameStateCampaign::getCrossoverHeroesFromPreviousScenarios() const
 {
 	CrossoverHeroesList crossoverHeroes;
 
 	auto campaignState = gameState->scenarioOps->campState;
-	auto bonus = campaignState->getBonusForCurrentMap();
+	auto bonus = currentBonus();
 	if(bonus && bonus->type == CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO)
 	{
 		auto scenarioID = static_cast<CampaignScenarioID>(bonus->info2);
@@ -253,7 +260,8 @@ void CGameStateCampaign::placeCampaignHeroes()
 	// - exception: if starting bonus is 'select player' then power placeholders are taken from 'scenario pool' of linked map
 
 	// place bonus hero
-	auto campaignBonus = gameState->scenarioOps->campState->getBonusForCurrentMap();
+	auto campaignState = gameState->scenarioOps->campState;
+	auto campaignBonus = campaignState->getBonus(*campaignState->currentScenario());
 	bool campaignGiveHero = campaignBonus && campaignBonus->type == CampaignBonusType::HERO;
 
 	if(campaignGiveHero)
@@ -281,7 +289,7 @@ void CGameStateCampaign::placeCampaignHeroes()
 		auto campaignHeroReplacements = generateCampaignHeroesToReplace(crossoverHeroes);
 
 		logGlobal->debug("\tPrepare crossover heroes");
-		trimCrossoverHeroesParameters(campaignHeroReplacements, gameState->scenarioOps->campState->getCurrentScenario().travelOptions);
+		trimCrossoverHeroesParameters(campaignHeroReplacements, campaignState->scenario(*campaignState->currentScenario()).travelOptions);
 
 		// remove same heroes on the map which will be added through crossover heroes
 		// INFO: we will remove heroes because later it may be possible that the API doesn't allow having heroes
@@ -338,7 +346,7 @@ void CGameStateCampaign::placeCampaignHeroes()
 
 void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero)
 {
-	const std::optional<CampaignBonus> & curBonus = gameState->scenarioOps->campState->getBonusForCurrentMap();
+	auto curBonus = currentBonus();
 	if(!curBonus)
 		return;
 
@@ -513,7 +521,7 @@ std::vector<CampaignHeroReplacement> CGameStateCampaign::generateCampaignHeroesT
 
 void CGameStateCampaign::initHeroes()
 {
-	auto chosenBonus = gameState->scenarioOps->campState->getBonusForCurrentMap();
+	auto chosenBonus = currentBonus();
 	if (chosenBonus && chosenBonus->isBonusForHero() && chosenBonus->info1 != 0xFFFE) //exclude generated heroes
 	{
 		//find human player
@@ -573,7 +581,7 @@ void CGameStateCampaign::initStartingResources()
 		return ret;
 	};
 
-	auto chosenBonus = gameState->scenarioOps->campState->getBonusForCurrentMap();
+	auto chosenBonus = currentBonus();
 	if(chosenBonus && chosenBonus->type == CampaignBonusType::RESOURCE)
 	{
 		std::vector<const PlayerSettings *> people = getHumanPlayerInfo(); //players we will give resource bonus
@@ -610,7 +618,7 @@ void CGameStateCampaign::initStartingResources()
 
 void CGameStateCampaign::initTowns()
 {
-	auto chosenBonus = gameState->scenarioOps->campState->getBonusForCurrentMap();
+	auto chosenBonus = currentBonus();
 
 	if (chosenBonus && chosenBonus->type == CampaignBonusType::BUILDING)
 	{
@@ -625,7 +633,7 @@ void CGameStateCampaign::initTowns()
 					gameState->map->towns[g]->pos == pi.posOfMainTown)
 				{
 					BuildingID buildingId;
-					if(gameState->scenarioOps->campState->getHeader().version == CampaignVersion::VCMI)
+					if(gameState->scenarioOps->campState->formatVCMI())
 						buildingId = BuildingID(chosenBonus->info1);
 					else
 						buildingId = CBuildingHandler::campToERMU(chosenBonus->info1, gameState->map->towns[g]->subID, gameState->map->towns[g]->builtBuildings);
@@ -640,7 +648,7 @@ void CGameStateCampaign::initTowns()
 
 bool CGameStateCampaign::playerHasStartingHero(PlayerColor playerColor) const
 {
-	auto campaignBonus = gameState->scenarioOps->campState->getBonusForCurrentMap();
+	auto campaignBonus = currentBonus();
 
 	if (!campaignBonus)
 		return false;

+ 3 - 0
lib/gameState/CGameStateCampaign.h

@@ -13,6 +13,7 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
+struct CampaignBonus;
 class CampaignTravel;
 class CGHeroInstance;
 class CGameState;
@@ -42,6 +43,8 @@ class CGameStateCampaign
 	/// returns heroes and placeholders in where heroes will be put
 	std::vector<CampaignHeroReplacement> generateCampaignHeroesToReplace(CrossoverHeroesList & crossoverHeroes);
 
+	std::optional<CampaignBonus> currentBonus() const;
+
 	/// Trims hero parameters that should not transfer between scenarios according to travelOptions flags
 	void trimCrossoverHeroesParameters(std::vector<CampaignHeroReplacement> & campaignHeroReplacements, const CampaignTravel & travelOptions);
 

+ 5 - 5
lib/mapping/CMapInfo.cpp

@@ -66,7 +66,7 @@ void CMapInfo::saveInit(const ResourceID & file)
 
 void CMapInfo::campaignInit()
 {
-	campaignHeader = CampaignHandler::getHeader(fileURI);
+	campaign = CampaignHandler::getHeader(fileURI);
 }
 
 void CMapInfo::countPlayers()
@@ -92,8 +92,8 @@ void CMapInfo::countPlayers()
 
 std::string CMapInfo::getName() const
 {
-	if(campaignHeader && !campaignHeader->name.empty())
-		return campaignHeader->name;
+	if(campaign && !campaign->getName().empty())
+		return campaign->getName();
 	else if(mapHeader && mapHeader->name.length())
 		return mapHeader->name;
 	else
@@ -117,8 +117,8 @@ std::string CMapInfo::getNameForList() const
 
 std::string CMapInfo::getDescription() const
 {
-	if(campaignHeader)
-		return campaignHeader->description;
+	if(campaign)
+		return campaign->getDescription();
 	else
 		return mapHeader->description;
 }

+ 3 - 3
lib/mapping/CMapInfo.h

@@ -14,7 +14,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 struct StartInfo;
 
 class CMapHeader;
-class CampaignHeader;
+class Campaign;
 class ResourceID;
 
 /**
@@ -25,7 +25,7 @@ class DLL_LINKAGE CMapInfo
 {
 public:
 	std::unique_ptr<CMapHeader> mapHeader; //may be nullptr if campaign
-	std::unique_ptr<CampaignHeader> campaignHeader; //may be nullptr if scenario
+	std::unique_ptr<Campaign> campaign; //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;
@@ -58,7 +58,7 @@ public:
 	template <typename Handler> void serialize(Handler &h, const int Version)
 	{
 		h & mapHeader;
-		h & campaignHeader;
+		h & campaign;
 		h & scenarioOptionsOfSave;
 		h & fileURI;
 		h & date;

+ 1 - 1
server/CVCMIServer.cpp

@@ -684,7 +684,7 @@ void CVCMIServer::updateStartInfoOnMapChange(std::shared_ptr<CMapInfo> mapInfo,
 	}
 	else if(si->mode == StartInfo::NEW_GAME || si->mode == StartInfo::CAMPAIGN)
 	{
-		if(mi->campaignHeader)
+		if(mi->campaign)
 			return;
 
 		for(int i = 0; i < mi->mapHeader->players.size(); i++)

+ 1 - 1
server/NetPacksLobbyServer.cpp

@@ -211,7 +211,7 @@ void ApplyOnServerNetPackVisitor::visitLobbySetMap(LobbySetMap & pack)
 
 void ApplyOnServerNetPackVisitor::visitLobbySetCampaign(LobbySetCampaign & pack)
 {
-	srv.si->mapname = pack.ourCampaign->getHeader().filename;
+	srv.si->mapname = pack.ourCampaign->getFilename();
 	srv.si->mode = StartInfo::CAMPAIGN;
 	srv.si->campState = pack.ourCampaign;
 	srv.si->turnTime = 0;