Browse Source

New battlegrounds (#758)

Nordsoft91 3 năm trước cách đây
mục cha
commit
c4035134e5

+ 25 - 17
client/Graphics.cpp

@@ -24,6 +24,7 @@
 #include "gui/CAnimation.h"
 #include <SDL_ttf.h>
 #include "../lib/CThreadHelper.h"
+#include "../lib/CModHandler.h"
 #include "CGameInfo.h"
 #include "../lib/VCMI_Lib.h"
 #include "../CCallback.h"
@@ -99,28 +100,35 @@ void Graphics::loadPaletteAndColors()
 
 void Graphics::initializeBattleGraphics()
 {
-	const JsonNode config(ResourceID("config/battles_graphics.json"));
+	auto allConfigs = VLC->modh->getActiveMods();
+	allConfigs.insert(allConfigs.begin(), "core");
+	for(auto & mod : allConfigs)
+	{
+		if(!CResourceHandler::get(mod)->existsResource(ResourceID("config/battles_graphics.json")))
+			continue;
+			
+		const JsonNode config(mod, ResourceID("config/battles_graphics.json"));
 
-	// Reserve enough space for the terrains
-	int idx = static_cast<int>(config["backgrounds"].Vector().size());
-	battleBacks.resize(idx+1);	// 1 to idx, 0 is unused
+		if(!config["backgrounds"].isNull())
+		for(auto & t : config["backgrounds"].Struct())
+		{
+			battleBacks[t.first] = t.second.String();
+		}
 
-	idx = 1;
-	for(const JsonNode &t : config["backgrounds"].Vector()) {
-		battleBacks[idx].push_back(t.String());
-		idx++;
-	}
+		//initialization of AC->def name mapping
+		if(!config["ac_mapping"].isNull())
+		for(const JsonNode &ac : config["ac_mapping"].Vector())
+		{
+			int ACid = static_cast<int>(ac["id"].Float());
+			std::vector< std::string > toAdd;
 
-	//initialization of AC->def name mapping
-	for(const JsonNode &ac : config["ac_mapping"].Vector()) {
-		int ACid = static_cast<int>(ac["id"].Float());
-		std::vector< std::string > toAdd;
+			for(const JsonNode &defname : ac["defnames"].Vector())
+			{
+				toAdd.push_back(defname.String());
+			}
 
-		for(const JsonNode &defname : ac["defnames"].Vector()) {
-			toAdd.push_back(defname.String());
+			battleACToDef[ACid] = toAdd;
 		}
-
-		battleACToDef[ACid] = toAdd;
 	}
 }
 Graphics::Graphics()

+ 1 - 1
client/Graphics.h

@@ -87,7 +87,7 @@ public:
 	//towns
 	std::map<int, std::string> ERMUtoPicture[GameConstants::F_NUMBER]; //maps building ID to it's picture's name for each town type
 	//for battles
-	std::vector< std::vector< std::string > > battleBacks; //battleBacks[terType] - vector of possible names for certain terrain type
+	std::map<std::string, std::string> battleBacks; //maps BattleField to it's picture's name
 	std::map< int, std::vector < std::string > > battleACToDef; //maps AC format to vector of appropriate def names
 
 	//functions

+ 6 - 7
client/battle/CBattleInterface.cpp

@@ -200,15 +200,14 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 	}
 	else
 	{
-		auto bfieldType = (int)curInt->cb->battleGetBattlefieldType();
-		if (graphics->battleBacks.size() <= bfieldType || bfieldType < 0)
-			logGlobal->error("%d is not valid battlefield type index!", bfieldType);
-		else if (graphics->battleBacks[bfieldType].empty())
-			logGlobal->error("%d battlefield type does not have any backgrounds!", bfieldType);
+		auto bfieldType = curInt->cb->battleGetBattlefieldType();
+		if(!vstd::contains(graphics->battleBacks, bfieldType))
+		{
+			logGlobal->error("%s is not valid battlefield type!", static_cast<std::string>(bfieldType));
+		}
 		else
 		{
-			const std::string bgName = *RandomGeneratorUtil::nextItem(graphics->battleBacks[bfieldType], CRandomGenerator::getDefault());
-			background = BitmapHandler::loadBitmap(bgName, false);
+			background = BitmapHandler::loadBitmap(graphics->battleBacks[bfieldType], false);
 		}
 	}
 

+ 27 - 27
config/battles_graphics.json

@@ -1,32 +1,32 @@
 {
 	// backgrounds of terrains battles can be fought on
-	"backgrounds": [
-		"CMBKBCH.BMP",
-		"CMBKDES.BMP",
-		"CMBKDRTR.BMP",
-		"CMBKDRMT.BMP",
-		"CMBKDRDD.BMP",
-		"CMBKGRMT.BMP",
-		"CMBKGRTR.BMP",
-		"CMBKLAVA.BMP",
-		"CMBKMAG.BMP",
-		"CMBKSNMT.BMP",
-		"CMBKSNTR.BMP",
-		"CMBKSUB.BMP",
-		"CMBKSWMP.BMP",
-		"CMBKFF.BMP",
-		"CMBKRK.BMP",
-		"CMBKMC.BMP",
-		"CMBKLP.BMP",
-		"CMBKHG.BMP",
-		"CMBKCF.BMP",
-		"CMBKEF.BMP",
-		"CMBKFW.BMP",
-		"CMBKCUR.BMP",
-		"CMBKRGH.BMP",
-		"CMBKBOAT.BMP",
-		"CMBKDECK.BMP"
-	],
+	"backgrounds": {
+		"sand_shore": "CMBKBCH.BMP",
+		"sand_mesas": "CMBKDES.BMP",
+		"dirt_birches": "CMBKDRTR.BMP",
+		"dirt_hills": "CMBKDRMT.BMP",
+		"dirt_pines": "CMBKDRDD.BMP",
+		"grass_hills": "CMBKGRMT.BMP",
+		"grass_pines": "CMBKGRTR.BMP",
+		"lava": "CMBKLAVA.BMP",
+		"magic_plains": "CMBKMAG.BMP",
+		"snow_mountains": "CMBKSNMT.BMP",
+		"snow_trees": "CMBKSNTR.BMP",
+		"subterranean": "CMBKSUB.BMP",
+		"swamp_trees": "CMBKSWMP.BMP",
+		"fiery_fields": "CMBKFF.BMP",
+		"rocklands": "CMBKRK.BMP",
+		"magic_clouds": "CMBKMC.BMP",
+		"lucid_pools": "CMBKLP.BMP",
+		"holy_ground": "CMBKHG.BMP",
+		"clover_field": "CMBKCF.BMP",
+		"evil_fog": "CMBKEF.BMP",
+		"favorable_winds": "CMBKFW.BMP",
+		"cursed_ground": "CMBKCUR.BMP",
+		"rough": "CMBKRGH.BMP",
+		"ship_to_ship": "CMBKBOAT.BMP",
+		"ship": "CMBKDECK.BMP"
+	},
 
 	// WoG_Ac_format_to_def_names_mapping
 	"ac_mapping": [

+ 69 - 69
config/obstacles.json

@@ -25,7 +25,7 @@
 	{
 		"id" : 1,
 		"allowedTerrain" : ["dirt", "sand", "rough", "subterra"],
-		"specialBattlefields" : [0],
+		"specialBattlefields" : ["sand_shore"],
 		"width" : 3,
 		"height" : 2,
 		"blockedTiles" :  [0, 1, 2],
@@ -45,7 +45,7 @@
 	{
 		"id" : 3,
 		"allowedTerrain" : ["dirt", "rough"],
-		"specialBattlefields" : [1],
+		"specialBattlefields" : ["cursed_ground"],
 		"width" : 2,
 		"height" : 1,
 		"blockedTiles" :  [0, 1],
@@ -55,7 +55,7 @@
 	{
 		"id" : 4,
 		"allowedTerrain" : ["dirt", "rough", "subterra"],
-		"specialBattlefields" : [0, 1],
+		"specialBattlefields" : ["sand_shore", "cursed_ground"],
 		"width" : 2,
 		"height" : 1,
 		"blockedTiles" :  [0, 1],
@@ -135,7 +135,7 @@
 	{
 		"id" : 12,
 		"allowedTerrain" : ["dirt", "rough"],
-		"specialBattlefields" : [1],
+		"specialBattlefields" : ["cursed_ground"],
 		"width" : 3,
 		"height" : 3,
 		"blockedTiles" :  [0, 1, 2, 3],
@@ -145,7 +145,7 @@
 	{
 		"id" : 13,
 		"allowedTerrain" : ["dirt", "rough"],
-		"specialBattlefields" : [1],
+		"specialBattlefields" : ["cursed_ground"],
 		"width" : 3,
 		"height" : 2,
 		"blockedTiles" :  [1, 2, -15],
@@ -155,7 +155,7 @@
 	{
 		"id" : 14,
 		"allowedTerrain" : ["dirt", "rough"],
-		"specialBattlefields" : [1],
+		"specialBattlefields" : ["cursed_ground"],
 		"width" : 3,
 		"height" : 2,
 		"blockedTiles" :  [2, -15, -16],
@@ -165,7 +165,7 @@
 	{
 		"id" : 15,
 		"allowedTerrain" : ["dirt", "rough"],
-		"specialBattlefields" : [1],
+		"specialBattlefields" : ["cursed_ground"],
 		"width" : 3,
 		"height" : 3,
 		"blockedTiles" :  [1, -16, -33],
@@ -215,7 +215,7 @@
 	{
 		"id" : 20,
 		"allowedTerrain" : ["grass", "swamp"],
-		"specialBattlefields" : [2],
+		"specialBattlefields" : ["magic_plains"],
 		"width" : 2,
 		"height" : 2,
 		"blockedTiles" :  [0, 1],
@@ -235,7 +235,7 @@
 	{
 		"id" : 22,
 		"allowedTerrain" : ["grass"],
-		"specialBattlefields" : [2],
+		"specialBattlefields" : ["magic_plains"],
 		"width" : 6,
 		"height" : 2,
 		"blockedTiles" :  [1, 2, 3, 4, -13, -14, -15, -16],
@@ -415,7 +415,7 @@
 	{
 		"id" : 40,
 		"allowedTerrain" : ["rough"],
-		"specialBattlefields" : [1],
+		"specialBattlefields" : ["cursed_ground"],
 		"width" : 2,
 		"height" : 2,
 		"blockedTiles" :  [0, 1, -16],
@@ -425,7 +425,7 @@
 	{
 		"id" : 41,
 		"allowedTerrain" : ["rough"],
-		"specialBattlefields" : [1],
+		"specialBattlefields" : ["cursed_ground"],
 		"width" : 4,
 		"height" : 3,
 		"blockedTiles" :  [-14, -15, -16, -32, -33],
@@ -435,7 +435,7 @@
 	{
 		"id" : 42,
 		"allowedTerrain" : ["rough"],
-		"specialBattlefields" : [1],
+		"specialBattlefields" : ["cursed_ground"],
 		"width" : 3,
 		"height" : 2,
 		"blockedTiles" :  [1, 2, -15, -16],
@@ -445,7 +445,7 @@
 	{
 		"id" : 43,
 		"allowedTerrain" : ["rough"],
-		"specialBattlefields" : [1],
+		"specialBattlefields" : ["cursed_ground"],
 		"width" : 3,
 		"height" : 3,
 		"blockedTiles" :  [-16, -32, -33],
@@ -455,7 +455,7 @@
 	{
 		"id" : 44,
 		"allowedTerrain" : ["rough"],
-		"specialBattlefields" : [1],
+		"specialBattlefields" : ["cursed_ground"],
 		"width" : 3,
 		"height" : 3,
 		"blockedTiles" :  [-15, -16, -32],
@@ -575,7 +575,7 @@
 	{
 		"id" : 56,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [0],
+		"specialBattlefields" : ["sand_shore"],
 		"width" : 3,
 		"height" : 2,
 		"blockedTiles" :  [1, -15, -16],
@@ -585,7 +585,7 @@
 	{
 		"id" : 57,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [0],
+		"specialBattlefields" : ["sand_shore"],
 		"width" : 3,
 		"height" : 2,
 		"blockedTiles" :  [0, 1, 2],
@@ -595,7 +595,7 @@
 	{
 		"id" : 58,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [0],
+		"specialBattlefields" : ["sand_shore"],
 		"width" : 5,
 		"height" : 2,
 		"blockedTiles" :  [1, 2, 3, -14, -15, -16],
@@ -605,7 +605,7 @@
 	{
 		"id" : 59,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [0],
+		"specialBattlefields" : ["sand_shore"],
 		"width" : 4,
 		"height" : 2,
 		"blockedTiles" :  [1, 2, -14, -15],
@@ -615,7 +615,7 @@
 	{
 		"id" : 60,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [0],
+		"specialBattlefields" : ["sand_shore"],
 		"width" : 2,
 		"height" : 2,
 		"blockedTiles" :  [0, 1, -16],
@@ -625,7 +625,7 @@
 	{
 		"id" : 61,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [3],
+		"specialBattlefields" : ["holy_ground"],
 		"width" : 1,
 		"height" : 1,
 		"blockedTiles" :  [0],
@@ -635,7 +635,7 @@
 	{
 		"id" : 62,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [3],
+		"specialBattlefields" : ["holy_ground"],
 		"width" : 2,
 		"height" : 1,
 		"blockedTiles" :  [0, 1],
@@ -645,7 +645,7 @@
 	{
 		"id" : 63,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [3],
+		"specialBattlefields" : ["holy_ground"],
 		"width" : 3,
 		"height" : 3,
 		"blockedTiles" :  [1],
@@ -655,7 +655,7 @@
 	{
 		"id" : 64,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [3],
+		"specialBattlefields" : ["holy_ground"],
 		"width" : 3,
 		"height" : 2,
 		"blockedTiles" :  [0, 1, 2],
@@ -665,7 +665,7 @@
 	{
 		"id" : 65,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [3],
+		"specialBattlefields" : ["holy_ground"],
 		"width" : 4,
 		"height" : 3,
 		"blockedTiles" :  [0, 1, 2, 3],
@@ -675,7 +675,7 @@
 	{
 		"id" : 66,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [4],
+		"specialBattlefields" : ["evil_fog"],
 		"width" : 1,
 		"height" : 1,
 		"blockedTiles" :  [0],
@@ -685,7 +685,7 @@
 	{
 		"id" : 67,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [4],
+		"specialBattlefields" : ["evil_fog"],
 		"width" : 2,
 		"height" : 1,
 		"blockedTiles" :  [0, 1],
@@ -695,7 +695,7 @@
 	{
 		"id" : 68,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [4],
+		"specialBattlefields" : ["evil_fog"],
 		"width" : 3,
 		"height" : 2,
 		"blockedTiles" :  [0, 1, 2],
@@ -705,7 +705,7 @@
 	{
 		"id" : 69,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [4],
+		"specialBattlefields" : ["evil_fog"],
 		"width" : 4,
 		"height" : 2,
 		"blockedTiles" :  [1, 2],
@@ -715,7 +715,7 @@
 	{
 		"id" : 70,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [4],
+		"specialBattlefields" : ["evil_fog"],
 		"width" : 6,
 		"height" : 2,
 		"blockedTiles" :  [1, 2, 3, -12, -13],
@@ -725,7 +725,7 @@
 	{
 		"id" : 71,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [5],
+		"specialBattlefields" : ["clover_field"],
 		"width" : 1,
 		"height" : 1,
 		"blockedTiles" :  [0],
@@ -735,7 +735,7 @@
 	{
 		"id" : 72,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [5],
+		"specialBattlefields" : ["clover_field"],
 		"width" : 3,
 		"height" : 1,
 		"blockedTiles" :  [0, 1, 2],
@@ -745,7 +745,7 @@
 	{
 		"id" : 73,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [5],
+		"specialBattlefields" : ["clover_field"],
 		"width" : 3,
 		"height" : 2,
 		"blockedTiles" :  [1, 2, -15, -16],
@@ -755,7 +755,7 @@
 	{
 		"id" : 74,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [5],
+		"specialBattlefields" : ["clover_field"],
 		"width" : 4,
 		"height" : 2,
 		"blockedTiles" :  [0, 1, 2, -14, -15, -16],
@@ -765,7 +765,7 @@
 	{
 		"id" : 75,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [6],
+		"specialBattlefields" : ["lucid_pools"],
 		"width" : 1,
 		"height" : 1,
 		"blockedTiles" :  [0],
@@ -775,7 +775,7 @@
 	{
 		"id" : 76,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [6],
+		"specialBattlefields" : ["lucid_pools"],
 		"width" : 2,
 		"height" : 1,
 		"blockedTiles" :  [0, 1],
@@ -785,7 +785,7 @@
 	{
 		"id" : 77,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [6],
+		"specialBattlefields" : ["lucid_pools"],
 		"width" : 3,
 		"height" : 2,
 		"blockedTiles" :  [0, -15, -16],
@@ -795,7 +795,7 @@
 	{
 		"id" : 78,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [6],
+		"specialBattlefields" : ["lucid_pools"],
 		"width" : 5,
 		"height" : 2,
 		"blockedTiles" :  [1, 2, 3, -13, -14, -15, -16],
@@ -805,7 +805,7 @@
 	{
 		"id" : 79,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [7],
+		"specialBattlefields" : ["fiery_fields"],
 		"width" : 1,
 		"height" : 1,
 		"blockedTiles" :  [0],
@@ -815,7 +815,7 @@
 	{
 		"id" : 80,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [7],
+		"specialBattlefields" : ["fiery_fields"],
 		"width" : 2,
 		"height" : 1,
 		"blockedTiles" :  [0, 1],
@@ -825,7 +825,7 @@
 	{
 		"id" : 81,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [7],
+		"specialBattlefields" : ["fiery_fields"],
 		"width" : 3,
 		"height" : 2,
 		"blockedTiles" :  [0, 1, 2, -15],
@@ -835,7 +835,7 @@
 	{
 		"id" : 82,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [7],
+		"specialBattlefields" : ["fiery_fields"],
 		"width" : 4,
 		"height" : 2,
 		"blockedTiles" :  [1, 2, 3, -15, -16],
@@ -845,7 +845,7 @@
 	{
 		"id" : 83,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [7],
+		"specialBattlefields" : ["fiery_fields"],
 		"width" : 3,
 		"height" : 3,
 		"blockedTiles" :  [0, 1, 2, 3, -14, -15, -16],
@@ -855,7 +855,7 @@
 	{
 		"id" : 84,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [8],
+		"specialBattlefields" : ["rocklands"],
 		"width" : 1,
 		"height" : 1,
 		"blockedTiles" :  [0],
@@ -865,7 +865,7 @@
 	{
 		"id" : 85,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [8],
+		"specialBattlefields" : ["rocklands"],
 		"width" : 2,
 		"height" : 1,
 		"blockedTiles" :  [0, 1],
@@ -875,7 +875,7 @@
 	{
 		"id" : 86,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [8],
+		"specialBattlefields" : ["rocklands"],
 		"width" : 3,
 		"height" : 1,
 		"blockedTiles" :  [0, 1, 2],
@@ -885,7 +885,7 @@
 	{
 		"id" : 87,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [8],
+		"specialBattlefields" : ["rocklands"],
 		"width" : 4,
 		"height" : 2,
 		"blockedTiles" :  [1, 2, 3, -15, -16],
@@ -895,7 +895,7 @@
 	{
 		"id" : 88,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [9],
+		"specialBattlefields" : ["magic_clouds"],
 		"width" : 1,
 		"height" : 1,
 		"blockedTiles" :  [0],
@@ -905,7 +905,7 @@
 	{
 		"id" : 89,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [9],
+		"specialBattlefields" : ["magic_clouds"],
 		"width" : 2,
 		"height" : 2,
 		"blockedTiles" :  [1, -16],
@@ -915,7 +915,7 @@
 	{
 		"id" : 90,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [9],
+		"specialBattlefields" : ["magic_clouds"],
 		"width" : 4,
 		"height" : 2,
 		"blockedTiles" :  [0, 1, -14, -15],
@@ -1053,7 +1053,7 @@
 	{
 		"id" : 14,
 		"allowedTerrain" : ["rough"],
-		"specialBattlefields" : [1],
+		"specialBattlefields" : ["cursed_ground"],
 		"width" : 186,
 		"height" : 212,
 		"blockedTiles" :  [55, 72, 90, 107, 125, 126, 127, 128, 129, 130, 131, 132],
@@ -1062,7 +1062,7 @@
 	{
 		"id" : 15,
 		"allowedTerrain" : ["rough"],
-		"specialBattlefields" : [1],
+		"specialBattlefields" : ["cursed_ground"],
 		"width" : 347,
 		"height" : 174,
 		"blockedTiles" :  [41, 59, 76, 94, 111, 129, 143, 144, 145],
@@ -1071,7 +1071,7 @@
 	{
 		"id" : 16,
 		"allowedTerrain" : ["rough"],
-		"specialBattlefields" : [1],
+		"specialBattlefields" : ["cursed_ground"],
 		"width" : 294,
 		"height" : 169,
 		"blockedTiles" :  [40, 41, 42, 43, 58, 75, 93, 110, 128, 145],
@@ -1080,7 +1080,7 @@
 	{
 		"id" : 17,
 		"allowedTerrain" : ["rough"],
-		"specialBattlefields" : [1],
+		"specialBattlefields" : ["cursed_ground"],
 		"width" : 165,
 		"height" : 257,
 		"blockedTiles" :  [72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 89, 105],
@@ -1089,7 +1089,7 @@
 	{
 		"id" : 18,
 		"allowedTerrain" : ["rough"],
-		"specialBattlefields" : [1],
+		"specialBattlefields" : ["cursed_ground"],
 		"width" : 208,
 		"height" : 268,
 		"blockedTiles" :  [72, 73, 74, 75, 76, 77, 78, 79, 80, 90, 91, 92, 93, 94, 95, 96, 97],
@@ -1098,7 +1098,7 @@
 	{
 		"id" : 19,
 		"allowedTerrain" : ["rough"],
-		"specialBattlefields" : [1],
+		"specialBattlefields" : ["cursed_ground"],
 		"width" : 252,
 		"height" : 254,
 		"blockedTiles" :  [73, 74, 75, 76, 77, 78, 91, 92, 93, 94],
@@ -1107,7 +1107,7 @@
 	{
 		"id" : 20,
 		"allowedTerrain" : ["rough"],
-		"specialBattlefields" : [1],
+		"specialBattlefields" : ["cursed_ground"],
 		"width" : 278,
 		"height" : 128,
 		"blockedTiles" :  [23, 40, 58, 75, 93, 110, 128, 145, 163],
@@ -1116,7 +1116,7 @@
 	{
 		"id" : 21,
 		"allowedTerrain" : ["rough"],
-		"specialBattlefields" : [1],
+		"specialBattlefields" : ["cursed_ground"],
 		"width" : 208,
 		"height" : 268,
 		"blockedTiles" :  [72, 73, 74, 75, 76, 77, 78, 79, 80, 90, 91, 92, 93, 94, 95, 96, 97],
@@ -1125,7 +1125,7 @@
 	{
 		"id" : 22,
 		"allowedTerrain" : ["rough"],
-		"specialBattlefields" : [1],
+		"specialBattlefields" : ["cursed_ground"],
 		"width" : 168,
 		"height" : 212,
 		"blockedTiles" :  [73, 74, 75, 76, 77, 78, 79, 90, 91, 92, 93, 94, 95, 96, 97, 106, 107, 108, 109, 110, 111, 112],
@@ -1134,7 +1134,7 @@
 	{
 		"id" : 23,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [0],
+		"specialBattlefields" : ["sand_shore"],
 		"width" : 147,
 		"height" : 264,
 		"blockedTiles" :  [72, 73, 74, 75, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98],
@@ -1143,7 +1143,7 @@
 	{
 		"id" : 24,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [0],
+		"specialBattlefields" : ["sand_shore"],
 		"width" : 178,
 		"height" : 262,
 		"blockedTiles" :  [71, 72, 73, 74, 75, 76, 77, 78, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98],
@@ -1152,7 +1152,7 @@
 	{
 		"id" : 25,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [0],
+		"specialBattlefields" : ["sand_shore"],
 		"width" : 173,
 		"height" : 257,
 		"blockedTiles" :  [72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 89, 90, 105, 106],
@@ -1161,7 +1161,7 @@
 	{
 		"id" : 26,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [0],
+		"specialBattlefields" : ["sand_shore"],
 		"width" : 241,
 		"height" : 272,
 		"blockedTiles" :  [73, 91, 108, 109, 110, 111, 112, 113],
@@ -1170,7 +1170,7 @@
 	{
 		"id" : 27,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [0],
+		"specialBattlefields" : ["sand_shore"],
 		"width" : 261,
 		"height" : 129,
 		"blockedTiles" :  [27, 28, 43, 44, 60, 61, 76, 77, 93, 94, 109, 110, 126, 127, 142, 143, 159],
@@ -1179,7 +1179,7 @@
 	{
 		"id" : 28,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [0],
+		"specialBattlefields" : ["sand_shore"],
 		"width" : 180,
 		"height" : 154,
 		"blockedTiles" :  [22, 38, 39, 40, 44, 45, 46, 55, 56, 57, 62, 63, 123, 124, 125, 130, 131, 140, 141, 146, 147, 148],
@@ -1188,7 +1188,7 @@
 	{
 		"id" : 29,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [5],
+		"specialBattlefields" : ["clover_field"],
 		"width" : 304,
 		"height" : 264,
 		"blockedTiles" :  [76, 77, 92, 93, 94, 95, 109, 110, 111],
@@ -1197,7 +1197,7 @@
 	{
 		"id" : 30,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [6],
+		"specialBattlefields" : ["lucid_pools"],
 		"width" : 256,
 		"height" : 257,
 		"blockedTiles" :  [76, 77, 78, 92, 93, 94, 107, 108, 109],
@@ -1206,7 +1206,7 @@
 	{
 		"id" : 31,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [7],
+		"specialBattlefields" : ["fiery_fields"],
 		"width" : 257,
 		"height" : 255,
 		"blockedTiles" :  [76, 77, 91, 92, 93, 94, 95, 108, 109, 110, 111],
@@ -1215,7 +1215,7 @@
 	{
 		"id" : 32,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [8],
+		"specialBattlefields" : ["rocklands"],
 		"width" : 277,
 		"height" : 218,
 		"blockedTiles" :  [60, 61, 75, 76, 77, 91, 92, 93, 94, 95],
@@ -1224,7 +1224,7 @@
 	{
 		"id" : 33,
 		"allowedTerrain" : [],
-		"specialBattlefields" : [9],
+		"specialBattlefields" : ["magic_clouds"],
 		"width" : 300,
 		"height" : 214,
 		"blockedTiles" :  [59, 60, 74, 75, 76, 93, 94, 95, 111, 112],

+ 27 - 10
config/terrains.json

@@ -6,7 +6,9 @@
 		"minimapBlocked"   : [ 57, 40, 8 ],
 		"music" : "Dirt.mp3",
 		"tiles" : "DIRTTL",
-		"code" : "dt"
+		"code" : "dt",
+		"battleFields" : ["dirt_birches", "dirt_hills", "dirt_pines"],
+		"terrainViewPatterns" : "dirt"
 	},
 	"sand" :
 	{
@@ -15,7 +17,10 @@
 		"minimapBlocked"   : [ 165, 158, 107 ],
 		"music" : "Sand.mp3",
 		"tiles" : "SANDTL",
-		"code" : "sa"
+		"code" : "sa",
+		"battleFields" : ["sand_mesas"],
+		"transitionRequired" : true,
+		"terrainViewPatterns" : "sand"
 	},
 	"grass" :
 	{
@@ -24,7 +29,8 @@
 		"minimapBlocked"   : [ 0, 48, 0 ],
 		"music" : "Grass.mp3",
 		"tiles" : "GRASTL",
-		"code" : "gr"
+		"code" : "gr",
+		"battleFields" : ["grass_hills", "grass_pines"]
 	},
 	"snow" :
 	{
@@ -33,7 +39,8 @@
 		"minimapBlocked"   : [ 140, 158, 156 ],
 		"music" : "Snow.mp3",
 		"tiles" : "SNOWTL",
-		"code" : "sn"
+		"code" : "sn",
+		"battleFields" : ["snow_mountains", "snow_trees"]
 	},
 	"swamp" :
 	{
@@ -42,7 +49,8 @@
 		"minimapBlocked"   : [ 33,  89,  66 ],
 		"music" : "Swamp.mp3",
 		"tiles" : "SWMPTL",
-		"code" : "sw"
+		"code" : "sw",
+		"battleFields" : ["swamp_trees"]
 	},
 	"rough" :
 	{
@@ -51,7 +59,8 @@
 		"minimapBlocked"   : [  99,  81, 33 ],
 		"music" : "Rough.mp3",
 		"tiles" : "ROUGTL",
-		"code" : "rg"
+		"code" : "rg",
+		"battleFields" : ["rough"]
 	},
 	"subterra" :
 	{
@@ -61,7 +70,8 @@
 		"music" : "Underground.mp3",
 		"tiles" : "SUBBTL",
 		"type" : "SUB",
-		"code" : "sb"
+		"code" : "sb",
+		"battleFields" : ["subterranean"]
 	},
 	"lava" :
 	{
@@ -70,7 +80,8 @@
 		"minimapBlocked"   : [ 41, 40, 41 ],
 		"music" : "Lava.mp3",
 		"tiles" : "LAVATL",
-		"code" : "lv"
+		"code" : "lv",
+		"battleFields" : ["lava"]
 	},
 	"water" :
 	{
@@ -80,7 +91,10 @@
 		"music" : "Water.mp3",
 		"tiles" : "WATRTL",
 		"type" : "WATER",
-		"code" : "wt"
+		"code" : "wt",
+		"battleFields" : ["ship"],
+		"transitionRequired" : true,
+		"terrainViewPatterns" : "water"
 	},
 	"rock" :
 	{
@@ -90,6 +104,9 @@
 		"music" : "Underground.mp3", // Impossible in H3
 		"tiles" : "ROCKTL",
 		"type" : "ROCK",
-		"code" : "rc"
+		"code" : "rc",
+		"battleFields" : ["rocklands"],
+		"transitionRequired" : true,
+		"terrainViewPatterns" : "rock"
 	}
 }

+ 15 - 39
lib/CGameState.cpp

@@ -1894,17 +1894,17 @@ void CGameState::initVisitingAndGarrisonedHeroes()
 	}
 }
 
-BFieldType CGameState::battleGetBattlefieldType(int3 tile, CRandomGenerator & rand)
+BattleField CGameState::battleGetBattlefieldType(int3 tile, CRandomGenerator & rand)
 {
 	if(!tile.valid() && curB)
 		tile = curB->tile;
 	else if(!tile.valid() && !curB)
-		return BFieldType::NONE;
+		return BattleField::NONE;
 
 	const TerrainTile &t = map->getTile(tile);
 	//fight in mine -> subterranean
 	if(dynamic_cast<const CGMine *>(t.visitableObjects.front()))
-		return BFieldType::SUBTERRANEAN;
+		return BattleField("subterranean");
 
 	for(auto &obj : map->objects)
 	{
@@ -1915,56 +1915,32 @@ BFieldType CGameState::battleGetBattlefieldType(int3 tile, CRandomGenerator & ra
 		switch(obj->ID)
 		{
 		case Obj::CLOVER_FIELD:
-			return BFieldType::CLOVER_FIELD;
+			return BattleField("clover_field");
 		case Obj::CURSED_GROUND1: case Obj::CURSED_GROUND2:
-			return BFieldType::CURSED_GROUND;
+			return BattleField("cursed_ground");
 		case Obj::EVIL_FOG:
-			return BFieldType::EVIL_FOG;
+			return BattleField("evil_fog");
 		case Obj::FAVORABLE_WINDS:
-			return BFieldType::FAVORABLE_WINDS;
+			return BattleField("favorable_winds");
 		case Obj::FIERY_FIELDS:
-			return BFieldType::FIERY_FIELDS;
+			return BattleField("fiery_fields");
 		case Obj::HOLY_GROUNDS:
-			return BFieldType::HOLY_GROUND;
+			return BattleField("holy_ground");
 		case Obj::LUCID_POOLS:
-			return BFieldType::LUCID_POOLS;
+			return BattleField("lucid_pools");
 		case Obj::MAGIC_CLOUDS:
-			return BFieldType::MAGIC_CLOUDS;
+			return BattleField("magic_clouds");
 		case Obj::MAGIC_PLAINS1: case Obj::MAGIC_PLAINS2:
-			return BFieldType::MAGIC_PLAINS;
+			return BattleField("magic_plains");
 		case Obj::ROCKLANDS:
-			return BFieldType::ROCKLANDS;
+			return BattleField("rocklands");
 		}
 	}
 
 	if(map->isCoastalTile(tile)) //coastal tile is always ground
-		return BFieldType::SAND_SHORE;
-
-	if(t.terType == Terrain("dirt"))
-		return BFieldType(rand.nextInt(3, 5));
-	if(t.terType == Terrain("sand"))
-		return BFieldType::SAND_MESAS; //TODO: coast support
-	if(t.terType == Terrain("grass"))
-		return BFieldType(rand.nextInt(6, 7));
-	if(t.terType == Terrain("snow"))
-		return BFieldType(rand.nextInt(10, 11));
-	if(t.terType == Terrain("swamp"))
-		return BFieldType::SWAMP_TREES;
-	if(t.terType == Terrain("rough"))
-		return BFieldType::ROUGH;
-	if(t.terType.isUnderground())
-		return BFieldType::SUBTERRANEAN;
-	if(t.terType == Terrain("lava"))
-		return BFieldType::LAVA;
-	if(t.terType.isWater())
-		return BFieldType::SHIP;
-	if(!t.terType.isPassable())
-		return BFieldType::ROCKLANDS;
+		return BattleField("sand_shore");
 	
-	//TODO: STUB, support new battlegrounds
-	return BFieldType::DIRT_HILLS;
-	
-	return BFieldType::NONE;
+	return *RandomGeneratorUtil::nextItem(Terrain::Manager::getInfo(t.terType).battleFields, rand);
 }
 
 UpgradeInfo CGameState::getUpgradeInfo(const CStackInstance &stack)

+ 1 - 1
lib/CGameState.h

@@ -179,7 +179,7 @@ public:
 	void giveHeroArtifact(CGHeroInstance *h, ArtifactID aid);
 
 	void apply(CPack *pack);
-	BFieldType battleGetBattlefieldType(int3 tile, CRandomGenerator & rand);
+	BattleField battleGetBattlefieldType(int3 tile, CRandomGenerator & rand);
 	UpgradeInfo getUpgradeInfo(const CStackInstance &stack);
 	PlayerRelations::PlayerRelations getPlayerRelations(PlayerColor color1, PlayerColor color2);
 	bool checkForVisitableDir(const int3 & src, const int3 & dst) const; //check if src tile is visitable from dst tile

+ 19 - 12
lib/CHeroHandler.cpp

@@ -177,9 +177,9 @@ std::vector<BattleHex> CObstacleInfo::getBlocked(BattleHex hex) const
 	return ret;
 }
 
-bool CObstacleInfo::isAppropriate(Terrain terrainType, int specialBattlefield) const
+bool CObstacleInfo::isAppropriate(const Terrain & terrainType, const BattleField & specialBattlefield) const
 {
-	if(specialBattlefield != -1)
+	if(!allowedSpecialBfields.empty() && specialBattlefield != BattleField::NONE)
 		return vstd::contains(allowedSpecialBfields, specialBattlefield);
 
 	return vstd::contains(allowedTerrains, terrainType);
@@ -817,28 +817,35 @@ void CHeroHandler::loadExperience()
 
 void CHeroHandler::loadObstacles()
 {
-	auto loadObstacles = [](const JsonNode &node, bool absolute, std::map<int, CObstacleInfo> &out)
+	auto loadObstacles = [](const JsonNode & node, bool absolute, std::vector<CObstacleInfo> & out)
 	{
 		for(const JsonNode &obs : node.Vector())
 		{
-			int ID = static_cast<int>(obs["id"].Float());
-			CObstacleInfo & obi = out[ID];
-			obi.ID = ID;
+			out.emplace_back();
+			CObstacleInfo & obi = out.back();
 			obi.defName = obs["defname"].String();
 			obi.width =  static_cast<si32>(obs["width"].Float());
 			obi.height = static_cast<si32>(obs["height"].Float());
 			for(auto & t : obs["allowedTerrain"].Vector())
 				obi.allowedTerrains.emplace_back(t.String());
-			obi.allowedSpecialBfields = obs["specialBattlefields"].convertTo<std::vector<BFieldType> >();
+			for(auto & t : obs["specialBattlefields"].Vector())
+				obi.allowedSpecialBfields.emplace_back(t.String());
 			obi.blockedTiles = obs["blockedTiles"].convertTo<std::vector<si16> >();
 			obi.isAbsoluteObstacle = absolute;
 		}
 	};
-
-	const JsonNode config(ResourceID("config/obstacles.json"));
-	loadObstacles(config["obstacles"], false, obstacles);
-	loadObstacles(config["absoluteObstacles"], true, absoluteObstacles);
-	//loadObstacles(config["moats"], true, moats);
+	
+	auto allConfigs = VLC->modh->getActiveMods();
+	allConfigs.insert(allConfigs.begin(), "core");
+	for(auto & mod : allConfigs)
+	{
+		if(!CResourceHandler::get(mod)->existsResource(ResourceID("config/obstacles.json")))
+			continue;
+		
+		const JsonNode config(mod, ResourceID("config/obstacles.json"));
+		loadObstacles(config["obstacles"], false, obstacles);
+		loadObstacles(config["absoluteObstacles"], true, absoluteObstacles);
+	}
 }
 
 /// convert h3-style ID (e.g. Gobin Wolf Rider) to vcmi (e.g. goblinWolfRider)

+ 5 - 6
lib/CHeroHandler.h

@@ -27,6 +27,7 @@ class JsonNode;
 class CRandomGenerator;
 class JsonSerializeFormat;
 class Terrain;
+class BattleField;
 
 struct SSpecialtyInfo
 {	si32 type;
@@ -224,10 +225,9 @@ public:
 
 struct DLL_LINKAGE CObstacleInfo
 {
-	si32 ID;
 	std::string defName;
 	std::vector<Terrain> allowedTerrains;
-	std::vector<BFieldType> allowedSpecialBfields;
+	std::vector<BattleField> allowedSpecialBfields;
 
 	ui8 isAbsoluteObstacle; //there may only one such obstacle in battle and its position is always the same
 	si32 width, height; //how much space to the right and up is needed to place obstacle (affects only placement algorithm)
@@ -235,11 +235,10 @@ struct DLL_LINKAGE CObstacleInfo
 
 	std::vector<BattleHex> getBlocked(BattleHex hex) const; //returns vector of hexes blocked by obstacle when it's placed on hex 'hex'
 
-	bool isAppropriate(Terrain terrainType, int specialBattlefield = -1) const;
+	bool isAppropriate(const Terrain & terrainType, const BattleField & specialBattlefield) const;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & ID;
 		h & defName;
 		h & allowedTerrains;
 		h & allowedSpecialBfields;
@@ -316,8 +315,8 @@ public:
 	};
 	std::vector<SBallisticsLevelInfo> ballistics; //info about ballistics ability per level; [0] - none; [1] - basic; [2] - adv; [3] - expert
 
-	std::map<int, CObstacleInfo> obstacles; //info about obstacles that may be placed on battlefield
-	std::map<int, CObstacleInfo> absoluteObstacles; //info about obstacles that may be placed on battlefield
+	std::vector<CObstacleInfo> obstacles; //info about obstacles that may be placed on battlefield
+	std::vector<CObstacleInfo> absoluteObstacles; //info about obstacles that may be placed on battlefield
 
 	ui32 level(ui64 experience) const; //calculates level corresponding to given experience amount
 	ui64 reqExp(ui32 level) const; //calculates experience required for given level

+ 0 - 43
lib/GameConstants.h

@@ -844,26 +844,6 @@ namespace SecSkillLevel
 	};
 }
 
-
-//follows ERM BI (battle image) format
-namespace BattlefieldBI
-{
-	enum BattlefieldBI
-	{
-		NONE = -1,
-		COASTAL,
-		CURSED_GROUND,
-		MAGIC_PLAINS,
-		HOLY_GROUND,
-		EVIL_FOG,
-		CLOVER_FIELD,
-		LUCID_POOLS,
-		FIERY_FIELDS,
-		ROCKLANDS,
-		MAGIC_CLOUDS
-	};
-}
-
 namespace Date
 {
 	enum EDateType
@@ -940,29 +920,6 @@ DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EPathfindingLayer
 
 ID_LIKE_OPERATORS(EPathfindingLayer, EPathfindingLayer::EEPathfindingLayer)
 
-class BFieldType
-{
-public:
-	//   1. sand/shore   2. sand/mesas   3. dirt/birches   4. dirt/hills   5. dirt/pines   6. grass/hills   7. grass/pines
-	//8. lava   9. magic plains   10. snow/mountains   11. snow/trees   12. subterranean   13. swamp/trees   14. fiery fields
-	//15. rock lands   16. magic clouds   17. lucid pools   18. holy ground   19. clover field   20. evil fog
-	//21. "favorable winds" text on magic plains background   22. cursed ground   23. rough   24. ship to ship   25. ship
-	enum EBFieldType {NONE = -1, NONE2, SAND_SHORE, SAND_MESAS, DIRT_BIRCHES, DIRT_HILLS, DIRT_PINES, GRASS_HILLS,
-		GRASS_PINES, LAVA, MAGIC_PLAINS, SNOW_MOUNTAINS, SNOW_TREES, SUBTERRANEAN, SWAMP_TREES, FIERY_FIELDS,
-		ROCKLANDS, MAGIC_CLOUDS, LUCID_POOLS, HOLY_GROUND, CLOVER_FIELD, EVIL_FOG, FAVORABLE_WINDS, CURSED_GROUND,
-		ROUGH, SHIP_TO_SHIP, SHIP
-	};
-
-	BFieldType(EBFieldType _num = NONE) : num(_num)
-	{}
-
-	ID_LIKE_CLASS_COMMON(BFieldType, EBFieldType)
-
-	EBFieldType num;
-};
-
-ID_LIKE_OPERATORS(BFieldType, BFieldType::EBFieldType)
-
 namespace EPlayerStatus
 {
 	enum EStatus {WRONG = -1, INGAME, LOSER, WINNER};

+ 49 - 0
lib/Terrain.cpp

@@ -18,6 +18,8 @@
 
 const Terrain Terrain::ANY("ANY");
 
+const BattleField BattleField::NONE("");
+
 Terrain Terrain::createTerrainTypeH3M(int tId)
 {
 	static std::array<std::string, 10> terrainsH3M
@@ -106,6 +108,25 @@ Terrain::Manager::Manager()
 				assert(info.typeCode.length() == 2);
 			}
 			
+			if(!terr.second["battleFields"].isNull())
+			{
+				for(auto & t : terr.second["battleFields"].Vector())
+				{
+					info.battleFields.emplace_back(t.String());
+				}
+			}
+			
+			info.transitionRequired = false;
+			if(!terr.second["transitionRequired"].isNull())
+			{
+				info.transitionRequired = terr.second["transitionRequired"].Bool();
+			}
+			
+			info.terrainViewPatterns = "normal";
+			if(!terr.second["terrainViewPatterns"].isNull())
+			{
+				info.terrainViewPatterns = terr.second["terrainViewPatterns"].String();
+			}
 			
 			terrainInfo[Terrain(terr.first)] = info;
 		}
@@ -202,3 +223,31 @@ bool Terrain::isNative() const
 {
 	return name.empty();
 }
+bool Terrain::isTransitionRequired() const
+{
+	return Terrain::Manager::getInfo(*this).transitionRequired;
+}
+
+bool operator==(const BattleField & l, const BattleField & r)
+{
+	return l.name == r.name;
+}
+
+bool operator!=(const BattleField & l, const BattleField & r)
+{
+	return l.name != r.name;
+}
+
+bool operator<(const BattleField & l, const BattleField & r)
+{
+	return l.name < r.name;
+}
+BattleField::operator std::string() const
+{
+	return name;
+}
+
+int BattleField::hash() const
+{
+	return std::hash<std::string>{}(name);
+}

+ 40 - 0
lib/Terrain.h

@@ -13,6 +13,41 @@
 #include "ConstTransitivePtr.h"
 #include "JsonNode.h"
 
+class DLL_LINKAGE BattleField
+{
+public:
+	//   1. sand/shore   2. sand/mesas   3. dirt/birches   4. dirt/hills   5. dirt/pines   6. grass/hills   7. grass/pines
+	//8. lava   9. magic plains   10. snow/mountains   11. snow/trees   12. subterranean   13. swamp/trees   14. fiery fields
+	//15. rock lands   16. magic clouds   17. lucid pools   18. holy ground   19. clover field   20. evil fog
+	//21. "favorable winds" text on magic plains background   22. cursed ground   23. rough   24. ship to ship   25. ship
+	/*enum EBFieldType {NONE = -1, NONE2, SAND_SHORE, SAND_MESAS, DIRT_BIRCHES, DIRT_HILLS, DIRT_PINES, GRASS_HILLS,
+	 GRASS_PINES, LAVA, MAGIC_PLAINS, SNOW_MOUNTAINS, SNOW_TREES, SUBTERRANEAN, SWAMP_TREES, FIERY_FIELDS,
+	 ROCKLANDS, MAGIC_CLOUDS, LUCID_POOLS, HOLY_GROUND, CLOVER_FIELD, EVIL_FOG, FAVORABLE_WINDS, CURSED_GROUND,
+	 ROUGH, SHIP_TO_SHIP, SHIP
+	 };*/
+	
+	BattleField(const std::string & type = "") : name(type)
+	{}
+	
+	static const BattleField NONE;
+	
+	DLL_LINKAGE friend bool operator==(const BattleField & l, const BattleField & r);
+	DLL_LINKAGE friend bool operator!=(const BattleField & l, const BattleField & r);
+	DLL_LINKAGE friend bool operator<(const BattleField & l, const BattleField & r);
+	
+	operator std::string() const;
+	int hash() const;
+	
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & name;
+	}
+	
+protected:
+	
+	std::string name;
+};
+
 class DLL_LINKAGE Terrain
 {
 public:
@@ -27,14 +62,17 @@ public:
 		};
 		
 		int moveCost;
+		bool transitionRequired;
 		std::array<int, 3> minimapBlocked;
 		std::array<int, 3> minimapUnblocked;
 		std::string musicFilename;
 		std::string tilesFilename;
 		std::string terrainText;
 		std::string typeCode;
+		std::string terrainViewPatterns;
 		int horseSoundId;
 		Type type;
+		std::vector<BattleField> battleFields;
 	};
 	
 	class DLL_LINKAGE Manager
@@ -77,6 +115,8 @@ public:
 	bool isPassable() const; //ROCK
 	bool isUnderground() const;
 	bool isNative() const;
+	bool isTransitionRequired() const;
+	
 		
 	operator std::string() const;
 	

+ 60 - 95
lib/battle/BattleInfo.cpp

@@ -187,7 +187,7 @@ struct RangeGenerator
 	std::function<int()> myRand;
 };
 
-BattleInfo * BattleInfo::setupBattle(int3 tile, Terrain terrain, BFieldType battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town)
+BattleInfo * BattleInfo::setupBattle(const int3 & tile, const Terrain & terrain, const BattleField & battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town)
 {
 	CMP_stack cmpst;
 	auto curB = new BattleInfo();
@@ -239,24 +239,24 @@ BattleInfo * BattleInfo::setupBattle(int3 tile, Terrain terrain, BFieldType batt
 	//randomize obstacles
  	if (town == nullptr && !creatureBank) //do it only when it's not siege and not creature bank
  	{
-		const int ABSOLUTE_OBSTACLES_COUNT = 34, USUAL_OBSTACLES_COUNT = 91; //shouldn't be changes if we want H3-like obstacle placement
+		const int ABSOLUTE_OBSTACLES_COUNT = VLC->heroh->absoluteObstacles.size();
+		const int USUAL_OBSTACLES_COUNT = VLC->heroh->obstacles.size(); //shouldn't be changes if we want H3-like obstacle placement
 
 		RandGen r;
 		auto ourRand = [&](){ return r.rand(); };
 		r.srand(tile);
 		r.rand(1,8); //battle sound ID to play... can't do anything with it here
 		int tilesToBlock = r.rand(5,12);
-		const int specialBattlefield = battlefieldTypeToBI(battlefieldType);
 
 		std::vector<BattleHex> blockedTiles;
 
 		auto appropriateAbsoluteObstacle = [&](int id)
 		{
-			return VLC->heroh->absoluteObstacles[id].isAppropriate(curB->terrainType, specialBattlefield);
+			return VLC->heroh->absoluteObstacles[id].isAppropriate(curB->terrainType, battlefieldType);
 		};
 		auto appropriateUsualObstacle = [&](int id) -> bool
 		{
-			return VLC->heroh->obstacles[id].isAppropriate(curB->terrainType, specialBattlefield);
+			return VLC->heroh->obstacles[id].isAppropriate(curB->terrainType, battlefieldType);
 		};
 
 		if(r.rand(1,100) <= 40) //put cliff-like obstacle
@@ -460,71 +460,60 @@ BattleInfo * BattleInfo::setupBattle(int3 tile, Terrain terrain, BFieldType batt
 
 	//giving terrain overlay premies
 	int bonusSubtype = -1;
-	switch(battlefieldType)
-	{
-	case BFieldType::MAGIC_PLAINS:
-		{
-			bonusSubtype = 0;
-		}
-		FALLTHROUGH
-	case BFieldType::FIERY_FIELDS:
-		{
-			if(bonusSubtype == -1) bonusSubtype = 1;
-		}
-		FALLTHROUGH
-	case BFieldType::ROCKLANDS:
-		{
-			if(bonusSubtype == -1) bonusSubtype = 8;
-		}
-		FALLTHROUGH
-	case BFieldType::MAGIC_CLOUDS:
-		{
-			if(bonusSubtype == -1) bonusSubtype = 2;
-		}
-		FALLTHROUGH
-	case BFieldType::LUCID_POOLS:
-		{
-			if(bonusSubtype == -1) bonusSubtype = 4;
-		}
 
-		{ //common part for cases 9, 14, 15, 16, 17
-			curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::MAGIC_SCHOOL_SKILL, Bonus::TERRAIN_OVERLAY, 3, battlefieldType, bonusSubtype));
-			break;
-		}
-	case BFieldType::HOLY_GROUND:
-		{
-			std::string goodArmyDesc = VLC->generaltexth->arraytxt[123];
-			goodArmyDesc.erase(goodArmyDesc.size() - 2, 2); //omitting hardcoded +1 in description
-			std::string evilArmyDesc = VLC->generaltexth->arraytxt[124];
-			evilArmyDesc.erase(evilArmyDesc.size() - 2, 2);
-			curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, +1, battlefieldType, goodArmyDesc, 0)->addLimiter(good));
-			curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, -1, battlefieldType, evilArmyDesc, 0)->addLimiter(evil));
-			break;
-		}
-	case BFieldType::CLOVER_FIELD:
-		{ //+2 luck bonus for neutral creatures
-			std::string desc = VLC->generaltexth->arraytxt[83];
-			desc.erase(desc.size() - 2, 2);
-			curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::LUCK, Bonus::TERRAIN_OVERLAY, +2, battlefieldType, desc, 0)->addLimiter(neutral));
-			break;
-		}
-	case BFieldType::EVIL_FOG:
-		{
-			std::string goodArmyDesc = VLC->generaltexth->arraytxt[126];
-			goodArmyDesc.erase(goodArmyDesc.size() - 2, 2);
-			std::string evilArmyDesc = VLC->generaltexth->arraytxt[125];
-			evilArmyDesc.erase(evilArmyDesc.size() - 2, 2);
-			curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, -1, battlefieldType, goodArmyDesc, 0)->addLimiter(good));
-			curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, +1, battlefieldType, evilArmyDesc, 0)->addLimiter(evil));
-			break;
-		}
-	case BFieldType::CURSED_GROUND:
-		{
-			curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::NO_MORALE, Bonus::TERRAIN_OVERLAY, 0, battlefieldType, VLC->generaltexth->arraytxt[112], 0));
-			curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::NO_LUCK, Bonus::TERRAIN_OVERLAY, 0, battlefieldType, VLC->generaltexth->arraytxt[81], 0));
-			curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::BLOCK_MAGIC_ABOVE, Bonus::TERRAIN_OVERLAY, 1, battlefieldType, 0, Bonus::INDEPENDENT_MIN));
-			break;
-		}
+	if(battlefieldType == BattleField("magic_plains"))
+	{
+		bonusSubtype = 0;
+	}
+	if(battlefieldType == BattleField("fiery_fields"))
+	{
+		if(bonusSubtype == -1) bonusSubtype = 1;
+	}
+	if(battlefieldType == BattleField("rocklands"))
+	{
+		if(bonusSubtype == -1) bonusSubtype = 8;
+	}
+	if(battlefieldType == BattleField("magic_clouds"))
+	{
+		if(bonusSubtype == -1) bonusSubtype = 2;
+	}
+	if(battlefieldType == BattleField("lucid_pools"))
+	{
+		if(bonusSubtype == -1) bonusSubtype = 4;
+	}
+	if(bonusSubtype == -1)
+	{ //common part for cases 9, 14, 15, 16, 17
+		curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::MAGIC_SCHOOL_SKILL,Bonus::TERRAIN_OVERLAY, 3, battlefieldType.hash(), bonusSubtype));
+	}
+	else if(battlefieldType == BattleField("holy_ground"))
+	{
+		std::string goodArmyDesc = VLC->generaltexth->arraytxt[123];
+		goodArmyDesc.erase(goodArmyDesc.size() - 2, 2); //omitting hardcoded +1 in description
+		std::string evilArmyDesc = VLC->generaltexth->arraytxt[124];
+		evilArmyDesc.erase(evilArmyDesc.size() - 2, 2);
+		curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, +1, battlefieldType.hash(), goodArmyDesc, 0)->addLimiter(good));
+		curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, -1, battlefieldType.hash(), evilArmyDesc, 0)->addLimiter(evil));
+	}
+	else if(battlefieldType == BattleField("clover_field"))
+	{ //+2 luck bonus for neutral creatures
+		std::string desc = VLC->generaltexth->arraytxt[83];
+		desc.erase(desc.size() - 2, 2);
+		curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::LUCK, Bonus::TERRAIN_OVERLAY, +2, battlefieldType.hash(), desc, 0)->addLimiter(neutral));
+	}
+	else if(battlefieldType == BattleField("evil_fog"))
+	{
+		std::string goodArmyDesc = VLC->generaltexth->arraytxt[126];
+		goodArmyDesc.erase(goodArmyDesc.size() - 2, 2);
+		std::string evilArmyDesc = VLC->generaltexth->arraytxt[125];
+		evilArmyDesc.erase(evilArmyDesc.size() - 2, 2);
+		curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, -1, battlefieldType.hash(), goodArmyDesc, 0)->addLimiter(good));
+		curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, +1, battlefieldType.hash(), evilArmyDesc, 0)->addLimiter(evil));
+	}
+	else if(battlefieldType == BattleField("cursed_ground"))
+	{
+		curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::NO_MORALE, Bonus::TERRAIN_OVERLAY, 0, battlefieldType.hash(), VLC->generaltexth->arraytxt[112], 0));
+		curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::NO_LUCK, Bonus::TERRAIN_OVERLAY, 0, battlefieldType.hash(), VLC->generaltexth->arraytxt[81], 0));
+		curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::BLOCK_MAGIC_ABOVE, Bonus::TERRAIN_OVERLAY, 1, battlefieldType.hash(), 0, Bonus::INDEPENDENT_MIN));
 	}
 	//overlay premies given
 
@@ -580,30 +569,6 @@ ui8 BattleInfo::whatSide(PlayerColor player) const
 	return -1;
 }
 
-BattlefieldBI::BattlefieldBI BattleInfo::battlefieldTypeToBI(BFieldType bfieldType)
-{
-	static const std::map<BFieldType, BattlefieldBI::BattlefieldBI> theMap =
-	{
-		{BFieldType::CLOVER_FIELD, BattlefieldBI::CLOVER_FIELD},
-		{BFieldType::CURSED_GROUND, BattlefieldBI::CURSED_GROUND},
-		{BFieldType::EVIL_FOG, BattlefieldBI::EVIL_FOG},
-		{BFieldType::FAVORABLE_WINDS, BattlefieldBI::NONE},
-		{BFieldType::FIERY_FIELDS, BattlefieldBI::FIERY_FIELDS},
-		{BFieldType::HOLY_GROUND, BattlefieldBI::HOLY_GROUND},
-		{BFieldType::LUCID_POOLS, BattlefieldBI::LUCID_POOLS},
-		{BFieldType::MAGIC_CLOUDS, BattlefieldBI::MAGIC_CLOUDS},
-		{BFieldType::MAGIC_PLAINS, BattlefieldBI::MAGIC_PLAINS},
-		{BFieldType::ROCKLANDS, BattlefieldBI::ROCKLANDS},
-		{BFieldType::SAND_SHORE, BattlefieldBI::COASTAL}
-	};
-
-	auto itr = theMap.find(bfieldType);
-	if(itr != theMap.end())
-		return itr->second;
-
-	return BattlefieldBI::NONE;
-}
-
 CStack * BattleInfo::getStack(int stackID, bool onlyAlive)
 {
 	return const_cast<CStack *>(battleGetStackByID(stackID, onlyAlive));
@@ -611,7 +576,7 @@ CStack * BattleInfo::getStack(int stackID, bool onlyAlive)
 
 BattleInfo::BattleInfo()
 	: round(-1), activeStack(-1), town(nullptr), tile(-1,-1,-1),
-	battlefieldType(BFieldType::NONE), terrainType(),
+	battlefieldType(BattleField::NONE), terrainType(),
 	tacticsSide(0), tacticDistance(0)
 {
 	setBattle(this);
@@ -640,7 +605,7 @@ battle::Units BattleInfo::getUnitsIf(battle::UnitFilter predicate) const
 }
 
 
-BFieldType BattleInfo::getBattlefieldType() const
+BattleField BattleInfo::getBattlefieldType() const
 {
 	return battlefieldType;
 }

+ 4 - 5
lib/battle/BattleInfo.h

@@ -19,6 +19,7 @@ class CStack;
 class CStackInstance;
 class CStackBasicDescriptor;
 class Terrain;
+class BattleField;
 
 class DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallback, public IBattleState
 {
@@ -36,7 +37,7 @@ public:
 	std::vector<std::shared_ptr<CObstacleInstance> > obstacles;
 	SiegeInfo si;
 
-	BFieldType battlefieldType; //like !!BA:B
+	BattleField battlefieldType; //like !!BA:B
 	Terrain terrainType; //used for some stack nativity checks (not the bonus limiters though that have their own copy)
 
 	ui8 tacticsSide; //which side is requested to play tactics phase
@@ -72,7 +73,7 @@ public:
 
 	battle::Units getUnitsIf(battle::UnitFilter predicate) const override;
 
-	BFieldType getBattlefieldType() const override;
+	BattleField getBattlefieldType() const override;
 	Terrain getTerrainType() const override;
 
 	ObstacleCList getAllObstacles() const override;
@@ -138,12 +139,10 @@ public:
 	const CGHeroInstance * getHero(PlayerColor player) const; //returns fighting hero that belongs to given player
 
 	void localInit();
-	static BattleInfo * setupBattle(int3 tile, Terrain terrain, BFieldType battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town);
+	static BattleInfo * setupBattle(const int3 & tile, const Terrain & terrain, const BattleField & battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town);
 
 	ui8 whatSide(PlayerColor player) const;
 
-	static BattlefieldBI::BattlefieldBI battlefieldTypeToBI(BFieldType bfieldType); //converts above to ERM BI format
-
 protected:
 	scripting::Pool * getContextPool() const override;
 };

+ 1 - 1
lib/battle/BattleProxy.cpp

@@ -42,7 +42,7 @@ battle::Units BattleProxy::getUnitsIf(battle::UnitFilter predicate) const
 	return subject->battleGetUnitsIf(predicate);
 }
 
-BFieldType BattleProxy::getBattlefieldType() const
+BattleField BattleProxy::getBattlefieldType() const
 {
 	return subject->battleGetBattlefieldType();
 }

+ 1 - 1
lib/battle/BattleProxy.h

@@ -29,7 +29,7 @@ public:
 
 	battle::Units getUnitsIf(battle::UnitFilter predicate) const override;
 
-	BFieldType getBattlefieldType() const override;
+	BattleField getBattlefieldType() const override;
 	Terrain getTerrainType() const override;
 
 	ObstacleCList getAllObstacles() const override;

+ 1 - 1
lib/battle/CBattleInfoCallback.cpp

@@ -1059,7 +1059,7 @@ AccessibilityInfo CBattleInfoCallback::getAccesibility() const
 
 	//special battlefields with logically unavailable tiles
 	std::vector<BattleHex> impassableHexes;
-	if(battleGetBattlefieldType().num == BFieldType::SHIP_TO_SHIP)
+	if(battleGetBattlefieldType() == BattleField("ship_to_ship"))
 	{
 		impassableHexes =
 		{

+ 2 - 2
lib/battle/CBattleInfoEssentials.cpp

@@ -20,9 +20,9 @@ Terrain CBattleInfoEssentials::battleTerrainType() const
 	return getBattle()->getTerrainType();
 }
 
-BFieldType CBattleInfoEssentials::battleGetBattlefieldType() const
+BattleField CBattleInfoEssentials::battleGetBattlefieldType() const
 {
-	RETURN_IF_NOT_BATTLE(BFieldType::NONE);
+	RETURN_IF_NOT_BATTLE(BattleField::NONE);
 	return getBattle()->getBattlefieldType();
 }
 

+ 1 - 1
lib/battle/CBattleInfoEssentials.h

@@ -47,7 +47,7 @@ public:
 	const IBonusBearer * getBattleNode() const;
 
 	Terrain battleTerrainType() const override;
-	BFieldType battleGetBattlefieldType() const override;
+	BattleField battleGetBattlefieldType() const override;
 	int32_t battleGetEnchanterCounter(ui8 side) const;
 
 	std::vector<std::shared_ptr<const CObstacleInstance> > battleGetAllObstacles(boost::optional<BattlePerspective::BattlePerspective> perspective = boost::none) const; //returns all obstacles on the battlefield

+ 2 - 2
lib/battle/IBattleInfoCallback.h

@@ -13,7 +13,7 @@
 #include "BattleHex.h"
 
 struct CObstacleInstance;
-class BFieldType;
+class BattleField;
 class Terrain;
 
 namespace battle
@@ -35,7 +35,7 @@ public:
 	virtual scripting::Pool * getContextPool() const = 0;
 
 	virtual Terrain battleTerrainType() const = 0;
-	virtual BFieldType battleGetBattlefieldType() const = 0;
+	virtual BattleField battleGetBattlefieldType() const = 0;
 
 	///return none if battle is ongoing; otherwise the victorious side (0/1) or 2 if it is a draw
 	virtual boost::optional<int> battleIsFinished() const = 0;

+ 2 - 1
lib/battle/IBattleState.h

@@ -16,6 +16,7 @@ class UnitChanges;
 struct Bonus;
 class JsonNode;
 class JsonSerializeFormat;
+class BattleField;
 
 namespace vstd
 {
@@ -40,7 +41,7 @@ public:
 
 	virtual battle::Units getUnitsIf(battle::UnitFilter predicate) const = 0;
 
-	virtual BFieldType getBattlefieldType() const = 0;
+	virtual BattleField getBattlefieldType() const = 0;
 	virtual Terrain getTerrainType() const = 0;
 
 	virtual ObstacleCList getAllObstacles() const = 0;

+ 35 - 67
lib/mapping/CMapEditManager.cpp

@@ -421,7 +421,6 @@ CTerrainViewPatternConfig::CTerrainViewPatternConfig()
 					}
 
 					// Add pattern to the patterns map
-					const auto & terGroup = getTerrainGroup(mappingPair.first);
 					std::vector<TerrainViewPattern> terrainViewPatternFlips;
 					terrainViewPatternFlips.push_back(terGroupPattern);
 
@@ -431,7 +430,8 @@ CTerrainViewPatternConfig::CTerrainViewPatternConfig()
 						flipPattern(terGroupPattern, i); //FIXME: we flip in place - doesn't make much sense now, but used to work
 						terrainViewPatternFlips.push_back(terGroupPattern);
 					}
-					terrainViewPatterns[terGroup].push_back(terrainViewPatternFlips);
+					
+					terrainViewPatterns[mappingPair.first].push_back(terrainViewPatternFlips);
 				}
 			}
 			else if(i == 1)
@@ -453,29 +453,17 @@ CTerrainViewPatternConfig::~CTerrainViewPatternConfig()
 
 }
 
-ETerrainGroup::ETerrainGroup CTerrainViewPatternConfig::getTerrainGroup(const std::string & terGroup) const
+const std::vector<CTerrainViewPatternConfig::TVPVector> & CTerrainViewPatternConfig::getTerrainViewPatterns(const Terrain & terrain) const
 {
-	static const std::map<std::string, ETerrainGroup::ETerrainGroup> terGroups =
-	{
-		{"normal", ETerrainGroup::NORMAL},
-		{"dirt", ETerrainGroup::DIRT},
-		{"sand", ETerrainGroup::SAND},
-		{"water", ETerrainGroup::WATER},
-		{"rock", ETerrainGroup::ROCK},
-	};
-	auto it = terGroups.find(terGroup);
-	if(it == terGroups.end()) throw std::runtime_error(boost::str(boost::format("Terrain group '%s' does not exist.") % terGroup));
-	return it->second;
+	auto iter = terrainViewPatterns.find(Terrain::Manager::getInfo(terrain).terrainViewPatterns);
+	if(iter == terrainViewPatterns.end())
+		return terrainViewPatterns.at("normal");
+	return iter->second;
 }
 
-const std::vector<CTerrainViewPatternConfig::TVPVector> & CTerrainViewPatternConfig::getTerrainViewPatternsForGroup(ETerrainGroup::ETerrainGroup terGroup) const
+boost::optional<const TerrainViewPattern &> CTerrainViewPatternConfig::getTerrainViewPatternById(const Terrain & terrain, const std::string & id) const
 {
-	return terrainViewPatterns.find(terGroup)->second;
-}
-
-boost::optional<const TerrainViewPattern &> CTerrainViewPatternConfig::getTerrainViewPatternById(ETerrainGroup::ETerrainGroup terGroup, const std::string & id) const
-{
-	const std::vector<TVPVector> & groupPatterns = getTerrainViewPatternsForGroup(terGroup);
+	const std::vector<TVPVector> & groupPatterns = getTerrainViewPatterns(terrain);
 	for (const TVPVector & patternFlips : groupPatterns)
 	{
 		const TerrainViewPattern & pattern = patternFlips.front();
@@ -486,9 +474,10 @@ boost::optional<const TerrainViewPattern &> CTerrainViewPatternConfig::getTerrai
 	}
 	return boost::optional<const TerrainViewPattern &>();
 }
-boost::optional<const CTerrainViewPatternConfig::TVPVector &> CTerrainViewPatternConfig::getTerrainViewPatternsById(ETerrainGroup::ETerrainGroup terGroup, const std::string & id) const
+
+boost::optional<const CTerrainViewPatternConfig::TVPVector &> CTerrainViewPatternConfig::getTerrainViewPatternsById(const Terrain & terrain, const std::string & id) const
 {
-	const std::vector<TVPVector> & groupPatterns = getTerrainViewPatternsForGroup(terGroup);
+	const std::vector<TVPVector> & groupPatterns = getTerrainViewPatterns(terrain);
 	for (const TVPVector & patternFlips : groupPatterns)
 	{
 		const TerrainViewPattern & pattern = patternFlips.front();
@@ -705,7 +694,7 @@ void CDrawTerrainOperation::updateTerrainViews()
 {
 	for(const auto & pos : invalidatedTerViews)
 	{
-		const auto & patterns = VLC->terviewh->getTerrainViewPatternsForGroup(getTerrainGroup(map->getTile(pos).terType));
+		const auto & patterns = VLC->terviewh->getTerrainViewPatterns(map->getTile(pos).terType);
 
 		// Detect a pattern which fits best
 		int bestPattern = -1;
@@ -760,19 +749,6 @@ void CDrawTerrainOperation::updateTerrainViews()
 	}
 }
 
-ETerrainGroup::ETerrainGroup CDrawTerrainOperation::getTerrainGroup(Terrain terType) const
-{
-	if(terType == Terrain("dirt"))
-		return ETerrainGroup::DIRT;
-	if(terType == Terrain("sand"))
-		return ETerrainGroup::SAND;
-	if(terType.isWater())
-		return ETerrainGroup::WATER;
-	if(!terType.isPassable())
-		return ETerrainGroup::ROCK;
-	return ETerrainGroup::NORMAL;
-}
-
 CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainView(const int3 & pos, const std::vector<TerrainViewPattern> * pattern, int recDepth) const
 {
 	for(int flip = 0; flip < 4; ++flip)
@@ -790,7 +766,6 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi
 CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainViewInner(const int3 & pos, const TerrainViewPattern & pattern, int recDepth) const
 {
 	auto centerTerType = map->getTile(pos).terType;
-	auto centerTerGroup = getTerrainGroup(centerTerType);
 	int totalPoints = 0;
 	std::string transitionReplacement;
 
@@ -857,8 +832,7 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi
 				{
 					if(terType == centerTerType)
 					{
-						const auto & group = getTerrainGroup(centerTerType);
-						const auto & patternForRule = VLC->terviewh->getTerrainViewPatternsById(group, rule.name);
+						const auto & patternForRule = VLC->terviewh->getTerrainViewPatternsById(centerTerType, rule.name);
 						if(auto p = patternForRule)
 						{
 							auto rslt = validateTerrainView(currentPos, &(*p), 1);
@@ -884,12 +858,30 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi
 			// Validate cell with the ruleset of the pattern
 			bool nativeTestOk, nativeTestStrongOk;
 			nativeTestOk = nativeTestStrongOk = (rule.isNativeStrong() || rule.isNativeRule()) && !isAlien;
-			if(centerTerGroup == ETerrainGroup::NORMAL)
+			
+			if(centerTerType == Terrain("dirt"))
+			{
+				nativeTestOk = rule.isNativeRule() && !terType.isTransitionRequired();
+				bool sandTestOk = (rule.isSandRule() || rule.isTransition())
+				&& terType.isTransitionRequired();
+				applyValidationRslt(rule.isAnyRule() || sandTestOk || nativeTestOk || nativeTestStrongOk);
+			}
+			else if(centerTerType == Terrain("sand"))
+			{
+				applyValidationRslt(true);
+			}
+			else if(centerTerType.isTransitionRequired()) //water, rock and some special terrains require sand transition
+			{
+				bool sandTestOk = (rule.isSandRule() || rule.isTransition())
+				&& isAlien;
+				applyValidationRslt(rule.isAnyRule() || sandTestOk || nativeTestOk);
+			}
+			else
 			{
 				bool dirtTestOk = (rule.isDirtRule() || rule.isTransition())
-						&& isAlien && !isSandType(terType);
+						&& isAlien && !terType.isTransitionRequired();
 				bool sandTestOk = (rule.isSandRule() || rule.isTransition())
-						&& isSandType(terType);
+						&& terType.isTransitionRequired();
 
 				if (transitionReplacement.empty() && rule.isTransition()
 						&& (dirtTestOk || sandTestOk))
@@ -906,23 +898,6 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi
 					applyValidationRslt(rule.isAnyRule() || dirtTestOk || sandTestOk || nativeTestOk);
 				}
 			}
-			else if(centerTerGroup == ETerrainGroup::DIRT)
-			{
-				nativeTestOk = rule.isNativeRule() && !isSandType(terType);
-				bool sandTestOk = (rule.isSandRule() || rule.isTransition())
-						&& isSandType(terType);
-				applyValidationRslt(rule.isAnyRule() || sandTestOk || nativeTestOk || nativeTestStrongOk);
-			}
-			else if(centerTerGroup == ETerrainGroup::SAND)
-			{
-				applyValidationRslt(true);
-			}
-			else if(centerTerGroup == ETerrainGroup::WATER || centerTerGroup == ETerrainGroup::ROCK)
-			{
-				bool sandTestOk = (rule.isSandRule() || rule.isTransition())
-						&& isAlien;
-				applyValidationRslt(rule.isAnyRule() || sandTestOk || nativeTestOk);
-			}
 		}
 
 		if(topPoints == -1)
@@ -945,13 +920,6 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi
 	}
 }
 
-bool CDrawTerrainOperation::isSandType(Terrain terType) const
-{
-	if(terType.isWater() || terType == Terrain("sand") || !terType.isPassable())
-		return true;
-	return false;
-}
-
 void CDrawTerrainOperation::invalidateTerrainViews(const int3 & centerPos)
 {
 	auto rect = extendTileAroundSafely(centerPos);

+ 4 - 20
lib/mapping/CMapEditManager.h

@@ -211,18 +211,6 @@ private:
 	std::list<std::unique_ptr<CMapOperation> > operations;
 };
 
-namespace ETerrainGroup
-{
-	enum ETerrainGroup
-	{
-		NORMAL,
-		DIRT,
-		SAND,
-		WATER,
-		ROCK
-	};
-}
-
 /// The terrain view pattern describes a specific composition of terrain tiles
 /// in a 3x3 matrix and notes which terrain view frame numbers can be used.
 struct DLL_LINKAGE TerrainViewPattern
@@ -338,15 +326,14 @@ public:
 	CTerrainViewPatternConfig();
 	~CTerrainViewPatternConfig();
 
-	const std::vector<TVPVector> & getTerrainViewPatternsForGroup(ETerrainGroup::ETerrainGroup terGroup) const;
-	boost::optional<const TerrainViewPattern &> getTerrainViewPatternById(ETerrainGroup::ETerrainGroup terGroup, const std::string & id) const;
-	boost::optional<const TVPVector &> getTerrainViewPatternsById(ETerrainGroup::ETerrainGroup terGroup, const std::string & id) const;
+	const std::vector<TVPVector> & getTerrainViewPatterns(const Terrain & terrain) const;
+	boost::optional<const TerrainViewPattern &> getTerrainViewPatternById(const Terrain & terrain, const std::string & id) const;
+	boost::optional<const TVPVector &> getTerrainViewPatternsById(const Terrain & terrain, const std::string & id) const;
 	const TVPVector * getTerrainTypePatternById(const std::string & id) const;
-	ETerrainGroup::ETerrainGroup getTerrainGroup(const std::string & terGroup) const;
 	void flipPattern(TerrainViewPattern & pattern, int flip) const;
 
 private:
-	std::map<ETerrainGroup::ETerrainGroup, std::vector<TVPVector> > terrainViewPatterns;
+	std::map<std::string, std::vector<TVPVector> > terrainViewPatterns;
 	std::map<std::string, TVPVector> terrainTypePatterns;
 };
 
@@ -385,13 +372,10 @@ private:
 	InvalidTiles getInvalidTiles(const int3 & centerPos) const;
 
 	void updateTerrainViews();
-	ETerrainGroup::ETerrainGroup getTerrainGroup(Terrain terType) const;
 	/// Validates the terrain view of the given position and with the given pattern. The first method wraps the
 	/// second method to validate the terrain view with the given pattern in all four flip directions(horizontal, vertical).
 	ValidationResult validateTerrainView(const int3 & pos, const std::vector<TerrainViewPattern> * pattern, int recDepth = 0) const;
 	ValidationResult validateTerrainViewInner(const int3 & pos, const TerrainViewPattern & pattern, int recDepth = 0) const;
-	/// Tests whether the given terrain type is a sand type. Sand types are: Water, Sand and Rock
-	bool isSandType(Terrain terType) const;
 
 	CTerrainSelection terrainSel;
 	Terrain terType;

+ 2 - 2
lib/serializer/CSerializer.h

@@ -12,8 +12,8 @@
 #include "../ConstTransitivePtr.h"
 #include "../GameConstants.h"
 
-const ui32 SERIALIZATION_VERSION = 802;
-const ui32 MINIMAL_SERIALIZATION_VERSION = 802;
+const ui32 SERIALIZATION_VERSION = 803;
+const ui32 MINIMAL_SERIALIZATION_VERSION = 803;
 const std::string SAVEGAME_MAGIC = "VCMISVG";
 
 class CHero;

+ 1 - 1
lib/spells/ISpellMechanics.cpp

@@ -520,7 +520,7 @@ bool BaseMechanics::adaptProblem(ESpellCastProblem::ESpellCastProblem source, Pr
 				caster->getCasterName(text);
 				target.add(std::move(text), spells::Problem::NORMAL);
 			}
-			else if(b && b->source == Bonus::TERRAIN_OVERLAY && b->sid == BFieldType::CURSED_GROUND)
+			else if(b && b->source == Bonus::TERRAIN_OVERLAY && b->sid == BattleField("cursed_ground").hash())
 			{
 				text.addTxt(MetaString::GENERAL_TXT, 537);
 				target.add(std::move(text), spells::Problem::NORMAL);

+ 1 - 1
scripting/lua/api/BattleCb.cpp

@@ -73,7 +73,7 @@ int BattleCbProxy::getBattlefieldType(lua_State * L)
 
 	auto ret = object->battleGetBattlefieldType();
 
-	return LuaStack::quickRetInt(L, static_cast<si32>(ret.num));
+	return LuaStack::quickRetStr(L, ret);
 }
 
 int BattleCbProxy::getTerrainType(lua_State * L)

+ 2 - 2
server/CGameHandler.cpp

@@ -2219,9 +2219,9 @@ void CGameHandler::setupBattle(int3 tile, const CArmedInstance *armies[2], const
 	if (gs->map->isCoastalTile(tile)) //coastal tile is always ground
 		terrain = Terrain("sand");
 
-	BFieldType terType = gs->battleGetBattlefieldType(tile, getRandomGenerator());
+	BattleField terType = gs->battleGetBattlefieldType(tile, getRandomGenerator());
 	if (heroes[0] && heroes[0]->boat && heroes[1] && heroes[1]->boat)
-		terType = BFieldType::SHIP_TO_SHIP;
+		terType = BattleField("ship_to_ship");
 
 	//send info about battles
 	BattleStart bs;

+ 2 - 2
test/erm/ERM_BU.cpp

@@ -155,7 +155,7 @@ TEST_F(ERM_BU_G, Get)
 	source << "!?PI;" << std::endl;
 	source << "!!BU:G?v1;" << std::endl;
 
-	EXPECT_CALL(binfoMock, battleGetBattlefieldType()).WillOnce(Return(BFieldType::SNOW_TREES));
+	EXPECT_CALL(binfoMock, battleGetBattlefieldType()).WillOnce(Return(BattleField("snow_trees")));
 
 
 	loadScript(VLC->scriptHandler->erm, source.str());
@@ -174,7 +174,7 @@ TEST_F(ERM_BU_G, Get2)
 	source << "!?PI;" << std::endl;
 	source << "!!BU:G?v1;" << std::endl;
 
-	EXPECT_CALL(binfoMock, battleGetBattlefieldType()).WillOnce(Return(BFieldType::EVIL_FOG));
+	EXPECT_CALL(binfoMock, battleGetBattlefieldType()).WillOnce(Return(BattleField("evil_fog")));
 
 	loadScript(VLC->scriptHandler->erm, source.str());
 	runServer();

+ 1 - 1
test/game/CGameStateTest.cpp

@@ -194,7 +194,7 @@ public:
 		const auto t = gameCallback->getTile(tile);
 
 		Terrain terrain = t->terType;
-		BFieldType terType = BFieldType::GRASS_HILLS;
+		BattleField terType = BattleField("grass_hills");
 
 		//send info about battles
 

+ 1 - 2
test/map/CMapEditManagerTest.cpp

@@ -130,10 +130,9 @@ TEST(MapManager, DrawTerrain_View)
 			if(patternParts.size() != 2) throw std::runtime_error("A pattern should consist of two parts, the group and the id. Continue with next pattern.");
 			const auto & groupStr = patternParts[0];
 			const auto & id = patternParts[1];
-			auto terGroup = VLC->terviewh->getTerrainGroup(groupStr);
 
 			// Get mapping range
-			const auto & pattern = VLC->terviewh->getTerrainViewPatternById(terGroup, id);
+			const auto & pattern = VLC->terviewh->getTerrainViewPatternById(groupStr, id);
 			const auto & mapping = (*pattern).mapping;
 
 			const auto & positionsNode = node["pos"].Vector();

+ 1 - 1
test/mock/BattleFake.cpp

@@ -92,7 +92,7 @@ void BattleFake::setupEmptyBattlefield()
 {
 	EXPECT_CALL(*this, getDefendedTown()).WillRepeatedly(Return(nullptr));
 	EXPECT_CALL(*this, getAllObstacles()).WillRepeatedly(Return(IBattleInfo::ObstacleCList()));
-	EXPECT_CALL(*this, getBattlefieldType()).WillRepeatedly(Return(BFieldType::NONE2));
+	EXPECT_CALL(*this, getBattlefieldType()).WillRepeatedly(Return(BattleField::NONE));
 }
 
 

+ 1 - 1
test/mock/mock_IBattleInfoCallback.h

@@ -18,7 +18,7 @@ class IBattleInfoCallbackMock : public IBattleInfoCallback
 public:
 	MOCK_CONST_METHOD0(getContextPool, scripting::Pool *());
 	MOCK_CONST_METHOD0(battleTerrainType, Terrain());
-	MOCK_CONST_METHOD0(battleGetBattlefieldType, BFieldType());
+	MOCK_CONST_METHOD0(battleGetBattlefieldType, BattleField());
 
 	MOCK_CONST_METHOD0(battleIsFinished, boost::optional<int>());
 

+ 1 - 1
test/mock/mock_battle_IBattleState.h

@@ -18,7 +18,7 @@ public:
 	MOCK_CONST_METHOD0(getActiveStackID, int32_t());
 	MOCK_CONST_METHOD1(getStacksIf, TStacks(TStackFilter));
 	MOCK_CONST_METHOD1(getUnitsIf, battle::Units(battle::UnitFilter));
-	MOCK_CONST_METHOD0(getBattlefieldType, BFieldType());
+	MOCK_CONST_METHOD0(getBattlefieldType, BattleField());
 	MOCK_CONST_METHOD0(getTerrainType, Terrain());
 	MOCK_CONST_METHOD0(getAllObstacles, IBattleInfo::ObstacleCList());
 	MOCK_CONST_METHOD0(getDefendedTown, const CGTownInstance *());