فهرست منبع

New terrain support - part 1 (#755)

Initial support of new terrains
Nordsoft91 3 سال پیش
والد
کامیت
aaa07e4d2e
100فایلهای تغییر یافته به همراه1047 افزوده شده و 1453 حذف شده
  1. 8 10
      AI/Nullkiller/Pathfinding/AINodeStorage.cpp
  2. 1 1
      AI/Nullkiller/Pathfinding/Actors.cpp
  3. 7 9
      AI/VCAI/Pathfinding/AINodeStorage.cpp
  4. 0 5
      AI/VCAI/VCAI.h
  5. 35 25
      client/CMusicHandler.cpp
  6. 10 7
      client/CMusicHandler.h
  7. 5 9
      client/CPlayerInterface.cpp
  8. 1 11
      client/Client.cpp
  9. 1 1
      client/battle/CBattleInterface.cpp
  10. 51 55
      client/mapHandler.cpp
  11. 2 2
      client/mapHandler.h
  12. 13 23
      client/widgets/AdventureMapClasses.cpp
  13. 3 2
      client/widgets/AdventureMapClasses.h
  14. 1 1
      client/windows/CAdvmapInterface.cpp
  15. 4 0
      config/gameConfig.json
  16. 79 79
      config/obstacles.json
  17. 2 2
      config/randomMap.json
  18. 33 10
      config/terrains.json
  19. 2 13
      lib/CArtHandler.h
  20. 6 10
      lib/CCreatureHandler.cpp
  21. 5 15
      lib/CCreatureHandler.h
  22. 1 1
      lib/CGameInfoCallback.cpp
  23. 18 18
      lib/CGameState.cpp
  24. 1 8
      lib/CGameState.h
  25. 14 1
      lib/CGeneralTextHandler.cpp
  26. 1 1
      lib/CGeneralTextHandler.h
  27. 10 9
      lib/CHeroHandler.cpp
  28. 8 30
      lib/CHeroHandler.h
  29. 2 0
      lib/CMakeLists.txt
  30. 3 26
      lib/CModHandler.h
  31. 11 17
      lib/CPathfinder.cpp
  32. 2 1
      lib/CPathfinder.h
  33. 0 8
      lib/CPlayerState.h
  34. 4 10
      lib/CSkillHandler.h
  35. 4 4
      lib/CStack.cpp
  36. 3 2
      lib/CStack.h
  37. 9 133
      lib/CTownHandler.cpp
  38. 13 48
      lib/CTownHandler.h
  39. 0 32
      lib/GameConstants.cpp
  40. 2 46
      lib/GameConstants.h
  41. 5 10
      lib/HeroBonus.cpp
  42. 8 31
      lib/HeroBonus.h
  43. 3 3
      lib/IGameCallback.cpp
  44. 1 1
      lib/JsonDetail.h
  45. 11 1
      lib/JsonNode.cpp
  46. 3 8
      lib/JsonNode.h
  47. 3 3
      lib/NetPacksLib.cpp
  48. 2 2
      lib/PathfinderUtil.h
  49. 1 13
      lib/StartInfo.h
  50. 0 4
      lib/StringConstants.h
  51. 204 0
      lib/Terrain.cpp
  52. 93 0
      lib/Terrain.h
  53. 0 12
      lib/VCMI_Lib.cpp
  54. 4 18
      lib/VCMI_Lib.h
  55. 4 3
      lib/battle/BattleInfo.cpp
  56. 4 3
      lib/battle/BattleInfo.h
  57. 2 1
      lib/battle/BattleProxy.cpp
  58. 1 1
      lib/battle/BattleProxy.h
  59. 2 2
      lib/battle/CBattleInfoEssentials.cpp
  60. 1 1
      lib/battle/CBattleInfoEssentials.h
  61. 2 2
      lib/battle/IBattleInfoCallback.h
  62. 1 1
      lib/battle/IBattleState.h
  63. 15 32
      lib/mapObjects/CGHeroInstance.cpp
  64. 3 22
      lib/mapObjects/CGHeroInstance.h
  65. 0 106
      lib/mapObjects/CGTownInstance.cpp
  66. 2 16
      lib/mapObjects/CGTownInstance.h
  67. 5 5
      lib/mapObjects/CObjectClassesHandler.cpp
  68. 10 28
      lib/mapObjects/CObjectClassesHandler.h
  69. 1 1
      lib/mapObjects/CObjectHandler.cpp
  70. 3 7
      lib/mapObjects/CObjectHandler.h
  71. 1 8
      lib/mapObjects/CQuest.h
  72. 0 5
      lib/mapObjects/CRewardableObject.h
  73. 33 11
      lib/mapObjects/ObjectTemplate.cpp
  74. 4 6
      lib/mapObjects/ObjectTemplate.h
  75. 0 11
      lib/mapping/CCampaignHandler.cpp
  76. 3 15
      lib/mapping/CCampaignHandler.h
  77. 3 3
      lib/mapping/CDrawRoadsOperation.cpp
  78. 2 2
      lib/mapping/CDrawRoadsOperation.h
  79. 7 7
      lib/mapping/CMap.cpp
  80. 5 19
      lib/mapping/CMap.h
  81. 3 3
      lib/mapping/CMapDefines.h
  82. 18 28
      lib/mapping/CMapEditManager.cpp
  83. 7 6
      lib/mapping/CMapEditManager.h
  84. 4 4
      lib/mapping/MapFormatH3M.cpp
  85. 12 35
      lib/mapping/MapFormatJson.cpp
  86. 21 30
      lib/rmg/CMapGenerator.cpp
  87. 4 6
      lib/rmg/CMapGenerator.h
  88. 35 21
      lib/rmg/CRmgTemplate.cpp
  89. 5 4
      lib/rmg/CRmgTemplate.h
  90. 57 20
      lib/rmg/CRmgTemplateZone.cpp
  91. 10 8
      lib/rmg/CRmgTemplateZone.h
  92. 18 20
      lib/rmg/CZonePlacer.cpp
  93. 2 2
      lib/serializer/CSerializer.h
  94. 0 51
      lib/spells/CSpellHandler.cpp
  95. 6 74
      lib/spells/CSpellHandler.h
  96. 1 3
      scripting/lua/api/BattleCb.cpp
  97. 7 7
      server/CGameHandler.cpp
  98. 8 20
      server/CGameHandler.h
  99. 1 1
      test/game/CGameStateTest.cpp
  100. 1 1
      test/googletest

+ 8 - 10
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -88,24 +88,22 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
 				for(pos.z = 0; pos.z < sizes.z; ++pos.z)
 				{
 					const TerrainTile * tile = &gs->map->getTile(pos);
-					switch(tile->terType)
+					if(!tile->terType.isPassable())
+						continue;
+					
+					if(tile->terType.isWater())
 					{
-					case ETerrainType::ROCK:
-						break;
-
-					case ETerrainType::WATER:
 						resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
 						if(useFlying)
 							resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
 						if(useWaterWalking)
 							resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility<ELayer::WATER>(pos, tile, fow, player, gs));
-						break;
-
-					default:
+					}
+					else
+					{
 						resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility<ELayer::LAND>(pos, tile, fow, player, gs));
 						if(useFlying)
 							resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
-						break;
 					}
 				}
 			}
@@ -1428,4 +1426,4 @@ std::string AIPath::toString() const
 		str << node.targetHero->name << "[" << std::hex << node.chainMask << std::dec << "]" << "->" << node.coord.toString() << "; ";
 
 	return str.str();
-}
+}

+ 1 - 1
AI/Nullkiller/Pathfinding/Actors.cpp

@@ -70,7 +70,7 @@ int ChainActor::maxMovePoints(CGPathNode::ELayer layer)
 {
 #if AI_TRACE_LEVEL > 0
 	if(!hero)
-		throw std::exception("Asking movement points for static actor");
+		throw std::logic_error("Asking movement points for static actor");
 #endif
 
 	return hero->maxMovePointsCached(layer, tiCache.get());

+ 7 - 9
AI/VCAI/Pathfinding/AINodeStorage.cpp

@@ -46,24 +46,22 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
 			for(pos.z=0; pos.z < sizes.z; ++pos.z)
 			{
 				const TerrainTile * tile = &gs->map->getTile(pos);
-				switch(tile->terType)
+				if(!tile->terType.isPassable())
+					continue;
+				
+				if(tile->terType.isWater())
 				{
-				case ETerrainType::ROCK:
-					break;
-
-				case ETerrainType::WATER:
 					resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
 					if(useFlying)
 						resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
 					if(useWaterWalking)
 						resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility<ELayer::WATER>(pos, tile, fow, player, gs));
-					break;
-
-				default:
+				}
+				else
+				{
 					resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility<ELayer::LAND>(pos, tile, fow, player, gs));
 					if(useFlying)
 						resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
-					break;
 				}
 			}
 		}

+ 0 - 5
AI/VCAI/VCAI.h

@@ -347,11 +347,6 @@ public:
 		h & visitableObjs;
 		h & alreadyVisited;
 		h & reservedObjs;
-		if (version < 788 && !h.saving)
-		{
-			TResources saving;
-			h & saving; //mind the ambiguity
-		}
 		h & status;
 		h & battlename;
 		h & heroesUnableToExplore;

+ 35 - 25
client/CMusicHandler.cpp

@@ -19,6 +19,7 @@
 #include "../lib/StringConstants.h"
 #include "../lib/CRandomGenerator.h"
 #include "../lib/VCMIDirs.h"
+#include "../lib/Terrain.h"
 
 #define VCMI_SOUND_NAME(x)
 #define VCMI_SOUND_FILE(y) #y,
@@ -92,20 +93,34 @@ CSoundHandler::CSoundHandler():
 		soundBase::pickup04, soundBase::pickup05, soundBase::pickup06, soundBase::pickup07
 	};
 
-	horseSounds =  // must be the same order as terrains (see ETerrainType);
-	{
-		soundBase::horseDirt, soundBase::horseSand, soundBase::horseGrass,
-		soundBase::horseSnow, soundBase::horseSwamp, soundBase::horseRough,
-		soundBase::horseSubterranean, soundBase::horseLava,
-		soundBase::horseWater, soundBase::horseRock
-	};
-
 	battleIntroSounds =
 	{
 		soundBase::battle00, soundBase::battle01,
 		soundBase::battle02, soundBase::battle03, soundBase::battle04,
 		soundBase::battle05, soundBase::battle06, soundBase::battle07
 	};
+	
+	//predefine terrain set
+	//TODO: need refactoring - support custom sounds for new terrains and load from json
+	int h3mTerrId = 0;
+	for(auto snd :
+	{
+		soundBase::horseDirt, soundBase::horseSand, soundBase::horseGrass,
+		soundBase::horseSnow, soundBase::horseSwamp, soundBase::horseRough,
+		soundBase::horseSubterranean, soundBase::horseLava,
+		soundBase::horseWater, soundBase::horseRock
+	})
+	{
+		horseSounds[Terrain::createTerrainTypeH3M(h3mTerrId++)] = snd;
+	}
+	for(auto & terrain : Terrain::Manager::terrains())
+	{
+		//since all sounds are hardcoded, let's keep it
+		if(vstd::contains(horseSounds, terrain))
+			continue;
+		
+		horseSounds[terrain] = horseSounds.at(Terrain::createTerrainTypeH3M(Terrain::Manager::getInfo(terrain).horseSoundId));
+	}
 };
 
 void CSoundHandler::init()
@@ -341,26 +356,22 @@ CMusicHandler::CMusicHandler():
 		return true;
 	});
 
-	int battleMusicID = 0;
-	int AIThemeID = 0;
-
 	for(const ResourceID & file : mp3files)
 	{
 		if(boost::algorithm::istarts_with(file.getName(), "MUSIC/Combat"))
-			addEntryToSet("battle", battleMusicID++, file.getName());
+			addEntryToSet("battle", file.getName(), file.getName());
 		else if(boost::algorithm::istarts_with(file.getName(), "MUSIC/AITheme"))
-			addEntryToSet("enemy-turn", AIThemeID++, file.getName());
+			addEntryToSet("enemy-turn", file.getName(), file.getName());
 	}
 
-	JsonNode terrains(ResourceID("config/terrains.json"));
-	for (auto entry : terrains.Struct())
+	for(auto & terrain : Terrain::Manager::terrains())
 	{
-		int terrIndex = vstd::find_pos(GameConstants::TERRAIN_NAMES, entry.first);
-		addEntryToSet("terrain", terrIndex, "Music/" + entry.second["music"].String());
+		auto & entry = Terrain::Manager::getInfo(terrain);
+		addEntryToSet("terrain", terrain, "Music/" + entry.musicFilename);
 	}
 }
 
-void CMusicHandler::addEntryToSet(std::string set, int musicID, std::string musicURI)
+void CMusicHandler::addEntryToSet(const std::string & set, const std::string & musicID, const std::string & musicURI)
 {
 	musicsSet[set][musicID] = musicURI;
 }
@@ -388,7 +399,7 @@ void CMusicHandler::release()
 	CAudioBase::release();
 }
 
-void CMusicHandler::playMusic(std::string musicURI, bool loop)
+void CMusicHandler::playMusic(const std::string & musicURI, bool loop)
 {
 	if (current && current->isTrack(musicURI))
 		return;
@@ -396,7 +407,7 @@ void CMusicHandler::playMusic(std::string musicURI, bool loop)
 	queueNext(this, "", musicURI, loop);
 }
 
-void CMusicHandler::playMusicFromSet(std::string whichSet, bool loop)
+void CMusicHandler::playMusicFromSet(const std::string & whichSet, bool loop)
 {
 	auto selectedSet = musicsSet.find(whichSet);
 	if (selectedSet == musicsSet.end())
@@ -412,8 +423,7 @@ void CMusicHandler::playMusicFromSet(std::string whichSet, bool loop)
 	queueNext(this, whichSet, "", loop);
 }
 
-
-void CMusicHandler::playMusicFromSet(std::string whichSet, int entryID, bool loop)
+void CMusicHandler::playMusicFromSet(const std::string & whichSet, const std::string & entryID, bool loop)
 {
 	auto selectedSet = musicsSet.find(whichSet);
 	if (selectedSet == musicsSet.end())
@@ -425,7 +435,7 @@ void CMusicHandler::playMusicFromSet(std::string whichSet, int entryID, bool loo
 	auto selectedEntry = selectedSet->second.find(entryID);
 	if (selectedEntry == selectedSet->second.end())
 	{
-		logGlobal->error("Error: playing non-existing entry %d from set: %s", entryID, whichSet);
+		logGlobal->error("Error: playing non-existing entry %s from set: %s", entryID, whichSet);
 		return;
 	}
 
@@ -452,7 +462,7 @@ void CMusicHandler::queueNext(std::unique_ptr<MusicEntry> queued)
 	}
 }
 
-void CMusicHandler::queueNext(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped)
+void CMusicHandler::queueNext(CMusicHandler *owner, const std::string & setName, const std::string & musicURI, bool looped)
 {
 	try
 	{
@@ -552,7 +562,7 @@ bool MusicEntry::play()
 
 	if (!setName.empty())
 	{
-		auto set = owner->musicsSet[setName];
+		const auto & set = owner->musicsSet[setName];
 		load(RandomGeneratorUtil::nextItem(set, CRandomGenerator::getDefault())->second);
 	}
 

+ 10 - 7
client/CMusicHandler.h

@@ -16,6 +16,7 @@ struct _Mix_Music;
 struct SDL_RWops;
 typedef struct _Mix_Music Mix_Music;
 struct Mix_Chunk;
+class Terrain;
 
 class CAudioBase {
 protected:
@@ -81,8 +82,8 @@ public:
 
 	// Sets
 	std::vector<soundBase::soundID> pickupSounds;
-	std::vector<soundBase::soundID> horseSounds;
 	std::vector<soundBase::soundID> battleIntroSounds;
+	std::map<Terrain, soundBase::soundID> horseSounds;
 };
 
 // Helper //now it looks somewhat useless
@@ -118,6 +119,7 @@ public:
 class CMusicHandler: public CAudioBase
 {
 private:
+	
 	//update volume on configuration change
 	SettingsListener listener;
 	void onVolumeChange(const JsonNode &volumeNode);
@@ -125,26 +127,27 @@ private:
 	std::unique_ptr<MusicEntry> current;
 	std::unique_ptr<MusicEntry> next;
 
-	void queueNext(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped);
+	void queueNext(CMusicHandler *owner, const std::string & setName, const std::string & musicURI, bool looped);
 	void queueNext(std::unique_ptr<MusicEntry> queued);
 
-	std::map<std::string, std::map<int, std::string> > musicsSet;
+	std::map<std::string, std::map<std::string, std::string>> musicsSet;
 public:
+	
 	CMusicHandler();
 
 	/// add entry with URI musicURI in set. Track will have ID musicID
-	void addEntryToSet(std::string set, int musicID, std::string musicURI);
+	void addEntryToSet(const std::string & set, const std::string & entryID, const std::string & musicURI);
 
 	void init() override;
 	void release() override;
 	void setVolume(ui32 percent) override;
 
 	/// play track by URI, if loop = true music will be looped
-	void playMusic(std::string musicURI, bool loop);
+	void playMusic(const std::string & musicURI, bool loop);
 	/// play random track from this set
-	void playMusicFromSet(std::string musicSet, bool loop);
+	void playMusicFromSet(const std::string & musicSet, bool loop);
 	/// play specific track from set
-	void playMusicFromSet(std::string musicSet, int entryID, bool loop);
+	void playMusicFromSet(const std::string & musicSet, const std::string & entryID, bool loop);
 	void stopMusic(int fade_ms=1000);
 	void musicFinishedCallback();
 

+ 5 - 9
client/CPlayerInterface.cpp

@@ -1351,12 +1351,6 @@ void CPlayerInterface::heroBonusChanged( const CGHeroInstance *hero, const Bonus
 
 template <typename Handler> void CPlayerInterface::serializeTempl( Handler &h, const int version )
 {
-	if(version < 774 && !h.saving)
-	{
-		bool observerInDuelMode = false;
-		h & observerInDuelMode;
-	}
-
 	h & wanderingHeroes;
 	h & towns;
 	h & sleepingHeroes;
@@ -2742,8 +2736,8 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
 
 	{
 		path.convert(0);
-		ETerrainType currentTerrain = ETerrainType::BORDER; // not init yet
-		ETerrainType newTerrain;
+		Terrain currentTerrain = Terrain("BORDER"); // not init yet
+		Terrain newTerrain;
 		int sh = -1;
 
 		auto canStop = [&](CGPathNode * node) -> bool
@@ -2779,7 +2773,9 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
 					destinationTeleportPos = int3(-1);
 				}
 				if(i != path.nodes.size() - 1)
+				{
 					sh = CCS->soundh->playSound(CCS->soundh->horseSounds[currentTerrain], -1);
+				}
 				continue;
 			}
 
@@ -2797,7 +2793,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
 #endif
 			{
 				newTerrain = cb->getTile(CGHeroInstance::convertPosition(currentCoord, false))->terType;
-				if (newTerrain != currentTerrain)
+				if(newTerrain != currentTerrain)
 				{
 					CCS->soundh->stopSound(sh);
 					sh = CCS->soundh->playSound(CCS->soundh->horseSounds[newTerrain], -1);

+ 1 - 11
client/Client.cpp

@@ -274,12 +274,6 @@ void CClient::serialize(BinarySerializer & h, const int version)
 void CClient::serialize(BinaryDeserializer & h, const int version)
 {
 	assert(!h.saving);
-	if(version < 787)
-	{
-		bool hotSeat = false;
-		h & hotSeat;
-	}
-
 	ui8 players = 0;
 	h & players;
 
@@ -337,11 +331,7 @@ void CClient::serialize(BinaryDeserializer & h, const int version)
 
 	{
 		JsonNode scriptsState;
-		if(version >= 800)
-		{
-			h & scriptsState;
-		}
-
+		h & scriptsState;
 		clientScripts->serializeState(h.saving, scriptsState);
 	}
 

+ 1 - 1
client/battle/CBattleInterface.cpp

@@ -454,7 +454,7 @@ CBattleInterface::~CBattleInterface()
 
 	if (adventureInt && adventureInt->selection)
 	{
-		int terrain = LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType;
+		auto & terrain = LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType;
 		CCS->musich->playMusicFromSet("terrain", terrain, true);
 	}
 	animsAreDisplayed.setn(false);

+ 51 - 55
client/mapHandler.cpp

@@ -20,6 +20,7 @@
 #include "../lib/CGameState.h"
 #include "../lib/CHeroHandler.h"
 #include "../lib/CTownHandler.h"
+#include "../lib/CModHandler.h"
 #include "Graphics.h"
 #include "../lib/mapping/CMap.h"
 #include "../lib/CConfigHandler.h"
@@ -29,6 +30,9 @@
 #include "CMT.h"
 #include "CMusicHandler.h"
 #include "../lib/CRandomGenerator.h"
+#include "../lib/Terrain.h"
+#include "../lib/filesystem/ResourceID.h"
+#include "../lib/JsonDetail.h"
 
 #define ADVOPT (conf.go()->ac)
 
@@ -142,79 +146,68 @@ EMapAnimRedrawStatus CMapHandler::drawTerrainRectNew(SDL_Surface * targetSurface
 
 void CMapHandler::initTerrainGraphics()
 {
-	static const std::vector<std::string> TERRAIN_FILES =
+	static const std::map<std::string, std::string> ROAD_FILES =
 	{
-		"DIRTTL",
-		"SANDTL",
-		"GRASTL",
-		"SNOWTL",
-		"SWMPTL",
-
-		"ROUGTL",
-		"SUBBTL",
-		"LAVATL",
-		"WATRTL",
-		"ROCKTL"
+		{ROAD_NAMES[1], "dirtrd"},
+		{ROAD_NAMES[2], "gravrd"},
+		{ROAD_NAMES[3], "cobbrd"}
 	};
 
-	static const std::vector<std::string> ROAD_FILES =
+	static const std::map<std::string, std::string> RIVER_FILES =
 	{
-		"dirtrd",
-		"gravrd",
-		"cobbrd"
+		{RIVER_NAMES[1], "clrrvr"},
+		{RIVER_NAMES[2], "icyrvr"},
+		{RIVER_NAMES[3], "mudrvr"},
+		{RIVER_NAMES[4], "lavrvr"}
 	};
+	
 
-	static const std::vector<std::string> RIVER_FILES =
+	auto loadFlipped = [](TFlippedAnimations & animation, TFlippedCache & cache, const std::map<std::string, std::string> & files)
 	{
-		"clrrvr",
-		"icyrvr",
-		"mudrvr",
-		"lavrvr"
-	};
-
-	auto loadFlipped = [](int types, TFlippedAnimations & animation, TFlippedCache & cache, const std::vector<std::string> & files)
-	{
-		animation.resize(types);
-		cache.resize(types);
-
 		//no rotation and basic setup
-		for(int i = 0; i < types; i++)
+		for(auto & type : files)
 		{
-			animation[i][0] = make_unique<CAnimation>(files[i]);
-			animation[i][0]->preload();
-			const size_t views = animation[i][0]->size(0);
-			cache[i].resize(views);
+			animation[type.first][0] = make_unique<CAnimation>(type.second);
+			animation[type.first][0]->preload();
+			const size_t views = animation[type.first][0]->size(0);
+			cache[type.first].resize(views);
 
 			for(int j = 0; j < views; j++)
-				cache[i][j][0] = animation[i][0]->getImage(j);
+				cache[type.first][j][0] = animation[type.first][0]->getImage(j);
 		}
 
 		for(int rotation = 1; rotation < 4; rotation++)
 		{
-			for(int i = 0; i < types; i++)
+			for(auto & type : files)
 			{
-				animation[i][rotation] = make_unique<CAnimation>(files[i]);
-				animation[i][rotation]->preload();
-				const size_t views = animation[i][rotation]->size(0);
+				animation[type.first][rotation] = make_unique<CAnimation>(type.second);
+				animation[type.first][rotation]->preload();
+				const size_t views = animation[type.first][rotation]->size(0);
 
 				for(int j = 0; j < views; j++)
 				{
-					auto image = animation[i][rotation]->getImage(j);
+					auto image = animation[type.first][rotation]->getImage(j);
 
 					if(rotation == 2 || rotation == 3)
 						image->horizontalFlip();
 					if(rotation == 1 || rotation == 3)
 						image->verticalFlip();
 
-					cache[i][j][rotation] = image;
+					cache[type.first][j][rotation] = image;
 				}
 			}
 		}
 	};
-
-	loadFlipped(GameConstants::TERRAIN_TYPES, terrainAnimations, terrainImages, TERRAIN_FILES);
-	loadFlipped(3, roadAnimations, roadImages, ROAD_FILES);
-	loadFlipped(4, riverAnimations, riverImages, RIVER_FILES);
+	
+	std::map<std::string, std::string> terrainFiles;
+	for(auto & terrain : Terrain::Manager::terrains())
+	{
+		terrainFiles[terrain] = Terrain::Manager::getInfo(terrain).tilesFilename;
+	}
+	
+	loadFlipped(terrainAnimations, terrainImages, terrainFiles);
+	loadFlipped(roadAnimations, roadImages, ROAD_FILES);
+	loadFlipped(riverAnimations, riverImages, RIVER_FILES);
 
 	// Create enough room for the whole map and its frame
 
@@ -626,6 +619,9 @@ void CMapHandler::CMapBlitter::drawTileTerrain(SDL_Surface * targetSurf, const T
 	Rect destRect(realTileRect);
 
 	ui8 rotation = tinfo.extTileFlags % 4;
+	
+	if(parent->terrainImages[tinfo.terType].size()<=tinfo.terView)
+		return;
 
 	drawElement(EMapCacheType::TERRAIN, parent->terrainImages[tinfo.terType][tinfo.terView][rotation], nullptr, targetSurf, &destRect);
 }
@@ -802,21 +798,21 @@ void CMapHandler::CMapBlitter::drawObjects(SDL_Surface * targetSurf, const Terra
 
 void CMapHandler::CMapBlitter::drawRoad(SDL_Surface * targetSurf, const TerrainTile & tinfo, const TerrainTile * tinfoUpper) const
 {
-	if (tinfoUpper && tinfoUpper->roadType != ERoadType::NO_ROAD)
+	if (tinfoUpper && tinfoUpper->roadType != ROAD_NAMES[0])
 	{
 		ui8 rotation = (tinfoUpper->extTileFlags >> 4) % 4;
 		Rect source(0, tileSize / 2, tileSize, tileSize / 2);
 		Rect dest(realPos.x, realPos.y, tileSize, tileSize / 2);
-		drawElement(EMapCacheType::ROADS, parent->roadImages[tinfoUpper->roadType - 1][tinfoUpper->roadDir][rotation],
+		drawElement(EMapCacheType::ROADS, parent->roadImages[tinfoUpper->roadType][tinfoUpper->roadDir][rotation],
 				&source, targetSurf, &dest);
 	}
 
-	if(tinfo.roadType != ERoadType::NO_ROAD) //print road from this tile
+	if(tinfo.roadType != ROAD_NAMES[0]) //print road from this tile
 	{
 		ui8 rotation = (tinfo.extTileFlags >> 4) % 4;
 		Rect source(0, 0, tileSize, halfTileSizeCeil);
 		Rect dest(realPos.x, realPos.y + tileSize / 2, tileSize, tileSize / 2);
-		drawElement(EMapCacheType::ROADS, parent->roadImages[tinfo.roadType - 1][tinfo.roadDir][rotation],
+		drawElement(EMapCacheType::ROADS, parent->roadImages[tinfo.roadType][tinfo.roadDir][rotation],
 				&source, targetSurf, &dest);
 	}
 }
@@ -825,7 +821,7 @@ void CMapHandler::CMapBlitter::drawRiver(SDL_Surface * targetSurf, const Terrain
 {
 	Rect destRect(realTileRect);
 	ui8 rotation = (tinfo.extTileFlags >> 2) % 4;
-	drawElement(EMapCacheType::RIVERS, parent->riverImages[tinfo.riverType-1][tinfo.riverDir][rotation], nullptr, targetSurf, &destRect);
+	drawElement(EMapCacheType::RIVERS, parent->riverImages[tinfo.riverType][tinfo.riverDir][rotation], nullptr, targetSurf, &destRect);
 }
 
 void CMapHandler::CMapBlitter::drawFow(SDL_Surface * targetSurf) const
@@ -876,7 +872,7 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn
 			if(isVisible || info->showAllTerrain)
 			{
 				drawTileTerrain(targetSurf, tinfo, tile);
-				if (tinfo.riverType)
+				if(tinfo.riverType != RIVER_NAMES[0])
 					drawRiver(targetSurf, tinfo);
 				drawRoad(targetSurf, tinfo, tinfoUpper);
 			}
@@ -1326,13 +1322,13 @@ bool CMapHandler::canStartHeroMovement()
 
 void CMapHandler::updateWater() //shift colors in palettes of water tiles
 {
-	for(auto & elem : terrainImages[7])
+	for(auto & elem : terrainImages["lava"])
 	{
 		for(auto img : elem)
 			img->shiftPalette(246, 9);
 	}
 
-	for(auto & elem : terrainImages[8])
+	for(auto & elem : terrainImages["water"])
 	{
 		for(auto img : elem)
 		{
@@ -1341,7 +1337,7 @@ void CMapHandler::updateWater() //shift colors in palettes of water tiles
 		}
 	}
 
-	for(auto & elem : riverImages[0])
+	for(auto & elem : riverImages["clrrvr"])
 	{
 		for(auto img : elem)
 		{
@@ -1350,7 +1346,7 @@ void CMapHandler::updateWater() //shift colors in palettes of water tiles
 		}
 	}
 
-	for(auto & elem : riverImages[2])
+	for(auto & elem : riverImages["mudrvr"])
 	{
 		for(auto img : elem)
 		{
@@ -1360,7 +1356,7 @@ void CMapHandler::updateWater() //shift colors in palettes of water tiles
 		}
 	}
 
-	for(auto & elem : riverImages[3])
+	for(auto & elem : riverImages["lavrvr"])
 	{
 		for(auto img : elem)
 			img->shiftPalette(240, 9);

+ 2 - 2
client/mapHandler.h

@@ -354,8 +354,8 @@ public:
 	//terrain graphics
 
 	//FIXME: unique_ptr should be enough, but fails to compile in MSVS 2013
-	typedef std::vector<std::array<std::shared_ptr<CAnimation>, 4>> TFlippedAnimations; //[type, rotation]
-	typedef std::vector<std::vector<std::array<std::shared_ptr<IImage>, 4>>> TFlippedCache;//[type, view type, rotation]
+	typedef std::map<std::string, std::array<std::shared_ptr<CAnimation>, 4>> TFlippedAnimations; //[type, rotation]
+	typedef std::map<std::string, std::vector<std::array<std::shared_ptr<IImage>, 4>>> TFlippedCache;//[type, view type, rotation]
 
 	TFlippedAnimations terrainAnimations;//[terrain type, rotation]
 	TFlippedCache terrainImages;//[terrain type, view type, rotation]

+ 13 - 23
client/widgets/AdventureMapClasses.cpp

@@ -42,6 +42,7 @@
 #include "../../lib/CHeroHandler.h"
 #include "../../lib/CModHandler.h"
 #include "../../lib/CTownHandler.h"
+#include "../../lib/Terrain.h"
 #include "../../lib/filesystem/Filesystem.h"
 #include "../../lib/JsonNode.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
@@ -494,41 +495,30 @@ void CMinimapInstance::showAll(SDL_Surface * to)
 	}
 }
 
-std::map<int, std::pair<SDL_Color, SDL_Color> > CMinimap::loadColors(std::string from)
+std::map<Terrain, std::pair<SDL_Color, SDL_Color> > CMinimap::loadColors()
 {
-	std::map<int, std::pair<SDL_Color, SDL_Color> > ret;
+	std::map<Terrain, std::pair<SDL_Color, SDL_Color> > ret;
 
-	const JsonNode config(ResourceID(from, EResType::TEXT));
-
-	for(auto &m : config.Struct())
+	for(auto & terrain : Terrain::Manager::terrains())
 	{
-		auto index = boost::find(GameConstants::TERRAIN_NAMES, m.first);
-		if (index == std::end(GameConstants::TERRAIN_NAMES))
-		{
-			logGlobal->error("Error: unknown terrain in terrains.json: %s", m.first);
-			continue;
-		}
-		int terrainID = static_cast<int>(index - std::begin(GameConstants::TERRAIN_NAMES));
-
-		const JsonVector &unblockedVec = m.second["minimapUnblocked"].Vector();
+		auto & m = Terrain::Manager::getInfo(terrain);
 		SDL_Color normal =
 		{
-			ui8(unblockedVec[0].Float()),
-			ui8(unblockedVec[1].Float()),
-			ui8(unblockedVec[2].Float()),
+			ui8(m.minimapUnblocked[0]),
+			ui8(m.minimapUnblocked[1]),
+			ui8(m.minimapUnblocked[2]),
 			ui8(255)
 		};
 
-		const JsonVector &blockedVec = m.second["minimapBlocked"].Vector();
 		SDL_Color blocked =
 		{
-			ui8(blockedVec[0].Float()),
-			ui8(blockedVec[1].Float()),
-			ui8(blockedVec[2].Float()),
+			ui8(m.minimapBlocked[0]),
+			ui8(m.minimapBlocked[1]),
+			ui8(m.minimapBlocked[2]),
 			ui8(255)
 		};
 
-		ret.insert(std::make_pair(terrainID, std::make_pair(normal, blocked)));
+		ret[terrain] = std::make_pair(normal, blocked);
 	}
 	return ret;
 }
@@ -536,7 +526,7 @@ std::map<int, std::pair<SDL_Color, SDL_Color> > CMinimap::loadColors(std::string
 CMinimap::CMinimap(const Rect & position)
 	: CIntObject(LCLICK | RCLICK | HOVER | MOVE, position.topLeft()),
 	level(0),
-	colors(loadColors("config/terrains.json"))
+	colors(loadColors())
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 	pos.w = position.w;

+ 3 - 2
client/widgets/AdventureMapClasses.h

@@ -30,6 +30,7 @@ struct InfoAboutTown;
 class CHeroTooltip;
 class CTownTooltip;
 class CTextBox;
+class Terrain;
 
 /// Base UI Element for hero\town lists
 class CList : public CIntObject
@@ -216,7 +217,7 @@ protected:
 	int level;
 
 	//to initialize colors
-	std::map<int, std::pair<SDL_Color, SDL_Color> > loadColors(std::string from);
+	std::map<Terrain, std::pair<SDL_Color, SDL_Color> > loadColors();
 
 	void clickLeft(tribool down, bool previousState) override;
 	void clickRight(tribool down, bool previousState) override;
@@ -227,7 +228,7 @@ protected:
 
 public:
 	// terrainID -> (normal color, blocked color)
-	const std::map<int, std::pair<SDL_Color, SDL_Color> > colors;
+	const std::map<Terrain, std::pair<SDL_Color, SDL_Color> > colors;
 
 	CMinimap(const Rect & position);
 

+ 1 - 1
client/windows/CAdvmapInterface.cpp

@@ -265,7 +265,7 @@ void CTerrainRect::showPath(const SDL_Rect * extRect, SDL_Surface * to)
 				{-1,  1,  2, 23, -1,  3, 22, 21, 12}
 			}; //table of magic values TODO meaning, change variable name
 
-	for (int i=0; i < (int)currentPath->nodes.size()-1; ++i)
+	for (int i = 0; i < -1 + (int)currentPath->nodes.size(); ++i)
 	{
 		const int3 &curPos = currentPath->nodes[i].coord, &nextPos = currentPath->nodes[i+1].coord;
 		if(curPos.z != adventureInt->position.z)

+ 4 - 0
config/gameConfig.json

@@ -80,5 +80,9 @@
 	"skills" :
 	[
         "config/skills.json"
+	],
+	"terrains":
+	[
+		"config/terrains.json"
 	]
 }

+ 79 - 79
config/obstacles.json

@@ -14,7 +14,7 @@
 "obstacles" : [
 	{
 		"id" : 0,
-		"allowedTerrain" : [0],
+		"allowedTerrain" : ["dirt"],
 		"specialBattlefields" : [],
 		"width" : 2,
 		"height" : 1,
@@ -24,7 +24,7 @@
 	},
 	{
 		"id" : 1,
-		"allowedTerrain" : [0, 1, 5, 6],
+		"allowedTerrain" : ["dirt", "sand", "rough", "subterra"],
 		"specialBattlefields" : [0],
 		"width" : 3,
 		"height" : 2,
@@ -34,7 +34,7 @@
 	},
 	{
 		"id" : 2,
-		"allowedTerrain" : [0],
+		"allowedTerrain" : ["dirt"],
 		"specialBattlefields" : [],
 		"width" : 4,
 		"height" : 2,
@@ -44,7 +44,7 @@
 	},
 	{
 		"id" : 3,
-		"allowedTerrain" : [0, 5],
+		"allowedTerrain" : ["dirt", "rough"],
 		"specialBattlefields" : [1],
 		"width" : 2,
 		"height" : 1,
@@ -54,7 +54,7 @@
 	},
 	{
 		"id" : 4,
-		"allowedTerrain" : [0, 5, 6],
+		"allowedTerrain" : ["dirt", "rough", "subterra"],
 		"specialBattlefields" : [0, 1],
 		"width" : 2,
 		"height" : 1,
@@ -64,7 +64,7 @@
 	},
 	{
 		"id" : 5,
-		"allowedTerrain" : [0],
+		"allowedTerrain" : ["dirt"],
 		"specialBattlefields" : [],
 		"width" : 4,
 		"height" : 2,
@@ -74,7 +74,7 @@
 	},
 	{
 		"id" : 6,
-		"allowedTerrain" : [0],
+		"allowedTerrain" : ["dirt"],
 		"specialBattlefields" : [],
 		"width" : 3,
 		"height" : 2,
@@ -84,7 +84,7 @@
 	},
 	{
 		"id" : 7,
-		"allowedTerrain" : [0],
+		"allowedTerrain" : ["dirt"],
 		"specialBattlefields" : [],
 		"width" : 2,
 		"height" : 2,
@@ -94,7 +94,7 @@
 	},
 	{
 		"id" : 8,
-		"allowedTerrain" : [0],
+		"allowedTerrain" : ["dirt"],
 		"specialBattlefields" : [],
 		"width" : 2,
 		"height" : 2,
@@ -104,7 +104,7 @@
 	},
 	{
 		"id" : 9,
-		"allowedTerrain" : [0],
+		"allowedTerrain" : ["dirt"],
 		"specialBattlefields" : [],
 		"width" : 2,
 		"height" : 2,
@@ -114,7 +114,7 @@
 	},
 	{
 		"id" : 10,
-		"allowedTerrain" : [0],
+		"allowedTerrain" : ["dirt"],
 		"specialBattlefields" : [],
 		"width" : 2,
 		"height" : 2,
@@ -124,7 +124,7 @@
 	},
 	{
 		"id" : 11,
-		"allowedTerrain" : [0],
+		"allowedTerrain" : ["dirt"],
 		"specialBattlefields" : [],
 		"width" : 2,
 		"height" : 1,
@@ -134,7 +134,7 @@
 	},
 	{
 		"id" : 12,
-		"allowedTerrain" : [0, 5],
+		"allowedTerrain" : ["dirt", "rough"],
 		"specialBattlefields" : [1],
 		"width" : 3,
 		"height" : 3,
@@ -144,7 +144,7 @@
 	},
 	{
 		"id" : 13,
-		"allowedTerrain" : [0, 5],
+		"allowedTerrain" : ["dirt", "rough"],
 		"specialBattlefields" : [1],
 		"width" : 3,
 		"height" : 2,
@@ -154,7 +154,7 @@
 	},
 	{
 		"id" : 14,
-		"allowedTerrain" : [0, 5],
+		"allowedTerrain" : ["dirt", "rough"],
 		"specialBattlefields" : [1],
 		"width" : 3,
 		"height" : 2,
@@ -164,7 +164,7 @@
 	},
 	{
 		"id" : 15,
-		"allowedTerrain" : [0, 5],
+		"allowedTerrain" : ["dirt", "rough"],
 		"specialBattlefields" : [1],
 		"width" : 3,
 		"height" : 3,
@@ -174,7 +174,7 @@
 	},
 	{
 		"id" : 16,
-		"allowedTerrain" : [1],
+		"allowedTerrain" : ["sand"],
 		"specialBattlefields" : [],
 		"width" : 4,
 		"height" : 4,
@@ -184,7 +184,7 @@
 	},
 	{
 		"id" : 17,
-		"allowedTerrain" : [1],
+		"allowedTerrain" : ["sand"],
 		"specialBattlefields" : [],
 		"width" : 3,
 		"height" : 2,
@@ -194,7 +194,7 @@
 	},
 	{
 		"id" : 18,
-		"allowedTerrain" : [1],
+		"allowedTerrain" : ["sand"],
 		"specialBattlefields" : [],
 		"width" : 4,
 		"height" : 2,
@@ -204,7 +204,7 @@
 	},
 	{
 		"id" : 19,
-		"allowedTerrain" : [2, 4],
+		"allowedTerrain" : ["grass", "swamp"],
 		"specialBattlefields" : [],
 		"width" : 2,
 		"height" : 1,
@@ -214,7 +214,7 @@
 	},
 	{
 		"id" : 20,
-		"allowedTerrain" : [2, 4],
+		"allowedTerrain" : ["grass", "swamp"],
 		"specialBattlefields" : [2],
 		"width" : 2,
 		"height" : 2,
@@ -224,7 +224,7 @@
 	},
 	{
 		"id" : 21,
-		"allowedTerrain" : [2, 4],
+		"allowedTerrain" : ["grass", "swamp"],
 		"specialBattlefields" : [],
 		"width" : 1,
 		"height" : 1,
@@ -234,7 +234,7 @@
 	},
 	{
 		"id" : 22,
-		"allowedTerrain" : [2],
+		"allowedTerrain" : ["grass"],
 		"specialBattlefields" : [2],
 		"width" : 6,
 		"height" : 2,
@@ -244,7 +244,7 @@
 	},
 	{
 		"id" : 23,
-		"allowedTerrain" : [2],
+		"allowedTerrain" : ["grass"],
 		"specialBattlefields" : [],
 		"width" : 7,
 		"height" : 1,
@@ -254,7 +254,7 @@
 	},
 	{
 		"id" : 24,
-		"allowedTerrain" : [3],
+		"allowedTerrain" : ["snow"],
 		"specialBattlefields" : [],
 		"width" : 3,
 		"height" : 1,
@@ -264,7 +264,7 @@
 	},
 	{
 		"id" : 25,
-		"allowedTerrain" : [3],
+		"allowedTerrain" : ["snow"],
 		"specialBattlefields" : [],
 		"width" : 5,
 		"height" : 1,
@@ -274,7 +274,7 @@
 	},
 	{
 		"id" : 26,
-		"allowedTerrain" : [3],
+		"allowedTerrain" : ["snow"],
 		"specialBattlefields" : [],
 		"width" : 3,
 		"height" : 3,
@@ -284,7 +284,7 @@
 	},
 	{
 		"id" : 27,
-		"allowedTerrain" : [3],
+		"allowedTerrain" : ["snow"],
 		"specialBattlefields" : [],
 		"width" : 3,
 		"height" : 1,
@@ -294,7 +294,7 @@
 	},
 	{
 		"id" : 28,
-		"allowedTerrain" : [3],
+		"allowedTerrain" : ["snow"],
 		"specialBattlefields" : [],
 		"width" : 3,
 		"height" : 1,
@@ -304,7 +304,7 @@
 	},
 	{
 		"id" : 29,
-		"allowedTerrain" : [3],
+		"allowedTerrain" : ["snow"],
 		"specialBattlefields" : [],
 		"width" : 3,
 		"height" : 2,
@@ -314,7 +314,7 @@
 	},
 	{
 		"id" : 30,
-		"allowedTerrain" : [3],
+		"allowedTerrain" : ["snow"],
 		"specialBattlefields" : [],
 		"width" : 2,
 		"height" : 1,
@@ -324,7 +324,7 @@
 	},
 	{
 		"id" : 31,
-		"allowedTerrain" : [3],
+		"allowedTerrain" : ["snow"],
 		"specialBattlefields" : [],
 		"width" : 3,
 		"height" : 2,
@@ -334,7 +334,7 @@
 	},
 	{
 		"id" : 32,
-		"allowedTerrain" : [3],
+		"allowedTerrain" : ["snow"],
 		"specialBattlefields" : [],
 		"width" : 7,
 		"height" : 2,
@@ -344,7 +344,7 @@
 	},
 	{
 		"id" : 33,
-		"allowedTerrain" : [3],
+		"allowedTerrain" : ["snow"],
 		"specialBattlefields" : [],
 		"width" : 5,
 		"height" : 5,
@@ -354,7 +354,7 @@
 	},
 	{
 		"id" : 34,
-		"allowedTerrain" : [4],
+		"allowedTerrain" : ["swamp"],
 		"specialBattlefields" : [],
 		"width" : 2,
 		"height" : 2,
@@ -364,7 +364,7 @@
 	},
 	{
 		"id" : 35,
-		"allowedTerrain" : [4],
+		"allowedTerrain" : ["swamp"],
 		"specialBattlefields" : [],
 		"width" : 8,
 		"height" : 3,
@@ -374,7 +374,7 @@
 	},
 	{
 		"id" : 36,
-		"allowedTerrain" : [4],
+		"allowedTerrain" : ["swamp"],
 		"specialBattlefields" : [],
 		"width" : 2,
 		"height" : 1,
@@ -384,7 +384,7 @@
 	},
 	{
 		"id" : 37,
-		"allowedTerrain" : [4],
+		"allowedTerrain" : ["swamp"],
 		"specialBattlefields" : [],
 		"width" : 3,
 		"height" : 1,
@@ -394,7 +394,7 @@
 	},
 	{
 		"id" : 38,
-		"allowedTerrain" : [4],
+		"allowedTerrain" : ["swamp"],
 		"specialBattlefields" : [],
 		"width" : 5,
 		"height" : 4,
@@ -404,7 +404,7 @@
 	},
 	{
 		"id" : 39,
-		"allowedTerrain" : [4],
+		"allowedTerrain" : ["swamp"],
 		"specialBattlefields" : [],
 		"width" : 4,
 		"height" : 3,
@@ -414,7 +414,7 @@
 	},
 	{
 		"id" : 40,
-		"allowedTerrain" : [5],
+		"allowedTerrain" : ["rough"],
 		"specialBattlefields" : [1],
 		"width" : 2,
 		"height" : 2,
@@ -424,7 +424,7 @@
 	},
 	{
 		"id" : 41,
-		"allowedTerrain" : [5],
+		"allowedTerrain" : ["rough"],
 		"specialBattlefields" : [1],
 		"width" : 4,
 		"height" : 3,
@@ -434,7 +434,7 @@
 	},
 	{
 		"id" : 42,
-		"allowedTerrain" : [5],
+		"allowedTerrain" : ["rough"],
 		"specialBattlefields" : [1],
 		"width" : 3,
 		"height" : 2,
@@ -444,7 +444,7 @@
 	},
 	{
 		"id" : 43,
-		"allowedTerrain" : [5],
+		"allowedTerrain" : ["rough"],
 		"specialBattlefields" : [1],
 		"width" : 3,
 		"height" : 3,
@@ -454,7 +454,7 @@
 	},
 	{
 		"id" : 44,
-		"allowedTerrain" : [5],
+		"allowedTerrain" : ["rough"],
 		"specialBattlefields" : [1],
 		"width" : 3,
 		"height" : 3,
@@ -464,7 +464,7 @@
 	},
 	{
 		"id" : 45,
-		"allowedTerrain" : [6],
+		"allowedTerrain" : ["subterra"],
 		"specialBattlefields" : [],
 		"width" : 3,
 		"height" : 3,
@@ -474,7 +474,7 @@
 	},
 	{
 		"id" : 46,
-		"allowedTerrain" : [6],
+		"allowedTerrain" : ["subterra"],
 		"specialBattlefields" : [],
 		"width" : 3,
 		"height" : 2,
@@ -484,7 +484,7 @@
 	},
 	{
 		"id" : 47,
-		"allowedTerrain" : [6],
+		"allowedTerrain" : ["subterra"],
 		"specialBattlefields" : [],
 		"width" : 4,
 		"height" : 3,
@@ -494,7 +494,7 @@
 	},
 	{
 		"id" : 48,
-		"allowedTerrain" : [7],
+		"allowedTerrain" : ["lava"],
 		"specialBattlefields" : [],
 		"width" : 4,
 		"height" : 3,
@@ -504,7 +504,7 @@
 	},
 	{
 		"id" : 49,
-		"allowedTerrain" : [7],
+		"allowedTerrain" : ["lava"],
 		"specialBattlefields" : [],
 		"width" : 4,
 		"height" : 2,
@@ -514,7 +514,7 @@
 	},
 	{
 		"id" : 50,
-		"allowedTerrain" : [7],
+		"allowedTerrain" : ["lava"],
 		"specialBattlefields" : [],
 		"width" : 5,
 		"height" : 3,
@@ -524,7 +524,7 @@
 	},
 	{
 		"id" : 51,
-		"allowedTerrain" : [7],
+		"allowedTerrain" : ["lava"],
 		"specialBattlefields" : [],
 		"width" : 3,
 		"height" : 2,
@@ -534,7 +534,7 @@
 	},
 	{
 		"id" : 52,
-		"allowedTerrain" : [7],
+		"allowedTerrain" : ["lava"],
 		"specialBattlefields" : [],
 		"width" : 4,
 		"height" : 4,
@@ -544,7 +544,7 @@
 	},
 	{
 		"id" : 53,
-		"allowedTerrain" : [7],
+		"allowedTerrain" : ["lava"],
 		"specialBattlefields" : [],
 		"width" : 5,
 		"height" : 3,
@@ -554,7 +554,7 @@
 	},
 	{
 		"id" : 54,
-		"allowedTerrain" : [7],
+		"allowedTerrain" : ["lava"],
 		"specialBattlefields" : [],
 		"width" : 5,
 		"height" : 3,
@@ -564,7 +564,7 @@
 	},
 	{
 		"id" : 55,
-		"allowedTerrain" : [8],
+		"allowedTerrain" : ["water"],
 		"specialBattlefields" : [],
 		"width" : 3,
 		"height" : 3,
@@ -926,7 +926,7 @@
 "absoluteObstacles" : [
 	{
 		"id" : 0,
-		"allowedTerrain" : [0],
+		"allowedTerrain" : ["dirt"],
 		"specialBattlefields" : [],
 		"width" : 124,
 		"height" : 254,
@@ -935,7 +935,7 @@
 	},
 	{
 		"id" : 1,
-		"allowedTerrain" : [0],
+		"allowedTerrain" : ["dirt"],
 		"specialBattlefields" : [],
 		"width" : 256,
 		"height" : 254,
@@ -944,7 +944,7 @@
 	},
 	{
 		"id" : 2,
-		"allowedTerrain" : [0],
+		"allowedTerrain" : ["dirt"],
 		"specialBattlefields" : [],
 		"width" : 168,
 		"height" : 212,
@@ -953,7 +953,7 @@
 	},
 	{
 		"id" : 3,
-		"allowedTerrain" : [0],
+		"allowedTerrain" : ["dirt"],
 		"specialBattlefields" : [],
 		"width" : 124,
 		"height" : 254,
@@ -962,7 +962,7 @@
 	},
 	{
 		"id" : 4,
-		"allowedTerrain" : [0],
+		"allowedTerrain" : ["dirt"],
 		"specialBattlefields" : [],
 		"width" : 146,
 		"height" : 254,
@@ -971,7 +971,7 @@
 	},
 	{
 		"id" : 5,
-		"allowedTerrain" : [2],
+		"allowedTerrain" : ["grass"],
 		"specialBattlefields" : [],
 		"width" : 173,
 		"height" : 221,
@@ -980,7 +980,7 @@
 	},
 	{
 		"id" : 6,
-		"allowedTerrain" : [2],
+		"allowedTerrain" : ["grass"],
 		"specialBattlefields" : [],
 		"width" : 180,
 		"height" : 264,
@@ -989,7 +989,7 @@
 	},
 	{
 		"id" : 7,
-		"allowedTerrain" : [3],
+		"allowedTerrain" : ["snow"],
 		"specialBattlefields" : [],
 		"width" : 166,
 		"height" : 255,
@@ -998,7 +998,7 @@
 	},
 	{
 		"id" : 8,
-		"allowedTerrain" : [3],
+		"allowedTerrain" : ["snow"],
 		"specialBattlefields" : [],
 		"width" : 302,
 		"height" : 172,
@@ -1007,7 +1007,7 @@
 	},
 	{
 		"id" : 9,
-		"allowedTerrain" : [4],
+		"allowedTerrain" : ["swamp"],
 		"specialBattlefields" : [],
 		"width" : 300,
 		"height" : 170,
@@ -1016,7 +1016,7 @@
 	},
 	{
 		"id" : 10,
-		"allowedTerrain" : [4],
+		"allowedTerrain" : ["swamp"],
 		"specialBattlefields" : [],
 		"width" : 278,
 		"height" : 171,
@@ -1025,7 +1025,7 @@
 	},
 	{
 		"id" : 11,
-		"allowedTerrain" : [4],
+		"allowedTerrain" : ["swamp"],
 		"specialBattlefields" : [],
 		"width" : 256,
 		"height" : 254,
@@ -1034,7 +1034,7 @@
 	},
 	{
 		"id" : 12,
-		"allowedTerrain" : [7],
+		"allowedTerrain" : ["lava"],
 		"specialBattlefields" : [],
 		"width" : 124,
 		"height" : 254,
@@ -1043,7 +1043,7 @@
 	},
 	{
 		"id" : 13,
-		"allowedTerrain" : [7],
+		"allowedTerrain" : ["lava"],
 		"specialBattlefields" : [],
 		"width" : 256,
 		"height" : 128,
@@ -1052,7 +1052,7 @@
 	},
 	{
 		"id" : 14,
-		"allowedTerrain" : [5],
+		"allowedTerrain" : ["rough"],
 		"specialBattlefields" : [1],
 		"width" : 186,
 		"height" : 212,
@@ -1061,7 +1061,7 @@
 	},
 	{
 		"id" : 15,
-		"allowedTerrain" : [5],
+		"allowedTerrain" : ["rough"],
 		"specialBattlefields" : [1],
 		"width" : 347,
 		"height" : 174,
@@ -1070,7 +1070,7 @@
 	},
 	{
 		"id" : 16,
-		"allowedTerrain" : [5],
+		"allowedTerrain" : ["rough"],
 		"specialBattlefields" : [1],
 		"width" : 294,
 		"height" : 169,
@@ -1079,7 +1079,7 @@
 	},
 	{
 		"id" : 17,
-		"allowedTerrain" : [5],
+		"allowedTerrain" : ["rough"],
 		"specialBattlefields" : [1],
 		"width" : 165,
 		"height" : 257,
@@ -1088,7 +1088,7 @@
 	},
 	{
 		"id" : 18,
-		"allowedTerrain" : [5],
+		"allowedTerrain" : ["rough"],
 		"specialBattlefields" : [1],
 		"width" : 208,
 		"height" : 268,
@@ -1097,7 +1097,7 @@
 	},
 	{
 		"id" : 19,
-		"allowedTerrain" : [5],
+		"allowedTerrain" : ["rough"],
 		"specialBattlefields" : [1],
 		"width" : 252,
 		"height" : 254,
@@ -1106,7 +1106,7 @@
 	},
 	{
 		"id" : 20,
-		"allowedTerrain" : [5],
+		"allowedTerrain" : ["rough"],
 		"specialBattlefields" : [1],
 		"width" : 278,
 		"height" : 128,
@@ -1115,7 +1115,7 @@
 	},
 	{
 		"id" : 21,
-		"allowedTerrain" : [5],
+		"allowedTerrain" : ["rough"],
 		"specialBattlefields" : [1],
 		"width" : 208,
 		"height" : 268,
@@ -1124,7 +1124,7 @@
 	},
 	{
 		"id" : 22,
-		"allowedTerrain" : [5],
+		"allowedTerrain" : ["rough"],
 		"specialBattlefields" : [1],
 		"width" : 168,
 		"height" : 212,

+ 2 - 2
config/randomMap.json

@@ -2,7 +2,7 @@
   "terrain" :
   {
     "undergroundAllow" : ["lava"], //others to be replaced by subterranena
-    "groundProhibit" : ["subterranean"] //to be replaced by dirt
+    "groundProhibit" : ["subterra"] //to be replaced by dirt
   },
   "waterZone" :
   {
@@ -31,7 +31,7 @@
     "extraResourcesLimit" : 3
   },
   "minGuardStrength" : 2000,
-  "defaultRoadType" : "cobblestone_road",
+  "defaultRoadType" : "pc", //pd - dirt, pg - gravel, pc - cobblestone
   "treasureValueLimit" : 20000, //generate pandora with gold for treasure above this limit
   "prisons" :
   {

+ 33 - 10
config/terrains.json

@@ -4,69 +4,92 @@
 		"moveCost" : 100,
 		"minimapUnblocked" : [ 82, 56, 8 ],
 		"minimapBlocked"   : [ 57, 40, 8 ],
-		"music" : "Dirt.mp3"
+		"music" : "Dirt.mp3",
+		"tiles" : "DIRTTL",
+		"code" : "dt"
 	},
 	"sand" :
 	{
 		"moveCost" : 150,
 		"minimapUnblocked" : [ 222, 207, 140 ],
 		"minimapBlocked"   : [ 165, 158, 107 ],
-		"music" : "Sand.mp3"
+		"music" : "Sand.mp3",
+		"tiles" : "SANDTL",
+		"code" : "sa"
 	},
 	"grass" :
 	{
 		"moveCost" : 100,
 		"minimapUnblocked" : [ 0, 65, 0 ],
 		"minimapBlocked"   : [ 0, 48, 0 ],
-		"music" : "Grass.mp3"
+		"music" : "Grass.mp3",
+		"tiles" : "GRASTL",
+		"code" : "gr"
 	},
 	"snow" :
 	{
 		"moveCost" : 150,
 		"minimapUnblocked" : [ 181, 199, 198 ],
 		"minimapBlocked"   : [ 140, 158, 156 ],
-		"music" : "Snow.mp3"
+		"music" : "Snow.mp3",
+		"tiles" : "SNOWTL",
+		"code" : "sn"
 	},
 	"swamp" :
 	{
 		"moveCost" : 175,
 		"minimapUnblocked" : [ 74, 134, 107 ],
 		"minimapBlocked"   : [ 33,  89,  66 ],
-		"music" : "Swamp.mp3"
+		"music" : "Swamp.mp3",
+		"tiles" : "SWMPTL",
+		"code" : "sw"
 	},
 	"rough" :
 	{
 		"moveCost" : 125,
 		"minimapUnblocked" : [ 132, 113, 49 ],
 		"minimapBlocked"   : [  99,  81, 33 ],
-		"music" : "Rough.mp3"
+		"music" : "Rough.mp3",
+		"tiles" : "ROUGTL",
+		"code" : "rg"
 	},
 	"subterra" :
 	{
 		"moveCost" : 100,
 		"minimapUnblocked" : [ 132, 48, 0 ],
 		"minimapBlocked"   : [  90,  8, 0 ],
-		"music" : "Underground.mp3"
+		"music" : "Underground.mp3",
+		"tiles" : "SUBBTL",
+		"type" : "SUB",
+		"code" : "sb"
 	},
 	"lava" :
 	{
 		"moveCost" : 100,
 		"minimapUnblocked" : [ 74, 73, 74 ],
 		"minimapBlocked"   : [ 41, 40, 41 ],
-		"music" : "Lava.mp3"
+		"music" : "Lava.mp3",
+		"tiles" : "LAVATL",
+		"code" : "lv"
 	},
 	"water" :
 	{
 		"moveCost" : 100,
 		"minimapUnblocked" : [ 8, 81, 148 ],
 		"minimapBlocked"   : [ 8, 81, 148 ],
-		"music" : "Water.mp3"
+		"music" : "Water.mp3",
+		"tiles" : "WATRTL",
+		"type" : "WATER",
+		"code" : "wt"
 	},
 	"rock" :
 	{
 		"moveCost" : -1,
 		"minimapUnblocked" : [ 0, 0, 0 ],
 		"minimapBlocked"   : [ 0, 0, 0 ],
-		"music" : "Underground.mp3" // Impossible in H3
+		"music" : "Underground.mp3", // Impossible in H3
+		"tiles" : "ROCKTL",
+		"type" : "ROCK",
+		"code" : "rc"
 	}
 }

+ 2 - 13
lib/CArtHandler.h

@@ -102,19 +102,8 @@ public:
 		h & constituentOf;
 		h & aClass;
 		h & id;
-		if(version >= 759)
-		{
-			h & identifier;
-		}
-
-		if(version >= 771)
-		{
-			h & warMachine;
-		}
-		else if(!h.saving)
-		{
-			fillWarMachine();
-		}
+		h & identifier;
+		h & warMachine;
 	}
 
 	CArtifact();

+ 6 - 10
lib/CCreatureHandler.cpp

@@ -16,6 +16,7 @@
 #include "CGameState.h"
 #include "CTownHandler.h"
 #include "CModHandler.h"
+#include "Terrain.h"
 #include "StringConstants.h"
 #include "serializer/JsonDeserializer.h"
 #include "serializer/JsonUpdater.h"
@@ -282,13 +283,13 @@ std::string CCreature::nodeName() const
 	return "\"" + namePl + "\"";
 }
 
-bool CCreature::isItNativeTerrain(ETerrainType::EETerrainType terrain) const
+bool CCreature::isItNativeTerrain(const Terrain & terrain) const
 {
 	auto native = getNativeTerrain();
-	return native == terrain || native == ETerrainType::ANY_TERRAIN;
+	return native == terrain || native == Terrain::ANY;
 }
 
-ETerrainType::EETerrainType CCreature::getNativeTerrain() const
+Terrain CCreature::getNativeTerrain() const
 {
 	const std::string cachingStringBlocksRetaliation = "type_NO_TERRAIN_PENALTY";
 	static const auto selectorBlocksRetaliation = Selector::type()(Bonus::NO_TERRAIN_PENALTY);
@@ -296,8 +297,8 @@ ETerrainType::EETerrainType CCreature::getNativeTerrain() const
 	//this code is used in the CreatureTerrainLimiter::limit to setup battle bonuses
 	//and in the CGHeroInstance::getNativeTerrain() to setup mevement bonuses or/and penalties.
 	return hasBonus(selectorBlocksRetaliation, selectorBlocksRetaliation)
-		? ETerrainType::ANY_TERRAIN
-		: (ETerrainType::EETerrainType)(*VLC->townh)[faction]->nativeTerrain;
+		? Terrain::ANY
+		: (Terrain)(*VLC->townh)[faction]->nativeTerrain;
 }
 
 void CCreature::updateFrom(const JsonNode & data)
@@ -1340,11 +1341,6 @@ void CCreatureHandler::removeBonusesFromAllCreatures()
 	allCreatures.removeBonuses(Selector::all);
 }
 
-void CCreatureHandler::restoreAllCreaturesNodeType794()
-{
-	allCreatures.setNodeType(CBonusSystemNode::ENodeTypes::ALL_CREATURES);
-}
-
 void CCreatureHandler::buildBonusTreeForTiers()
 {
 	for(CCreature * c : objects)

+ 5 - 15
lib/CCreatureHandler.h

@@ -24,6 +24,7 @@ class CLegacyConfigParser;
 class CCreatureHandler;
 class CCreature;
 class JsonSerializeFormat;
+class Terrain;
 
 class DLL_LINKAGE CCreature : public Creature, public CBonusSystemNode
 {
@@ -118,14 +119,14 @@ public:
 
 	ArtifactID warMachine;
 
-	bool isItNativeTerrain(ETerrainType::EETerrainType terrain) const;
+	bool isItNativeTerrain(const Terrain & terrain) const;
 	/**
 	Returns creature native terrain considering some terrain bonuses.
 	@param considerBonus is used to avoid Dead Lock when this method is called inside getAllBonuses
 	considerBonus = true is called from Pathfinder and fills actual nativeTerrain considering bonus(es).
 	considerBonus = false is called on Battle init and returns already prepared nativeTerrain without Bonus system calling.
 	*/
-	ETerrainType::EETerrainType getNativeTerrain() const;
+	Terrain getNativeTerrain() const;
 	int32_t getIndex() const override;
 	int32_t getIconIndex() const override;
 	const std::string & getName() const override;
@@ -211,18 +212,8 @@ public:
 
 		h & doubleWide;
 		h & special;
-		if(version>=759)
-		{
-			h & identifier;
-		}
-		if(version >= 771)
-		{
-			h & warMachine;
-		}
-		else if(!h.saving)
-		{
-			fillWarMachine();
-		}
+		h & identifier;
+		h & warMachine;
 	}
 
 	CCreature();
@@ -281,7 +272,6 @@ public:
 	void addBonusForTier(int tier, const std::shared_ptr<Bonus> & b); //tier must be <1-7>
 	void addBonusForAllCreatures(const std::shared_ptr<Bonus> & b); //due to CBonusSystem::addNewBonus(const std::shared_ptr<Bonus>& b);
 	void removeBonusesFromAllCreatures();
-	void restoreAllCreaturesNodeType794(); //restore ALL_CREATURES node type for old saves
 
 	CCreatureHandler();
 	~CCreatureHandler();

+ 1 - 1
lib/CGameInfoCallback.cpp

@@ -574,7 +574,7 @@ EBuildingState::EBuildingState CGameInfoCallback::canBuildStructure( const CGTow
 	{
 		const TerrainTile *tile = getTile(t->bestLocation(), false);
 
-		if(!tile || tile->terType != ETerrainType::WATER)
+		if(!tile || tile->terType.isLand())
 			return EBuildingState::NO_WATER; //lack of water
 	}
 

+ 18 - 18
lib/CGameState.cpp

@@ -963,8 +963,8 @@ void CGameState::initGrailPosition()
 					const TerrainTile &t = map->getTile(int3(i, j, k));
 					if(!t.blocked
 						&& !t.visitable
-						&& t.terType != ETerrainType::WATER
-						&& t.terType != ETerrainType::ROCK
+						&& t.terType.isLand()
+						&& t.terType.isPassable()
 						&& (int)map->grailPos.dist2dSQ(int3(i, j, k)) <= (map->grailRadius * map->grailRadius))
 						allowedPos.push_back(int3(i,j,k));
 				}
@@ -1940,31 +1940,31 @@ BFieldType CGameState::battleGetBattlefieldType(int3 tile, CRandomGenerator & ra
 	if(map->isCoastalTile(tile)) //coastal tile is always ground
 		return BFieldType::SAND_SHORE;
 
-	switch(t.terType)
-	{
-	case ETerrainType::DIRT:
+	if(t.terType == Terrain("dirt"))
 		return BFieldType(rand.nextInt(3, 5));
-	case ETerrainType::SAND:
+	if(t.terType == Terrain("sand"))
 		return BFieldType::SAND_MESAS; //TODO: coast support
-	case ETerrainType::GRASS:
+	if(t.terType == Terrain("grass"))
 		return BFieldType(rand.nextInt(6, 7));
-	case ETerrainType::SNOW:
+	if(t.terType == Terrain("snow"))
 		return BFieldType(rand.nextInt(10, 11));
-	case ETerrainType::SWAMP:
+	if(t.terType == Terrain("swamp"))
 		return BFieldType::SWAMP_TREES;
-	case ETerrainType::ROUGH:
+	if(t.terType == Terrain("rough"))
 		return BFieldType::ROUGH;
-	case ETerrainType::SUBTERRANEAN:
+	if(t.terType.isUnderground())
 		return BFieldType::SUBTERRANEAN;
-	case ETerrainType::LAVA:
+	if(t.terType == Terrain("lava"))
 		return BFieldType::LAVA;
-	case ETerrainType::WATER:
+	if(t.terType.isWater())
 		return BFieldType::SHIP;
-	case ETerrainType::ROCK:
+	if(!t.terType.isPassable())
 		return BFieldType::ROCKLANDS;
-	default:
-		return BFieldType::NONE;
-	}
+	
+	//TODO: STUB, support new battlegrounds
+	return BFieldType::DIRT_HILLS;
+	
+	return BFieldType::NONE;
 }
 
 UpgradeInfo CGameState::getUpgradeInfo(const CStackInstance &stack)
@@ -2145,7 +2145,7 @@ void CGameState::updateRumor()
 			rumorId = *RandomGeneratorUtil::nextItem(sRumorTypes, rand);
 			if(rumorId == RumorState::RUMOR_GRAIL)
 			{
-				rumorExtra = getTile(map->grailPos)->terType;
+				rumorExtra = getTile(map->grailPos)->terType.id();
 				break;
 			}
 

+ 1 - 8
lib/CGameState.h

@@ -227,14 +227,7 @@ public:
 		h & hpool;
 		h & globalEffects;
 		h & rand;
-		if(version >= 755) //save format backward compatibility
-		{
-			h & rumor;
-		}
-		else if(!h.saving)
-		{
-			rumor = RumorState();
-		}
+		h & rumor;
 
 		BONUS_TREE_DESERIALIZATION_FIX
 	}

+ 14 - 1
lib/CGeneralTextHandler.cpp

@@ -17,6 +17,7 @@
 #include "CModHandler.h"
 #include "GameConstants.h"
 #include "VCMI_Lib.h"
+#include "Terrain.h"
 
 size_t Unicode::getCharacterSize(char firstByte)
 {
@@ -309,6 +310,7 @@ void CGeneralTextHandler::readToVector(std::string sourceName, std::vector<std::
 
 CGeneralTextHandler::CGeneralTextHandler()
 {
+	std::vector<std::string> h3mTerrainNames;
 	readToVector("DATA/VCDESC.TXT",   victoryConditions);
 	readToVector("DATA/LCDESC.TXT",   lossCondtions);
 	readToVector("DATA/TCOMMAND.TXT", tcommands);
@@ -317,7 +319,7 @@ CGeneralTextHandler::CGeneralTextHandler()
 	readToVector("DATA/ADVEVENT.TXT", advobtxt);
 	readToVector("DATA/XTRAINFO.TXT", xtrainfo);
 	readToVector("DATA/RESTYPES.TXT", restypes);
-	readToVector("DATA/TERRNAME.TXT", terrainNames);
+	readToVector("DATA/TERRNAME.TXT", h3mTerrainNames);
 	readToVector("DATA/RANDSIGN.TXT", randsign);
 	readToVector("DATA/CRGEN1.TXT",   creGens);
 	readToVector("DATA/CRGEN4.TXT",   creGens4);
@@ -331,6 +333,17 @@ CGeneralTextHandler::CGeneralTextHandler()
 	readToVector("DATA/HEROSCRN.TXT", heroscrn);
 	readToVector("DATA/TENTCOLR.TXT", tentColors);
 	readToVector("DATA/SKILLLEV.TXT", levels);
+	
+	for(int i = 0; i < h3mTerrainNames.size(); ++i)
+	{
+		terrainNames[Terrain::createTerrainTypeH3M(i)] = h3mTerrainNames[i];
+	}
+	for(auto & terrain : Terrain::Manager::terrains())
+	{
+		if(!Terrain::Manager::getInfo(terrain).terrainText.empty())
+			terrainNames[terrain] = Terrain::Manager::getInfo(terrain).terrainText;
+	}
+	
 
 	static const char * QE_MOD_COMMANDS = "DATA/QECOMMANDS.TXT";
 	if (CResourceHandler::get()->existsResource(ResourceID(QE_MOD_COMMANDS, EResType::TEXT)))

+ 1 - 1
lib/CGeneralTextHandler.h

@@ -122,7 +122,7 @@ public:
 	std::vector<std::string> advobtxt;
 	std::vector<std::string> xtrainfo;
 	std::vector<std::string> restypes; //names of resources
-	std::vector<std::string> terrainNames;
+	std::map<std::string, std::string> terrainNames;
 	std::vector<std::string> randsign;
 	std::vector<std::pair<std::string,std::string>> mines; //first - name; second - event description
 	std::vector<std::string> seerEmpty;

+ 10 - 9
lib/CHeroHandler.cpp

@@ -19,6 +19,7 @@
 #include "CCreatureHandler.h"
 #include "CModHandler.h"
 #include "CTownHandler.h"
+#include "Terrain.h"
 #include "mapObjects/CObjectHandler.h" //for hero specialty
 #include "CSkillHandler.h"
 #include <math.h>
@@ -176,7 +177,7 @@ std::vector<BattleHex> CObstacleInfo::getBlocked(BattleHex hex) const
 	return ret;
 }
 
-bool CObstacleInfo::isAppropriate(ETerrainType terrainType, int specialBattlefield) const
+bool CObstacleInfo::isAppropriate(Terrain terrainType, int specialBattlefield) const
 {
 	if(specialBattlefield != -1)
 		return vstd::contains(allowedSpecialBfields, specialBattlefield);
@@ -376,9 +377,9 @@ CHeroHandler::CHeroHandler()
 {
 	loadObstacles();
 	loadTerrains();
-	for (int i = 0; i < GameConstants::TERRAIN_TYPES; ++i)
+	for(int i = 0; i < Terrain::Manager::terrains().size(); ++i)
 	{
-		VLC->modh->identifiers.registerObject("core", "terrain", GameConstants::TERRAIN_NAMES[i], i);
+		VLC->modh->identifiers.registerObject("core", "terrain", Terrain::Manager::terrains()[i], i);
 	}
 	loadBallistics();
 	loadExperience();
@@ -826,7 +827,8 @@ void CHeroHandler::loadObstacles()
 			obi.defName = obs["defname"].String();
 			obi.width =  static_cast<si32>(obs["width"].Float());
 			obi.height = static_cast<si32>(obs["height"].Float());
-			obi.allowedTerrains = obs["allowedTerrain"].convertTo<std::vector<ETerrainType> >();
+			for(auto & t : obs["allowedTerrain"].Vector())
+				obi.allowedTerrains.emplace_back(t.String());
 			obi.allowedSpecialBfields = obs["specialBattlefields"].convertTo<std::vector<BFieldType> >();
 			obi.blockedTiles = obs["blockedTiles"].convertTo<std::vector<si16> >();
 			obi.isAbsoluteObstacle = absolute;
@@ -1029,11 +1031,10 @@ ui64 CHeroHandler::reqExp (ui32 level) const
 
 void CHeroHandler::loadTerrains()
 {
-	const JsonNode config(ResourceID("config/terrains.json"));
-
-	terrCosts.reserve(GameConstants::TERRAIN_TYPES);
-	for(const std::string & name : GameConstants::TERRAIN_NAMES)
-		terrCosts.push_back((int)config[name]["moveCost"].Float());
+	for(auto & terrain : Terrain::Manager::terrains())
+	{
+		terrCosts[terrain] = Terrain::Manager::getInfo(terrain).moveCost;
+	}
 }
 
 std::vector<bool> CHeroHandler::getDefaultAllowed() const

+ 8 - 30
lib/CHeroHandler.h

@@ -26,6 +26,7 @@ struct BattleHex;
 class JsonNode;
 class CRandomGenerator;
 class JsonSerializeFormat;
+class Terrain;
 
 struct SSpecialtyInfo
 {	si32 type;
@@ -119,15 +120,7 @@ public:
 		h & initialArmy;
 		h & heroClass;
 		h & secSkillsInit;
-		if(version >= 781)
-		{
-			h & specialty;
-		}
-		else
-		{
-			h & specDeprecated;
-			h & specialtyDeprecated;
-		}
+		h & specialty;
 		h & spells;
 		h & haveSpellBook;
 		h & sex;
@@ -141,14 +134,8 @@ public:
 		h & iconSpecLarge;
 		h & portraitSmall;
 		h & portraitLarge;
-		if(version >= 759)
-		{
-			h & identifier;
-		}
-		if(version >= 790)
-		{
-			h & battleImage;
-		}
+		h & identifier;
+		h & battleImage;
 	}
 };
 
@@ -211,16 +198,7 @@ public:
 		h & identifier;
 		h & name;
 		h & faction;
-		if(version >= 800)
-		{
-			h & id;
-		}
-		else
-		{
-			ui8 old_id = 0;
-			h & old_id;
-			id = HeroClassID(old_id);
-		}
+		h & id;
 		h & defaultTavernChance;
 		h & primarySkillInitial;
 		h & primarySkillLowLevel;
@@ -248,7 +226,7 @@ struct DLL_LINKAGE CObstacleInfo
 {
 	si32 ID;
 	std::string defName;
-	std::vector<ETerrainType> allowedTerrains;
+	std::vector<Terrain> allowedTerrains;
 	std::vector<BFieldType> allowedSpecialBfields;
 
 	ui8 isAbsoluteObstacle; //there may only one such obstacle in battle and its position is always the same
@@ -257,7 +235,7 @@ 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(ETerrainType terrainType, int specialBattlefield = -1) const;
+	bool isAppropriate(Terrain terrainType, int specialBattlefield = -1) const;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
@@ -315,7 +293,7 @@ public:
 	CHeroClassHandler classes;
 
 	//default costs of going through terrains. -1 means terrain is impassable
-	std::vector<int> terrCosts;
+	std::map<Terrain, int> terrCosts;
 
 	struct SBallisticsLevelInfo
 	{

+ 2 - 0
lib/CMakeLists.txt

@@ -163,6 +163,7 @@ set(lib_SRCS
 		StartInfo.cpp
 		ResourceSet.cpp
 		ScriptHandler.cpp
+		Terrain.cpp
 		VCMIDirs.cpp
 		VCMI_Lib.cpp
 
@@ -385,6 +386,7 @@ set(lib_HEADERS
 		ScopeGuard.h
 		StartInfo.h
 		StringConstants.h
+		Terrain.h
 		UnlockGuard.h
 		VCMIDirs.h
 		vcmi_endian.h

+ 3 - 26
lib/CModHandler.h

@@ -303,32 +303,9 @@ public:
 			h & ALL_CREATURES_GET_DOUBLE_MONTHS;
 			h & MAX_HEROES_AVAILABLE_PER_PLAYER;
 			h & MAX_HEROES_ON_MAP_PER_PLAYER;
-			if(version >= 756)
-			{
-				h & WINNING_HERO_WITH_NO_TROOPS_RETREATS;
-			}
-			else if(!h.saving)
-			{
-				WINNING_HERO_WITH_NO_TROOPS_RETREATS = true;
-			}
-
-			if(version >= 776)
-			{
-				h & BLACK_MARKET_MONTHLY_ARTIFACTS_CHANGE;
-			}
-			else if(!h.saving)
-			{
-				BLACK_MARKET_MONTHLY_ARTIFACTS_CHANGE = true;
-			}
-
-			if(version >= 791)
-			{
-				h & NO_RANDOM_SPECIAL_WEEKS_AND_MONTHS;
-			}
-			else if(!h.saving)
-			{
-				NO_RANDOM_SPECIAL_WEEKS_AND_MONTHS = false;
-			}
+			h & WINNING_HERO_WITH_NO_TROOPS_RETREATS;
+			h & BLACK_MARKET_MONTHLY_ARTIFACTS_CHANGE;
+			h & NO_RANDOM_SPECIAL_WEEKS_AND_MONTHS;
 		}
 	} settings;
 

+ 11 - 17
lib/CPathfinder.cpp

@@ -46,24 +46,19 @@ void NodeStorage::initialize(const PathfinderOptions & options, const CGameState
 			for(pos.z=0; pos.z < sizes.z; ++pos.z)
 			{
 				const TerrainTile * tile = &gs->map->getTile(pos);
-				switch(tile->terType)
+				if(tile->terType.isWater())
 				{
-				case ETerrainType::ROCK:
-					break;
-
-				case ETerrainType::WATER:
 					resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
 					if(useFlying)
 						resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
 					if(useWaterWalking)
 						resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility<ELayer::WATER>(pos, tile, fow, player, gs));
-					break;
-
-				default:
+				}
+				if(tile->terType.isLand())
+				{
 					resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility<ELayer::LAND>(pos, tile, fow, player, gs));
 					if(useFlying)
 						resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
-					break;
 				}
 			}
 		}
@@ -1012,8 +1007,7 @@ bool CPathfinderHelper::passOneTurnLimitCheck(const PathNodeInfo & source) const
 
 TurnInfo::BonusCache::BonusCache(TConstBonusListPtr bl)
 {
-	noTerrainPenalty.reserve(ETerrainType::ROCK);
-	for(int i = 0; i < ETerrainType::ROCK; i++)
+	for(int i = 0; i < Terrain::Manager::terrains().size(); ++i)
 	{
 		noTerrainPenalty.push_back(static_cast<bool>(
 				bl->getFirst(Selector::type()(Bonus::NO_TERRAIN_PENALTY).And(Selector::subtype()(i)))));
@@ -1179,7 +1173,7 @@ void CPathfinderHelper::getNeighbours(
 			continue;
 
 		const TerrainTile & hlpt = map->getTile(hlp);
-		if(hlpt.terType == ETerrainType::ROCK)
+		if(!hlpt.terType.isPassable())
 			continue;
 
 // 		//we cannot visit things from blocked tiles
@@ -1189,18 +1183,18 @@ void CPathfinderHelper::getNeighbours(
 // 		}
 
 		/// Following condition let us avoid diagonal movement over coast when sailing
-		if(srct.terType == ETerrainType::WATER && limitCoastSailing && hlpt.terType == ETerrainType::WATER && dir.x && dir.y) //diagonal move through water
+		if(srct.terType.isWater() && limitCoastSailing && hlpt.terType.isWater() && dir.x && dir.y) //diagonal move through water
 		{
 			int3 hlp1 = tile,
 				hlp2 = tile;
 			hlp1.x += dir.x;
 			hlp2.y += dir.y;
 
-			if(map->getTile(hlp1).terType != ETerrainType::WATER || map->getTile(hlp2).terType != ETerrainType::WATER)
+			if(map->getTile(hlp1).terType.isLand() || map->getTile(hlp2).terType.isLand())
 				continue;
 		}
 
-		if(indeterminate(onLand) || onLand == (hlpt.terType != ETerrainType::WATER))
+		if(indeterminate(onLand) || onLand == hlpt.terType.isLand())
 		{
 			vec.push_back(hlp);
 		}
@@ -1238,7 +1232,7 @@ int CPathfinderHelper::getMovementCost(
 	{
 		ret = static_cast<int>(ret * (100.0 + ti->valOfBonuses(Bonus::FLYING_MOVEMENT)) / 100.0);
 	}
-	else if(dt->terType == ETerrainType::WATER)
+	else if(dt->terType.isWater())
 	{
 		if(hero->boat && ct->hasFavorableWinds() && dt->hasFavorableWinds())
 			ret = static_cast<int>(ret * 0.666);
@@ -1266,7 +1260,7 @@ int CPathfinderHelper::getMovementCost(
 	{
 		std::vector<int3> vec;
 		vec.reserve(8); //optimization
-		getNeighbours(*dt, dst, vec, ct->terType != ETerrainType::WATER, true);
+		getNeighbours(*dt, dst, vec, ct->terType.isLand(), true);
 		for(auto & elem : vec)
 		{
 			int fcost = getMovementCost(dst, elem, nullptr, nullptr, left, false);

+ 2 - 1
lib/CPathfinder.h

@@ -13,6 +13,7 @@
 #include "IGameCallback.h"
 #include "HeroBonus.h"
 #include "int3.h"
+#include "Terrain.h"
 
 #include <boost/heap/fibonacci_heap.hpp>
 
@@ -521,7 +522,7 @@ struct DLL_LINKAGE TurnInfo
 	TConstBonusListPtr bonuses;
 	mutable int maxMovePointsLand;
 	mutable int maxMovePointsWater;
-	ETerrainType::EETerrainType nativeTerrain;
+	Terrain nativeTerrain;
 
 	TurnInfo(const CGHeroInstance * Hero, const int Turn = 0);
 	bool isLayerAvailable(const EPathfindingLayer layer) const;

+ 0 - 8
lib/CPlayerState.h

@@ -67,14 +67,6 @@ public:
 		h & dwellings;
 		h & quests;
 		h & visitedObjects;
-
-		if(version < 760)
-		{
-			//was: h & getBonusList();
-			BonusList junk;
-			h & junk;
-		}
-
 		h & status;
 		h & daysWithoutCastle;
 		h & enteredLosingCheatCode;

+ 4 - 10
lib/CSkillHandler.h

@@ -35,12 +35,9 @@ public:
 		template <typename Handler> void serialize(Handler & h, const int version)
 		{
 			h & description;
-			if(version >= 785)
-			{
-				h & iconSmall;
-				h & iconMedium;
-				h & iconLarge;
-			}
+			h & iconSmall;
+			h & iconMedium;
+			h & iconLarge;
 			h & effects;
 		}
 	};
@@ -78,10 +75,7 @@ public:
 		h & id;
 		h & identifier;
 		h & name;
-		if(version >= 785)
-		{
-			h & gainChance;
-		}
+		h & gainChance;
 		h & levels;
 	}
 

+ 4 - 4
lib/CStack.cpp

@@ -32,7 +32,7 @@ CStack::CStack(const CStackInstance * Base, PlayerColor O, int I, ui8 Side, Slot
 	slot(S),
 	side(Side),
 	initialPosition(),
-	nativeTerrain(ETerrainType::WRONG)
+	nativeTerrain()
 {
 	health.init(); //???
 }
@@ -40,7 +40,7 @@ CStack::CStack(const CStackInstance * Base, PlayerColor O, int I, ui8 Side, Slot
 CStack::CStack()
 	: CBonusSystemNode(STACK_BATTLE),
 	CUnitState(),
-	nativeTerrain(ETerrainType::WRONG)
+	nativeTerrain()
 {
 	base = nullptr;
 	type = nullptr;
@@ -328,11 +328,11 @@ bool CStack::canBeHealed() const
 bool CStack::isOnNativeTerrain() const
 {
 	//this code is called from CreatureTerrainLimiter::limit on battle start
-	auto res = nativeTerrain == ETerrainType::ANY_TERRAIN || nativeTerrain == battle->getTerrainType();
+	auto res = nativeTerrain == Terrain::ANY || nativeTerrain == battle->getTerrainType();
 	return res;
 }
 
-bool CStack::isOnTerrain(int terrain) const
+bool CStack::isOnTerrain(const Terrain & terrain) const
 {
 	return battle->getTerrainType() == terrain;
 }

+ 3 - 2
lib/CStack.h

@@ -14,6 +14,7 @@
 #include "CCreatureHandler.h" //todo: remove
 #include "battle/BattleHex.h"
 #include "mapObjects/CGHeroInstance.h" // for commander serialization
+#include "Terrain.h"
 
 #include "battle/CUnitState.h"
 
@@ -28,7 +29,7 @@ public:
 
 	ui32 ID; //unique ID of stack
 	const CCreature * type;
-	ETerrainType::EETerrainType nativeTerrain; //tmp variable to save native terrain value on battle init
+	Terrain nativeTerrain; //tmp variable to save native terrain value on battle init
 	ui32 baseAmount;
 
 	PlayerColor owner; //owner - player color (255 for neutrals)
@@ -50,7 +51,7 @@ public:
 
 	bool canBeHealed() const; //for first aid tent - only harmed stacks that are not war machines
 	bool isOnNativeTerrain() const;
-	bool isOnTerrain(int terrain) const;
+	bool isOnTerrain(const Terrain & terrain) const;
 
 	ui32 level() const;
 	si32 magicResistance() const override; //include aura of resistance

+ 9 - 133
lib/CTownHandler.cpp

@@ -26,6 +26,10 @@
 
 const int NAMES_PER_TOWN=16; // number of town names per faction in H3 files. Json can define any number
 
+const Terrain CTownHandler::defaultGoodTerrain{"grass"};
+const Terrain CTownHandler::defaultEvilTerrain{"lava"};
+const Terrain CTownHandler::defaultNeutralTerrain{"rough"};
+
 const std::map<std::string, CBuilding::EBuildMode> CBuilding::MODES =
 {
 	{ "normal", CBuilding::BUILD_NORMAL },
@@ -77,135 +81,11 @@ si32 CBuilding::getDistance(BuildingID buildID) const
 	return -1;
 }
 
-void CBuilding::deserializeFix()
-{
-	//default value for mode was broken, have to fix it here for old saves (v777 and older)
-	switch(mode)
-	{
-	case BUILD_NORMAL:
-	case BUILD_AUTO:
-	case BUILD_SPECIAL:
-	case BUILD_GRAIL:
-		break;
-
-	default:
-		mode = BUILD_NORMAL;
-		break;
-	}
-}
-
 void CBuilding::addNewBonus(std::shared_ptr<Bonus> b, BonusList & bonusList)
 {
 	bonusList.push_back(b);
 }
 
-const JsonNode & CBuilding::getCurrentFactionForUpdateRoutine() const
-{
-	const auto & faction = town->faction->identifier;
-	const auto & factionsContent = (*VLC->modh->content)["factions"];
-	const auto & coreData = factionsContent.modData.at("core");
-	const auto & coreFactions = coreData.modData;
-	const auto & currentFaction = coreFactions[faction];
-
-	if(currentFaction.isNull())
-	{
-		const auto index = faction.find(':');
-		const std::string factionDir = index == std::string::npos ? faction : faction.substr(0, index);
-		const auto it = factionsContent.modData.find(factionDir);
-
-		if(it == factionsContent.modData.end())
-		{
-			logMod->warn("Warning: Update old save failed: Faction: '%s' is not found.", factionDir);
-			return currentFaction;
-		}
-		const std::string modFaction = index == std::string::npos ? faction : faction.substr(index + 1);
-		return it->second.modData[modFaction];
-	}
-	return currentFaction;
-}
-
-void CBuilding::update792()
-{
-	subId = BuildingSubID::NONE;
-	height = ETowerHeight::HEIGHT_NO_TOWER;
-
-	if(!bid.IsSpecialOrGrail() || town == nullptr || town->faction == nullptr || town->faction->identifier.empty())
-		return;
-
-	const auto buildingName = CTownHandler::getMappedValue<std::string, BuildingID>(bid, std::string(), MappedKeys::BUILDING_TYPES_TO_NAMES);
-
-	if(buildingName.empty())
-		return;
-
-	auto & currentFaction = getCurrentFactionForUpdateRoutine();
-
-	if(!currentFaction.isNull() && currentFaction.getType() == JsonNode::JsonType::DATA_STRUCT)
-	{
-		const auto & buildings = currentFaction["town"]["buildings"];
-		const auto & currentBuilding = buildings[buildingName];
-
-		subId = CTownHandler::getMappedValue<BuildingSubID::EBuildingSubID>(currentBuilding["type"], BuildingSubID::NONE, MappedKeys::SPECIAL_BUILDINGS);
-		height = subId == BuildingSubID::LOOKOUT_TOWER || bid == BuildingID::GRAIL
-			? CTownHandler::getMappedValue<CBuilding::ETowerHeight>(currentBuilding["height"], CBuilding::HEIGHT_NO_TOWER, CBuilding::TOWER_TYPES)
-			: height = CBuilding::HEIGHT_NO_TOWER;
-	}
-}
-
-void CBuilding::update794()
-{
-	if(bid == BuildingID::TAVERN || subId == BuildingSubID::BROTHERHOOD_OF_SWORD)
-	{
-		VLC->townh->addBonusesForVanilaBuilding(this);
-		return;
-	}
-	if(!bid.IsSpecialOrGrail())
-		return;
-
-	VLC->townh->addBonusesForVanilaBuilding(this);
-
-	if(!buildingBonuses.empty() //addBonusesForVanilaBuilding has done all work
-		|| town->faction == nullptr //or faction data is not valid
-		|| town->faction->identifier.empty())
-		return;
-
-	const auto buildingName = CTownHandler::getMappedValue<std::string, BuildingID>(bid, std::string(), MappedKeys::BUILDING_TYPES_TO_NAMES, false);
-
-	if(buildingName.empty())
-		return;
-
-	auto & currentFaction = getCurrentFactionForUpdateRoutine();
-
-	if(currentFaction.isNull() || currentFaction.getType() != JsonNode::JsonType::DATA_STRUCT)
-		return;
-
-	const auto & buildings = currentFaction["town"]["buildings"];
-	const auto & currentBuilding = buildings[buildingName];
-
-	CTownHandler::loadSpecialBuildingBonuses(currentBuilding["bonuses"], buildingBonuses, this);
-	CTownHandler::loadSpecialBuildingBonuses(currentBuilding["onVisitBonuses"], onVisitBonuses, this);
-
-	if(!onVisitBonuses.empty())
-	{
-		if(subId == BuildingSubID::NONE)
-			subId = BuildingSubID::CUSTOM_VISITING_BONUS;
-
-		for(auto & bonus : onVisitBonuses)
-			bonus->sid = Bonus::getSid32(town->faction->index, bid);
-	}
-	const auto & overriddenBids = currentBuilding["overrides"];
-
-	if(overriddenBids.isNull())
-		return;
-
-	auto scope = town->getBuildingScope();
-
-	for(auto b : overriddenBids.Vector())
-	{
-		auto bid = BuildingID(VLC->modh->identifiers.getIdentifier(scope, b).get());
-		overrideBids.insert(bid);
-	}
-}
-
 CFaction::CFaction()
 {
 	town = nullptr;
@@ -1062,9 +942,9 @@ void CTownHandler::loadPuzzle(CFaction &faction, const JsonNode &source)
 	assert(faction.puzzleMap.size() == GameConstants::PUZZLE_MAP_PIECES);
 }
 
-ETerrainType::EETerrainType CTownHandler::getDefaultTerrainForAlignment(EAlignment::EAlignment alignment) const
+Terrain CTownHandler::getDefaultTerrainForAlignment(EAlignment::EAlignment alignment) const
 {
-	ETerrainType::EETerrainType terrain = defaultGoodTerrain;
+	Terrain terrain = defaultGoodTerrain;
 
 	switch(alignment)
 	{
@@ -1095,19 +975,15 @@ CFaction * CTownHandler::loadFromJson(const std::string & scope, const JsonNode
 		faction->alignment = EAlignment::NEUTRAL;
 	else
 		faction->alignment = static_cast<EAlignment::EAlignment>(alignment);
-
-	auto nativeTerrain = source["nativeTerrain"];
-	int terrainNum = nativeTerrain.isNull()
-		? -1
-		: vstd::find_pos(GameConstants::TERRAIN_NAMES, nativeTerrain.String());
 	
 	auto preferUndergound = source["preferUndergroundPlacement"];
 	faction->preferUndergroundPlacement = preferUndergound.isNull() ? false : preferUndergound.Bool();
 
 	//Contructor is not called here, but operator=
-	faction->nativeTerrain = terrainNum < 0
+	auto nativeTerrain = source["nativeTerrain"];
+	faction->nativeTerrain = nativeTerrain.isNull()
 		? getDefaultTerrainForAlignment(faction->alignment)
-		: static_cast<ETerrainType::EETerrainType>(terrainNum);
+		: Terrain(nativeTerrain.String());
 
 	if (!source["town"].isNull())
 	{

+ 13 - 48
lib/CTownHandler.h

@@ -20,6 +20,7 @@
 #include "LogicalExpression.h"
 #include "battle/BattleHex.h"
 #include "HeroBonus.h"
+#include "Terrain.h"
 
 class CLegacyConfigParser;
 class JsonNode;
@@ -111,8 +112,6 @@ public:
 	}
 
 	void addNewBonus(std::shared_ptr<Bonus> b, BonusList & bonusList);
-	void update792();
-	void update794();
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
@@ -126,33 +125,14 @@ public:
 		h & requirements;
 		h & upgrade;
 		h & mode;
-
-		if(version >= 792)
-		{
-			h & subId;
-			h & height;
-		}
-		if(!h.saving && version < 793)
-			update792(); //adjust height, subId
-
-		if(version >= 794)
-		{
-			h & overrideBids;
-			h & buildingBonuses;
-			h & onVisitBonuses;
-		}
-		else if(!h.saving)
-			update794(); //populate overrideBids, buildingBonuses, onVisitBonuses
-
-		if(!h.saving)
-			deserializeFix();
+		h & subId;
+		h & height;
+		h & overrideBids;
+		h & buildingBonuses;
+		h & onVisitBonuses;
 	}
 
 	friend class CTownHandler;
-
-private:
-	void deserializeFix();
-	const JsonNode & getCurrentFactionForUpdateRoutine() const;
 };
 
 /// This is structure used only by client
@@ -205,7 +185,7 @@ public:
 
 	TFaction index;
 
-	ETerrainType nativeTerrain;
+	Terrain nativeTerrain;
 	EAlignment::EAlignment alignment;
 	bool preferUndergroundPlacement;
 
@@ -356,14 +336,7 @@ public:
 		h & warMachine;
 		h & clientInfo;
 		h & moatDamage;
-		if(version >= 758)
-		{
-			h & moatHexes;
-		}
-		else if(!h.saving)
-		{
-			moatHexes = defaultMoatHexes();
-		}
+		h & moatHexes;
 		h & defaultTavernChance;
 	}
 	
@@ -385,9 +358,9 @@ class DLL_LINKAGE CTownHandler : public CHandlerBase<FactionID, Faction, CFactio
 	std::vector<BuildingRequirementsHelper> requirementsToLoad;
 	std::vector<BuildingRequirementsHelper> overriddenBidsToLoad; //list of buildings, which bonuses should be overridden.
 
-	const static ETerrainType::EETerrainType defaultGoodTerrain = ETerrainType::EETerrainType::GRASS;
-	const static ETerrainType::EETerrainType defaultEvilTerrain = ETerrainType::EETerrainType::LAVA;
-	const static ETerrainType::EETerrainType defaultNeutralTerrain = ETerrainType::EETerrainType::ROUGH;
+	const static Terrain defaultGoodTerrain;
+	const static Terrain defaultEvilTerrain;
+	const static Terrain defaultNeutralTerrain;
 
 	static TPropagatorPtr & emptyPropagator();
 
@@ -418,7 +391,7 @@ class DLL_LINKAGE CTownHandler : public CHandlerBase<FactionID, Faction, CFactio
 
 	void loadPuzzle(CFaction & faction, const JsonNode & source);
 
-	ETerrainType::EETerrainType getDefaultTerrainForAlignment(EAlignment::EAlignment aligment) const;
+	Terrain getDefaultTerrainForAlignment(EAlignment::EAlignment aligment) const;
 	void loadRandomFaction();
 
 
@@ -450,15 +423,7 @@ public:
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & objects;
-
-		if(version >= 770)
-		{
-			h & randomTown;
-		}
-		else if(!h.saving)
-		{
-			loadRandomFaction();
-		}
+		h & randomTown;
 	}
 
 protected:

+ 0 - 32
lib/GameConstants.cpp

@@ -236,38 +236,6 @@ std::ostream & operator<<(std::ostream & os, const EActionType actionType)
 	else return os << it->second;
 }
 
-std::ostream & operator<<(std::ostream & os, const ETerrainType terrainType)
-{
-	static const std::map<ETerrainType::EETerrainType, std::string> terrainTypeToString =
-	{
-	#define DEFINE_ELEMENT(element) {ETerrainType::element, #element}
-		DEFINE_ELEMENT(WRONG),
-		DEFINE_ELEMENT(BORDER),
-		DEFINE_ELEMENT(DIRT),
-		DEFINE_ELEMENT(SAND),
-		DEFINE_ELEMENT(GRASS),
-		DEFINE_ELEMENT(SNOW),
-		DEFINE_ELEMENT(SWAMP),
-		DEFINE_ELEMENT(ROUGH),
-		DEFINE_ELEMENT(SUBTERRANEAN),
-		DEFINE_ELEMENT(LAVA),
-		DEFINE_ELEMENT(WATER),
-		DEFINE_ELEMENT(ROCK)
-	#undef DEFINE_ELEMENT
-	};
-
-	auto it = terrainTypeToString.find(terrainType.num);
-	if (it == terrainTypeToString.end()) return os << "<Unknown type>";
-	else return os << it->second;
-}
-
-std::string ETerrainType::toString() const
-{
-	std::stringstream ss;
-	ss << *this;
-	return ss.str();
-}
-
 std::ostream & operator<<(std::ostream & os, const EPathfindingLayer pathfindingLayer)
 {
 	static const std::map<EPathfindingLayer::EEPathfindingLayer, std::string> pathfinderLayerToString

+ 2 - 46
lib/GameConstants.h

@@ -61,7 +61,6 @@ namespace GameConstants
 
 	const int SKILL_QUANTITY=28;
 	const int PRIMARY_SKILLS=4;
-	const int TERRAIN_TYPES=10;
 	const int RESOURCE_QUANTITY=8;
 	const int HEROES_PER_TYPE=8; //amount of heroes of each type
 
@@ -678,21 +677,8 @@ enum class ETeleportChannelType
 };
 
 
-namespace ERiverType
-{
-	enum ERiverType
-	{
-		NO_RIVER, CLEAR_RIVER, ICY_RIVER, MUDDY_RIVER, LAVA_RIVER
-	};
-}
-
-namespace ERoadType
-{
-	enum ERoadType
-	{
-		NO_ROAD, DIRT_ROAD, GRAVEL_ROAD, COBBLESTONE_ROAD
-	};
-}
+static std::vector<std::string> RIVER_NAMES {"", "rw", "ri", "rm", "rl"};
+static std::vector<std::string> ROAD_NAMES {"", "pd", "pg", "pc"};
 
 class Obj
 {
@@ -912,36 +898,6 @@ enum class EActionType : int32_t
 
 DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EActionType actionType);
 
-class DLL_LINKAGE ETerrainType
-{
-public:
-	enum EETerrainType
-	{
-		ANY_TERRAIN = -3,
-		WRONG = -2, BORDER = -1, DIRT, SAND, GRASS, SNOW, SWAMP,
-		ROUGH, SUBTERRANEAN, LAVA, WATER, ROCK // ROCK is also intended to be max value.
-	};
-
-	ETerrainType(EETerrainType _num = WRONG) : num(_num)
-	{}
-
-	ETerrainType& operator=(EETerrainType _num)
-	{
-		num = _num;
-		return *this;
-	}
-
-	ID_LIKE_CLASS_COMMON(ETerrainType, EETerrainType)
-
-	EETerrainType num;
-
-	std::string toString() const;
-};
-
-DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const ETerrainType terrainType);
-
-ID_LIKE_OPERATORS(ETerrainType, ETerrainType::EETerrainType)
-
 class DLL_LINKAGE EDiggingStatus
 {
 public:

+ 5 - 10
lib/HeroBonus.cpp

@@ -2078,13 +2078,8 @@ bool CPropagatorNodeType::shouldBeAttached(CBonusSystemNode *dest)
 	return nodeType == dest->getNodeType();
 }
 
-CreatureTerrainLimiter::CreatureTerrainLimiter(int TerrainType)
-	: terrainType(TerrainType)
-{
-}
-
 CreatureTerrainLimiter::CreatureTerrainLimiter()
-	: terrainType(-1)
+	: terrainType()
 {
 
 }
@@ -2094,7 +2089,7 @@ int CreatureTerrainLimiter::limit(const BonusLimitationContext &context) const
 	const CStack *stack = retrieveStackBattle(&context.node);
 	if(stack)
 	{
-		if(terrainType == -1)//terrainType not specified = native
+		if(terrainType.isNative())//terrainType not specified = native
 			return !stack->isOnNativeTerrain();
 		return !stack->isOnTerrain(terrainType);
 	}
@@ -2105,7 +2100,7 @@ int CreatureTerrainLimiter::limit(const BonusLimitationContext &context) const
 std::string CreatureTerrainLimiter::toString() const
 {
 	boost::format fmt("CreatureTerrainLimiter(terrainType=%s)");
-	fmt % (terrainType >= 0 ? GameConstants::TERRAIN_NAMES[terrainType] : "native");
+	fmt % (terrainType.isNative() ? "native" : static_cast<std::string>(terrainType));
 	return fmt.str();
 }
 
@@ -2114,8 +2109,8 @@ JsonNode CreatureTerrainLimiter::toJsonNode() const
 	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
 
 	root["type"].String() = "CREATURE_TERRAIN_LIMITER";
-	if(terrainType >= 0)
-		root["parameters"].Vector().push_back(JsonUtils::stringNode(GameConstants::TERRAIN_NAMES[terrainType]));
+	if(!terrainType.isNative())
+		root["parameters"].Vector().push_back(JsonUtils::stringNode(terrainType));
 
 	return root;
 }

+ 8 - 31
lib/HeroBonus.h

@@ -11,6 +11,7 @@
 
 #include "GameConstants.h"
 #include "JsonNode.h"
+#include "Terrain.h"
 
 class CCreature;
 struct Bonus;
@@ -438,36 +439,15 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
 		h & val;
 		h & sid;
 		h & description;
-		if(version >= 783)
-		{
-			h & additionalInfo;
-		}
-		else
-		{
-			additionalInfo.resize(1, -1);
-			h & additionalInfo[0];
-		}
+		h & additionalInfo;
 		h & turnsRemain;
 		h & valType;
-		if(version >= 784)
-		{
-			h & stacking;
-		}
+		h & stacking;
 		h & effectRange;
 		h & limiter;
 		h & propagator;
-		if(version >= 781)
-		{
-			h & updater;
-		}
-		if(version >= 801)
-		{
-			h & propagationUpdater;
-		}
-		if(version < 801 && !h.saving) //Opposite Side bonuses are introduced
-		{
-			updateOppositeBonuses();
-		}
+		h & updater;
+		h & propagationUpdater;
 	}
 
 	template <typename Ptr>
@@ -999,10 +979,7 @@ public:
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 		h & static_cast<ILimiter&>(*this);
-		if(version >= 786)
-		{
-			h & limiters;
-		}
+		h & limiters;
 	}
 };
 
@@ -1081,9 +1058,9 @@ public:
 class DLL_LINKAGE CreatureTerrainLimiter : public ILimiter //applies only to creatures that are on specified terrain, default native terrain
 {
 public:
-	int terrainType;
+	Terrain terrainType;
 	CreatureTerrainLimiter();
-	CreatureTerrainLimiter(int TerrainType);
+	CreatureTerrainLimiter(const Terrain& terrain);
 
 	int limit(const BonusLimitationContext &context) const override;
 	virtual std::string toString() const override;

+ 3 - 3
lib/IGameCallback.cpp

@@ -49,7 +49,7 @@ void CPrivilegedInfoCallback::getFreeTiles(std::vector<int3> & tiles) const
 			for (int yd = 0; yd < gs->map->height; yd++)
 			{
 				tinfo = getTile(int3 (xd,yd,zd));
-				if (tinfo->terType != ETerrainType::WATER && tinfo->terType != ETerrainType::ROCK && !tinfo->blocked) //land and free
+				if (tinfo->terType.isLand() && tinfo->terType.isPassable() && !tinfo->blocked) //land and free
 					tiles.push_back (int3 (xd,yd,zd));
 			}
 		}
@@ -116,8 +116,8 @@ void CPrivilegedInfoCallback::getAllTiles(std::unordered_set<int3, ShashInt3> &
 		{
 			for (int yd = 0; yd < gs->map->height; yd++)
 			{
-				if ((getTile (int3 (xd,yd,zd))->terType == ETerrainType::WATER && water)
-					|| (getTile (int3 (xd,yd,zd))->terType != ETerrainType::WATER && land))
+				if ((getTile (int3 (xd,yd,zd))->terType.isWater() && water)
+					|| (getTile (int3 (xd,yd,zd))->terType.isLand() && land))
 					tiles.insert(int3(xd,yd,zd));
 			}
 		}

+ 1 - 1
lib/JsonDetail.h

@@ -57,7 +57,7 @@ public:
 };
 
 //Internal class for string -> JsonNode conversion
-class JsonParser
+class DLL_LINKAGE JsonParser
 {
 	std::string errors;     // Contains description of all encountered errors
 	constString input;      // Input data

+ 11 - 1
lib/JsonNode.cpp

@@ -59,6 +59,15 @@ JsonNode::JsonNode(const ResourceID & fileURI):
 	*this = parser.parse(fileURI.getName());
 }
 
+JsonNode::JsonNode(const std::string & idx, const ResourceID & fileURI):
+type(JsonType::DATA_NULL)
+{
+	auto file = CResourceHandler::get(idx)->load(fileURI)->readAll();
+	
+	JsonParser parser(reinterpret_cast<char*>(file.first.get()), file.second);
+	*this = parser.parse(fileURI.getName());
+}
+
 JsonNode::JsonNode(ResourceID && fileURI, bool &isValidSyntax):
 	type(JsonType::DATA_NULL)
 {
@@ -711,7 +720,8 @@ std::shared_ptr<ILimiter> JsonUtils::parseLimiter(const JsonNode & limiter)
 				{
 					VLC->modh->identifiers.requestIdentifier("terrain", parameters[0], [=](si32 terrain)
 					{
-						terrainLimiter->terrainType = terrain;
+						//TODO: support limiters
+						//terrainLimiter->terrainType = terrain;
 					});
 				}
 				return terrainLimiter;

+ 3 - 8
lib/JsonNode.h

@@ -60,6 +60,7 @@ public:
 	//Create tree from JSON file
  	explicit JsonNode(ResourceID && fileURI);
  	explicit JsonNode(const ResourceID & fileURI);
+	explicit JsonNode(const std::string& idx, const ResourceID & fileURI);
 	explicit JsonNode(ResourceID && fileURI, bool & isValidSyntax);
 	//Copy c-tor
 	JsonNode(const JsonNode &copy);
@@ -127,10 +128,7 @@ public:
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & meta;
-		if(version >= 782)
-		{
-			h & flags;
-		}
+		h & flags;
 		h & type;
 		switch(type)
 		{
@@ -152,10 +150,7 @@ public:
 			h & data.Struct;
 			break;
 		case JsonType::DATA_INTEGER:
-			if(version >= 770)
-			{
-				h & data.Integer;
-			}
+			h & data.Integer;
 			break;
 		}
 	}

+ 3 - 3
lib/NetPacksLib.cpp

@@ -699,13 +699,13 @@ DLL_LINKAGE void GiveHero::applyGs(CGameState *gs)
 
 DLL_LINKAGE void NewObject::applyGs(CGameState *gs)
 {
-	ETerrainType terrainType;
+	Terrain terrainType;
 
 	if(ID == Obj::BOAT && !gs->isInTheMap(pos)) //special handling for bug #3060 - pos outside map but visitablePos is not
 	{
 		CGObjectInstance testObject = CGObjectInstance();
 		testObject.pos = pos;
-		testObject.appearance = VLC->objtypeh->getHandlerFor(ID, subID)->getTemplates(ETerrainType::WATER).front();
+		testObject.appearance = VLC->objtypeh->getHandlerFor(ID, subID)->getTemplates(Terrain("water")).front();
 
 		const int3 previousXAxisTile = int3(pos.x - 1, pos.y, pos.z);
 		assert(gs->isInTheMap(previousXAxisTile) && (testObject.visitablePos() == previousXAxisTile));
@@ -722,7 +722,7 @@ DLL_LINKAGE void NewObject::applyGs(CGameState *gs)
 	{
 	case Obj::BOAT:
 		o = new CGBoat();
-		terrainType = ETerrainType::WATER; //TODO: either boat should only spawn on water, or all water objects should be handled this way
+		terrainType = Terrain("water"); //TODO: either boat should only spawn on water, or all water objects should be handled this way
 		break;
 	case Obj::MONSTER: //probably more options will be needed
 		o = new CGCreature();

+ 2 - 2
lib/PathfinderUtil.h

@@ -59,13 +59,13 @@ namespace PathfinderUtil
 			break;
 
 		case ELayer::WATER:
-			if(tinfo->blocked || tinfo->terType != ETerrainType::WATER)
+			if(tinfo->blocked || tinfo->terType.isLand())
 				return CGPathNode::BLOCKED;
 
 			break;
 
 		case ELayer::AIR:
-			if(tinfo->blocked || tinfo->terType == ETerrainType::WATER)
+			if(tinfo->blocked || tinfo->terType.isLand())
 				return CGPathNode::FLYABLE;
 
 			break;

+ 1 - 13
lib/StartInfo.h

@@ -56,19 +56,7 @@ struct DLL_LINKAGE PlayerSettings
 		h & color;
 		h & handicap;
 		h & name;
-		if(version < 787)
-		{
-			ui8 oldConnectedId = 0;
-			h & oldConnectedId;
-			if(oldConnectedId)
-			{
-				connectedPlayerIDs.insert(oldConnectedId);
-			}
-		}
-		else
-		{
-			h & connectedPlayerIDs;
-		}
+		h & connectedPlayerIDs;
 		h & team;
 		h & compOnly;
 	}

+ 0 - 4
lib/StringConstants.h

@@ -16,10 +16,6 @@
 ///
 namespace GameConstants
 {
-	const std::string TERRAIN_NAMES [TERRAIN_TYPES] = {
-	    "dirt", "sand", "grass", "snow", "swamp", "rough", "subterra", "lava", "water", "rock"
-	};
-
 	const std::string RESOURCE_NAMES [RESOURCE_QUANTITY] = {
 	    "wood", "mercury", "ore", "sulfur", "crystal", "gems", "gold", "mithril"
 	};

+ 204 - 0
lib/Terrain.cpp

@@ -0,0 +1,204 @@
+/*
+ * Terrain.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 "Terrain.h"
+#include "VCMI_Lib.h"
+#include "CModHandler.h"
+
+//regular expression to change id for string at config
+//("allowedTerrain"\s*:\s*\[.*)9(.*\],\n)
+//\1"rock"\2
+
+const Terrain Terrain::ANY("ANY");
+
+Terrain Terrain::createTerrainTypeH3M(int tId)
+{
+	static std::array<std::string, 10> terrainsH3M
+	{
+		"dirt", "sand", "grass", "snow", "swamp", "rough", "subterra", "lava", "water", "rock"
+	};
+	return Terrain(terrainsH3M.at(tId));
+}
+
+Terrain Terrain::createTerrainByCode(const std::string & typeCode)
+{
+	for(const auto & terrain : Manager::terrains())
+	{
+		if(Manager::getInfo(terrain).typeCode == typeCode)
+			return terrain;
+	}
+	return Terrain::ANY;
+}
+
+Terrain::Manager::Manager()
+{
+	auto allConfigs = VLC->modh->getActiveMods();
+	allConfigs.insert(allConfigs.begin(), "core");
+	for(auto & mod : allConfigs)
+	{
+		if(!CResourceHandler::get(mod)->existsResource(ResourceID("config/terrains.json")))
+			continue;
+		
+		JsonNode terrs(mod, ResourceID("config/terrains.json"));
+		for(auto & terr : terrs.Struct())
+		{
+			Terrain::Info info;
+			info.moveCost = terr.second["moveCost"].Integer();
+			const JsonVector &unblockedVec = terr.second["minimapUnblocked"].Vector();
+			info.minimapUnblocked =
+			{
+				ui8(unblockedVec[0].Float()),
+				ui8(unblockedVec[1].Float()),
+				ui8(unblockedVec[2].Float())
+			};
+			
+			const JsonVector &blockedVec = terr.second["minimapBlocked"].Vector();
+			info.minimapBlocked =
+			{
+				ui8(blockedVec[0].Float()),
+				ui8(blockedVec[1].Float()),
+				ui8(blockedVec[2].Float())
+			};
+			info.musicFilename = terr.second["music"].String();
+			info.tilesFilename = terr.second["tiles"].String();
+			
+			if(terr.second["type"].isNull())
+			{
+				info.type = Terrain::Info::Type::Land;
+			}
+			else
+			{
+				auto s = terr.second["type"].String();
+				if(s == "LAND") info.type = Terrain::Info::Type::Land;
+				if(s == "WATER") info.type = Terrain::Info::Type::Water;
+				if(s == "SUB") info.type = Terrain::Info::Type::Subterranean;
+				if(s == "ROCK") info.type = Terrain::Info::Type::Rock;
+			}
+			
+			if(terr.second["horseSoundId"].isNull())
+			{
+				info.horseSoundId = 9; //rock sound as default
+			}
+			else
+			{
+				info.horseSoundId = terr.second["horseSoundId"].Integer();
+			}
+			
+			if(!terr.second["text"].isNull())
+			{
+				info.terrainText = terr.second["text"].String();
+			}
+			
+			if(terr.second["code"].isNull())
+			{
+				info.typeCode = terr.first.substr(0, 2);
+			}
+			else
+			{
+				info.typeCode = terr.second["code"].String();
+				assert(info.typeCode.length() == 2);
+			}
+			
+			
+			terrainInfo[Terrain(terr.first)] = info;
+		}
+	}
+}
+
+Terrain::Manager & Terrain::Manager::get()
+{
+	static Terrain::Manager manager;
+	return manager;
+}
+
+std::vector<Terrain> Terrain::Manager::terrains()
+{
+	std::vector<Terrain> _terrains;
+	for(const auto & info : Terrain::Manager::get().terrainInfo)
+		_terrains.push_back(info.first);
+	return _terrains;
+}
+
+const Terrain::Info & Terrain::Manager::getInfo(const Terrain & terrain)
+{
+	return Terrain::Manager::get().terrainInfo.at(terrain);
+}
+
+std::ostream & operator<<(std::ostream & os, const Terrain terrainType)
+{
+	return os << static_cast<const std::string &>(terrainType);
+}
+	
+Terrain::operator std::string() const
+{
+	return name;
+}
+
+Terrain::Terrain(const std::string & _name) : name(_name)
+{}
+	
+Terrain& Terrain::operator=(const Terrain & _name)
+{
+	name = _name.name;
+	return *this;
+}
+	
+Terrain& Terrain::operator=(const std::string & _name)
+{
+	name = _name;
+	return *this;
+}
+
+bool operator==(const Terrain & l, const Terrain & r)
+{
+	return l.name == r.name;
+}
+
+bool operator!=(const Terrain & l, const Terrain & r)
+{
+	return l.name != r.name;
+}
+	
+bool operator<(const Terrain & l, const Terrain & r)
+{
+	return l.name < r.name;
+}
+	
+int Terrain::id() const
+{
+	if(name == "ANY") return -3;
+	if(name == "WRONG") return -2;
+	if(name == "BORDER") return -1;
+	
+	auto _terrains = Terrain::Manager::terrains();
+	auto iter = std::find(_terrains.begin(), _terrains.end(), *this);
+	return iter - _terrains.begin();
+}
+	
+bool Terrain::isLand() const
+{
+	return !isWater();
+}
+bool Terrain::isWater() const
+{
+	return Terrain::Manager::getInfo(*this).type == Terrain::Info::Type::Water;
+}
+bool Terrain::isPassable() const
+{
+	return Terrain::Manager::getInfo(*this).type != Terrain::Info::Type::Rock;
+}
+bool Terrain::isUnderground() const
+{
+	return Terrain::Manager::getInfo(*this).type == Terrain::Info::Type::Subterranean;
+}
+bool Terrain::isNative() const
+{
+	return name.empty();
+}

+ 93 - 0
lib/Terrain.h

@@ -0,0 +1,93 @@
+/*
+ * Terrain.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 "ConstTransitivePtr.h"
+#include "JsonNode.h"
+
+class DLL_LINKAGE Terrain
+{
+public:
+	
+	friend class Manager;
+	
+	struct Info
+	{
+		enum class Type
+		{
+			Land, Water, Subterranean, Rock
+		};
+		
+		int moveCost;
+		std::array<int, 3> minimapBlocked;
+		std::array<int, 3> minimapUnblocked;
+		std::string musicFilename;
+		std::string tilesFilename;
+		std::string terrainText;
+		std::string typeCode;
+		int horseSoundId;
+		Type type;
+	};
+	
+	class DLL_LINKAGE Manager
+	{
+	public:
+		static std::vector<Terrain> terrains();
+		static const Info & getInfo(const Terrain &);
+		
+	private:
+		static Manager & get();
+		Manager();
+		
+		std::map<Terrain, Info> terrainInfo;
+	};
+	
+	/*enum EETerrainType
+	 {
+	 ANY_TERRAIN = -3,
+	 WRONG = -2, BORDER = -1, DIRT, SAND, GRASS, SNOW, SWAMP,
+	 ROUGH, SUBTERRANEAN, LAVA, WATER, ROCK // ROCK is also intended to be max value.
+	 };*/
+	
+	Terrain(const std::string & _type = "");
+	static Terrain createTerrainTypeH3M(int tId);
+	static Terrain createTerrainByCode(const std::string & typeCode);
+	
+	int id() const; //TODO: has to be completely removed
+	
+	Terrain& operator=(const Terrain & _type);
+	Terrain& operator=(const std::string & _type);
+	
+	DLL_LINKAGE friend bool operator==(const Terrain & l, const Terrain & r);
+	DLL_LINKAGE friend bool operator!=(const Terrain & l, const Terrain & r);
+	DLL_LINKAGE friend bool operator<(const Terrain & l, const Terrain & r);
+	
+	static const Terrain ANY;
+	
+	bool isLand() const;
+	bool isWater() const;
+	bool isPassable() const; //ROCK
+	bool isUnderground() const;
+	bool isNative() const;
+		
+	operator std::string() const;
+	
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & name;
+	}
+	
+protected:
+	
+	std::string name;
+};
+
+DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const Terrain terrainType);

+ 0 - 12
lib/VCMI_Lib.cpp

@@ -288,15 +288,3 @@ void LibClasses::setContent(std::shared_ptr<CContentHandler> content)
 {
 	modh->content = content;
 }
-
-void LibClasses::restoreAllCreaturesNodeType794()
-{
-	creh->restoreAllCreaturesNodeType794();
-}
-
-void LibClasses::update800()
-{
-	vstd::clear_pointer(scriptHandler);
-	scriptHandler = new scripting::ScriptHandler();
-}
-

+ 4 - 18
lib/VCMI_Lib.h

@@ -44,7 +44,6 @@ class DLL_LINKAGE LibClasses : public Services
 	void makeNull(); //sets all handler pointers to null
 	std::shared_ptr<CContentHandler> getContent() const;
 	void setContent(std::shared_ptr<CContentHandler> content);
-	void restoreAllCreaturesNodeType794();
 
 public:
 	bool IS_AI_ENABLED; //unused?
@@ -91,33 +90,20 @@ public:
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		if(version >= 800)
-		{
-			h & scriptHandler;//must be first (or second after modh), it can modify factories other handlers depends on
-			if(!h.saving)
-			{
-				scriptsLoaded();
-			}
-		}
-		else if(!h.saving)
+		h & scriptHandler;//must be first (or second after modh), it can modify factories other handlers depends on
+		if(!h.saving)
 		{
-			update800();
+			scriptsLoaded();
 		}
 
 		h & heroh;
 		h & arth;
 		h & creh;
-		if(!h.saving && version < 794)
-			restoreAllCreaturesNodeType794();
-
 		h & townh;
 		h & objh;
 		h & objtypeh;
 		h & spellh;
-		if(version >= 777)
-		{
-			h & skillh;
-		}
+		h & skillh;
 		if(!h.saving)
 		{
 			//modh will be changed and modh->content will be empty after deserialization

+ 4 - 3
lib/battle/BattleInfo.cpp

@@ -15,6 +15,7 @@
 #include "../filesystem/Filesystem.h"
 #include "../mapObjects/CGTownInstance.h"
 #include "../CGeneralTextHandler.h"
+#include "../Terrain.h"
 
 //TODO: remove
 #include "../IGameCallback.h"
@@ -186,7 +187,7 @@ struct RangeGenerator
 	std::function<int()> myRand;
 };
 
-BattleInfo * BattleInfo::setupBattle(int3 tile, ETerrainType terrain, BFieldType battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town)
+BattleInfo * BattleInfo::setupBattle(int3 tile, Terrain terrain, BFieldType battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town)
 {
 	CMP_stack cmpst;
 	auto curB = new BattleInfo();
@@ -610,7 +611,7 @@ CStack * BattleInfo::getStack(int stackID, bool onlyAlive)
 
 BattleInfo::BattleInfo()
 	: round(-1), activeStack(-1), town(nullptr), tile(-1,-1,-1),
-	battlefieldType(BFieldType::NONE), terrainType(ETerrainType::WRONG),
+	battlefieldType(BFieldType::NONE), terrainType(),
 	tacticsSide(0), tacticDistance(0)
 {
 	setBattle(this);
@@ -644,7 +645,7 @@ BFieldType BattleInfo::getBattlefieldType() const
 	return battlefieldType;
 }
 
-ETerrainType BattleInfo::getTerrainType() const
+Terrain BattleInfo::getTerrainType() const
 {
 	return terrainType;
 }

+ 4 - 3
lib/battle/BattleInfo.h

@@ -18,6 +18,7 @@
 class CStack;
 class CStackInstance;
 class CStackBasicDescriptor;
+class Terrain;
 
 class DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallback, public IBattleState
 {
@@ -36,7 +37,7 @@ public:
 	SiegeInfo si;
 
 	BFieldType battlefieldType; //like !!BA:B
-	ETerrainType terrainType; //used for some stack nativity checks (not the bonus limiters though that have their own copy)
+	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
 	ui8 tacticDistance; //how many hexes we can go forward (1 = only hexes adjacent to margin line)
@@ -72,7 +73,7 @@ public:
 	battle::Units getUnitsIf(battle::UnitFilter predicate) const override;
 
 	BFieldType getBattlefieldType() const override;
-	ETerrainType getTerrainType() const override;
+	Terrain getTerrainType() const override;
 
 	ObstacleCList getAllObstacles() const override;
 
@@ -137,7 +138,7 @@ public:
 	const CGHeroInstance * getHero(PlayerColor player) const; //returns fighting hero that belongs to given player
 
 	void localInit();
-	static BattleInfo * setupBattle(int3 tile, ETerrainType terrain, BFieldType battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town);
+	static BattleInfo * setupBattle(int3 tile, Terrain terrain, BFieldType battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town);
 
 	ui8 whatSide(PlayerColor player) const;
 

+ 2 - 1
lib/battle/BattleProxy.cpp

@@ -10,6 +10,7 @@
 #include "StdInc.h"
 #include "BattleProxy.h"
 #include "Unit.h"
+#include "Terrain.h"
 
 ///BattleProxy
 
@@ -46,7 +47,7 @@ BFieldType BattleProxy::getBattlefieldType() const
 	return subject->battleGetBattlefieldType();
 }
 
-ETerrainType BattleProxy::getTerrainType() const
+Terrain BattleProxy::getTerrainType() const
 {
 	return subject->battleTerrainType();
 }

+ 1 - 1
lib/battle/BattleProxy.h

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

+ 2 - 2
lib/battle/CBattleInfoEssentials.cpp

@@ -14,9 +14,9 @@
 #include "../NetPacks.h"
 #include "../mapObjects/CGTownInstance.h"
 
-ETerrainType CBattleInfoEssentials::battleTerrainType() const
+Terrain CBattleInfoEssentials::battleTerrainType() const
 {
-	RETURN_IF_NOT_BATTLE(ETerrainType::WRONG);
+	RETURN_IF_NOT_BATTLE(Terrain());
 	return getBattle()->getTerrainType();
 }
 

+ 1 - 1
lib/battle/CBattleInfoEssentials.h

@@ -46,7 +46,7 @@ public:
 	BattlePerspective::BattlePerspective battleGetMySide() const;
 	const IBonusBearer * getBattleNode() const;
 
-	ETerrainType battleTerrainType() const override;
+	Terrain battleTerrainType() const override;
 	BFieldType battleGetBattlefieldType() const override;
 	int32_t battleGetEnchanterCounter(ui8 side) const;
 

+ 2 - 2
lib/battle/IBattleInfoCallback.h

@@ -14,7 +14,7 @@
 
 struct CObstacleInstance;
 class BFieldType;
-class ETerrainType;
+class Terrain;
 
 namespace battle
 {
@@ -34,7 +34,7 @@ class DLL_LINKAGE IBattleInfoCallback
 public:
 	virtual scripting::Pool * getContextPool() const = 0;
 
-	virtual ETerrainType battleTerrainType() const = 0;
+	virtual Terrain battleTerrainType() const = 0;
 	virtual BFieldType battleGetBattlefieldType() const = 0;
 
 	///return none if battle is ongoing; otherwise the victorious side (0/1) or 2 if it is a draw

+ 1 - 1
lib/battle/IBattleState.h

@@ -41,7 +41,7 @@ public:
 	virtual battle::Units getUnitsIf(battle::UnitFilter predicate) const = 0;
 
 	virtual BFieldType getBattlefieldType() const = 0;
-	virtual ETerrainType getTerrainType() const = 0;
+	virtual Terrain getTerrainType() const = 0;
 
 	virtual ObstacleCList getAllObstacles() const = 0;
 

+ 15 - 32
lib/mapObjects/CGHeroInstance.cpp

@@ -79,28 +79,28 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile & dest, const TerrainTile & f
 	int64_t ret = GameConstants::BASE_MOVEMENT_COST;
 
 	//if there is road both on dest and src tiles - use road movement cost
-	if(dest.roadType != ERoadType::NO_ROAD && from.roadType != ERoadType::NO_ROAD)
+	if(dest.roadType != ROAD_NAMES[0] && from.roadType != ROAD_NAMES[0])
 	{
-		int road = std::min(dest.roadType,from.roadType); //used road ID
-		switch(road)
+		int roadPos = std::min(vstd::find_pos(ROAD_NAMES, dest.roadType), vstd::find_pos(ROAD_NAMES, from.roadType)); //used road ID
+		switch(roadPos)
 		{
-		case ERoadType::DIRT_ROAD:
+		case 1:
 			ret = 75;
 			break;
-		case ERoadType::GRAVEL_ROAD:
+		case 2:
 			ret = 65;
 			break;
-		case ERoadType::COBBLESTONE_ROAD:
+		case 3:
 			ret = 50;
 			break;
 		default:
-			logGlobal->error("Unknown road type: %d", road);
+			logGlobal->error("Unknown road type: %d", roadPos);
 			break;
 		}
 	}
 	else if(ti->nativeTerrain != from.terType //the terrain is not native
-		&& ti->nativeTerrain != ETerrainType::ANY_TERRAIN //no special creature bonus
-		&& !ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType) //no special movement bonus
+		&& ti->nativeTerrain != Terrain::ANY //no special creature bonus
+		&& !ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType.id()) //no special movement bonus
 		)
 	{
 		static const CSelector selectorPATHFINDING = Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::PATHFINDING);
@@ -114,7 +114,7 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile & dest, const TerrainTile & f
 	return (ui32)ret;
 }
 
-ETerrainType::EETerrainType CGHeroInstance::getNativeTerrain() const
+Terrain CGHeroInstance::getNativeTerrain() const
 {
 	// NOTE: in H3 neutral stacks will ignore terrain penalty only if placed as topmost stack(s) in hero army.
 	// This is clearly bug in H3 however intended behaviour is not clear.
@@ -122,18 +122,18 @@ ETerrainType::EETerrainType CGHeroInstance::getNativeTerrain() const
 	// will always have best penalty without any influence from player-defined stacks order
 
 	// TODO: What should we do if all hero stacks are neutral creatures?
-	ETerrainType::EETerrainType nativeTerrain = ETerrainType::BORDER;
+	Terrain nativeTerrain("BORDER");
 
 	for(auto stack : stacks)
 	{
-		ETerrainType::EETerrainType stackNativeTerrain = stack.second->type->getNativeTerrain(); //consider terrain bonuses e.g. Lodestar.
+		Terrain stackNativeTerrain = stack.second->type->getNativeTerrain(); //consider terrain bonuses e.g. Lodestar.
 
-		if(stackNativeTerrain == ETerrainType::BORDER)
+		if(stackNativeTerrain == Terrain("BORDER"))
 			continue;
-		if(nativeTerrain == ETerrainType::BORDER)
+		if(nativeTerrain == Terrain("BORDER"))
 			nativeTerrain = stackNativeTerrain;
 		else if(nativeTerrain != stackNativeTerrain)
-			return ETerrainType::BORDER;
+			return Terrain("BORDER");
 	}
 	return nativeTerrain;
 }
@@ -560,23 +560,6 @@ void CGHeroInstance::recreateSecondarySkillsBonuses()
 			updateSkillBonus(SecondarySkill(skill_info.first), skill_info.second);
 }
 
-void CGHeroInstance::recreateSpecialtyBonuses(std::vector<HeroSpecial *> & specialtyDeprecated)
-{
-	auto HeroSpecialToSpecialtyBonus = [](HeroSpecial & hs) -> SSpecialtyBonus
-	{
-		SSpecialtyBonus sb;
-		sb.growsWithLevel = hs.growsWithLevel;
-		sb.bonuses = hs.getBonusList();
-		return sb;
-	};
-
-	for(HeroSpecial * hs : specialtyDeprecated)
-	{
-		for(std::shared_ptr<Bonus> b : SpecialtyBonusToBonuses(HeroSpecialToSpecialtyBonus(*hs), type->ID.getNum()))
-			addNewBonus(b);
-	}
-}
-
 void CGHeroInstance::updateSkillBonus(SecondarySkill which, int val)
 {
 	removeBonuses(Selector::source(Bonus::SECONDARY_SKILL, which));

+ 3 - 22
lib/mapObjects/CGHeroInstance.h

@@ -1,4 +1,4 @@
-/*
+/*
  * CGHeroInstance.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
@@ -89,15 +89,7 @@ public:
 		template <typename Handler> void serialize(Handler &h, const int version)
 		{
 			h & patrolling;
-			if(version >= 755) //save format backward compatibility
-			{
-				h & initialPos;
-			}
-			else if(!h.saving)
-			{
-				patrolling = false;
-				initialPos = int3();
-			}
+			h & initialPos;
 			h & patrolRadius;
 		}
 	} patrol;
@@ -163,7 +155,7 @@ public:
 	bool needsLastStack()const override;
 
 	ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from, const TurnInfo * ti) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling
-	ETerrainType::EETerrainType getNativeTerrain() const;
+	Terrain getNativeTerrain() const;
 	ui32 getLowestCreatureSpeed() const;
 	int3 getPosition(bool h3m = false) const; //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation'
 	si32 manaRegain() const; //how many points of mana can hero regain "naturally" in one day
@@ -287,7 +279,6 @@ protected:
 
 private:
 	void levelUpAutomatically(CRandomGenerator & rand);
-	void recreateSpecialtyBonuses(std::vector<HeroSpecial*> & specialtyDeprecated);
 
 public:
 	std::string getHeroTypeName() const;
@@ -316,18 +307,8 @@ public:
 		h & visitedTown;
 		h & boat;
 		h & type;
-		if(version < 781)
-		{
-			std::vector<HeroSpecial*> specialtyDeprecated;
-			h & specialtyDeprecated;
-			if(!h.saving)
-				recreateSpecialtyBonuses(specialtyDeprecated);
-		}
 		h & commander;
 		h & visitedObjects;
 		BONUS_TREE_DESERIALIZATION_FIX
-		//visitied town pointer will be restored by map serialization method
-		if(version < 777 && !h.saving)
-			recreateSecondarySkillsBonuses();
 	}
 };

+ 0 - 106
lib/mapObjects/CGTownInstance.cpp

@@ -796,28 +796,6 @@ void CGTownInstance::addTownBonuses()
 	}
 }
 
-void CGTownInstance::fixBonusingDuplicates() //For versions 794-800
-{
-	std::map<BuildingID::EBuildingID, int> bids;
-
-	for(auto i = 0; i != bonusingBuildings.size(); i++)
-	{
-		auto bid = bonusingBuildings[i]->getBuildingType();
-		if(!bids.count(bid))
-			bids.insert({ bid, 0 });
-		else
-			bids[bid]++;
-	}
-	for(auto & pair : bids)
-	{
-		if(!pair.second)
-			continue;
-
-		for(auto i = 0; i < pair.second; i++)
-			deleteTownBonus(pair.first);
-	}
-}
-
 void CGTownInstance::deleteTownBonus(BuildingID::EBuildingID bid)
 {
 	size_t i = 0;
@@ -875,90 +853,6 @@ void CGTownInstance::initObj(CRandomGenerator & rand) ///initialize town structu
 	updateAppearance();
 }
 
-void CGTownInstance::updateBonusingBuildings() //update to version 792
-{
-	if(this->town->faction != nullptr)
-	{
-		//firstly, update subtype for the Bonusing objects, which are already stored in the bonusing list
-		for(auto building : bonusingBuildings) //no garrison bonuses here, only week and visiting bonuses
-		{
-			switch(this->town->faction->index)
-			{
-			case ETownType::CASTLE:
-					building->setBuildingSubtype(BuildingSubID::STABLES);
-			break;
-
-			case ETownType::DUNGEON:
-				if(building->getBuildingType() == BuildingID::SPECIAL_2)
-					building->setBuildingSubtype(BuildingSubID::MANA_VORTEX);
-				else if(building->getBuildingType() == BuildingID::SPECIAL_4)
-					building->setBuildingSubtype(BuildingSubID::EXPERIENCE_VISITING_BONUS);
-				break;
-
-			case ETownType::TOWER:
-				building->setBuildingSubtype(BuildingSubID::KNOWLEDGE_VISITING_BONUS);
-				break;
-
-			case ETownType::STRONGHOLD:
-				building->setBuildingSubtype(BuildingSubID::ATTACK_VISITING_BONUS);
-				break;
-
-			case ETownType::INFERNO:
-				building->setBuildingSubtype(BuildingSubID::SPELL_POWER_VISITING_BONUS);
-				break;
-
-			case ETownType::FORTRESS:
-				building->setBuildingSubtype(BuildingSubID::DEFENSE_VISITING_BONUS);
-				break;
-			}
-		}
-	}
-	//secondly, supplement bonusing buildings list and active bonuses; subtypes for these objects are already set in update792
-	for(auto & kvp : town->buildings)
-	{
-		auto & building = kvp.second;
-
-		if(building->subId == BuildingSubID::PORTAL_OF_SUMMONING)
-		{
-			if(!hasBuiltInOldWay(ETownType::DUNGEON, BuildingID::PORTAL_OF_SUMMON))
-				creatures.resize(GameConstants::CREATURES_PER_TOWN + 1);
-			continue;
-		}
-		if(!building->IsVisitingBonus() && !building->IsWeekBonus()) //it's not bonusing => nothing to handle
-			continue;
-
-		if(getBonusingBuilding(building->subId) != nullptr) //it's already added => already handled
-			continue;
-
-		///'hasBuilt' checking for bonuses is in the onHeroVisit handler
-		if(building->IsWeekBonus())
-			tryAddOnePerWeekBonus(building->subId);
-
-		if(building->IsVisitingBonus())
-			tryAddVisitingBonus(building->subId);
-	}
-	recreateBuildingsBonuses(); ///Clear all bonuses and recreate
-}
-
-void CGTownInstance::updateTown794()
-{
-	for(auto builtBuilding : builtBuildings)
-	{
-		auto building = town->buildings.at(builtBuilding);
-
-		for(auto overriddenBid : building->overrideBids)
-			overriddenBuildings.insert(overriddenBid);
-	}
-	for(auto & kvp : town->buildings)
-	{
-		auto & building = kvp.second;
-		//The building acts as a visiting bonus and it has not been overridden.
-		if(building->IsVisitingBonus() && overriddenBuildings.find(kvp.first) == overriddenBuildings.end())
-			tryAddVisitingBonus(building->subId);
-	}
-	recreateBuildingsBonuses();
-}
-
 bool CGTownInstance::hasBuiltInOldWay(ETownType::ETownType type, BuildingID bid) const
 {
 	return (this->town->faction != nullptr && this->town->faction->index == type && hasBuilt(bid));

+ 2 - 16
lib/mapObjects/CGTownInstance.h

@@ -130,9 +130,7 @@ public:
 	{
 		h & bID;
 		h & indexOnTV;
-
-		if(version >= 792)
-			h & bType;
+		h & bType;
 	}
 
 protected:
@@ -267,16 +265,7 @@ public:
 			return false;
 		});
 
-		if(!h.saving && version < 793)
-			updateBonusingBuildings();
-
-		if(version >= 794)
-			h & overriddenBuildings;
-		else if(!h.saving)
-			updateTown794();
-
-		if(!h.saving && (version >= 794 && version < 801))
-			fixBonusingDuplicates();
+		h & overriddenBuildings;
 
 		if(!h.saving)
 			this->setNodeType(CBonusSystemNode::TOWN);
@@ -367,7 +356,6 @@ private:
 	void setOwner(const PlayerColor owner) const;
 	void onTownCaptured(const PlayerColor winner) const;
 	int getDwellingBonus(const std::vector<CreatureID>& creatureIds, const std::vector<ConstTransitivePtr<CGDwelling> >& dwellings) const;
-	void updateBonusingBuildings();
 	bool hasBuiltInOldWay(ETownType::ETownType type, BuildingID bid) const;
 	bool townEnvisagesBuilding(BuildingSubID::EBuildingSubID bid) const;
 	bool isBonusingBuildingAdded(BuildingID::EBuildingID bid) const;
@@ -375,6 +363,4 @@ private:
 	void tryAddVisitingBonus(BuildingSubID::EBuildingSubID subID);
 	void initOverriddenBids();
 	void addTownBonuses();
-	void updateTown794(); //populate overriddenBuildings and vanila bonuses for old saves 
-	void fixBonusingDuplicates(); //For versions 794-800.
 };

+ 5 - 5
lib/mapObjects/CObjectClassesHandler.cpp

@@ -1,4 +1,4 @@
-/*
+/*
  * CObjectClassesHandler.cpp, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
@@ -195,7 +195,7 @@ void CObjectClassesHandler::loadObjectEntry(const std::string & identifier, cons
 	else
 		handler->init(entry);
 
-	if (handler->getTemplates().empty())
+	//if (handler->getTemplates().empty())
 	{
 		auto range = legacyTemplates.equal_range(std::make_pair(obj->id, id));
 		for (auto & templ : boost::make_iterator_range(range.first, range.second))
@@ -569,14 +569,14 @@ std::vector<ObjectTemplate> AObjectTypeHandler::getTemplates() const
 	return templates;
 }
 
-std::vector<ObjectTemplate> AObjectTypeHandler::getTemplates(si32 terrainType) const// FIXME: replace with ETerrainType
+std::vector<ObjectTemplate> AObjectTypeHandler::getTemplates(const Terrain & terrainType) const
 {
 	std::vector<ObjectTemplate> templates = getTemplates();
 	std::vector<ObjectTemplate> filtered;
 
 	std::copy_if(templates.begin(), templates.end(), std::back_inserter(filtered), [&](const ObjectTemplate & obj)
 	{
-		return obj.canBePlacedAt(ETerrainType(terrainType));
+		return obj.canBePlacedAt(terrainType);
 	});
 	// H3 defines allowed terrains in a weird way - artifacts, monsters and resources have faulty masks here
 	// Perhaps we should re-define faulty templates and remove this workaround (already done for resources)
@@ -586,7 +586,7 @@ std::vector<ObjectTemplate> AObjectTypeHandler::getTemplates(si32 terrainType) c
 		return filtered;
 }
 
-boost::optional<ObjectTemplate> AObjectTypeHandler::getOverride(si32 terrainType, const CGObjectInstance * object) const
+boost::optional<ObjectTemplate> AObjectTypeHandler::getOverride(const Terrain & terrainType, const CGObjectInstance * object) const
 {
 	std::vector<ObjectTemplate> ret = getTemplates(terrainType);
 	for (auto & tmpl : ret)

+ 10 - 28
lib/mapObjects/CObjectClassesHandler.h

@@ -178,11 +178,11 @@ public:
 
 	/// returns all templates matching parameters
 	std::vector<ObjectTemplate> getTemplates() const;
-	std::vector<ObjectTemplate> getTemplates(si32 terrainType) const;
+	std::vector<ObjectTemplate> getTemplates(const Terrain & terrainType) const;
 
 	/// returns preferred template for this object, if present (e.g. one of 3 possible templates for town - village, fort and castle)
 	/// note that appearance will not be changed - this must be done separately (either by assignment or via pack from server)
-	boost::optional<ObjectTemplate> getOverride(si32 terrainType, const CGObjectInstance * object) const;
+	boost::optional<ObjectTemplate> getOverride(const Terrain & terrainType, const CGObjectInstance * object) const;
 
 	const RandomMapInfo & getRMGInfo();
 
@@ -210,19 +210,10 @@ public:
 		h & templates;
 		h & rmgInfo;
 		h & objectName;
-		if(version >= 759)
-		{
-			h & typeName;
-			h & subTypeName;
-		}
-		if(version >= 778)
-		{
-			h & sounds;
-		}
-		if(version >= 789)
-		{
-			h & aiValue;
-		}
+		h & typeName;
+		h & subTypeName;
+		h & sounds;
+		h & aiValue;
 	}
 };
 
@@ -253,19 +244,10 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase
 			h & handlerName;
 			h & base;
 			h & subObjects;
-			if(version >= 759)
-			{
-				h & identifier;
-				h & subIds;
-			}
-			if(version >= 778)
-			{
-				h & sounds;
-			}
-			if(version >= 789)
-			{
-				h & groupDefaultAiValue;
-			}
+			h & identifier;
+			h & subIds;
+			h & sounds;
+			h & groupDefaultAiValue;
 		}
 	};
 

+ 1 - 1
lib/mapObjects/CObjectHandler.cpp

@@ -419,7 +419,7 @@ int3 IBoatGenerator::bestLocation() const
 	{
 		if(const TerrainTile *tile = IObjectInterface::cb->getTile(o->pos + offset, false)) //tile is in the map
 		{
-			if(tile->terType == ETerrainType::WATER  &&  (!tile->blocked || tile->blockingObjects.front()->ID == Obj::BOAT)) //and is water and is not blocked or is blocked by boat
+			if(tile->terType.isWater()  &&  (!tile->blocked || tile->blockingObjects.front()->ID == Obj::BOAT)) //and is water and is not blocked or is blocked by boat
 				return o->pos + offset;
 		}
 	}

+ 3 - 7
lib/mapObjects/CObjectHandler.h

@@ -213,13 +213,9 @@ public:
 	///Entry point of binary (de-)serialization
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		if(version >= 759)
-		{
-			h & instanceName;
-			h & typeName;
-			h & subTypeName;
-		}
-
+		h & instanceName;
+		h & typeName;
+		h & subTypeName;
 		h & pos;
 		h & ID;
 		h & subID;

+ 1 - 8
lib/mapObjects/CQuest.h

@@ -87,14 +87,7 @@ public:
 		h & isCustomFirst;
 		h & isCustomNext;
 		h & isCustomComplete;
-		if(version >= 757)
-		{
-			h & completedOption;
-		}
-		else if(!h.saving)
-		{
-			completedOption = 1;
-		}
+		h & completedOption;
 	}
 
 	void serializeJson(JsonSerializeFormat & handler, const std::string & fieldName);

+ 0 - 5
lib/mapObjects/CRewardableObject.h

@@ -269,11 +269,6 @@ public:
 		h & onVisited;
 		h & onEmpty;
 		h & visitMode;
-		if(version < 778)
-		{
-			ui16 soundID = 0;
-			h & soundID;
-		}
 		h & selectMode;
 		h & selectedReward;
 	}

+ 33 - 11
lib/mapObjects/ObjectTemplate.cpp

@@ -19,6 +19,7 @@
 #include "CObjectHandler.h"
 #include "../CModHandler.h"
 #include "../JsonNode.h"
+#include "../Terrain.h"
 
 #include "CRewardableConstructor.h"
 
@@ -143,7 +144,17 @@ void ObjectTemplate::readTxt(CLegacyConfigParser & parser)
 	for (size_t i=0; i<9; i++)
 	{
 		if (terrStr[8-i] == '1')
-			allowedTerrains.insert(ETerrainType((si32)i));
+			allowedTerrains.insert(Terrain::createTerrainTypeH3M(i));
+	}
+	
+	//assuming that object can be placed on other land terrains
+	if(allowedTerrains.size() >= 8 && !allowedTerrains.count(Terrain("water")))
+	{
+		for(auto & terrain : Terrain::Manager::terrains())
+		{
+			if(terrain.isLand() && terrain.isPassable())
+				allowedTerrains.insert(terrain);
+		}
 	}
 
 	id    = Obj(boost::lexical_cast<int>(strings[5]));
@@ -205,7 +216,17 @@ void ObjectTemplate::readMap(CBinaryReader & reader)
 	for (size_t i=0; i<9; i++)
 	{
 		if (((terrMask >> i) & 1 ) != 0)
-			allowedTerrains.insert(ETerrainType((si32)i));
+			allowedTerrains.insert(Terrain::createTerrainTypeH3M(i));
+	}
+	
+	//assuming that object can be placed on other land terrains
+	if(allowedTerrains.size() >= 8 && !allowedTerrains.count(Terrain("water")))
+	{
+		for(auto & terrain : Terrain::Manager::terrains())
+		{
+			if(terrain.isLand() && terrain.isPassable())
+				allowedTerrains.insert(terrain);
+		}
 	}
 
 	id = Obj(reader.readUInt32());
@@ -247,15 +268,16 @@ void ObjectTemplate::readJson(const JsonNode &node, const bool withTerrain)
 	if(withTerrain && !node["allowedTerrains"].isNull())
 	{
 		for (auto & entry : node["allowedTerrains"].Vector())
-			allowedTerrains.insert(ETerrainType(vstd::find_pos(GameConstants::TERRAIN_NAMES, entry.String())));
+			allowedTerrains.insert(entry.String());
 	}
 	else
 	{
-		for (size_t i=0; i< GameConstants::TERRAIN_TYPES; i++)
-			allowedTerrains.insert(ETerrainType((si32)i));
-
-		allowedTerrains.erase(ETerrainType::ROCK);
-		allowedTerrains.erase(ETerrainType::WATER);
+		for(auto & i : Terrain::Manager::terrains())
+		{
+			if(!i.isPassable() || i.isWater())
+				continue;
+			allowedTerrains.insert(i);
+		}
 	}
 
 	if(withTerrain && allowedTerrains.empty())
@@ -329,14 +351,14 @@ void ObjectTemplate::writeJson(JsonNode & node, const bool withTerrain) const
 	if(withTerrain)
 	{
 		//assumed that ROCK and WATER terrains are not included
-		if(allowedTerrains.size() < (GameConstants::TERRAIN_TYPES - 2))
+		if(allowedTerrains.size() < (Terrain::Manager::terrains().size() - 2))
 		{
 			JsonVector & data = node["allowedTerrains"].Vector();
 
 			for(auto type : allowedTerrains)
 			{
 				JsonNode value(JsonNode::JsonType::DATA_STRING);
-				value.String() = GameConstants::TERRAIN_NAMES[type.num];
+				value.String() = type;
 				data.push_back(value);
 			}
 		}
@@ -511,7 +533,7 @@ bool ObjectTemplate::isVisitableFromTop() const
 	//return isVisitableFrom (0, 1);
 }
 
-bool ObjectTemplate::canBePlacedAt(ETerrainType terrain) const
+bool ObjectTemplate::canBePlacedAt(Terrain terrain) const
 {
 	return allowedTerrains.count(terrain) != 0;
 }

+ 4 - 6
lib/mapObjects/ObjectTemplate.h

@@ -15,6 +15,7 @@ class CBinaryReader;
 class CLegacyConfigParser;
 class JsonNode;
 class int3;
+class Terrain;
 
 class DLL_LINKAGE ObjectTemplate
 {
@@ -30,7 +31,7 @@ class DLL_LINKAGE ObjectTemplate
 	/// directions from which object can be entered, format same as for moveDir in CGHeroInstance(but 0 - 7)
 	ui8 visitDir;
 	/// list of terrains on which this object can be placed
-	std::set<ETerrainType> allowedTerrains;
+	std::set<Terrain> allowedTerrains;
 
 	void afterLoadFixup();
 
@@ -70,7 +71,7 @@ public:
 	bool isVisitableFromTop() const;
 
 	// Checks if object can be placed on specific terrain
-	bool canBePlacedAt(ETerrainType terrain) const;
+	bool canBePlacedAt(Terrain terrain) const;
 
 	ObjectTemplate();
 	//custom copy constructor is required
@@ -96,10 +97,7 @@ public:
 		h & subid;
 		h & printPriority;
 		h & visitDir;
-		if(version >= 770)
-		{
-			h & editorAnimationFile;
-		}
+		h & editorAnimationFile;
 	}
 };
 

+ 0 - 11
lib/mapping/CCampaignHandler.cpp

@@ -422,17 +422,6 @@ std::vector<CGHeroInstance *> CCampaignScenario::getLostCrossoverHeroes()
 	return lostCrossoverHeroes;
 }
 
-std::vector<JsonNode> CCampaignScenario::update787(std::vector<CGHeroInstance *> & heroes)
-{
-	static_assert(MINIMAL_SERIALIZATION_VERSION < 787, "No longer needed CCampaignScenario::update787");
-	std::vector<JsonNode> heroesNew;
-	for(auto hero : heroes)
-	{
-		heroesNew.push_back(CCampaignState::crossoverSerialize(hero));
-	}
-	return heroesNew;
-}
-
 void CCampaignState::setCurrentMapAsConquered(const std::vector<CGHeroInstance *> & heroes)
 {
 	camp->scenarios[*currentMap].crossoverHeroes.clear();

+ 3 - 15
lib/mapping/CCampaignHandler.h

@@ -146,8 +146,7 @@ public:
 	// FIXME: due to usage of JsonNode I can't make these methods const
 	const CGHeroInstance * strongestHero(PlayerColor owner);
 	std::vector<CGHeroInstance *> getLostCrossoverHeroes(); /// returns a list of crossover heroes which started the scenario, but didn't complete it
-	std::vector<JsonNode> update787(std::vector<CGHeroInstance *> & heroes);
-
+	
 	CCampaignScenario();
 
 	template <typename Handler> void serialize(Handler &h, const int formatVersion)
@@ -163,19 +162,8 @@ public:
 		h & prolog;
 		h & epilog;
 		h & travelOptions;
-		if(formatVersion < 787)
-		{
-			std::vector<CGHeroInstance *> crossoverHeroesOld, placedCrossoverHeroesOld;
-			h & crossoverHeroesOld;
-			h & placedCrossoverHeroesOld;
-			crossoverHeroes = update787(crossoverHeroesOld);
-			placedCrossoverHeroes = update787(placedCrossoverHeroesOld);
-		}
-		else
-		{
-			h & crossoverHeroes;
-			h & placedCrossoverHeroes;
-		}
+		h & crossoverHeroes;
+		h & placedCrossoverHeroes;
 		h & keepHeroes;
 	}
 };

+ 3 - 3
lib/mapping/CDrawRoadsOperation.cpp

@@ -148,7 +148,7 @@ static bool ruleIsAny(const std::string & rule)
 #endif
 
 ///CDrawRoadsOperation
-CDrawRoadsOperation::CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, ERoadType::ERoadType roadType, CRandomGenerator * gen):
+CDrawRoadsOperation::CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, const std::string & roadType, CRandomGenerator * gen):
 	CMapOperation(map),terrainSel(terrainSel), roadType(roadType), gen(gen)
 {
 
@@ -225,7 +225,7 @@ void CDrawRoadsOperation::flipPattern(RoadPattern& pattern, int flip) const
 
 bool CDrawRoadsOperation::needUpdateTile(const TerrainTile & tile) const
 {
-	return tile.roadType != ERoadType::NO_ROAD; //TODO: this method should be virtual for river support
+	return tile.roadType != ROAD_NAMES[0]; //TODO: this method should be virtual for river support
 }
 
 void CDrawRoadsOperation::updateTiles(std::set<int3> & invalidated)
@@ -263,7 +263,7 @@ bool CDrawRoadsOperation::tileHasSomething(const int3& pos) const
 {
 //TODO: this method should be virtual for river support
 
-   return map->getTile(pos).roadType != ERoadType::NO_ROAD;
+   return map->getTile(pos).roadType != ROAD_NAMES[0];
 }
 
 

+ 2 - 2
lib/mapping/CDrawRoadsOperation.h

@@ -18,7 +18,7 @@ struct TerrainTile;
 class CDrawRoadsOperation : public CMapOperation
 {
 public:
-	CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, ERoadType::ERoadType roadType, CRandomGenerator * gen);
+	CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, const std::string & roadType, CRandomGenerator * gen);
 	void execute() override;
 	void undo() override;
 	void redo() override;
@@ -53,6 +53,6 @@ private:
 	bool tileHasSomething(const int3 & pos) const;
 	
 	CTerrainSelection terrainSel;
-	ERoadType::ERoadType roadType;
+	std::string roadType;
 	CRandomGenerator * gen;	
 };

+ 7 - 7
lib/mapping/CMap.cpp

@@ -123,8 +123,8 @@ CCastleEvent::CCastleEvent() : town(nullptr)
 
 }
 
-TerrainTile::TerrainTile() : terType(ETerrainType::BORDER), terView(0), riverType(ERiverType::NO_RIVER),
-	riverDir(0), roadType(ERoadType::NO_ROAD), roadDir(0), extTileFlags(0), visitable(false),
+TerrainTile::TerrainTile() : terType("BORDER"), terView(0), riverType(RIVER_NAMES[0]),
+	riverDir(0), roadType(ROAD_NAMES[0]), roadDir(0), extTileFlags(0), visitable(false),
 	blocked(false)
 {
 
@@ -132,13 +132,13 @@ TerrainTile::TerrainTile() : terType(ETerrainType::BORDER), terView(0), riverTyp
 
 bool TerrainTile::entrableTerrain(const TerrainTile * from) const
 {
-	return entrableTerrain(from ? from->terType != ETerrainType::WATER : true, from ? from->terType == ETerrainType::WATER : true);
+	return entrableTerrain(from ? from->terType.isLand() : true, from ? from->terType.isWater() : true);
 }
 
 bool TerrainTile::entrableTerrain(bool allowLand, bool allowSea) const
 {
-	return terType != ETerrainType::ROCK
-			&& ((allowSea && terType == ETerrainType::WATER)  ||  (allowLand && terType != ETerrainType::WATER));
+	return terType.isPassable()
+			&& ((allowSea && terType.isWater())  ||  (allowLand && terType.isLand()));
 }
 
 bool TerrainTile::isClear(const TerrainTile * from) const
@@ -164,7 +164,7 @@ CGObjectInstance * TerrainTile::topVisitableObj(bool excludeTop) const
 
 EDiggingStatus TerrainTile::getDiggingStatus(const bool excludeTop) const
 {
-	if(terType == ETerrainType::WATER || terType == ETerrainType::ROCK)
+	if(terType.isWater() || !terType.isPassable())
 		return EDiggingStatus::WRONG_TERRAIN;
 
 	int allowedBlocked = excludeTop ? 1 : 0;
@@ -181,7 +181,7 @@ bool TerrainTile::hasFavorableWinds() const
 
 bool TerrainTile::isWater() const
 {
-	return terType == ETerrainType::WATER;
+	return terType.isWater();
 }
 
 void CMapHeader::setupEvents()

+ 5 - 19
lib/mapping/CMap.h

@@ -110,11 +110,7 @@ struct DLL_LINKAGE PlayerInfo
 		h & posOfMainTown;
 		h & team;
 		h & generateHero;
-
-		if(version >= 770)
-		{
-			h & mainHeroInstance;
-		}
+		h & mainHeroInstance;
 	}
 };
 
@@ -164,16 +160,9 @@ struct DLL_LINKAGE EventCondition
 		h & objectType;
 		h & position;
 		h & condition;
-		//(!!!) should be `version >= 759` here, but do not try to "fix" it
-		if(version > 759)
-		{
-			h & objectSubtype;
-			h & objectInstanceName;
-		}
-		if(version >= 770)
-		{
-			h & metaType;
-		}
+		h & objectSubtype;
+		h & objectInstanceName;
+		h & metaType;
 	}
 };
 
@@ -495,9 +484,6 @@ public:
 		h & CGTownInstance::merchantArtifacts;
 		h & CGTownInstance::universitySkills;
 
-		if(formatVersion >= 759)
-		{
-			h & instanceNames;
-		}
+		h & instanceNames;
 	}
 };

+ 3 - 3
lib/mapping/CMapDefines.h

@@ -80,11 +80,11 @@ struct DLL_LINKAGE TerrainTile
 	EDiggingStatus getDiggingStatus(const bool excludeTop = true) const;
 	bool hasFavorableWinds() const;
 
-	ETerrainType terType;
+	Terrain terType;
 	ui8 terView;
-	ERiverType::ERiverType riverType;
+	std::string riverType;
 	ui8 riverDir;
-	ERoadType::ERoadType roadType;
+	std::string roadType;
 	ui8 roadDir;
 	/// first two bits - how to rotate terrain graphic (next two - river graphic, next two - road);
 	///	7th bit - whether tile is coastal (allows disembarking if land or block movement if water); 8th bit - Favorable Winds effect

+ 18 - 28
lib/mapping/CMapEditManager.cpp

@@ -243,13 +243,13 @@ void CMapEditManager::clearTerrain(CRandomGenerator * gen)
 	execute(make_unique<CClearTerrainOperation>(map, gen ? gen : &(this->gen)));
 }
 
-void CMapEditManager::drawTerrain(ETerrainType terType, CRandomGenerator * gen)
+void CMapEditManager::drawTerrain(Terrain terType, CRandomGenerator * gen)
 {
 	execute(make_unique<CDrawTerrainOperation>(map, terrainSel, terType, gen ? gen : &(this->gen)));
 	terrainSel.clearSelection();
 }
 
-void CMapEditManager::drawRoad(ERoadType::ERoadType roadType, CRandomGenerator* gen)
+void CMapEditManager::drawRoad(const std::string & roadType, CRandomGenerator* gen)
 {
 	execute(make_unique<CDrawRoadsOperation>(map, terrainSel, roadType, gen ? gen : &(this->gen)));
 	terrainSel.clearSelection();
@@ -534,7 +534,7 @@ void CTerrainViewPatternConfig::flipPattern(TerrainViewPattern & pattern, int fl
 }
 
 
-CDrawTerrainOperation::CDrawTerrainOperation(CMap * map, const CTerrainSelection & terrainSel, ETerrainType terType, CRandomGenerator * gen)
+CDrawTerrainOperation::CDrawTerrainOperation(CMap * map, const CTerrainSelection & terrainSel, Terrain terType, CRandomGenerator * gen)
 	: CMapOperation(map), terrainSel(terrainSel), terType(terType), gen(gen)
 {
 
@@ -760,21 +760,17 @@ void CDrawTerrainOperation::updateTerrainViews()
 	}
 }
 
-ETerrainGroup::ETerrainGroup CDrawTerrainOperation::getTerrainGroup(ETerrainType terType) const
+ETerrainGroup::ETerrainGroup CDrawTerrainOperation::getTerrainGroup(Terrain terType) const
 {
-	switch(terType)
-	{
-	case ETerrainType::DIRT:
+	if(terType == Terrain("dirt"))
 		return ETerrainGroup::DIRT;
-	case ETerrainType::SAND:
+	if(terType == Terrain("sand"))
 		return ETerrainGroup::SAND;
-	case ETerrainType::WATER:
+	if(terType.isWater())
 		return ETerrainGroup::WATER;
-	case ETerrainType::ROCK:
+	if(!terType.isPassable())
 		return ETerrainGroup::ROCK;
-	default:
-		return ETerrainGroup::NORMAL;
-	}
+	return ETerrainGroup::NORMAL;
 }
 
 CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainView(const int3 & pos, const std::vector<TerrainViewPattern> * pattern, int recDepth) const
@@ -811,7 +807,7 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi
 		int cy = pos.y + (i / 3) - 1;
 		int3 currentPos(cx, cy, pos.z);
 		bool isAlien = false;
-		ETerrainType terType;
+		Terrain terType;
 		if(!map->isInTheMap(currentPos))
 		{
 			// position is not in the map, so take the ter type from the neighbor tile
@@ -949,17 +945,11 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi
 	}
 }
 
-bool CDrawTerrainOperation::isSandType(ETerrainType terType) const
+bool CDrawTerrainOperation::isSandType(Terrain terType) const
 {
-	switch(terType)
-	{
-	case ETerrainType::WATER:
-	case ETerrainType::SAND:
-	case ETerrainType::ROCK:
+	if(terType.isWater() || terType == Terrain("sand") || !terType.isPassable())
 		return true;
-	default:
-		return false;
-	}
+	return false;
 }
 
 void CDrawTerrainOperation::invalidateTerrainViews(const int3 & centerPos)
@@ -986,7 +976,7 @@ CDrawTerrainOperation::InvalidTiles CDrawTerrainOperation::getInvalidTiles(const
 			auto valid = validateTerrainView(pos, ptrConfig->getTerrainTypePatternById("n1")).result;
 
 			// Special validity check for rock & water
-			if(valid && (terType == ETerrainType::WATER || terType == ETerrainType::ROCK))
+			if(valid && (terType.isWater() || !terType.isPassable()))
 			{
 				static const std::string patternIds[] = { "s1", "s2" };
 				for(auto & patternId : patternIds)
@@ -996,7 +986,7 @@ CDrawTerrainOperation::InvalidTiles CDrawTerrainOperation::getInvalidTiles(const
 				}
 			}
 			// Additional validity check for non rock OR water
-			else if(!valid && (terType != ETerrainType::WATER && terType != ETerrainType::ROCK))
+			else if(!valid && (terType.isLand() && terType.isPassable()))
 			{
 				static const std::string patternIds[] = { "n2", "n3" };
 				for(auto & patternId : patternIds)
@@ -1040,7 +1030,7 @@ void CTerrainViewPatternUtils::printDebuggingInfoAboutTile(const CMap * map, int
 			{
 				auto debugTile = map->getTile(debugPos);
 
-				std::string terType = debugTile.terType.toString().substr(0, 6);
+				std::string terType = static_cast<std::string>(debugTile.terType).substr(0, 6);
 				line += terType;
 				line.insert(line.end(), PADDED_LENGTH - terType.size(), ' ');
 			}
@@ -1059,12 +1049,12 @@ CClearTerrainOperation::CClearTerrainOperation(CMap * map, CRandomGenerator * ge
 {
 	CTerrainSelection terrainSel(map);
 	terrainSel.selectRange(MapRect(int3(0, 0, 0), map->width, map->height));
-	addOperation(make_unique<CDrawTerrainOperation>(map, terrainSel, ETerrainType::WATER, gen));
+	addOperation(make_unique<CDrawTerrainOperation>(map, terrainSel, Terrain("water"), gen));
 	if(map->twoLevel)
 	{
 		terrainSel.clearSelection();
 		terrainSel.selectRange(MapRect(int3(0, 0, 1), map->width, map->height));
-		addOperation(make_unique<CDrawTerrainOperation>(map, terrainSel, ETerrainType::ROCK, gen));
+		addOperation(make_unique<CDrawTerrainOperation>(map, terrainSel, Terrain("rock"), gen));
 	}
 }
 

+ 7 - 6
lib/mapping/CMapEditManager.h

@@ -13,6 +13,7 @@
 #include "../CRandomGenerator.h"
 #include "../int3.h"
 #include "../GameConstants.h"
+#include "Terrain.h"
 
 class CGObjectInstance;
 class CTerrainViewPatternConfig;
@@ -168,10 +169,10 @@ public:
 	void clearTerrain(CRandomGenerator * gen = nullptr);
 
 	/// Draws terrain at the current terrain selection. The selection will be cleared automatically.
-	void drawTerrain(ETerrainType terType, CRandomGenerator * gen = nullptr);
+	void drawTerrain(Terrain terType, CRandomGenerator * gen = nullptr);
 
 	/// Draws roads at the current terrain selection. The selection will be cleared automatically.
-	void drawRoad(ERoadType::ERoadType roadType, CRandomGenerator * gen = nullptr);
+	void drawRoad(const std::string & roadType, CRandomGenerator * gen = nullptr);
 
 	void insertObject(CGObjectInstance * obj);
 
@@ -353,7 +354,7 @@ private:
 class CDrawTerrainOperation : public CMapOperation
 {
 public:
-	CDrawTerrainOperation(CMap * map, const CTerrainSelection & terrainSel, ETerrainType terType, CRandomGenerator * gen);
+	CDrawTerrainOperation(CMap * map, const CTerrainSelection & terrainSel, Terrain terType, CRandomGenerator * gen);
 
 	void execute() override;
 	void undo() override;
@@ -384,16 +385,16 @@ private:
 	InvalidTiles getInvalidTiles(const int3 & centerPos) const;
 
 	void updateTerrainViews();
-	ETerrainGroup::ETerrainGroup getTerrainGroup(ETerrainType terType) const;
+	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(ETerrainType terType) const;
+	bool isSandType(Terrain terType) const;
 
 	CTerrainSelection terrainSel;
-	ETerrainType terType;
+	Terrain terType;
 	CRandomGenerator * gen;
 	std::set<int3> invalidatedTerViews;
 };

+ 4 - 4
lib/mapping/MapFormatH3M.cpp

@@ -935,14 +935,14 @@ void CMapLoaderH3M::readTerrain()
 			for(int z = 0; z < map->height; z++)
 			{
 				auto & tile = map->getTile(int3(z, c, a));
-				tile.terType = ETerrainType(reader.readUInt8());
+				tile.terType = Terrain::createTerrainTypeH3M(reader.readUInt8());
 				tile.terView = reader.readUInt8();
-				tile.riverType = static_cast<ERiverType::ERiverType>(reader.readUInt8());
+				tile.riverType = RIVER_NAMES[reader.readUInt8()];
 				tile.riverDir = reader.readUInt8();
-				tile.roadType = static_cast<ERoadType::ERoadType>(reader.readUInt8());
+				tile.roadType = ROAD_NAMES[reader.readUInt8()];
 				tile.roadDir = reader.readUInt8();
 				tile.extTileFlags = reader.readUInt8();
-				tile.blocked = ((tile.terType == ETerrainType::ROCK || tile.terType == ETerrainType::BORDER ) ? true : false); //underground tiles are always blocked
+				tile.blocked = ((!tile.terType.isPassable() || tile.terType == Terrain("BORDER") ) ? true : false); //underground tiles are always blocked
 				tile.visitable = 0;
 			}
 		}

+ 12 - 35
lib/mapping/MapFormatJson.cpp

@@ -323,20 +323,6 @@ namespace TriggeredEventsDetail
 
 namespace TerrainDetail
 {
-	static const std::array<std::string, 10> terrainCodes =
-	{
-		"dt", "sa", "gr", "sn", "sw", "rg", "sb", "lv", "wt", "rc"
-	};
-	static const std::array<std::string, 4> roadCodes =
-	{
-		"", "pd", "pg", "pc"
-	};
-
-	static const std::array<std::string, 5> riverCodes =
-	{
-		"", "rw", "ri", "rm", "rl"
-	};
-
 	static const std::array<char, 4> flipCodes =
 	{
 		'_', '-', '|', '+'
@@ -959,13 +945,7 @@ void CMapLoaderJson::readTerrainTile(const std::string & src, TerrainTile & tile
 	using namespace TerrainDetail;
 	{//terrain type
 		const std::string typeCode = src.substr(0, 2);
-
-		int rawType = vstd::find_pos(terrainCodes, typeCode);
-
-		if(rawType < 0)
-			throw std::runtime_error("Invalid terrain type code in "+src);
-
-		tile.terType = ETerrainType(rawType);
+		tile.terType = Terrain::createTerrainByCode(typeCode);
 	}
 	int startPos = 2; //0+typeCode fixed length
 	{//terrain view
@@ -992,20 +972,18 @@ void CMapLoaderJson::readTerrainTile(const std::string & src, TerrainTile & tile
 	{//road type
 		const std::string typeCode = src.substr(startPos, 2);
 		startPos+=2;
-		int rawType = vstd::find_pos(roadCodes, typeCode);
-		if(rawType < 0)
+		if(vstd::find_pos(ROAD_NAMES, typeCode) < 0)
 		{
-			rawType = vstd::find_pos(riverCodes, typeCode);
-			if(rawType < 0)
+			if(vstd::find_pos(RIVER_NAMES, typeCode) < 0)
 				throw std::runtime_error("Invalid river type in "+src);
 			else
 			{
-				tile.riverType = ERiverType::ERiverType(rawType);
+				tile.riverType = typeCode;
 				hasRoad = false;
 			}
 		}
 		else
-			tile.roadType = ERoadType::ERoadType(rawType);
+			tile.roadType = typeCode;
 	}
 	if(hasRoad)
 	{//road dir
@@ -1033,10 +1011,9 @@ void CMapLoaderJson::readTerrainTile(const std::string & src, TerrainTile & tile
 	{//river type
 		const std::string typeCode = src.substr(startPos, 2);
 		startPos+=2;
-		int rawType = vstd::find_pos(riverCodes, typeCode);
-		if(rawType < 0)
+		if(vstd::find_pos(RIVER_NAMES, typeCode) < 0)
 			throw std::runtime_error("Invalid river type in "+src);
-		tile.riverType = ERiverType::ERiverType(rawType);
+		tile.riverType = typeCode;
 	}
 	{//river dir
 		int pos = startPos;
@@ -1298,13 +1275,13 @@ std::string CMapSaverJson::writeTerrainTile(const TerrainTile & tile)
 	out.setf(std::ios::dec, std::ios::basefield);
 	out.unsetf(std::ios::showbase);
 
-	out << terrainCodes.at(int(tile.terType)) << (int)tile.terView << flipCodes[tile.extTileFlags % 4];
+	out << Terrain::Manager::getInfo(tile.terType).typeCode << (int)tile.terView << flipCodes[tile.extTileFlags % 4];
 
-	if(tile.roadType != ERoadType::NO_ROAD)
-		out << roadCodes.at(int(tile.roadType)) << (int)tile.roadDir << flipCodes[(tile.extTileFlags >> 4) % 4];
+	if(tile.roadType != ROAD_NAMES[0])
+		out << tile.roadType << (int)tile.roadDir << flipCodes[(tile.extTileFlags >> 4) % 4];
 
-	if(tile.riverType != ERiverType::NO_RIVER)
-		out << riverCodes.at(int(tile.riverType)) << (int)tile.riverDir << flipCodes[(tile.extTileFlags >> 2) % 4];
+	if(tile.riverType != RIVER_NAMES[0])
+		out << tile.riverType << (int)tile.riverDir << flipCodes[(tile.extTileFlags >> 2) % 4];
 
 	return out.str();
 }

+ 21 - 30
lib/rmg/CMapGenerator.cpp

@@ -69,17 +69,6 @@ CMapGenerator::CMapGenerator(CMapGenOptions& mapGenOptions, int RandomSeed) :
 
 void CMapGenerator::loadConfig()
 {
-	static std::map<std::string, ETerrainType> terrainMap
-	{
-		{"dirt", ETerrainType::DIRT},
-		{"sand", ETerrainType::SAND},
-		{"grass", ETerrainType::GRASS},
-		{"snow", ETerrainType::SNOW},
-		{"swamp", ETerrainType::SWAMP},
-		{"subterranean", ETerrainType::SUBTERRANEAN},
-		{"lava", ETerrainType::LAVA},
-		{"rough", ETerrainType::ROUGH}
-	};
 	static const std::map<std::string, Res::ERes> resMap
 	{
 		{"wood", Res::ERes::WOOD},
@@ -90,23 +79,17 @@ void CMapGenerator::loadConfig()
 		{"sulfur", Res::ERes::SULFUR},
 		{"gold", Res::ERes::GOLD},
 	};
-	static std::map<std::string, ERoadType::ERoadType> roadTypeMap
-	{
-		{"dirt_road", ERoadType::DIRT_ROAD},
-		{"gravel_road", ERoadType::GRAVEL_ROAD},
-		{"cobblestone_road", ERoadType::COBBLESTONE_ROAD}
-	};
 	static const ResourceID path("config/randomMap.json");
 	JsonNode randomMapJson(path);
 	for(auto& s : randomMapJson["terrain"]["undergroundAllow"].Vector())
 	{
 		if(!s.isNull())
-			config.terrainUndergroundAllowed.push_back(terrainMap[s.String()]);
+			config.terrainUndergroundAllowed.emplace_back(s.String());
 	}
 	for(auto& s : randomMapJson["terrain"]["groundProhibit"].Vector())
 	{
 		if(!s.isNull())
-			config.terrainGroundProhibit.push_back(terrainMap[s.String()]);
+			config.terrainGroundProhibit.emplace_back(s.String());
 	}
 	config.shipyardGuard = randomMapJson["waterZone"]["shipyard"]["value"].Integer();
 	for(auto & treasure : randomMapJson["waterZone"]["treasure"].Vector())
@@ -119,7 +102,7 @@ void CMapGenerator::loadConfig()
 	}
 	config.mineExtraResources = randomMapJson["mines"]["extraResourcesLimit"].Integer();
 	config.minGuardStrength = randomMapJson["minGuardStrength"].Integer();
-	config.defaultRoadType = roadTypeMap[randomMapJson["defaultRoadType"].String()];
+	config.defaultRoadType = randomMapJson["defaultRoadType"].String();
 	config.treasureValueLimit = randomMapJson["treasureValueLimit"].Integer();
 	for(auto & i : randomMapJson["prisons"]["experience"].Vector())
 		config.prisonExperience.push_back(i.Integer());
@@ -357,7 +340,7 @@ void CMapGenerator::genZones()
 {
 	getEditManager()->clearTerrain(&rand);
 	getEditManager()->getTerrainSelection().selectRange(MapRect(int3(0, 0, 0), mapGenOptions.getWidth(), mapGenOptions.getHeight()));
-	getEditManager()->drawTerrain(ETerrainType::GRASS, &rand);
+	getEditManager()->drawTerrain(Terrain("grass"), &rand);
 
 	auto tmpl = mapGenOptions.getMapTemplate();
 	zones.clear();
@@ -530,14 +513,14 @@ void CMapGenerator::fillZones()
 
 void CMapGenerator::createObstaclesCommon1()
 {
-	if (map->twoLevel) //underground
+	if(map->twoLevel) //underground
 	{
 		//negative approach - create rock tiles first, then make sure all accessible tiles have no rock
 		std::vector<int3> rockTiles;
 
-		for (int x = 0; x < map->width; x++)
+		for(int x = 0; x < map->width; x++)
 		{
-			for (int y = 0; y < map->height; y++)
+			for(int y = 0; y < map->height; y++)
 			{
 				int3 tile(x, y, 1);
 				if (shouldBeBlocked(tile))
@@ -547,21 +530,29 @@ void CMapGenerator::createObstaclesCommon1()
 			}
 		}
 		getEditManager()->getTerrainSelection().setSelection(rockTiles);
-		getEditManager()->drawTerrain(ETerrainType::ROCK, &rand);
+		
+		//collect all rock terrain types
+		std::vector<Terrain> rockTerrains;
+		for(auto & terrain : Terrain::Manager::terrains())
+			if(!terrain.isPassable())
+				rockTerrains.push_back(terrain);
+		auto rockTerrain = *RandomGeneratorUtil::nextItem(rockTerrains, rand);
+		
+		getEditManager()->drawTerrain(rockTerrain, &rand);
 	}
 }
 
 void CMapGenerator::createObstaclesCommon2()
 {
-	if (map->twoLevel)
+	if(map->twoLevel)
 	{
 		//finally mark rock tiles as occupied, spawn no obstacles there
-		for (int x = 0; x < map->width; x++)
+		for(int x = 0; x < map->width; x++)
 		{
-			for (int y = 0; y < map->height; y++)
+			for(int y = 0; y < map->height; y++)
 			{
 				int3 tile(x, y, 1);
-				if (map->getTile(tile).terType == ETerrainType::ROCK)
+				if(!map->getTile(tile).terType.isPassable())
 				{
 					setOccupied(tile, ETileType::USED);
 				}
@@ -904,7 +895,7 @@ void CMapGenerator::setOccupied(const int3 &tile, ETileType::ETileType state)
 	tiles[tile.x][tile.y][tile.z].setOccupied(state);
 }
 
-void CMapGenerator::setRoad(const int3& tile, ERoadType::ERoadType roadType)
+void CMapGenerator::setRoad(const int3& tile, const std::string & roadType)
 {
 	checkIsOnMap(tile);
 

+ 4 - 6
lib/rmg/CMapGenerator.h

@@ -26,8 +26,6 @@ class JsonNode;
 class CMapGenerator;
 class CTileInfo;
 
-//#define _BETA
-
 typedef std::vector<JsonNode> JsonVector;
 
 class rmgException : public std::exception
@@ -54,14 +52,14 @@ class DLL_LINKAGE CMapGenerator
 public:
 	struct Config
 	{
-		std::vector<ETerrainType> terrainUndergroundAllowed;
-		std::vector<ETerrainType> terrainGroundProhibit;
+		std::vector<Terrain> terrainUndergroundAllowed;
+		std::vector<Terrain> terrainGroundProhibit;
 		std::vector<CTreasureInfo> waterTreasure;
 		int shipyardGuard;
 		int mineExtraResources;
 		std::map<Res::ERes, int> mineValues;
 		int minGuardStrength;
-		ERoadType::ERoadType defaultRoadType;
+		std::string defaultRoadType;
 		int treasureValueLimit;
 		std::vector<int> prisonExperience, prisonValues;
 		std::vector<int> scrollValues;
@@ -101,7 +99,7 @@ public:
 	bool isRoad(const int3 &tile) const;
 
 	void setOccupied(const int3 &tile, ETileType::ETileType state);
-	void setRoad(const int3 &tile, ERoadType::ERoadType roadType);
+	void setRoad(const int3 &tile, const std::string & roadType);
 
 	CTileInfo getTile(const int3 & tile) const;
 	bool isAllowedSpell(SpellID sid) const;

+ 35 - 21
lib/rmg/CRmgTemplate.cpp

@@ -16,6 +16,7 @@
 #include "../mapping/CMap.h"
 #include "../VCMI_Lib.h"
 #include "../CTownHandler.h"
+#include "../Terrain.h"
 #include "../serializer/JsonSerializeFormat.h"
 #include "../StringConstants.h"
 
@@ -66,12 +67,12 @@ class TerrainEncoder
 public:
 	static si32 decode(const std::string & identifier)
 	{
-		return vstd::find_pos(GameConstants::TERRAIN_NAMES, identifier);
+		return vstd::find_pos(Terrain::Manager::terrains(), identifier);
 	}
 
 	static std::string encode(const si32 index)
 	{
-		return (index >=0 && index < GameConstants::TERRAIN_TYPES) ? GameConstants::TERRAIN_NAMES[index] : "<INVALID TERRAIN>";
+		return (index >=0 && index < Terrain::Manager::terrains().size()) ? static_cast<std::string>(Terrain::Manager::terrains()[index]) : "<INVALID TERRAIN>";
 	}
 };
 
@@ -89,18 +90,6 @@ public:
 	}
 };
 
-const std::set<ETerrainType> ZoneOptions::DEFAULT_TERRAIN_TYPES =
-{
-	ETerrainType::DIRT,
-	ETerrainType::SAND,
-	ETerrainType::GRASS,
-	ETerrainType::SNOW,
-	ETerrainType::SWAMP,
-	ETerrainType::ROUGH,
-	ETerrainType::SUBTERRANEAN,
-	ETerrainType::LAVA
-};
-
 const TRmgTemplateZoneId ZoneOptions::NO_ZONE = -1;
 
 ZoneOptions::CTownInfo::CTownInfo()
@@ -149,7 +138,6 @@ ZoneOptions::ZoneOptions()
 	playerTowns(),
 	neutralTowns(),
 	matchTerrainToTown(true),
-	terrainTypes(DEFAULT_TERRAIN_TYPES),
 	townsAreSameType(false),
 	townTypes(),
 	monsterTypes(),
@@ -161,7 +149,9 @@ ZoneOptions::ZoneOptions()
 	terrainTypeLikeZone(NO_ZONE),
 	treasureLikeZone(NO_ZONE)
 {
-
+	for(auto & terr : Terrain::Manager::terrains())
+		if(terr.isLand() && terr.isPassable())
+			terrainTypes.insert(terr);
 }
 
 ZoneOptions & ZoneOptions::operator=(const ZoneOptions & other)
@@ -224,15 +214,15 @@ boost::optional<int> ZoneOptions::getOwner() const
 	return owner;
 }
 
-const std::set<ETerrainType> & ZoneOptions::getTerrainTypes() const
+const std::set<Terrain> & ZoneOptions::getTerrainTypes() const
 {
 	return terrainTypes;
 }
 
-void ZoneOptions::setTerrainTypes(const std::set<ETerrainType> & value)
+void ZoneOptions::setTerrainTypes(const std::set<Terrain> & value)
 {
-	assert(value.find(ETerrainType::WRONG) == value.end() && value.find(ETerrainType::BORDER) == value.end() &&
-		   value.find(ETerrainType::WATER) == value.end() && value.find(ETerrainType::ROCK) == value.end());
+	//assert(value.find(ETerrainType::WRONG) == value.end() && value.find(ETerrainType::BORDER) == value.end() &&
+	//	   value.find(ETerrainType::WATER) == value.end() && value.find(ETerrainType::ROCK) == value.end());
 	terrainTypes = value;
 }
 
@@ -339,7 +329,31 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler)
 	#undef SERIALIZE_ZONE_LINK
 
 	if(terrainTypeLikeZone == NO_ZONE)
-		handler.serializeIdArray<ETerrainType, TerrainEncoder>("terrainTypes", terrainTypes, DEFAULT_TERRAIN_TYPES);
+	{
+		JsonNode node;
+		if(handler.saving)
+		{
+			node.setType(JsonNode::JsonType::DATA_VECTOR);
+			for(auto & ttype : terrainTypes)
+			{
+				JsonNode n;
+				n.String() = ttype;
+				node.Vector().push_back(n);
+			}
+		}
+		handler.serializeRaw("terrainTypes", node, boost::none);
+		if(!handler.saving)
+		{
+			if(!node.Vector().empty())
+			{
+				terrainTypes.clear();
+				for(auto ttype : node.Vector())
+				{
+					terrainTypes.emplace(ttype.String());
+				}
+			}
+		}
+	}
 
 	handler.serializeBool("townsAreSameType", townsAreSameType, false);
 	handler.serializeIdArray<TFaction, FactionID>("allowedMonsters", monsterTypes, VLC->townh->getAllowedFactions(false));

+ 5 - 4
lib/rmg/CRmgTemplate.h

@@ -13,9 +13,11 @@
 #include "../int3.h"
 #include "../GameConstants.h"
 #include "../ResourceSet.h"
+#include "../Terrain.h"
 #include "CMapGenOptions.h"
 
 class JsonSerializeFormat;
+class Terrain;
 
 namespace ETemplateZoneType
 {
@@ -65,7 +67,6 @@ private:
 class DLL_LINKAGE ZoneOptions
 {
 public:
-	static const std::set<ETerrainType> DEFAULT_TERRAIN_TYPES;
 	static const TRmgTemplateZoneId NO_ZONE;
 
 	class DLL_LINKAGE CTownInfo
@@ -101,8 +102,8 @@ public:
 	void setSize(int value);
 	boost::optional<int> getOwner() const;
 
-	const std::set<ETerrainType> & getTerrainTypes() const;
-	void setTerrainTypes(const std::set<ETerrainType> & value);
+	const std::set<Terrain> & getTerrainTypes() const;
+	void setTerrainTypes(const std::set<Terrain> & value);
 
 	std::set<TFaction> getDefaultTownTypes() const;
 	const std::set<TFaction> & getTownTypes() const;
@@ -134,7 +135,7 @@ protected:
 	CTownInfo playerTowns;
 	CTownInfo neutralTowns;
 	bool matchTerrainToTown;
-	std::set<ETerrainType> terrainTypes;
+	std::set<Terrain> terrainTypes;
 	bool townsAreSameType;
 
 	std::set<TFaction> townTypes;

+ 57 - 20
lib/rmg/CRmgTemplateZone.cpp

@@ -36,7 +36,7 @@ void CRmgTemplateZone::addRoadNode(const int3& node)
 	roadNodes.insert(node);
 }
 
-CTileInfo::CTileInfo():nearestObjectDistance(float(INT_MAX)), terrain(ETerrainType::WRONG),roadType(ERoadType::NO_ROAD)
+CTileInfo::CTileInfo():nearestObjectDistance(float(INT_MAX)), terrain()
 {
 	occupied = ETileType::POSSIBLE; //all tiles are initially possible to place objects or passages
 }
@@ -69,7 +69,7 @@ bool CTileInfo::isFree() const
 
 bool CTileInfo::isRoad() const
 {
-	return roadType != ERoadType::NO_ROAD;
+	return roadType != ROAD_NAMES[0];
 }
 
 bool CTileInfo::isUsed() const
@@ -86,17 +86,17 @@ ETileType::ETileType CTileInfo::getTileType() const
 	return occupied;
 }
 
-ETerrainType CTileInfo::getTerrainType() const
+Terrain CTileInfo::getTerrainType() const
 {
 	return terrain;
 }
 
-void CTileInfo::setTerrainType(ETerrainType value)
+void CTileInfo::setTerrainType(Terrain value)
 {
 	terrain = value;
 }
 
-void CTileInfo::setRoadType(ERoadType::ERoadType value)
+void CTileInfo::setRoadType(const std::string & value)
 {
 	roadType = value;
 //	setOccupied(ETileType::FREE);
@@ -106,7 +106,7 @@ void CTileInfo::setRoadType(ERoadType::ERoadType value)
 CRmgTemplateZone::CRmgTemplateZone(CMapGenerator * Gen)
 	: ZoneOptions(),
 	townType(ETownType::NEUTRAL),
-	terrainType (ETerrainType::GRASS),
+	terrainType (Terrain("grass")),
 	minGuardedValue(0),
 	questArtZone(),
 	gen(Gen)
@@ -989,7 +989,7 @@ bool CRmgTemplateZone::createRoad(const int3& src, const int3& dst)
 	std::map<int3, int3> cameFrom;  // The map of navigated nodes.
 	std::map<int3, float> distances;
 
-	gen->setRoad (src, ERoadType::NO_ROAD); //just in case zone guard already has road under it. Road under nodes will be added at very end
+	gen->setRoad (src, ROAD_NAMES[0]); //just in case zone guard already has road under it. Road under nodes will be added at very end
 
 	cameFrom[src] = int3(-1, -1, -1); //first node points to finish condition
 	pq.push(std::make_pair(src, 0.f));
@@ -1012,8 +1012,8 @@ bool CRmgTemplateZone::createRoad(const int3& src, const int3& dst)
 			while (cameFrom[backTracking].valid())
 			{
 				// add node to path
-				roads.insert (backTracking);
-				gen->setRoad (backTracking, ERoadType::COBBLESTONE_ROAD);
+				roads.insert(backTracking);
+				gen->setRoad(backTracking, gen->getConfig().defaultRoadType);
 				//logGlobal->trace("Setting road at tile %s", backTracking);
 				// do the same for the predecessor
 				backTracking = cameFrom[backTracking];
@@ -1250,6 +1250,17 @@ void CRmgTemplateZone::addToConnectLater(const int3& src)
 	tilesToConnectLater.insert(src);
 }
 
+int CRmgTemplateZone::chooseRandomAppearance(si32 ObjID) const
+{
+	auto factories = VLC->objtypeh->knownSubObjects(ObjID);
+	vstd::erase_if(factories, [this, ObjID](si32 f)
+	{
+		return VLC->objtypeh->getHandlerFor(ObjID, f)->getTemplates(terrainType).empty();
+	});
+	
+	return *RandomGeneratorUtil::nextItem(factories, gen->rand);
+}
+
 bool CRmgTemplateZone::addMonster(int3 &pos, si32 strength, bool clearSurroundingTiles, bool zoneGuard)
 {
 	//precalculate actual (randomized) monster strength based on this post
@@ -1731,33 +1742,51 @@ void CRmgTemplateZone::initTerrainType ()
 {
 	if (type==ETemplateZoneType::WATER)
 	{
-		terrainType = ETerrainType::WATER;
+		//collect all water terrain types
+		std::vector<Terrain> waterTerrains;
+		for(auto & terrain : Terrain::Manager::terrains())
+			if(terrain.isWater())
+				waterTerrains.push_back(terrain);
+		
+		terrainType = *RandomGeneratorUtil::nextItem(waterTerrains, gen->rand);
 	}
 	else
 	{
 		if (matchTerrainToTown && townType != ETownType::NEUTRAL)
+		{
 			terrainType = (*VLC->townh)[townType]->nativeTerrain;
+		}
 		else
+		{
 			terrainType = *RandomGeneratorUtil::nextItem(terrainTypes, gen->rand);
+		}
 
 		//TODO: allow new types of terrain?
 		{
 			if(isUnderground())
 			{
 				if(!vstd::contains(gen->getConfig().terrainUndergroundAllowed, terrainType))
-					terrainType = ETerrainType::SUBTERRANEAN;
+				{
+					//collect all underground terrain types
+					std::vector<Terrain> undegroundTerrains;
+					for(auto & terrain : Terrain::Manager::terrains())
+						if(terrain.isUnderground())
+							undegroundTerrains.push_back(terrain);
+					
+					terrainType = *RandomGeneratorUtil::nextItem(undegroundTerrains, gen->rand);
+				}
 			}
 			else
 			{
-				if(vstd::contains(gen->getConfig().terrainGroundProhibit, terrainType))
-					terrainType = ETerrainType::DIRT;
+				if(vstd::contains(gen->getConfig().terrainGroundProhibit, terrainType) || terrainType.isUnderground())
+					terrainType = Terrain("dirt");
 			}
 		}
 	}
 	paintZoneTerrain (terrainType);
 }
 
-void CRmgTemplateZone::paintZoneTerrain (ETerrainType terrainType)
+void CRmgTemplateZone::paintZoneTerrain (Terrain terrainType)
 {
 	std::vector<int3> tiles(tileinfo.begin(), tileinfo.end());
 	gen->getEditManager()->getTerrainSelection().setSelection(tiles);
@@ -1837,6 +1866,9 @@ bool CRmgTemplateZone::createRequiredObjects()
 	for(const auto &object : requiredObjects)
 	{
 		auto obj = object.first;
+		if (!obj->appearance.canBePlacedAt(terrainType))
+			continue;
+		
 		int3 pos;
 		while (true)
 		{
@@ -1845,6 +1877,7 @@ bool CRmgTemplateZone::createRequiredObjects()
 				logGlobal->error("Failed to fill zone %d due to lack of space", id);
 				return false;
 			}
+
 			if (tryToPlaceObjectAndConnectToPath(obj, pos) == EObjectPlacingResult::SUCCESS)
 			{
 				//paths to required objects constitute main paths of zone. otherwise they just may lead to middle and create dead zones
@@ -1858,6 +1891,9 @@ bool CRmgTemplateZone::createRequiredObjects()
 	for (const auto &obj : closeObjects)
 	{
 		setTemplateForObject(obj.first);
+		if (!obj.first->appearance.canBePlacedAt(terrainType))
+			continue;
+		
 		auto tilesBlockedByObject = obj.first->getBlockedOffsets();
 
 		bool finished = false;
@@ -2069,8 +2105,8 @@ int3 CRmgTemplateZone::createShipyard(const std::set<int3> & lake, si32 guardStr
 
 bool CRmgTemplateZone::createShipyard(const int3 & position, si32 guardStrength)
 {
-	auto subObjects = VLC->objtypeh->knownSubObjects(Obj::SHIPYARD);
-	auto shipyard = (CGShipyard*) VLC->objtypeh->getHandlerFor(Obj::SHIPYARD, *RandomGeneratorUtil::nextItem(subObjects, gen->rand))->create(ObjectTemplate());
+	int subtype = chooseRandomAppearance(Obj::SHIPYARD);
+	auto shipyard = (CGShipyard*) VLC->objtypeh->getHandlerFor(Obj::SHIPYARD, subtype)->create(ObjectTemplate());
 	shipyard->tempOwner = PlayerColor::NEUTRAL;
 	
 	setTemplateForObject(shipyard);
@@ -2569,7 +2605,8 @@ void CRmgTemplateZone::checkAndPlaceObject(CGObjectInstance* object, const int3
 	if (object->appearance.id == Obj::NO_OBJ)
 	{
 		auto terrainType = gen->map->getTile(pos).terType;
-		auto templates = VLC->objtypeh->getHandlerFor(object->ID, object->subID)->getTemplates(terrainType);
+		auto h = VLC->objtypeh->getHandlerFor(object->ID, object->subID);
+		auto templates = h->getTemplates(terrainType);
 		if (templates.empty())
 			throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s (terrain %d)") % object->ID % object->subID % pos.toString() % terrainType));
 
@@ -3239,7 +3276,7 @@ void CRmgTemplateZone::addAllPossibleObjects()
 			if (!creaturesAmount)
 				continue;
 
-			int randomAppearance = *RandomGeneratorUtil::nextItem(VLC->objtypeh->knownSubObjects(Obj::SEER_HUT), gen->rand);
+			int randomAppearance = chooseRandomAppearance(Obj::SEER_HUT);
 
 			oi.generateObject = [creature, creaturesAmount, randomAppearance, this, generateArtInfo]() -> CGObjectInstance *
 			{
@@ -3270,7 +3307,7 @@ void CRmgTemplateZone::addAllPossibleObjects()
 		static int seerLevels = std::min(gen->getConfig().questValues.size(), gen->getConfig().questRewardValues.size());
 		for(int i = 0; i < seerLevels; i++) //seems that code for exp and gold reward is similiar
 		{
-			int randomAppearance = *RandomGeneratorUtil::nextItem(VLC->objtypeh->knownSubObjects(Obj::SEER_HUT), gen->rand);
+			int randomAppearance = chooseRandomAppearance(Obj::SEER_HUT);
 
 			oi.setTemplate(Obj::SEER_HUT, randomAppearance, terrainType);
 			oi.value = gen->getConfig().questValues[i];
@@ -3332,7 +3369,7 @@ ObjectInfo::ObjectInfo()
 
 }
 
-void ObjectInfo::setTemplate (si32 type, si32 subtype, ETerrainType terrainType)
+void ObjectInfo::setTemplate (si32 type, si32 subtype, Terrain terrainType)
 {
 	auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype);
 	if(!templHandler)

+ 10 - 8
lib/rmg/CRmgTemplateZone.h

@@ -17,6 +17,7 @@
 #include "CRmgTemplate.h"
 #include "../mapObjects/ObjectTemplate.h"
 #include <boost/heap/priority_queue.hpp> //A*
+#include "Terrain.h"
 
 class CMapGenerator;
 class CTileInfo;
@@ -48,16 +49,16 @@ public:
 	bool isUsed() const;
 	bool isRoad() const;
 	void setOccupied(ETileType::ETileType value);
-	ETerrainType getTerrainType() const;
+	Terrain getTerrainType() const;
 	ETileType::ETileType getTileType() const;
-	void setTerrainType(ETerrainType value);
+	void setTerrainType(Terrain value);
 
-	void setRoadType(ERoadType::ERoadType value);
+	void setRoadType(const std::string & value);
 private:
 	float nearestObjectDistance;
 	ETileType::ETileType occupied;
-	ETerrainType terrain;
-	ERoadType::ERoadType roadType;
+	Terrain terrain;
+	std::string roadType;
 };
 
 struct DLL_LINKAGE ObjectInfo
@@ -69,7 +70,7 @@ struct DLL_LINKAGE ObjectInfo
 	//ui32 maxPerMap; //unused
 	std::function<CGObjectInstance *()> generateObject;
 
-	void setTemplate (si32 type, si32 subtype, ETerrainType terrain);
+	void setTemplate (si32 type, si32 subtype, Terrain terrain);
 
 	ObjectInfo();
 
@@ -120,7 +121,7 @@ public:
 	bool fill ();
 	bool placeMines ();
 	void initTownType ();
-	void paintZoneTerrain (ETerrainType terrainType);
+	void paintZoneTerrain (Terrain terrainType);
 	void randomizeTownType(bool matchUndergroundType = false); //helper function
 	void initTerrainType ();
 	void createBorder();
@@ -192,7 +193,7 @@ private:
 	
 	//template info
 	si32 townType;
-	ETerrainType terrainType;
+	Terrain terrainType;
 	std::weak_ptr<CRmgTemplateZone> questArtZone; //artifacts required for Seer Huts will be placed here - or not if null
 
 	std::vector<ObjectInfo> possibleObjects;
@@ -230,6 +231,7 @@ private:
 	bool canObstacleBePlacedHere(ObjectTemplate &temp, int3 &pos);
 	void setTemplateForObject(CGObjectInstance* obj);
 	void checkAndPlaceObject(CGObjectInstance* object, const int3 &pos);
+	int chooseRandomAppearance(si32 ObjID) const;
 	
 	bool isGuardNeededForTreasure(int value);
 };

+ 18 - 20
lib/rmg/CZonePlacer.cpp

@@ -184,28 +184,26 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const
 					zonesToPlace.push_back(zone);
 				else
 				{
-					switch ((*VLC->townh)[faction]->nativeTerrain)
+					auto & tt = (*VLC->townh)[faction]->nativeTerrain;
+					if(tt == Terrain("dirt"))
 					{
-					case ETerrainType::GRASS:
-					case ETerrainType::SWAMP:
-					case ETerrainType::SNOW:
-					case ETerrainType::SAND:
-					case ETerrainType::ROUGH:
-						//surface
-						zonesOnLevel[0]++;
-						levels[zone.first] = 0;
-						break;
-					case ETerrainType::LAVA:
-					case ETerrainType::SUBTERRANEAN:
-						//underground
-						zonesOnLevel[1]++;
-						levels[zone.first] = 1;
-						break;
-					case ETerrainType::DIRT:
-					default:
 						//any / random
 						zonesToPlace.push_back(zone);
-						break;
+					}
+					else
+					{
+						if(tt.isUnderground())
+						{
+							//underground
+							zonesOnLevel[1]++;
+							levels[zone.first] = 1;
+						}
+						else
+						{
+							//surface
+							zonesOnLevel[0]++;
+							levels[zone.first] = 0;
+						}
 					}
 				}
 			}
@@ -564,7 +562,7 @@ void CZonePlacer::assignZones()
 
 			//make sure that terrain inside zone is not a rock
 			//FIXME: reorder actions?
-			zone.second->paintZoneTerrain (ETerrainType::SUBTERRANEAN);
+			zone.second->paintZoneTerrain (Terrain("subterra"));
 		}
 	}
 	logGlobal->info("Finished zone colouring");

+ 2 - 2
lib/serializer/CSerializer.h

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

+ 0 - 51
lib/spells/CSpellHandler.cpp

@@ -1018,54 +1018,3 @@ std::vector<bool> CSpellHandler::getDefaultAllowed() const
 
 	return allowedSpells;
 }
-
-void CSpellHandler::update780()
-{
-    static_assert(MINIMAL_SERIALIZATION_VERSION < 780, "No longer needed CSpellHandler::update780");
-
-	auto spellsContent = (*VLC->modh->content)["spells"];
-
-	const ContentTypeHandler::ModInfo & coreData = spellsContent.modData.at("core");
-
-	const JsonNode & coreSpells = coreData.modData;
-
-	const int levelsCount = GameConstants::SPELL_SCHOOL_LEVELS;
-
-	for(CSpell * spell : objects)
-	{
-		auto identifier = spell->identifier;
-		size_t colonPos = identifier.find(':');
-		if(colonPos != std::string::npos)
-			continue;
-
-		const JsonNode & actualConfig = coreSpells[spell->identifier];
-
-		if(actualConfig.getType() != JsonNode::JsonType::DATA_STRUCT)
-		{
-			logGlobal->error("Spell not found %s", spell->identifier);
-			continue;
-		}
-
-		if(actualConfig["targetCondition"].getType() == JsonNode::JsonType::DATA_STRUCT && !actualConfig["targetCondition"].Struct().empty())
-		{
-			spell->targetCondition = actualConfig["targetCondition"];
-		}
-
-		for(int levelIndex = 0; levelIndex < levelsCount; levelIndex++)
-		{
-			const JsonNode & levelNode = actualConfig["levels"][SpellConfig::LEVEL_NAMES[levelIndex]];
-
-			logGlobal->debug(levelNode.toJson());
-
-			CSpell::LevelInfo & levelObject = spell->levels[levelIndex];
-
-			if(levelNode["battleEffects"].getType() == JsonNode::JsonType::DATA_STRUCT && !levelNode["battleEffects"].Struct().empty())
-			{
-				levelObject.battleEffects = levelNode["battleEffects"];
-
-				logGlobal->trace("Updated special effects for level %d of spell %s", levelIndex, spell->identifier);
-			}
-		}
-
-	}
-}

+ 6 - 74
lib/spells/CSpellHandler.h

@@ -83,14 +83,7 @@ public:
 		{
 			h & resourceName;
 			h & verticalPosition;
-			if(version >= 754)
-			{
-				h & pause;
-			}
-			else if(!h.saving)
-			{
-				pause = 0;
-			}
+			h & pause;
 		}
 	};
 
@@ -120,10 +113,7 @@ public:
 			h & projectile;
 			h & hit;
 			h & cast;
-			if(version >= 762)
-			{
-				h & affect;
-			}
+			h & affect;
 		}
 
 		std::string selectProjectile(const double angle) const;
@@ -158,32 +148,11 @@ public:
 			h & AIValue;
 			h & smartTarget;
 			h & range;
-
-			if(version >= 773)
-			{
-				h & effects;
-				h & cumulativeEffects;
-			}
-			else
-			{
-				//all old effects treated as not cumulative, special cases handled by CSpell::serialize
-				std::vector<Bonus> old;
-				h & old;
-
-				if(!h.saving)
-				{
-					effects.clear();
-					cumulativeEffects.clear();
-					for(const Bonus & oldBonus : old)
-						effects.push_back(std::make_shared<Bonus>(oldBonus));
-				}
-			}
-
+			h & effects;
+			h & cumulativeEffects;
 			h & clearTarget;
 			h & clearAffected;
-
-			if(version >= 780)
-				h & battleEffects;
+			h & battleEffects;
 		}
 	};
 
@@ -316,27 +285,7 @@ public:
 		h & damage;
 		h & offensive;
 		h & targetType;
-
-		if(version >= 780)
-		{
-			h & targetCondition;
-		}
-		else
-		{
-			BTVector immunities;
-			BTVector absoluteImmunities;
-			BTVector limiters;
-			BTVector absoluteLimiters;
-
-			h & immunities;
-			h & limiters;
-			h & absoluteImmunities;
-			h & absoluteLimiters;
-
-			if(!h.saving)
-				targetCondition = convertTargetCondition(immunities, absoluteImmunities, limiters, absoluteLimiters);
-		}
-
+		h & targetCondition;
 		h & iconImmune;
 		h & defaultProbability;
 		h & special;
@@ -348,16 +297,6 @@ public:
 		h & levels;
 		h & school;
 		h & animationInfo;
-
-		//backward compatibility
-		//can not be added to level structure as level structure does not know spell id
-		if(!h.saving && version < 773)
-		{
-			if(id == SpellID::DISRUPTING_RAY || id == SpellID::ACID_BREATH_DEFENSE)
-				for(auto & level : levels)
-					std::swap(level.effects, level.cumulativeEffects);
-		}
-
 	}
 	friend class CSpellHandler;
 	friend class Graphics;
@@ -442,11 +381,6 @@ public:
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 		h & objects;
-		if(!h.saving && version < 780)
-		{
-			update780();
-		}
-
 		if(!h.saving)
 		{
 			afterLoadFinalization();
@@ -456,6 +390,4 @@ public:
 protected:
 	const std::vector<std::string> & getTypeNames() const override;
 	CSpell * loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) override;
-private:
-	void update780();
 };

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

@@ -84,9 +84,7 @@ int BattleCbProxy::getTerrainType(lua_State * L)
 	if(!S.tryGet(1, object))
 		return S.retVoid();
 
-	auto ret = object->battleTerrainType();
-
-	return LuaStack::quickRetInt(L, static_cast<si32>(ret.num));
+	return LuaStack::quickRetStr(L, object->battleTerrainType());
 }
 
 int BattleCbProxy::getUnitByPos(lua_State * L)

+ 7 - 7
server/CGameHandler.cpp

@@ -1911,7 +1911,7 @@ void CGameHandler::newTurn()
 			hth.id = h->id;
 			auto ti = make_unique<TurnInfo>(h, 1);
 			// TODO: this code executed when bonuses of previous day not yet updated (this happen in NewTurn::applyGs). See issue 2356
-			hth.move = h->maxMovePointsCached(gs->map->getTile(h->getPosition(false)).terType != ETerrainType::WATER, ti.get());
+			hth.move = h->maxMovePointsCached(gs->map->getTile(h->getPosition(false)).terType.isLand(), ti.get());
 			hth.mana = h->getManaNewTurn();
 
 			n.heroes.insert(hth);
@@ -2215,9 +2215,9 @@ void CGameHandler::setupBattle(int3 tile, const CArmedInstance *armies[2], const
 	battleResult.set(nullptr);
 
 	const auto t = getTile(tile);
-	ETerrainType terrain = t->terType;
+	Terrain terrain = t->terType;
 	if (gs->map->isCoastalTile(tile)) //coastal tile is always ground
-		terrain = ETerrainType::SAND;
+		terrain = Terrain("sand");
 
 	BFieldType terType = gs->battleGetBattlefieldType(tile, getRandomGenerator());
 	if (heroes[0] && heroes[0]->boat && heroes[1] && heroes[1]->boat)
@@ -2314,7 +2314,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
 	const int3 guardPos = gs->guardingCreaturePosition(hmpos);
 
 	const bool embarking = !h->boat && !t.visitableObjects.empty() && t.visitableObjects.back()->ID == Obj::BOAT;
-	const bool disembarking = h->boat && t.terType != ETerrainType::WATER && !t.blocked;
+	const bool disembarking = h->boat && t.terType.isLand() && !t.blocked;
 
 	//result structure for start - movement failed, no move points used
 	TryMoveHero tmh;
@@ -2336,11 +2336,11 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
 
 	//it's a rock or blocked and not visitable tile
 	//OR hero is on land and dest is water and (there is not present only one object - boat)
-	if (((t.terType == ETerrainType::ROCK  ||  (t.blocked && !t.visitable && !canFly))
+	if (((!t.terType.isPassable()  ||  (t.blocked && !t.visitable && !canFly))
 			&& complain("Cannot move hero, destination tile is blocked!"))
-		|| ((!h->boat && !canWalkOnSea && !canFly && t.terType == ETerrainType::WATER && (t.visitableObjects.size() < 1 ||  (t.visitableObjects.back()->ID != Obj::BOAT && t.visitableObjects.back()->ID != Obj::HERO)))  //hero is not on boat/water walking and dst water tile doesn't contain boat/hero (objs visitable from land) -> we test back cause boat may be on top of another object (#276)
+		|| ((!h->boat && !canWalkOnSea && !canFly && t.terType.isWater() && (t.visitableObjects.size() < 1 ||  (t.visitableObjects.back()->ID != Obj::BOAT && t.visitableObjects.back()->ID != Obj::HERO)))  //hero is not on boat/water walking and dst water tile doesn't contain boat/hero (objs visitable from land) -> we test back cause boat may be on top of another object (#276)
 			&& complain("Cannot move hero, destination tile is on water!"))
-		|| ((h->boat && t.terType != ETerrainType::WATER && t.blocked)
+		|| ((h->boat && t.terType.isLand() && t.blocked)
 			&& complain("Cannot disembark hero, tile is blocked!"))
 		|| ((distance(h->pos, dst) >= 1.5 && !teleporting)
 			&& complain("Tiles are not neighboring!"))

+ 8 - 20
server/CGameHandler.h

@@ -272,21 +272,14 @@ public:
 		h & QID;
 		h & states;
 		h & finishingBattle;
-
-		if(version >= 761)
-		{
-			h & getRandomGenerator();
-		}
-
-		if(version >= 800)
-		{
-			JsonNode scriptsState;
-			if(h.saving)
-				serverScripts->serializeState(h.saving, scriptsState);
-			h & scriptsState;
-			if(!h.saving)
-				serverScripts->serializeState(h.saving, scriptsState);
-		}
+		h & getRandomGenerator();
+
+		JsonNode scriptsState;
+		if(h.saving)
+			serverScripts->serializeState(h.saving, scriptsState);
+		h & scriptsState;
+		if(!h.saving)
+			serverScripts->serializeState(h.saving, scriptsState);
 	}
 
 	void sendMessageToAll(const std::string &message);
@@ -314,11 +307,6 @@ public:
 			h & loserHero;
 			h & victor;
 			h & loser;
-			if(version < 774 && !h.saving)
-			{
-				bool duel;
-				h & duel;
-			}
 			h & remainingBattleQueriesCount;
 		}
 	};

+ 1 - 1
test/game/CGameStateTest.cpp

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

+ 1 - 1
test/googletest

@@ -1 +1 @@
-Subproject commit e2239ee6043f73722e7aa812a459f54a28552929
+Subproject commit 4bab34d2084259cba67f3bfb51217c10d606e175

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است