Explorar el Código

Merge pull request #4668 from Laserlicht/scenario_name

Chronicles improvements
Ivan Savenko hace 1 año
padre
commit
7f8f09c8fa

+ 20 - 6
client/lobby/CBonusSelection.cpp

@@ -55,6 +55,7 @@
 #include "../../lib/campaign/CampaignState.h"
 #include "../../lib/mapping/CMapService.h"
 #include "../../lib/mapping/CMapInfo.h"
+#include "../../lib/mapping/CMapHeader.h"
 
 #include "../../lib/mapObjects/CGHeroInstance.h"
 
@@ -126,9 +127,11 @@ CBonusSelection::CBonusSelection()
 	for(auto scenarioID : getCampaign()->allScenarios())
 	{
 		if(getCampaign()->isAvailable(scenarioID))
-			regions.push_back(std::make_shared<CRegion>(scenarioID, true, true, getCampaign()->getRegions()));
+			regions.push_back(std::make_shared<CRegion>(scenarioID, true, true, false, getCampaign()->getRegions()));
 		else if(getCampaign()->isConquered(scenarioID)) //display as striped
-			regions.push_back(std::make_shared<CRegion>(scenarioID, false, false, getCampaign()->getRegions()));
+			regions.push_back(std::make_shared<CRegion>(scenarioID, false, false, false, getCampaign()->getRegions()));
+		else
+			regions.push_back(std::make_shared<CRegion>(scenarioID, false, false, true, getCampaign()->getRegions()));
 	}
 
 	if (!getCampaign()->getMusic().empty())
@@ -400,6 +403,7 @@ void CBonusSelection::goBack()
 		CSH->state = EClientState::LOBBY;
 	}
 */
+	CMM->playMusic();
 }
 
 void CBonusSelection::startMap()
@@ -476,8 +480,8 @@ void CBonusSelection::decreaseDifficulty()
 		CSH->setDifficulty(CSH->si->difficulty - 1);
 }
 
-CBonusSelection::CRegion::CRegion(CampaignScenarioID id, bool accessible, bool selectable, const CampaignRegions & campDsc)
-	: CIntObject(LCLICK | SHOW_POPUP), idOfMapAndRegion(id), accessible(accessible), selectable(selectable)
+CBonusSelection::CRegion::CRegion(CampaignScenarioID id, bool accessible, bool selectable, bool labelOnly, const CampaignRegions & campDsc)
+	: CIntObject(LCLICK | SHOW_POPUP), idOfMapAndRegion(id), accessible(accessible), selectable(selectable), labelOnly(labelOnly)
 {
 	OBJECT_CONSTRUCTION;
 
@@ -493,10 +497,20 @@ CBonusSelection::CRegion::CRegion(CampaignScenarioID id, bool accessible, bool s
 	graphicsStriped->disable();
 	pos.w = graphicsNotSelected->pos.w;
 	pos.h = graphicsNotSelected->pos.h;
+
+	auto labelPos = campDsc.getLabelPosition(id);
+	if(labelPos)
+	{
+		auto mapHeader = CSH->si->campState->getMapHeader(idOfMapAndRegion);
+		label = std::make_shared<CLabel>((*labelPos).x, (*labelPos).y, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, mapHeader->name.toString());
+	}
 }
 
 void CBonusSelection::CRegion::updateState()
 {
+	if(labelOnly)
+		return;
+
 	if(!accessible)
 	{
 		graphicsNotSelected->disable();
@@ -519,7 +533,7 @@ void CBonusSelection::CRegion::updateState()
 
 void CBonusSelection::CRegion::clickReleased(const Point & cursorPosition)
 {
-	if(selectable && !graphicsNotSelected->getSurface()->isTransparent(cursorPosition - pos.topLeft()))
+	if(!labelOnly && selectable && !graphicsNotSelected->getSurface()->isTransparent(cursorPosition - pos.topLeft()))
 	{
 		CSH->setCampaignMap(idOfMapAndRegion);
 	}
@@ -529,7 +543,7 @@ void CBonusSelection::CRegion::showPopupWindow(const Point & cursorPosition)
 {
 	// FIXME: For some reason "down" is only ever contain indeterminate_value
 	auto & text = CSH->si->campState->scenario(idOfMapAndRegion).regionText;
-	if(!graphicsNotSelected->getSurface()->isTransparent(cursorPosition - pos.topLeft()) && !text.empty())
+	if(!labelOnly && !graphicsNotSelected->getSurface()->isTransparent(cursorPosition - pos.topLeft()) && !text.empty())
 	{
 		CRClickPopup::createAndPush(text.toString());
 	}

+ 3 - 1
client/lobby/CBonusSelection.h

@@ -49,8 +49,10 @@ public:
 		CampaignScenarioID idOfMapAndRegion;
 		bool accessible; // false if region should be striped
 		bool selectable; // true if region should be selectable
+		bool labelOnly;
+		std::shared_ptr<CLabel> label;
 	public:
-		CRegion(CampaignScenarioID id, bool accessible, bool selectable, const CampaignRegions & campDsc);
+		CRegion(CampaignScenarioID id, bool accessible, bool selectable, bool labelOnly, const CampaignRegions & campDsc);
 		void updateState();
 		void clickReleased(const Point & cursorPosition) override;
 		void showPopupWindow(const Point & cursorPosition) override;

+ 66 - 66
config/campaignOverrides.json

@@ -6,7 +6,7 @@
 		"introVideo": "H3X1intr",
 		"videoRim": "IntroRm2"
 	},
-	"MAPS/HC1_MAIN" : { // Heroes Chronicles 1
+	"MAPS/CHRONICLES/HC1_MAIN" : { // Heroes Chronicles 1
 		"regions":
 		{
 			"background": "chronicles_1/CamBkHc",
@@ -14,14 +14,14 @@
 			"suffix": ["1", "2", "3"],
 			"color_suffix_length": 0,
 			"desc": [
-				{ "infix": "1", "x": 27, "y": 43 },
-				{ "infix": "2", "x": 231, "y": 43 },
-				{ "infix": "3", "x": 27, "y": 178 },
-				{ "infix": "4", "x": 231, "y": 178 },
-				{ "infix": "5", "x": 27, "y": 312 },
-				{ "infix": "6", "x": 231, "y": 312 },
-				{ "infix": "7", "x": 27, "y": 447 },
-				{ "infix": "8", "x": 231, "y": 447 }
+				{ "infix": "1", "x": 27, "y": 43, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "2", "x": 231, "y": 43, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "3", "x": 27, "y": 178, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "4", "x": 231, "y": 178, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "5", "x": 27, "y": 312, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "6", "x": 231, "y": 312, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "7", "x": 27, "y": 447, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "8", "x": 231, "y": 447, "labelPos": { "x": 98, "y": 112 } }
 			]
 		},
 		"scenarioCount": 8,
@@ -39,7 +39,7 @@
 		"videoRim": "chronicles_1/INTRORIM",
 		"introVideo": "chronicles_1/Intro"
 	},
-	"MAPS/HC2_MAIN" : { // Heroes Chronicles 2
+	"MAPS/CHRONICLES/HC2_MAIN" : { // Heroes Chronicles 2
 		"regions":
 		{
 			"background": "chronicles_2/CamBkHc",
@@ -47,14 +47,14 @@
 			"suffix": ["1", "2", "3"],
 			"color_suffix_length": 0,
 			"desc": [
-				{ "infix": "1", "x": 27, "y": 43 },
-				{ "infix": "2", "x": 231, "y": 43 },
-				{ "infix": "3", "x": 27, "y": 178 },
-				{ "infix": "4", "x": 231, "y": 178 },
-				{ "infix": "5", "x": 27, "y": 312 },
-				{ "infix": "6", "x": 231, "y": 312 },
-				{ "infix": "7", "x": 27, "y": 447 },
-				{ "infix": "8", "x": 231, "y": 447 }
+				{ "infix": "1", "x": 27, "y": 43, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "2", "x": 231, "y": 43, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "3", "x": 27, "y": 178, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "4", "x": 231, "y": 178, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "5", "x": 27, "y": 312, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "6", "x": 231, "y": 312, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "7", "x": 27, "y": 447, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "8", "x": 231, "y": 447, "labelPos": { "x": 98, "y": 112 } }
 			]
 		},
 		"scenarioCount": 8,
@@ -72,7 +72,7 @@
 		"videoRim": "chronicles_2/INTRORIM",
 		"introVideo": "chronicles_2/Intro"
 	},
-	"MAPS/HC3_MAIN" : { // Heroes Chronicles 3
+	"MAPS/CHRONICLES/HC3_MAIN" : { // Heroes Chronicles 3
 		"regions":
 		{
 			"background": "chronicles_3/CamBkHc",
@@ -80,14 +80,14 @@
 			"suffix": ["1", "2", "3"],
 			"color_suffix_length": 0,
 			"desc": [
-				{ "infix": "1", "x": 27, "y": 43 },
-				{ "infix": "2", "x": 231, "y": 43 },
-				{ "infix": "3", "x": 27, "y": 178 },
-				{ "infix": "4", "x": 231, "y": 178 },
-				{ "infix": "5", "x": 27, "y": 312 },
-				{ "infix": "6", "x": 231, "y": 312 },
-				{ "infix": "7", "x": 27, "y": 447 },
-				{ "infix": "8", "x": 231, "y": 447 }
+				{ "infix": "1", "x": 27, "y": 43, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "2", "x": 231, "y": 43, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "3", "x": 27, "y": 178, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "4", "x": 231, "y": 178, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "5", "x": 27, "y": 312, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "6", "x": 231, "y": 312, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "7", "x": 27, "y": 447, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "8", "x": 231, "y": 447, "labelPos": { "x": 98, "y": 112 } }
 			]
 		},
 		"scenarioCount": 8,
@@ -105,7 +105,7 @@
 		"videoRim": "chronicles_3/INTRORIM",
 		"introVideo": "chronicles_3/Intro"
 	},
-	"MAPS/HC4_MAIN" : { // Heroes Chronicles 4
+	"MAPS/CHRONICLES/HC4_MAIN" : { // Heroes Chronicles 4
 		"regions":
 		{
 			"background": "chronicles_4/CamBkHc",
@@ -113,14 +113,14 @@
 			"suffix": ["1", "2", "3"],
 			"color_suffix_length": 0,
 			"desc": [
-				{ "infix": "1", "x": 27, "y": 43 },
-				{ "infix": "2", "x": 231, "y": 43 },
-				{ "infix": "3", "x": 27, "y": 178 },
-				{ "infix": "4", "x": 231, "y": 178 },
-				{ "infix": "5", "x": 27, "y": 312 },
-				{ "infix": "6", "x": 231, "y": 312 },
-				{ "infix": "7", "x": 27, "y": 447 },
-				{ "infix": "8", "x": 231, "y": 447 }
+				{ "infix": "1", "x": 27, "y": 43, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "2", "x": 231, "y": 43, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "3", "x": 27, "y": 178, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "4", "x": 231, "y": 178, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "5", "x": 27, "y": 312, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "6", "x": 231, "y": 312, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "7", "x": 27, "y": 447, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "8", "x": 231, "y": 447, "labelPos": { "x": 98, "y": 112 } }
 			]
 		},
 		"scenarioCount": 8,
@@ -138,7 +138,7 @@
 		"videoRim": "chronicles_4/INTRORIM",
 		"introVideo": "chronicles_4/Intro"
 	},
-	"MAPS/HC5_MAIN" : { // Heroes Chronicles 5
+	"MAPS/CHRONICLES/HC5_MAIN" : { // Heroes Chronicles 5
 		"regions":
 		{
 			"background": "chronicles_5/CamBkHc",
@@ -146,11 +146,11 @@
 			"suffix": ["1", "2", "3"],
 			"color_suffix_length": 0,
 			"desc": [
-				{ "infix": "1", "x": 34, "y": 184 },
-				{ "infix": "2", "x": 235, "y": 184 },
-				{ "infix": "3", "x": 34, "y": 320 },
-				{ "infix": "4", "x": 235, "y": 320 },
-				{ "infix": "5", "x": 129, "y": 459 }
+				{ "infix": "1", "x": 34, "y": 184, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "2", "x": 235, "y": 184, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "3", "x": 34, "y": 320, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "4", "x": 235, "y": 320, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "5", "x": 129, "y": 459, "labelPos": { "x": 98, "y": 112 } }
 			]
 		},
 		"scenarioCount": 5,
@@ -165,7 +165,7 @@
 		"videoRim": "chronicles_5/INTRORIM",
 		"introVideo": "chronicles_5/Intro"
 	},
-	"MAPS/HC6_MAIN" : { // Heroes Chronicles 6
+	"MAPS/CHRONICLES/HC6_MAIN" : { // Heroes Chronicles 6
 		"regions":
 		{
 			"background": "chronicles_6/CamBkHc",
@@ -173,11 +173,11 @@
 			"suffix": ["1", "2", "3"],
 			"color_suffix_length": 0,
 			"desc": [
-				{ "infix": "1", "x": 34, "y": 184 },
-				{ "infix": "2", "x": 235, "y": 184 },
-				{ "infix": "3", "x": 34, "y": 320 },
-				{ "infix": "4", "x": 235, "y": 320 },
-				{ "infix": "5", "x": 129, "y": 459 }
+				{ "infix": "1", "x": 34, "y": 184, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "2", "x": 235, "y": 184, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "3", "x": 34, "y": 320, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "4", "x": 235, "y": 320, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "5", "x": 129, "y": 459, "labelPos": { "x": 98, "y": 112 } }
 			]
 		},
 		"scenarioCount": 5,
@@ -192,7 +192,7 @@
 		"videoRim": "chronicles_6/INTRORIM",
 		"introVideo": "chronicles_6/Intro"
 	},
-	"MAPS/HC7_MAIN" : { // Heroes Chronicles 7
+	"MAPS/CHRONICLES/HC7_MAIN" : { // Heroes Chronicles 7
 		"regions":
 		{
 			"background": "chronicles_7/CamBkHc",
@@ -200,14 +200,14 @@
 			"suffix": ["1", "2", "3"],
 			"color_suffix_length": 0,
 			"desc": [
-				{ "infix": "1", "x": 27, "y": 43 },
-				{ "infix": "2", "x": 231, "y": 43 },
-				{ "infix": "3", "x": 27, "y": 178 },
-				{ "infix": "4", "x": 231, "y": 178 },
-				{ "infix": "5", "x": 27, "y": 312 },
-				{ "infix": "6", "x": 231, "y": 312 },
-				{ "infix": "7", "x": 27, "y": 447 },
-				{ "infix": "8", "x": 231, "y": 447 }
+				{ "infix": "1", "x": 27, "y": 43, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "2", "x": 231, "y": 43, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "3", "x": 27, "y": 178, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "4", "x": 231, "y": 178, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "5", "x": 27, "y": 312, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "6", "x": 231, "y": 312, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "7", "x": 27, "y": 447, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "8", "x": 231, "y": 447, "labelPos": { "x": 98, "y": 112 } }
 			]
 		},
 		"scenarioCount": 8,
@@ -225,7 +225,7 @@
 		"videoRim": "chronicles_7/INTRORIM",
 		"introVideo": "chronicles_7/Intro5"
 	},
-	"MAPS/HC8_MAIN" : { // Heroes Chronicles 8
+	"MAPS/CHRONICLES/HC8_MAIN" : { // Heroes Chronicles 8
 		"regions":
 		{
 			"background": "chronicles_8/CamBkHc",
@@ -233,14 +233,14 @@
 			"suffix": ["1", "2", "3"],
 			"color_suffix_length": 0,
 			"desc": [
-				{ "infix": "1", "x": 27, "y": 43 },
-				{ "infix": "2", "x": 231, "y": 43 },
-				{ "infix": "3", "x": 27, "y": 178 },
-				{ "infix": "4", "x": 231, "y": 178 },
-				{ "infix": "5", "x": 27, "y": 312 },
-				{ "infix": "6", "x": 231, "y": 312 },
-				{ "infix": "7", "x": 27, "y": 447 },
-				{ "infix": "8", "x": 231, "y": 447 }
+				{ "infix": "1", "x": 27, "y": 43, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "2", "x": 231, "y": 43, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "3", "x": 27, "y": 178, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "4", "x": 231, "y": 178, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "5", "x": 27, "y": 312, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "6", "x": 231, "y": 312, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "7", "x": 27, "y": 447, "labelPos": { "x": 98, "y": 112 } },
+				{ "infix": "8", "x": 231, "y": 447, "labelPos": { "x": 98, "y": 112 } }
 			]
 		},
 		"scenarioCount": 8,

+ 4 - 3
docs/modders/Campaign_Format.md

@@ -191,9 +191,9 @@ Predefined campaign regions are located in file `campaign_regions.json`
     "prefix": "G3",
     "colorSuffixLength": 1,
     "desc": [
-        { "infix": "A", "x": 289, "y": 376 },
-        { "infix": "B", "x": 60, "y": 147 },
-        { "infix": "C", "x": 131, "y": 202 }
+        { "infix": "A", "x": 289, "y": 376, "labelPos": { "x": 98, "y": 112 } },
+        { "infix": "B", "x": 60, "y": 147, "labelPos": { "x": 98, "y": 112 } },
+        { "infix": "C", "x": 131, "y": 202, "labelPos": { "x": 98, "y": 112 } }
     ]
 },
 ```
@@ -202,6 +202,7 @@ Predefined campaign regions are located in file `campaign_regions.json`
 - `"prefix"` used to identify all images related to campaign. In this example (if background parameter wouldn't exists), background picture will be `G3_BG`
 - `"suffix"` optional - use other suffixes than the default `En`, `Se` and `Co` for the three different images
 - `"infix"` used to identify all images related to region. In this example, it will be pictures whose files names begin with `G3A_..., G3B_..., G3C_..."` 
+- `"labelPos"` optional -  to add scenario name as label on map
 - `"colorSuffixLength"` identifies suffix length for region colourful frames. 0 is no color suffix (no colorisation), 1 is used for `R, B, N, G, O, V, T, P`, value 2 is used for `Re, Bl, Br, Gr, Or, Vi, Te, Pi`
 
 ## Packing campaign

+ 1 - 1
launcher/modManager/chroniclesextractor.cpp

@@ -168,7 +168,7 @@ void ChroniclesExtractor::extractFiles(int no) const
 	QDir outDirSprites(pathToQString(basePath / "Sprites" / chroniclesDir));
 	QDir outDirVideo(pathToQString(basePath / "Video" / chroniclesDir));
 	QDir outDirSounds(pathToQString(basePath / "Sounds" / chroniclesDir));
-	QDir outDirMaps(pathToQString(basePath / "Maps"));
+	QDir outDirMaps(pathToQString(basePath / "Maps" / "Chronicles"));
 
 	auto extract = [](QDir scrDir, QDir dest, QString file, std::vector<std::string> files = {}){
 		CArchiveLoader archive("", scrDir.filePath(scrDir.entryList({file}).front()).toStdString(), false);

+ 12 - 3
lib/campaign/CampaignState.cpp

@@ -37,8 +37,11 @@ CampaignRegions::RegionDescription CampaignRegions::RegionDescription::fromJson(
 {
 	CampaignRegions::RegionDescription rd;
 	rd.infix = node["infix"].String();
-	rd.xpos = static_cast<int>(node["x"].Float());
-	rd.ypos = static_cast<int>(node["y"].Float());
+	rd.pos = Point(static_cast<int>(node["x"].Float()), static_cast<int>(node["y"].Float()));
+	if(!node["labelPos"].isNull())
+		rd.labelPos = Point(static_cast<int>(node["labelPos"]["x"].Float()), static_cast<int>(node["labelPos"]["y"].Float()));
+	else
+		rd.labelPos = std::nullopt;
 	return rd;
 }
 
@@ -80,7 +83,13 @@ ImagePath CampaignRegions::getBackgroundName() const
 Point CampaignRegions::getPosition(CampaignScenarioID which) const
 {
 	auto const & region = regions[which.getNum()];
-	return Point(region.xpos, region.ypos);
+	return region.pos;
+}
+
+std::optional<Point> CampaignRegions::getLabelPosition(CampaignScenarioID which) const
+{
+	auto const & region = regions[which.getNum()];
+	return region.labelPos;
 }
 
 ImagePath CampaignRegions::getNameFor(CampaignScenarioID which, int colorIndex, std::string type) const

+ 14 - 5
lib/campaign/CampaignState.h

@@ -16,6 +16,7 @@
 #include "CampaignConstants.h"
 #include "CampaignScenarioPrologEpilog.h"
 #include "../gameState/HighScore.h"
+#include "../Point.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -27,7 +28,6 @@ class CMap;
 class CMapHeader;
 class CMapInfo;
 class JsonNode;
-class Point;
 class IGameCallback;
 
 class DLL_LINKAGE CampaignRegions
@@ -40,14 +40,22 @@ class DLL_LINKAGE CampaignRegions
 	struct DLL_LINKAGE RegionDescription
 	{
 		std::string infix;
-		int xpos;
-		int ypos;
+		Point pos;
+		std::optional<Point> labelPos;
 
 		template <typename Handler> void serialize(Handler &h)
 		{
 			h & infix;
-			h & xpos;
-			h & ypos;
+			if (h.version >= Handler::Version::REGION_LABEL)
+			{
+				h & pos;
+				h & labelPos;
+			}
+			else
+			{
+				h & pos.x;
+				h & pos.y;
+			}
 		}
 
 		static CampaignRegions::RegionDescription fromJson(const JsonNode & node);
@@ -60,6 +68,7 @@ class DLL_LINKAGE CampaignRegions
 public:
 	ImagePath getBackgroundName() const;
 	Point getPosition(CampaignScenarioID which) const;
+	std::optional<Point> getLabelPosition(CampaignScenarioID which) const;
 	ImagePath getAvailableName(CampaignScenarioID which, int color) const;
 	ImagePath getSelectedName(CampaignScenarioID which, int color) const;
 	ImagePath getConqueredName(CampaignScenarioID which, int color) const;

+ 2 - 1
lib/serializer/ESerializationVersion.h

@@ -60,6 +60,7 @@ enum class ESerializationVersion : int32_t
 	PER_MAP_GAME_SETTINGS, // 861 - game settings are now stored per-map
 	CAMPAIGN_OUTRO_SUPPORT, // 862 - support for campaign outro video
 	REWARDABLE_BANKS, // 863 - team state contains list of scouted objects, coast visitable rewardable objects
+	REGION_LABEL, // 864 - labels for campaign regions
 
-	CURRENT = REWARDABLE_BANKS
+	CURRENT = REGION_LABEL
 };