Просмотр исходного кода

Support for defining new campaign regions in mods, for hota h3c

Ivan Savenko 4 месяцев назад
Родитель
Сommit
a842dfb3c4

+ 238 - 0
config/campaignRegions.json

@@ -0,0 +1,238 @@
+{
+	// RoE
+	
+	"good1" : {
+		"prefix": "G1",
+		"colorSuffixLength": 1,
+		"desc": [
+			{ "infix": "A", "x": 57, "y": 314 },
+			{ "infix": "B", "x": 137, "y": 309 },
+			{ "infix": "C", "x": 44, "y": 163 }
+		]
+	},
+
+	"good2" : {
+		"prefix": "G2",
+		"colorSuffixLength": 1,
+		"desc": [
+			{ "infix": "A", "x": 56, "y": 90 },
+			{ "infix": "B", "x": 316, "y": 49 },
+			{ "infix": "C", "x": 54, "y": 378 },
+			{ "infix": "D", "x": 151, "y": 126 }
+		]
+	},
+
+	"good3" : {
+		"prefix": "G3",
+		"colorSuffixLength": 1,
+		"desc": [
+			{ "infix": "A", "x": 289, "y": 376 },
+			{ "infix": "B", "x": 60, "y": 147 },
+			{ "infix": "C", "x": 131, "y": 202 }
+		]
+	},
+
+	"evil1" : {
+		"prefix": "E1",
+		"colorSuffixLength": 1,
+		"desc": [
+			{ "infix": "A", "x": 270, "y": 332 },
+			{ "infix": "B", "x": 138, "y": 113 },
+			{ "infix": "C", "x": 26, "y": 70 },
+			{ "infix": "P1", "x": 256, "y": 127 },
+			{ "infix": "P2", "x": 57, "y": 314 },
+			{ "infix": "P3", "x": 137, "y": 310 },
+			{ "infix": "P4", "x": 44, "y": 163 }
+		]
+	},
+
+	"evil2" : {
+		"prefix": "E2",
+		"colorSuffixLength": 1,
+		"desc": [
+			{ "infix": "A", "x": 131, "y": 202 },
+			{ "infix": "B", "x": 60, "y": 145 },
+			{ "infix": "C", "x": 92, "y": 261 },
+			{ "infix": "D", "x": 218, "y": 307 }
+		]
+	},
+
+	"neutral1" : {
+		"prefix": "N1",
+		"colorSuffixLength": 1,
+		"desc": [
+			{ "infix": "A", "x": 42, "y": 94 },
+			{ "infix": "B", "x": 309, "y": 290 },
+			{ "infix": "CD", "x": 188, "y": 202 }
+		]
+	},
+
+	"secret1" : {
+		"prefix": "S1",
+		"colorSuffixLength": 1,
+		"desc": [
+			{ "infix": "A", "x": 263, "y": 199 },
+			{ "infix": "B", "x": 182, "y": 210 },
+			{ "infix": "C", "x": 82, "y": 152 }
+		]
+	},
+
+	// AB
+
+	"dragonSlayer" : {
+		"prefix": "BR",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 18, "y": 233 },
+			{ "infix": "B", "x": 125, "y": 381 },
+			{ "infix": "C", "x": 224, "y": 357 },
+			{ "infix": "D", "x": 192, "y": 320 }
+		]
+	},
+
+	"foolhardyWaywardness" : {
+		"prefix": "IS",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 294, "y": 399 },
+			{ "infix": "B", "x": 183, "y": 293 },
+			{ "infix": "C", "x": 40, "y": 92 },
+			{ "infix": "D", "x": 294, "y": 398 }
+		]
+	},
+
+	"festivalOfLife" : {
+		"prefix": "KR",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 148, "y": 323 },
+			{ "infix": "B", "x": 192, "y": 235 },
+			{ "infix": "C", "x": 136, "y": 158 },
+			{ "infix": "D", "x": 87, "y": 107 }
+		]
+	},
+
+	"dragonsBlood" : {
+		"prefix": "NI",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 118, "y": 111 },
+			{ "infix": "B", "x": 223, "y": 145 },
+			{ "infix": "C", "x": 320, "y": 213 },
+			{ "infix": "D", "x": 233, "y": 250 }
+		]
+	},
+
+	"playingWithFire" : {
+		"prefix": "TA",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 228, "y": 233 },
+			{ "infix": "B", "x": 147, "y": 194 },
+			{ "infix": "C", "x": 112, "y": 97 }
+		]
+	},
+
+	"armageddonsBlade" : {
+		"prefix": "AR",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 135, "y": 238 },
+			{ "infix": "B", "x": 135, "y": 121 },
+			{ "infix": "C", "x": 206, "y": 155 },
+			{ "infix": "D", "x": 105, "y": 397 },
+			{ "infix": "E", "x": 109, "y": 275 },
+			{ "infix": "F", "x": 158, "y": 188 },
+			{ "infix": "G", "x": 200, "y": 261 },
+			{ "infix": "H", "x": 232, "y": 197 }
+		]
+	},
+
+	// SoD
+
+	"hackAndSlash" : {
+		"prefix": "HS",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 141, "y": 326 },
+			{ "infix": "B", "x": 238, "y": 275 },
+			{ "infix": "C", "x": 22, "y": 161 },
+			{ "infix": "D", "x": 5, "y": 9 }
+		]
+	},
+
+	"birthOfBarbarian" : {
+		"prefix": "BB",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 167, "y": 342 },
+			{ "infix": "B", "x": 217, "y": 263 },
+			{ "infix": "C", "x": 0, "y": 71 },
+			{ "infix": "D", "x": 291, "y": 79 },
+			{ "infix": "E", "x": 316, "y": 199 }
+		]
+	},
+
+	"newBeginning" : {
+		"prefix": "NB",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 6, "y": 292 },
+			{ "infix": "B", "x": 161, "y": 334 },
+			{ "infix": "C", "x": 63, "y": 195 },
+			{ "infix": "D", "x": 56, "y": 46 }
+		]
+	},
+
+	"elixirOfLife" : {
+		"prefix": "EL",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 11, "y": 73 },
+			{ "infix": "B", "x": 0, "y": 241 },
+			{ "infix": "C", "x": 254, "y": 34 },
+			{ "infix": "D", "x": 91, "y": 144 }
+		]
+	},
+
+	"riseOfTheNecromancer" : {
+		"prefix": "RN",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 84, "y": 319 },
+			{ "infix": "B", "x": 194, "y": 275 },
+			{ "infix": "C", "x": 67, "y": 185 },
+			{ "infix": "D", "x": 77, "y": 30 }
+		]
+	},
+
+	"unholyAlliance" : {
+		"prefix": "UA",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 157, "y": 409 },
+			{ "infix": "B", "x": 62, "y": 346 },
+			{ "infix": "C", "x": 8, "y": 8 },
+			{ "infix": "D", "x": 206, "y": 1 },
+			{ "infix": "E", "x": 132, "y": 357 },
+			{ "infix": "F", "x": 184, "y": 83 },
+			{ "infix": "G", "x": 159, "y": 263 },
+			{ "infix": "H", "x": 108, "y": 173 },
+			{ "infix": "I", "x": 55, "y": 127 },
+			{ "infix": "J", "x": 9, "y": 252 },
+			{ "infix": "K", "x": 210, "y": 176 },
+			{ "infix": "L", "x": 260, "y": 210 }
+		]
+	},
+
+	"spectreOfPower" : {
+		"prefix": "SP",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 7, "y": 295 },
+			{ "infix": "B", "x": 44, "y": 141 },
+			{ "infix": "C", "x": 141, "y": 21 },
+			{ "infix": "D", "x": 243, "y": 156 }
+		]
+	}
+}

+ 0 - 234
config/campaign_regions.json

@@ -1,234 +0,0 @@
-{
-	"campaign_regions": [
-		{
-			"prefix": "G1",
-			"colorSuffixLength": 1,
-			"desc": [
-				{ "infix": "A", "x": 57, "y": 314 },
-				{ "infix": "B", "x": 137, "y": 309 },
-				{ "infix": "C", "x": 44, "y": 163 }
-			]
-		},
-
-		{
-			"prefix": "G2",
-			"colorSuffixLength": 1,
-			"desc": [
-				{ "infix": "A", "x": 56, "y": 90 },
-				{ "infix": "B", "x": 316, "y": 49 },
-				{ "infix": "C", "x": 54, "y": 378 },
-				{ "infix": "D", "x": 151, "y": 126 }
-			]
-		},
-
-		{
-			"prefix": "G3",
-			"colorSuffixLength": 1,
-			"desc": [
-				{ "infix": "A", "x": 289, "y": 376 },
-				{ "infix": "B", "x": 60, "y": 147 },
-				{ "infix": "C", "x": 131, "y": 202 }
-			]
-		},
-
-		{
-			"prefix": "E1",
-			"colorSuffixLength": 1,
-			"desc": [
-				{ "infix": "A", "x": 270, "y": 332 },
-				{ "infix": "B", "x": 138, "y": 113 },
-				{ "infix": "C", "x": 26, "y": 70 },
-				{ "infix": "P1", "x": 256, "y": 127 },
-				{ "infix": "P2", "x": 57, "y": 314 },
-				{ "infix": "P3", "x": 137, "y": 310 },
-				{ "infix": "P4", "x": 44, "y": 163 }
-			]
-		},
-
-		{
-			"prefix": "E2",
-			"colorSuffixLength": 1,
-			"desc": [
-				{ "infix": "A", "x": 131, "y": 202 },
-				{ "infix": "B", "x": 60, "y": 145 },
-				{ "infix": "C", "x": 92, "y": 261 },
-				{ "infix": "D", "x": 218, "y": 307 }
-			]
-		},
-
-		{
-			"prefix": "N1",
-			"colorSuffixLength": 1,
-			"desc": [
-				{ "infix": "A", "x": 42, "y": 94 },
-				{ "infix": "B", "x": 309, "y": 290 },
-				{ "infix": "CD", "x": 188, "y": 202 }
-			]
-		},
-
-		{
-			"prefix": "S1",
-			"colorSuffixLength": 1,
-			"desc": [
-				{ "infix": "A", "x": 263, "y": 199 },
-				{ "infix": "B", "x": 182, "y": 210 },
-				{ "infix": "C", "x": 82, "y": 152 }
-			]
-		},
-
-		{
-			"prefix": "BR",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 18, "y": 233 },
-				{ "infix": "B", "x": 125, "y": 381 },
-				{ "infix": "C", "x": 224, "y": 357 },
-				{ "infix": "D", "x": 192, "y": 320 }
-			]
-		},
-
-		{
-			"prefix": "IS",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 294, "y": 399 },
-				{ "infix": "B", "x": 183, "y": 293 },
-				{ "infix": "C", "x": 40, "y": 92 },
-				{ "infix": "D", "x": 294, "y": 398 }
-			]
-		},
-
-		{
-			"prefix": "KR",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 148, "y": 323 },
-				{ "infix": "B", "x": 192, "y": 235 },
-				{ "infix": "C", "x": 136, "y": 158 },
-				{ "infix": "D", "x": 87, "y": 107 }
-			]
-		},
-
-		{
-			"prefix": "NI",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 118, "y": 111 },
-				{ "infix": "B", "x": 223, "y": 145 },
-				{ "infix": "C", "x": 320, "y": 213 },
-				{ "infix": "D", "x": 233, "y": 250 }
-			]
-		},
-
-		{
-			"prefix": "TA",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 228, "y": 233 },
-				{ "infix": "B", "x": 147, "y": 194 },
-				{ "infix": "C", "x": 112, "y": 97 }
-			]
-		},
-
-		{
-			"prefix": "AR",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 135, "y": 238 },
-				{ "infix": "B", "x": 135, "y": 121 },
-				{ "infix": "C", "x": 206, "y": 155 },
-				{ "infix": "D", "x": 105, "y": 397 },
-				{ "infix": "E", "x": 109, "y": 275 },
-				{ "infix": "F", "x": 158, "y": 188 },
-				{ "infix": "G", "x": 200, "y": 261 },
-				{ "infix": "H", "x": 232, "y": 197 }
-			]
-		},
-
-		{
-			"prefix": "HS",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 141, "y": 326 },
-				{ "infix": "B", "x": 238, "y": 275 },
-				{ "infix": "C", "x": 22, "y": 161 },
-				{ "infix": "D", "x": 5, "y": 9 }
-			]
-		},
-
-		{
-			"prefix": "BB",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 167, "y": 342 },
-				{ "infix": "B", "x": 217, "y": 263 },
-				{ "infix": "C", "x": 0, "y": 71 },
-				{ "infix": "D", "x": 291, "y": 79 },
-				{ "infix": "E", "x": 316, "y": 199 }
-			]
-		},
-
-		{
-			"prefix": "NB",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 6, "y": 292 },
-				{ "infix": "B", "x": 161, "y": 334 },
-				{ "infix": "C", "x": 63, "y": 195 },
-				{ "infix": "D", "x": 56, "y": 46 }
-			]
-		},
-
-		{
-			"prefix": "EL",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 11, "y": 73 },
-				{ "infix": "B", "x": 0, "y": 241 },
-				{ "infix": "C", "x": 254, "y": 34 },
-				{ "infix": "D", "x": 91, "y": 144 }
-			]
-		},
-
-		{
-			"prefix": "RN",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 84, "y": 319 },
-				{ "infix": "B", "x": 194, "y": 275 },
-				{ "infix": "C", "x": 67, "y": 185 },
-				{ "infix": "D", "x": 77, "y": 30 }
-			]
-		},
-
-		{
-			"prefix": "UA",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 157, "y": 409 },
-				{ "infix": "B", "x": 62, "y": 346 },
-				{ "infix": "C", "x": 8, "y": 8 },
-				{ "infix": "D", "x": 206, "y": 1 },
-				{ "infix": "E", "x": 132, "y": 357 },
-				{ "infix": "F", "x": 184, "y": 83 },
-				{ "infix": "G", "x": 159, "y": 263 },
-				{ "infix": "H", "x": 108, "y": 173 },
-				{ "infix": "I", "x": 55, "y": 127 },
-				{ "infix": "J", "x": 9, "y": 252 },
-				{ "infix": "K", "x": 210, "y": 176 },
-				{ "infix": "L", "x": 260, "y": 210 }
-			]
-		},
-
-		{
-			"prefix": "SP",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 7, "y": 295 },
-				{ "infix": "B", "x": 44, "y": 141 },
-				{ "infix": "C", "x": 141, "y": 21 },
-				{ "infix": "D", "x": 243, "y": 156 }
-			]
-		}
-	]
-}

+ 33 - 0
config/gameConfig.json

@@ -127,6 +127,11 @@
 	[
 		"config/obstacles.json"
 	],
+	"campaignRegions" :
+	[
+		"config/campaignRegions.json"
+	],
+	
 	
 	"settings":
 	{
@@ -244,6 +249,16 @@
 				"portraits" : {
 					"catherine" : 128, // In "RoE" Catherine only has portrait
 					"portraitGeneralKendal" : 129
+				},
+				"campaignRegions" : {
+					
+					"good1" : 1,    // Long Live the Queen
+					"good2" : 2,    // Liberation
+					"good3" : 3,    // Song for the Father
+					"evil1" : 4,    // Dungeons and devils
+					"evil2" : 5,    // Long Live the King
+					"neutral1" : 6, // Spoils of War
+					"secret1" : 7   // Seeds Of Discontent 
 				}
 			},
 			"armageddonsBlade" : {
@@ -262,6 +277,14 @@
 					"portraitGeneralKendal" : 156,
 					"portraitYoungCristian" : 157,
 					"portraitOrdwald" : 158
+				},
+				"campaignRegions" : {
+					"dragonSlayer" : 8,
+					"foolhardyWaywardness" : 9,
+					"festivalOfLife" : 10,
+					"dragonsBlood" : 11,
+					"playingWithFire" : 12,
+					"armageddonsBlade" : 13
 				}
 			},
 			"shadowOfDeath" : { 
@@ -276,6 +299,16 @@
 					"portraitYoungGem" : 160,
 					"portraitYoungSandro" : 161,
 					"portraitYoungYog" : 162
+				},
+				
+				"campaignRegions" : {
+					"hackAndSlash" : 14,
+					"birthOfBarbarian" : 15,
+					"newBeginning" : 16,
+					"elixirOfLife" : 17,
+					"riseOfTheNecromancer" : 18,
+					"unholyAlliance" : 19,
+					"spectreOfPower" : 20
 				}
 			},
 			"chronicles" : { 

+ 40 - 0
config/schemas/campaignRegion.json

@@ -0,0 +1,40 @@
+{
+	"type" : "object",
+	"$schema" : "http://json-schema.org/draft-04/schema",
+	"title" : "VCMI campaign region format",
+	"description" : "Format used to define campaign regions for h3c maps in VCMI",
+	"required" : [ "prefix", "colorSuffixLength", "desc" ],
+	"additionalProperties" : false,
+	"properties" : {
+		"prefix" :
+		{
+			"type" : "string",
+			"description" : "Prefix for all images from this region"
+		},
+		"colorSuffixLength" :
+		{
+			"type" : "number",
+			"description" : "Number of symbols used to encode color, 1 or 2"
+		},
+		"desc" :
+		{
+			"type" : "array",
+			"description" : "List of regions in this campaign",
+				"items" : {
+				"type" : "object",
+				"additionalProperties" : false,
+				"properties" : {
+					"infix" : {
+						"type" : "string"
+					},
+					"x" : {
+						"type" : "number"
+					},
+					"y" : {
+						"type" : "number"
+					}
+				}
+			}
+		}
+	}
+}

+ 4 - 0
lib/CMakeLists.txt

@@ -89,6 +89,8 @@ set(lib_MAIN_SRCS
 	campaign/CampaignBonus.cpp
 	campaign/CampaignHandler.cpp
 	campaign/CampaignState.cpp
+	campaign/CampaignRegions.cpp
+	campaign/CampaignRegionsHandler.cpp
 
 	constants/EntityIdentifiers.cpp
 
@@ -485,6 +487,8 @@ set(lib_MAIN_HEADERS
 	campaign/CampaignBonus.h
 	campaign/CampaignConstants.h
 	campaign/CampaignHandler.h
+	campaign/CampaignRegions.h
+	campaign/CampaignRegionsHandler.h
 	campaign/CampaignScenarioPrologEpilog.h
 	campaign/CampaignState.h
 

+ 2 - 0
lib/GameLibrary.cpp

@@ -26,6 +26,7 @@
 #include "entities/hero/CHeroClassHandler.h"
 #include "entities/hero/CHeroHandler.h"
 #include "texts/CGeneralTextHandler.h"
+#include "campaign/CampaignRegionsHandler.h"
 #include "mapping/MapFormatSettings.h"
 #include "modding/CModHandler.h"
 #include "modding/IdentifierStorage.h"
@@ -184,6 +185,7 @@ void GameLibrary::initializeLibrary()
 	createHandler(spellh);
 	createHandler(skillh);
 	createHandler(terviewh);
+	createHandler(campaignRegions);
 	createHandler(tplh); //templates need already resolved identifiers (refactor?)
 #if SCRIPTING_ENABLED
 	createHandler(scriptHandler);

+ 2 - 0
lib/GameLibrary.h

@@ -42,6 +42,7 @@ class GameSettings;
 class CIdentifierStorage;
 class SpellSchoolHandler;
 class MapFormatSettings;
+class CampaignRegionsHandler;
 
 #if SCRIPTING_ENABLED
 namespace scripting
@@ -99,6 +100,7 @@ public:
 	std::unique_ptr<GameSettings> settingsHandler;
 	std::unique_ptr<ObstacleSetHandler> biomeHandler;
 	std::unique_ptr<MapFormatSettings> mapFormat;
+	std::unique_ptr<CampaignRegionsHandler> campaignRegions;
 
 #if SCRIPTING_ENABLED
 	std::unique_ptr<scripting::ScriptHandler> scriptHandler;

+ 3 - 2
lib/IHandlerBase.h

@@ -15,7 +15,7 @@ class JsonNode;
 class Entity;
 
 /// base class for all handlers that can be accessed from mod system
-class DLL_LINKAGE IHandlerBase
+class DLL_LINKAGE IHandlerBase : boost::noncopyable
 {
 protected:
 	static std::string getScopeBuiltin();
@@ -44,7 +44,8 @@ public:
 	virtual ~IHandlerBase() = default;
 };
 
-template <class _ObjectID, class _ObjectBase, class _Object, class _ServiceBase> class CHandlerBase : public _ServiceBase, public IHandlerBase
+template <class _ObjectID, class _ObjectBase, class _Object, class _ServiceBase>
+class CHandlerBase : public _ServiceBase, public IHandlerBase
 {
 	const _Object * getObjectImpl(const int32_t index) const
 	{

+ 8 - 7
lib/campaign/CampaignHandler.cpp

@@ -11,6 +11,7 @@
 #include "CampaignHandler.h"
 
 #include "CampaignState.h"
+#include "CampaignRegionsHandler.h"
 
 #include "../filesystem/Filesystem.h"
 #include "../filesystem/CCompressedStream.h"
@@ -152,7 +153,7 @@ void CampaignHandler::readHeaderFromJson(CampaignHeader & ret, JsonNode & reader
 	}
 	
 	ret.version = CampaignVersion::VCMI;
-	ret.campaignRegions = CampaignRegions::fromJson(reader["regions"]);
+	ret.campaignRegions = CampaignRegions(reader["regions"]);
 	ret.numberOfScenarios = reader["scenarios"].Vector().size();
 	ret.name.appendTextID(readLocalizedString(ret, reader["name"].String(), filename, modName, "name"));
 	ret.description.appendTextID(readLocalizedString(ret, reader["description"].String(), filename, modName, "description"));
@@ -350,14 +351,14 @@ void CampaignHandler::readHeaderFromMemory( CampaignHeader & ret, CBinaryReader
 		assert(unknownB == 1);
 		assert(unknownC == 0);
 		assert(ret.numberOfScenarios <= 8);
-
-		// TODO. Or they are hardcoded in this hota version?
-		// ret.campaignRegions = ???;
 	}
 
-	ui8 campId = reader.readUInt8() - 1;//change range of it from [1, 20] to [0, 19]
-	if(ret.version < CampaignVersion::Chr) // For chronicles: Will be overridden later; Chronicles uses own logic (reusing OH3 ID's)
-		ret.loadLegacyData(campId);
+	const auto & mapping = LIBRARY->mapFormat->getMapping(ret.version);
+
+	CampaignRegionID campaignMapId(reader.readUInt8());
+	ret.campaignRegions = *LIBRARY->campaignRegions->getByIndex(mapping.remap(campaignMapId));
+	if(ret.version != CampaignVersion::HotA)
+		ret.numberOfScenarios = ret.campaignRegions.regionsCount();
 	ret.name.appendTextID(readLocalizedString(ret, reader, filename, modName, encoding, "name"));
 	ret.description.appendTextID(readLocalizedString(ret, reader, filename, modName, encoding, "description"));
 	ret.author.appendRawString("");

+ 148 - 0
lib/campaign/CampaignRegions.cpp

@@ -0,0 +1,148 @@
+/*
+ * CampaignRegions.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "CampaignRegions.h"
+
+#include "../json/JsonNode.h"
+
+CampaignRegions::RegionDescription CampaignRegions::RegionDescription::fromJson(const JsonNode & node)
+{
+	CampaignRegions::RegionDescription rd;
+	rd.infix = node["infix"].String();
+	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;
+}
+
+JsonNode CampaignRegions::RegionDescription::toJson(CampaignRegions::RegionDescription & rd)
+{
+	JsonNode node;
+	node["infix"].String() = rd.infix;
+	node["x"].Float() = rd.pos.x;
+	node["y"].Float() = rd.pos.y;
+	if(rd.labelPos != std::nullopt)
+	{
+		node["labelPos"]["x"].Float() = (*rd.labelPos).x;
+		node["labelPos"]["y"].Float() = (*rd.labelPos).y;
+	}
+	else
+		node["labelPos"].clear();
+	return node;
+}
+
+CampaignRegions::CampaignRegions(const JsonNode & node)
+{
+	campPrefix = node["prefix"].String();
+	colorSuffixLength = static_cast<int>(node["colorSuffixLength"].Float());
+	campSuffix = node["suffix"].isNull() ? std::vector<std::string>() : std::vector<std::string>{node["suffix"].Vector()[0].String(), node["suffix"].Vector()[1].String(), node["suffix"].Vector()[2].String()};
+	campBackground = node["background"].isNull() ? "" : node["background"].String();
+
+	for(const JsonNode & desc : node["desc"].Vector())
+		regions.push_back(CampaignRegions::RegionDescription::fromJson(desc));
+}
+
+JsonNode CampaignRegions::toJson(CampaignRegions cr)
+{
+	JsonNode node;
+	node["prefix"].String() = cr.campPrefix;
+	node["colorSuffixLength"].Float() = cr.colorSuffixLength;
+	if(cr.campSuffix.empty())
+		node["suffix"].clear();
+	else
+		node["suffix"].Vector() = JsonVector{ JsonNode(cr.campSuffix[0]), JsonNode(cr.campSuffix[1]), JsonNode(cr.campSuffix[2]) };
+	if(cr.campBackground.empty())
+		node["background"].clear();
+	else
+		node["background"].String() = cr.campBackground;
+	node["desc"].Vector() = JsonVector();
+	for(auto & region : cr.regions)
+		node["desc"].Vector().push_back(CampaignRegions::RegionDescription::toJson(region));
+	return node;
+}
+
+CampaignRegions CampaignRegions::getLegacy(int campId)
+{
+	static std::vector<CampaignRegions> campDescriptions;
+	if(campDescriptions.empty()) //read once
+	{
+		const JsonNode config(JsonPath::builtin("config/campaign_regions.json"));
+		for(const JsonNode & campaign : config["campaign_regions"].Vector())
+			campDescriptions.push_back(CampaignRegions(campaign));
+	}
+
+	return campDescriptions.at(campId);
+}
+
+ImagePath CampaignRegions::getBackgroundName() const
+{
+	if(campBackground.empty())
+		return ImagePath::builtin(campPrefix + "_BG.BMP");
+	else
+		return ImagePath::builtin(campBackground);
+}
+
+Point CampaignRegions::getPosition(CampaignScenarioID which) const
+{
+	const auto & region = regions[which.getNum()];
+	return region.pos;
+}
+
+std::optional<Point> CampaignRegions::getLabelPosition(CampaignScenarioID which) const
+{
+	const auto & region = regions[which.getNum()];
+	return region.labelPos;
+}
+
+ImagePath CampaignRegions::getNameFor(CampaignScenarioID which, int colorIndex, const std::string & type) const
+{
+	const auto & region = regions[which.getNum()];
+
+	static const std::array<std::array<std::string, 8>, 3> colors = {{
+			{ "", "", "", "", "", "", "", "" },
+			{ "R", "B", "N", "G", "O", "V", "T", "P" },
+			{ "Re", "Bl", "Br", "Gr", "Or", "Vi", "Te", "Pi" }
+		}};
+
+	std::string color = colors[colorSuffixLength][colorIndex];
+
+	return ImagePath::builtin(campPrefix + region.infix + "_" + type + color + ".BMP");
+}
+
+ImagePath CampaignRegions::getAvailableName(CampaignScenarioID which, int color) const
+{
+	if(campSuffix.empty())
+		return getNameFor(which, color, "En");
+	else
+		return getNameFor(which, color, campSuffix[0]);
+}
+
+ImagePath CampaignRegions::getSelectedName(CampaignScenarioID which, int color) const
+{
+	if(campSuffix.empty())
+		return getNameFor(which, color, "Se");
+	else
+		return getNameFor(which, color, campSuffix[1]);
+}
+
+ImagePath CampaignRegions::getConqueredName(CampaignScenarioID which, int color) const
+{
+	if(campSuffix.empty())
+		return getNameFor(which, color, "Co");
+	else
+		return getNameFor(which, color, campSuffix[2]);
+}
+
+int CampaignRegions::regionsCount() const
+{
+	return regions.size();
+}

+ 77 - 0
lib/campaign/CampaignRegions.h

@@ -0,0 +1,77 @@
+/*
+ * CampaignRegions.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../Point.h"
+#include "../filesystem/ResourcePath.h"
+#include "../constants/EntityIdentifiers.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class DLL_LINKAGE CampaignRegions
+{
+	// Campaign editor
+	friend class CampaignEditor;
+	friend class CampaignProperties;
+	friend class ScenarioProperties;
+
+	std::string campPrefix;
+	std::vector<std::string> campSuffix;
+	std::string campBackground;
+	int colorSuffixLength = 0;
+
+	struct DLL_LINKAGE RegionDescription
+	{
+		std::string infix;
+		Point pos;
+		std::optional<Point> labelPos;
+
+		template <typename Handler> void serialize(Handler &h)
+		{
+			h & infix;
+			h & pos;
+			h & labelPos;
+		}
+
+		static CampaignRegions::RegionDescription fromJson(const JsonNode & node);
+		static JsonNode toJson(CampaignRegions::RegionDescription & rd);
+	};
+
+	std::vector<RegionDescription> regions;
+
+	ImagePath getNameFor(CampaignScenarioID which, int color, const std::string & type) const;
+
+public:
+	CampaignRegions() = default;
+	explicit CampaignRegions(const JsonNode & node);
+
+	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;
+	int regionsCount() const;
+
+	template <typename Handler> void serialize(Handler &h)
+	{
+		h & campPrefix;
+		h & colorSuffixLength;
+		h & regions;
+		h & campSuffix;
+		h & campBackground;
+	}
+
+
+	static JsonNode toJson(CampaignRegions cr);
+	static CampaignRegions getLegacy(int campId);
+};
+
+VCMI_LIB_NAMESPACE_END

+ 30 - 0
lib/campaign/CampaignRegionsHandler.cpp

@@ -0,0 +1,30 @@
+/*
+ * CampaignRegionsHandler.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "CampaignRegionsHandler.h"
+
+#include "../json/JsonNode.h"
+
+std::vector<JsonNode> CampaignRegionsHandler::loadLegacyData()
+{
+	return {};
+}
+
+void CampaignRegionsHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
+{
+	auto object = std::make_shared<CampaignRegions>(data);
+	registerObject(scope, "campaignRegion", name, objects.size());
+	objects.push_back(object);
+}
+
+void CampaignRegionsHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
+{
+	throw std::runtime_error("CampaignRegionsHandler::loadObject - load by index is not supported!");
+}

+ 38 - 0
lib/campaign/CampaignRegionsHandler.h

@@ -0,0 +1,38 @@
+/*
+ * CampaignRegionsHandler.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "CampaignRegions.h"
+
+#include "../IHandlerBase.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+/// Managed campaign region sets - "map" of campaign locations, with selectable scenarios
+/// Used only for .h3c campaigns. .vmap's embed campaign regions layout in its format
+class DLL_LINKAGE CampaignRegionsHandler : public IHandlerBase
+{
+public:
+	std::vector<JsonNode> loadLegacyData() override;
+
+	/// loads single object into game. Scope is namespace of this object, same as name of source mod
+	void loadObject(std::string scope, std::string name, const JsonNode & data) override;
+	void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override;
+
+	const CampaignRegions * getByIndex(int index) const
+	{
+		return objects.at(index).get();
+	}
+
+private:
+	std::vector<std::shared_ptr<CampaignRegions>> objects;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 4 - 147
lib/campaign/CampaignState.cpp

@@ -33,151 +33,6 @@ void CampaignScenario::loadPreconditionRegions(ui32 regions)
 	}
 }
 
-CampaignRegions::RegionDescription CampaignRegions::RegionDescription::fromJson(const JsonNode & node)
-{
-	CampaignRegions::RegionDescription rd;
-	rd.infix = node["infix"].String();
-	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;
-}
-
-JsonNode CampaignRegions::RegionDescription::toJson(CampaignRegions::RegionDescription & rd)
-{
-	JsonNode node;
-	node["infix"].String() = rd.infix;
-	node["x"].Float() = rd.pos.x;
-	node["y"].Float() = rd.pos.y;
-	if(rd.labelPos != std::nullopt)
-	{
-		node["labelPos"]["x"].Float() = (*rd.labelPos).x;
-		node["labelPos"]["y"].Float() = (*rd.labelPos).y;
-	}
-	else
-		node["labelPos"].clear();
-	return node;
-}
-
-CampaignRegions CampaignRegions::fromJson(const JsonNode & node)
-{
-	CampaignRegions cr;
-	cr.campPrefix = node["prefix"].String();
-	cr.colorSuffixLength = static_cast<int>(node["colorSuffixLength"].Float());
-	cr.campSuffix = node["suffix"].isNull() ? std::vector<std::string>() : std::vector<std::string>{node["suffix"].Vector()[0].String(), node["suffix"].Vector()[1].String(), node["suffix"].Vector()[2].String()};
-	cr.campBackground = node["background"].isNull() ? "" : node["background"].String();
-
-	for(const JsonNode & desc : node["desc"].Vector())
-		cr.regions.push_back(CampaignRegions::RegionDescription::fromJson(desc));
-
-	return cr;
-}
-
-JsonNode CampaignRegions::toJson(CampaignRegions cr)
-{
-	JsonNode node;
-	node["prefix"].String() = cr.campPrefix;
-	node["colorSuffixLength"].Float() = cr.colorSuffixLength;
-	if(cr.campSuffix.empty())
-		node["suffix"].clear();
-	else
-		node["suffix"].Vector() = JsonVector{ JsonNode(cr.campSuffix[0]), JsonNode(cr.campSuffix[1]), JsonNode(cr.campSuffix[2]) };
-	if(cr.campBackground.empty())
-		node["background"].clear();
-	else
-		node["background"].String() = cr.campBackground;
-	node["desc"].Vector() = JsonVector();
-	for(auto & region : cr.regions)
-		node["desc"].Vector().push_back(CampaignRegions::RegionDescription::toJson(region));
-	return node;
-}
-
-CampaignRegions CampaignRegions::getLegacy(int campId)
-{
-	static std::vector<CampaignRegions> campDescriptions;
-	if(campDescriptions.empty()) //read once
-	{
-		const JsonNode config(JsonPath::builtin("config/campaign_regions.json"));
-		for(const JsonNode & campaign : config["campaign_regions"].Vector())
-			campDescriptions.push_back(CampaignRegions::fromJson(campaign));
-	}
-
-	return campDescriptions.at(campId);
-}
-
-ImagePath CampaignRegions::getBackgroundName() const
-{
-	if(campBackground.empty())
-		return ImagePath::builtin(campPrefix + "_BG.BMP");
-	else
-		return ImagePath::builtin(campBackground);
-}
-
-Point CampaignRegions::getPosition(CampaignScenarioID which) const
-{
-	auto const & region = regions[which.getNum()];
-	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, const std::string & type) const
-{
-	auto const & region = regions[which.getNum()];
-
-	static const std::array<std::array<std::string, 8>, 3> colors = {{
-		{ "", "", "", "", "", "", "", "" },
-		{ "R", "B", "N", "G", "O", "V", "T", "P" },
-		{ "Re", "Bl", "Br", "Gr", "Or", "Vi", "Te", "Pi" }
-	}};
-
-	std::string color = colors[colorSuffixLength][colorIndex];
-
-	return ImagePath::builtin(campPrefix + region.infix + "_" + type + color + ".BMP");
-}
-
-ImagePath CampaignRegions::getAvailableName(CampaignScenarioID which, int color) const
-{
-	if(campSuffix.empty())
-		return getNameFor(which, color, "En");
-	else
-		return getNameFor(which, color, campSuffix[0]);
-}
-
-ImagePath CampaignRegions::getSelectedName(CampaignScenarioID which, int color) const
-{
-	if(campSuffix.empty())
-		return getNameFor(which, color, "Se");
-	else
-		return getNameFor(which, color, campSuffix[1]);
-}
-
-ImagePath CampaignRegions::getConqueredName(CampaignScenarioID which, int color) const
-{
-	if(campSuffix.empty())
-		return getNameFor(which, color, "Co");
-	else
-		return getNameFor(which, color, campSuffix[2]);
-}
-
-void CampaignHeader::loadLegacyData(ui8 campId)
-{
-	campaignRegions = CampaignRegions::getLegacy(campId);
-	numberOfScenarios = LIBRARY->generaltexth->getCampaignLength(campId);
-}
-
-void CampaignHeader::loadLegacyData(const CampaignRegions & regions, int numOfScenario)
-{
-	campaignRegions = regions;
-	numberOfScenarios = numOfScenario;
-}
-
 bool CampaignHeader::playerSelectedDifficulty() const
 {
 	return difficultyChosenByPlayer;
@@ -526,8 +381,10 @@ void Campaign::overrideCampaign()
 	for (auto & entry : node.Struct())
 		if(filename == entry.first)
 		{
-			if(!entry.second["regions"].isNull() && !entry.second["scenarioCount"].isNull())
-				loadLegacyData(CampaignRegions::fromJson(entry.second["regions"]), entry.second["scenarioCount"].Integer());
+			if(!entry.second["regions"].isNull())
+				campaignRegions = CampaignRegions(entry.second["regions"]);
+			if (!entry.second["scenarioCount"].isNull())
+				numberOfScenarios = entry.second["scenarioCount"].Integer();
 			if(!entry.second["loadingBackground"].isNull())
 				loadingBackground = ImagePath::builtin(entry.second["loadingBackground"].String());
 			if(!entry.second["videoRim"].isNull())

+ 5 - 62
lib/campaign/CampaignState.h

@@ -9,13 +9,14 @@
  */
 #pragma once
 
-#include "../filesystem/ResourcePath.h"
-#include "../serializer/Serializeable.h"
-#include "../texts/TextLocalizationContainer.h"
 #include "CampaignBonus.h"
+#include "CampaignRegions.h"
 #include "CampaignScenarioPrologEpilog.h"
+
+#include "../filesystem/ResourcePath.h"
 #include "../gameState/HighScore.h"
-#include "../Point.h"
+#include "../serializer/Serializeable.h"
+#include "../texts/TextLocalizationContainer.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -29,61 +30,6 @@ class CMapInfo;
 class JsonNode;
 class IGameInfoCallback;
 
-class DLL_LINKAGE CampaignRegions
-{
-	// Campaign editor
-	friend class CampaignEditor;
-	friend class CampaignProperties;
-	friend class ScenarioProperties;
-
-	std::string campPrefix;
-	std::vector<std::string> campSuffix;
-	std::string campBackground;
-	int colorSuffixLength;
-
-	struct DLL_LINKAGE RegionDescription
-	{
-		std::string infix;
-		Point pos;
-		std::optional<Point> labelPos;
-
-		template <typename Handler> void serialize(Handler &h)
-		{
-			h & infix;
-			h & pos;
-			h & labelPos;
-		}
-
-		static CampaignRegions::RegionDescription fromJson(const JsonNode & node);
-		static JsonNode toJson(CampaignRegions::RegionDescription & rd);
-	};
-
-	std::vector<RegionDescription> regions;
-
-	ImagePath getNameFor(CampaignScenarioID which, int color, const std::string & type) const;
-
-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;
-
-	template <typename Handler> void serialize(Handler &h)
-	{
-		h & campPrefix;
-		h & colorSuffixLength;
-		h & regions;
-		h & campSuffix;
-		h & campBackground;
-	}
-
-	static CampaignRegions fromJson(const JsonNode & node);
-	static JsonNode toJson(CampaignRegions cr);
-	static CampaignRegions getLegacy(int campId);
-};
-
 class DLL_LINKAGE CampaignHeader : public boost::noncopyable
 {
 	friend class CampaignHandler;
@@ -114,9 +60,6 @@ class DLL_LINKAGE CampaignHeader : public boost::noncopyable
 	int numberOfScenarios = 0;
 	bool difficultyChosenByPlayer = false;
 
-	void loadLegacyData(ui8 campId);
-	void loadLegacyData(const CampaignRegions & regions, int numOfScenario);
-
 	TextContainerRegistrable textContainer;
 
 public:

+ 7 - 0
lib/constants/EntityIdentifiers.h

@@ -1122,6 +1122,13 @@ public:
 	static const CampaignScenarioID NONE;
 };
 
+class DLL_LINKAGE CampaignRegionID : public StaticIdentifier<CampaignRegionID>
+{
+public:
+	using StaticIdentifier<CampaignRegionID>::StaticIdentifier;
+};
+
+
 // Deprecated
 // TODO: remove
 using Obj = MapObjectID;

+ 2 - 0
lib/json/JsonBonus.cpp

@@ -239,6 +239,8 @@ static void loadBonusAddInfo(CAddInfo & var, BonusType type, const JsonNode & va
 		case BonusType::SPELL_BEFORE_ATTACK:
 		case BonusType::SPELL_AFTER_ATTACK:
 			// 3 numbers
+			if (!value.isVector())
+				break;
 			var.resize(3);
 			var[0] = value[0].Integer();
 			var[1] = value[1].Integer();

+ 2 - 2
lib/mapObjectConstructors/CObjectClassesHandler.h

@@ -45,7 +45,7 @@ public:
 };
 
 /// Main class responsible for creation of all adventure map objects
-class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase, boost::noncopyable
+class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase
 {
 	/// list of object handlers, each of them handles only one type
 	std::vector< std::unique_ptr<ObjectClass> > mapObjectTypes;
@@ -106,4 +106,4 @@ public:
 	std::string getJsonKey(MapObjectID type) const;
 };
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

+ 3 - 5
lib/mapObjects/CGHeroInstance.cpp

@@ -750,16 +750,14 @@ uint64_t CGHeroInstance::getValueForDiplomacy() const
 
 uint32_t CGHeroInstance::getValueForCampaign() const
 {
-	/// Determined by testing H3: hero is preferred for transfer in campaigns if total sum of his primary skills and his secondary skill levels is greatest
-	uint32_t score = 0;
+	// Determined by testing H3: hero is preferred for transfer in campaigns if total sum of his primary skills and his secondary skill levels is greatest
+	// Additional info from wiki: https://heroes.thelazy.net/index.php/Power_rating
+	uint32_t score = level;
 	score += getPrimSkillLevel(PrimarySkill::ATTACK);
 	score += getPrimSkillLevel(PrimarySkill::DEFENSE);
 	score += getPrimSkillLevel(PrimarySkill::SPELL_POWER);
 	score += getPrimSkillLevel(PrimarySkill::DEFENSE);
 
-	for (const auto& secondary : secSkills)
-		score += secondary.second;
-
 	return score;
 }
 

+ 1 - 1
lib/mapObjects/ObstacleSetHandler.h

@@ -102,7 +102,7 @@ private:
 };
 
 // TODO: Instantiate ObstacleSetHandler
-class DLL_LINKAGE ObstacleSetHandler : public IHandlerBase, boost::noncopyable
+class DLL_LINKAGE ObstacleSetHandler : public IHandlerBase
 {
 public:
 

+ 3 - 0
lib/mapping/MapFormatSettings.cpp

@@ -15,6 +15,7 @@
 
 #include "../GameLibrary.h"
 #include "../IGameSettings.h"
+#include "../json/JsonUtils.h"
 
 MapIdentifiersH3M MapFormatSettings::generateMapping(EMapFormat format)
 {
@@ -79,6 +80,8 @@ std::map<EMapFormat, MapIdentifiersH3M> MapFormatSettings::generateMappings()
 MapFormatSettings::MapFormatSettings()
 	: mapping(generateMappings())
 	, campaignToMap(generateCampaignMapping())
+	, campaignOverridesConfig(JsonUtils::assembleFromFiles("config/campaignOverrides.json"))
+	, mapOverridesConfig(JsonUtils::assembleFromFiles("config/mapOverrides.json"))
 {
 }
 

+ 15 - 1
lib/mapping/MapFormatSettings.h

@@ -13,10 +13,11 @@
 #include "MapIdentifiersH3M.h"
 #include "MapFormat.h"
 #include "../campaign/CampaignConstants.h"
+#include "../json/JsonNode.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class MapFormatSettings
+	class MapFormatSettings : boost::noncopyable
 {
 	static MapIdentifiersH3M generateMapping(EMapFormat format);
 	static std::map<EMapFormat, MapIdentifiersH3M> generateMappings();
@@ -24,6 +25,9 @@ class MapFormatSettings
 
 	std::map<EMapFormat, MapIdentifiersH3M> mapping;
 	std::map<CampaignVersion, EMapFormat> campaignToMap;
+
+	JsonNode campaignOverridesConfig;
+	JsonNode mapOverridesConfig;
 public:
 	MapFormatSettings();
 
@@ -46,6 +50,16 @@ public:
 	{
 		return mapping.at(campaignToMap.at(format));
 	}
+
+	const JsonNode & campaignOverrides(std::string & campaignName)
+	{
+		return campaignOverridesConfig[campaignName];
+	}
+
+	const JsonNode & mapOverrides(std::string & mapName)
+	{
+		return mapOverridesConfig[mapName];
+	}
 };
 
 VCMI_LIB_NAMESPACE_END

+ 8 - 0
lib/mapping/MapIdentifiersH3M.cpp

@@ -98,6 +98,7 @@ void MapIdentifiersH3M::loadMapping(const JsonNode & mapping)
 	loadMapping(mappingTerrain, mapping["terrains"], "terrain");
 	loadMapping(mappingArtifact, mapping["artifacts"], "artifact");
 	loadMapping(mappingSecondarySkill, mapping["skills"], "skill");
+	loadMapping(mappingCampaignRegions, mapping["campaignRegions"], "campaignRegion");
 }
 
 void MapIdentifiersH3M::remapTemplate(ObjectTemplate & objectTemplate)
@@ -213,4 +214,11 @@ SecondarySkill MapIdentifiersH3M::remap(SecondarySkill input) const
 	return input;
 }
 
+CampaignRegionID MapIdentifiersH3M::remap(CampaignRegionID input) const
+{
+	if (mappingCampaignRegions.count(input))
+		return mappingCampaignRegions.at(input);
+	return input;
+}
+
 VCMI_LIB_NAMESPACE_END

+ 2 - 0
lib/mapping/MapIdentifiersH3M.h

@@ -43,6 +43,7 @@ class MapIdentifiersH3M
 	std::map<TerrainId, TerrainId> mappingTerrain;
 	std::map<ArtifactID, ArtifactID> mappingArtifact;
 	std::map<SecondarySkill, SecondarySkill> mappingSecondarySkill;
+	std::map<CampaignRegionID, CampaignRegionID> mappingCampaignRegions;
 
 	std::map<AnimationPath, AnimationPath> mappingObjectTemplate;
 	std::map<ObjectTypeIdentifier, ObjectTypeIdentifier> mappingObjectIndex;
@@ -63,6 +64,7 @@ public:
 	TerrainId remap(TerrainId input) const;
 	ArtifactID remap(ArtifactID input) const;
 	SecondarySkill remap(SecondarySkill input) const;
+	CampaignRegionID remap(CampaignRegionID input) const;
 
 };
 

+ 2 - 0
lib/modding/ContentTypeHandler.cpp

@@ -18,6 +18,7 @@
 #include "../BattleFieldHandler.h"
 #include "../CCreatureHandler.h"
 #include "../CConfigHandler.h"
+#include "../campaign/CampaignRegionsHandler.h"
 #include "../entities/artifact/CArtHandler.h"
 #include "../entities/faction/CTownHandler.h"
 #include "../entities/hero/CHeroClassHandler.h"
@@ -245,6 +246,7 @@ void CContentHandler::init()
 	handlers.insert(std::make_pair("artifacts", ContentTypeHandler(LIBRARY->arth.get(), "artifact")));
 	handlers.insert(std::make_pair("bonuses", ContentTypeHandler(LIBRARY->bth.get(), "bonus")));
 	handlers.insert(std::make_pair("creatures", ContentTypeHandler(LIBRARY->creh.get(), "creature")));
+	handlers.insert(std::make_pair("campaignRegions", ContentTypeHandler(LIBRARY->campaignRegions.get(), "campaignRegion")));
 	handlers.insert(std::make_pair("factions", ContentTypeHandler(LIBRARY->townh.get(), "faction")));
 	handlers.insert(std::make_pair("objects", ContentTypeHandler(LIBRARY->objtypeh.get(), "object")));
 	handlers.insert(std::make_pair("heroes", ContentTypeHandler(LIBRARY->heroh.get(), "hero")));

+ 0 - 11
lib/texts/CGeneralTextHandler.cpp

@@ -288,8 +288,6 @@ CGeneralTextHandler::CGeneralTextHandler():
 				}
 			}
 			while (parser.endLine() && !text.empty());
-
-			scenariosCountPerCampaign.push_back(region);
 		}
 	}
 }
@@ -306,15 +304,6 @@ int32_t CGeneralTextHandler::pluralText(const int32_t textIndex, const int32_t c
 	return textIndex + 1;
 }
 
-size_t CGeneralTextHandler::getCampaignLength(size_t campaignID) const
-{
-	assert(campaignID < scenariosCountPerCampaign.size());
-
-	if(campaignID < scenariosCountPerCampaign.size())
-		return scenariosCountPerCampaign[campaignID];
-	return 0;
-}
-
 std::string CGeneralTextHandler::getPreferredLanguage()
 {
 	assert(!settings["general"]["language"].String().empty());

+ 0 - 5
lib/texts/CGeneralTextHandler.h

@@ -42,9 +42,6 @@ class DLL_LINKAGE CGeneralTextHandler: public TextLocalizationContainer
 {
 	void readToVector(const std::string & sourceID, const std::string & sourceName);
 
-	/// number of scenarios in specific campaign. TODO: move to a better location
-	std::vector<size_t> scenariosCountPerCampaign;
-
 public:
 	LegacyTextContainer allTexts;
 
@@ -78,8 +75,6 @@ public:
 
 	int32_t pluralText(int32_t textIndex, int32_t count) const;
 
-	size_t getCampaignLength(size_t campaignID) const;
-
 	CGeneralTextHandler();
 	CGeneralTextHandler(const CGeneralTextHandler&) = delete;
 	CGeneralTextHandler operator=(const CGeneralTextHandler&) = delete;