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

Merge pull request #963 from vcmi/terrain-rewrite

Terrain rewrite
DjWarmonger 3 лет назад
Родитель
Сommit
58a3abb643
100 измененных файлов с 1170 добавлено и 695 удалено
  1. 3 3
      AI/Nullkiller/Pathfinding/AINodeStorage.cpp
  2. 3 3
      AI/VCAI/Pathfinding/AINodeStorage.cpp
  3. 1 0
      client/CGameInfo.cpp
  4. 2 0
      client/CGameInfo.h
  5. 30 19
      client/CMusicHandler.cpp
  6. 3 1
      client/CMusicHandler.h
  7. 8 4
      client/CPlayerInterface.cpp
  8. 2 2
      client/battle/CBattleInterface.cpp
  9. 26 29
      client/mapHandler.cpp
  10. 13 13
      client/widgets/AdventureMapClasses.cpp
  11. 2 2
      client/widgets/AdventureMapClasses.h
  12. 1 1
      client/windows/CAdvmapInterface.cpp
  13. 0 5
      config/randomMap.json
  14. 30 0
      config/rivers.json
  15. 23 0
      config/roads.json
  16. 11 0
      config/terrains.json
  17. 8 8
      lib/CCreatureHandler.cpp
  18. 2 2
      lib/CCreatureHandler.h
  19. 1 1
      lib/CGameInfoCallback.cpp
  20. 4 4
      lib/CGameState.cpp
  21. 4 4
      lib/CGeneralTextHandler.cpp
  22. 1 1
      lib/CGeneralTextHandler.h
  23. 4 4
      lib/CHeroHandler.cpp
  24. 1 1
      lib/CHeroHandler.h
  25. 11 11
      lib/CPathfinder.cpp
  26. 1 1
      lib/CPathfinder.h
  27. 2 2
      lib/CStack.cpp
  28. 2 2
      lib/CStack.h
  29. 6 6
      lib/CTownHandler.cpp
  30. 5 5
      lib/CTownHandler.h
  31. 53 4
      lib/GameConstants.h
  32. 16 6
      lib/HeroBonus.cpp
  33. 2 2
      lib/HeroBonus.h
  34. 3 3
      lib/IGameCallback.cpp
  35. 4 4
      lib/NetPacksLib.cpp
  36. 2 2
      lib/ObstacleHandler.cpp
  37. 2 2
      lib/ObstacleHandler.h
  38. 7 7
      lib/PathfinderUtil.h
  39. 344 96
      lib/Terrain.cpp
  40. 157 66
      lib/Terrain.h
  41. 2 0
      lib/VCMI_Lib.cpp
  42. 3 0
      lib/VCMI_Lib.h
  43. 2 2
      lib/battle/BattleInfo.cpp
  44. 3 4
      lib/battle/BattleInfo.h
  45. 1 1
      lib/battle/BattleProxy.cpp
  46. 1 1
      lib/battle/BattleProxy.h
  47. 2 2
      lib/battle/CBattleInfoEssentials.cpp
  48. 1 1
      lib/battle/CBattleInfoEssentials.h
  49. 2 2
      lib/battle/IBattleInfoCallback.h
  50. 1 1
      lib/battle/IBattleState.h
  51. 14 29
      lib/mapObjects/CGHeroInstance.cpp
  52. 1 1
      lib/mapObjects/CGHeroInstance.h
  53. 2 1
      lib/mapObjects/CGTownInstance.cpp
  54. 11 4
      lib/mapObjects/CObjectClassesHandler.cpp
  55. 2 2
      lib/mapObjects/CObjectClassesHandler.h
  56. 3 3
      lib/mapObjects/CObjectHandler.cpp
  57. 1 1
      lib/mapObjects/CommonConstructors.cpp
  58. 19 20
      lib/mapObjects/ObjectTemplate.cpp
  59. 2 3
      lib/mapObjects/ObjectTemplate.h
  60. 8 8
      lib/mapping/CDrawRoadsOperation.cpp
  61. 4 4
      lib/mapping/CDrawRoadsOperation.h
  62. 14 8
      lib/mapping/CMap.cpp
  63. 3 3
      lib/mapping/CMapDefines.h
  64. 3 3
      lib/mapping/CMapEditManager.cpp
  65. 3 3
      lib/mapping/CMapEditManager.h
  66. 23 20
      lib/mapping/CMapOperation.cpp
  67. 2 2
      lib/mapping/CMapOperation.h
  68. 4 4
      lib/mapping/MapEditUtils.cpp
  69. 2 2
      lib/mapping/MapEditUtils.h
  70. 8 10
      lib/mapping/MapFormatH3M.cpp
  71. 99 88
      lib/mapping/MapFormatJson.cpp
  72. 1 10
      lib/rmg/CMapGenerator.cpp
  73. 0 2
      lib/rmg/CMapGenerator.h
  74. 8 7
      lib/rmg/CRmgTemplate.cpp
  75. 3 3
      lib/rmg/CRmgTemplate.h
  76. 5 4
      lib/rmg/CZonePlacer.cpp
  77. 3 2
      lib/rmg/ConnectionsPlacer.cpp
  78. 18 22
      lib/rmg/Functions.cpp
  79. 2 2
      lib/rmg/Functions.h
  80. 1 1
      lib/rmg/ObstaclePlacer.cpp
  81. 1 1
      lib/rmg/ObstaclePlacer.h
  82. 10 15
      lib/rmg/RiverPlacer.cpp
  83. 2 2
      lib/rmg/RmgMap.cpp
  84. 1 1
      lib/rmg/RmgMap.h
  85. 12 7
      lib/rmg/RmgObject.cpp
  86. 2 3
      lib/rmg/RmgObject.h
  87. 2 1
      lib/rmg/RoadPlacer.cpp
  88. 4 4
      lib/rmg/RockPlacer.cpp
  89. 1 1
      lib/rmg/RockPlacer.h
  90. 6 6
      lib/rmg/TileInfo.cpp
  91. 5 5
      lib/rmg/TileInfo.h
  92. 1 1
      lib/rmg/TreasurePlacer.cpp
  93. 1 1
      lib/rmg/TreasurePlacer.h
  94. 1 1
      lib/rmg/WaterProxy.cpp
  95. 3 3
      lib/rmg/Zone.cpp
  96. 3 3
      lib/rmg/Zone.h
  97. 1 1
      scripting/lua/api/BattleCb.cpp
  98. 8 8
      server/CGameHandler.cpp
  99. 2 2
      test/game/CGameStateTest.cpp
  100. 17 17
      test/map/CMapEditManagerTest.cpp

+ 3 - 3
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -88,11 +88,11 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
 			{
 				for(pos.y = 0; pos.y < sizes.y; ++pos.y)
 				{
-					const TerrainTile* tile = &gs->map->getTile(pos);
-					if (!tile->terType.isPassable())
+					const TerrainTile & tile = gs->map->getTile(pos);
+					if (!tile.terType->isPassable())
 						continue;
 
-					if (tile->terType.isWater())
+					if (tile.terType->isWater())
 					{
 						resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
 						if (useFlying)

+ 3 - 3
AI/VCAI/Pathfinding/AINodeStorage.cpp

@@ -43,11 +43,11 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
 		{
 			for(pos.y=0; pos.y < sizes.y; ++pos.y)
 			{
-				const TerrainTile * tile = &gs->map->getTile(pos);
-				if(!tile->terType.isPassable())
+				const TerrainTile & tile = gs->map->getTile(pos);
+				if(!tile.terType->isPassable())
 					continue;
 				
-				if(tile->terType.isWater())
+				if(tile.terType->isWater())
 				{
 					resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
 					if(useFlying)

+ 1 - 0
client/CGameInfo.cpp

@@ -37,6 +37,7 @@ void CGameInfo::setFromLib()
 	spellh = VLC->spellh;
 	skillh = VLC->skillh;
 	objtypeh = VLC->objtypeh;
+	terrainTypeHandler = VLC->terrainTypeHandler;
 	battleFieldHandler = VLC->battlefieldsHandler;
 	obstacleHandler = VLC->obstacleHandler;
 }

+ 2 - 0
client/CGameInfo.h

@@ -29,6 +29,7 @@ class CConsoleHandler;
 class CGameState;
 class BattleFieldHandler;
 class ObstacleHandler;
+class TerrainTypeHandler;
 
 class CMap;
 
@@ -84,6 +85,7 @@ public:
 	ConstTransitivePtr<CSpellHandler> spellh;
 	ConstTransitivePtr<CSkillHandler> skillh;
 	ConstTransitivePtr<CObjectHandler> objh;
+	ConstTransitivePtr<TerrainTypeHandler> terrainTypeHandler;
 	ConstTransitivePtr<CObjectClassesHandler> objtypeh;
 	ConstTransitivePtr<ObstacleHandler> obstacleHandler;
 	CGeneralTextHandler * generaltexth;

+ 30 - 19
client/CMusicHandler.cpp

@@ -101,27 +101,35 @@ CSoundHandler::CSoundHandler():
 	};
 	
 	//predefine terrain set
-	//TODO: need refactoring - support custom sounds for new terrains and load from json
-	int h3mTerrId = 0;
-	for(auto snd :
+	//TODO: support custom sounds for new terrains and load from json
+	horseSounds =
 	{
-		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())
+		{Terrain::DIRT, soundBase::horseDirt},
+		{Terrain::SAND, soundBase::horseSand},
+		{Terrain::GRASS, soundBase::horseGrass},
+		{Terrain::SNOW, soundBase::horseSnow},
+		{Terrain::SWAMP, soundBase::horseSwamp},
+		{Terrain::ROUGH, soundBase::horseRough},
+		{Terrain::SUBTERRANEAN, soundBase::horseSubterranean},
+		{Terrain::LAVA, soundBase::horseLava},
+		{Terrain::WATER, soundBase::horseWater},
+		{Terrain::ROCK, soundBase::horseRock}
+	};
+}
+
+void CSoundHandler::loadHorseSounds()
+{
+	const auto & terrains = CGI->terrainTypeHandler->terrains();
+	for(const auto & terrain : terrains)
 	{
 		//since all sounds are hardcoded, let's keep it
-		if(vstd::contains(horseSounds, terrain))
+		if(vstd::contains(horseSounds, terrain.id))
 			continue;
-		
-		horseSounds[terrain] = horseSounds.at(Terrain::createTerrainTypeH3M(Terrain::Manager::getInfo(terrain).horseSoundId));
+
+		//Use already existing horse sound
+		horseSounds[terrain.id] = horseSounds.at(terrains[terrain.id].horseSoundId);
 	}
-};
+}
 
 void CSoundHandler::init()
 {
@@ -364,10 +372,13 @@ CMusicHandler::CMusicHandler():
 			addEntryToSet("enemy-turn", file.getName(), file.getName());
 	}
 
-	for(auto & terrain : Terrain::Manager::terrains())
+}
+
+void CMusicHandler::loadTerrainSounds()
+{
+	for (const auto & terrain : CGI->terrainTypeHandler->terrains())
 	{
-		auto & entry = Terrain::Manager::getInfo(terrain);
-		addEntryToSet("terrain", terrain, "Music/" + entry.musicFilename);
+		addEntryToSet("terrain", terrain.name, "Music/" + terrain.musicFilename);
 	}
 }
 

+ 3 - 1
client/CMusicHandler.h

@@ -61,6 +61,7 @@ public:
 	CSoundHandler();
 
 	void init() override;
+	void loadHorseSounds();
 	void release() override;
 
 	void setVolume(ui32 percent) override;
@@ -83,7 +84,7 @@ public:
 	// Sets
 	std::vector<soundBase::soundID> pickupSounds;
 	std::vector<soundBase::soundID> battleIntroSounds;
-	std::map<Terrain, soundBase::soundID> horseSounds;
+	std::map<TerrainId, soundBase::soundID> horseSounds;
 };
 
 // Helper //now it looks somewhat useless
@@ -139,6 +140,7 @@ public:
 	void addEntryToSet(const std::string & set, const std::string & entryID, const std::string & musicURI);
 
 	void init() override;
+	void loadTerrainSounds();
 	void release() override;
 	void setVolume(ui32 percent) override;
 

+ 8 - 4
client/CPlayerInterface.cpp

@@ -152,6 +152,10 @@ void CPlayerInterface::init(std::shared_ptr<Environment> ENV, std::shared_ptr<CC
 {
 	cb = CB;
 	env = ENV;
+
+	CCS->soundh->loadHorseSounds();
+	CCS->musich->loadTerrainSounds();
+
 	initializeHeroTownList();
 
 	// always recreate advmap interface to avoid possible memory-corruption bugs
@@ -274,7 +278,7 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose)
 	{
 		updateAmbientSounds();
 		//We may need to change music - select new track, music handler will change it if needed
-		CCS->musich->playMusicFromSet("terrain", LOCPLINT->cb->getTile(hero->visitablePos())->terType, true);
+		CCS->musich->playMusicFromSet("terrain", LOCPLINT->cb->getTile(hero->visitablePos())->terType->name, true);
 
 		if(details.result == TryMoveHero::TELEPORTATION)
 		{
@@ -2742,8 +2746,8 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
 
 	{
 		path.convert(0);
-		Terrain currentTerrain = Terrain("BORDER"); // not init yet
-		Terrain newTerrain;
+		TerrainId currentTerrain = Terrain::BORDER; // not init yet
+		TerrainId newTerrain;
 		int sh = -1;
 
 		auto canStop = [&](CGPathNode * node) -> bool
@@ -2798,7 +2802,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
 				sh = CCS->soundh->playSound(soundBase::horseFlying, -1);
 #endif
 			{
-				newTerrain = cb->getTile(CGHeroInstance::convertPosition(currentCoord, false))->terType;
+				newTerrain = cb->getTile(CGHeroInstance::convertPosition(currentCoord, false))->terType->id;
 				if(newTerrain != currentTerrain)
 				{
 					CCS->soundh->stopSound(sh);

+ 2 - 2
client/battle/CBattleInterface.cpp

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

+ 26 - 29
client/mapHandler.cpp

@@ -135,22 +135,6 @@ EMapAnimRedrawStatus CMapHandler::drawTerrainRectNew(SDL_Surface * targetSurface
 
 void CMapHandler::initTerrainGraphics()
 {
-	static const std::map<std::string, std::string> ROAD_FILES =
-	{
-		{ROAD_NAMES[1], "dirtrd"},
-		{ROAD_NAMES[2], "gravrd"},
-		{ROAD_NAMES[3], "cobbrd"}
-	};
-
-	static const std::map<std::string, std::string> RIVER_FILES =
-	{
-		{RIVER_NAMES[1], "clrrvr"},
-		{RIVER_NAMES[2], "icyrvr"},
-		{RIVER_NAMES[3], "mudrvr"},
-		{RIVER_NAMES[4], "lavrvr"}
-	};
-	
-
 	auto loadFlipped = [](TFlippedAnimations & animation, TFlippedCache & cache, const std::map<std::string, std::string> & files)
 	{
 		//no rotation and basic setup
@@ -189,14 +173,24 @@ void CMapHandler::initTerrainGraphics()
 	};
 	
 	std::map<std::string, std::string> terrainFiles;
-	for(auto & terrain : Terrain::Manager::terrains())
+	std::map<std::string, std::string> riverFiles;
+	std::map<std::string, std::string> roadFiles;
+	for(const auto & terrain : VLC->terrainTypeHandler->terrains())
+	{
+		terrainFiles[terrain.name] = terrain.tilesFilename;
+	}
+	for(const auto & river : VLC->terrainTypeHandler->rivers())
 	{
-		terrainFiles[terrain] = Terrain::Manager::getInfo(terrain).tilesFilename;
+		riverFiles[river.fileName] = river.fileName;
+	}
+	for(const auto & road : VLC->terrainTypeHandler->roads())
+	{
+		roadFiles[road.fileName] = road.fileName;
 	}
 	
 	loadFlipped(terrainAnimations, terrainImages, terrainFiles);
-	loadFlipped(roadAnimations, roadImages, ROAD_FILES);
-	loadFlipped(riverAnimations, riverImages, RIVER_FILES);
+	loadFlipped(riverAnimations, riverImages, riverFiles);
+	loadFlipped(roadAnimations, roadImages, roadFiles);
 
 	// Create enough room for the whole map and its frame
 
@@ -609,10 +603,13 @@ void CMapHandler::CMapBlitter::drawTileTerrain(SDL_Surface * targetSurf, const T
 
 	ui8 rotation = tinfo.extTileFlags % 4;
 	
-	if(parent->terrainImages[tinfo.terType].size()<=tinfo.terView)
+	//TODO: use ui8 instead of string key
+	auto terrainName = tinfo.terType->name;
+
+	if(parent->terrainImages[terrainName].size()<=tinfo.terView)
 		return;
 
-	drawElement(EMapCacheType::TERRAIN, parent->terrainImages[tinfo.terType][tinfo.terView][rotation], nullptr, targetSurf, &destRect);
+	drawElement(EMapCacheType::TERRAIN, parent->terrainImages[terrainName][tinfo.terView][rotation], nullptr, targetSurf, &destRect);
 }
 
 void CMapHandler::CMapWorldViewBlitter::init(const MapDrawingInfo * drawingInfo)
@@ -787,21 +784,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 != ROAD_NAMES[0])
+	if (tinfoUpper && tinfoUpper->roadType->id != Road::NO_ROAD)
 	{
 		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][tinfoUpper->roadDir][rotation],
+		drawElement(EMapCacheType::ROADS, parent->roadImages[tinfoUpper->roadType->fileName][tinfoUpper->roadDir][rotation],
 				&source, targetSurf, &dest);
 	}
 
-	if(tinfo.roadType != ROAD_NAMES[0]) //print road from this tile
+	if(tinfo.roadType->id != Road::NO_ROAD) //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][tinfo.roadDir][rotation],
+		drawElement(EMapCacheType::ROADS, parent->roadImages[tinfo.roadType->fileName][tinfo.roadDir][rotation],
 				&source, targetSurf, &dest);
 	}
 }
@@ -810,7 +807,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][tinfo.riverDir][rotation], nullptr, targetSurf, &destRect);
+	drawElement(EMapCacheType::RIVERS, parent->riverImages[tinfo.riverType->fileName][tinfo.riverDir][rotation], nullptr, targetSurf, &destRect);
 }
 
 void CMapHandler::CMapBlitter::drawFow(SDL_Surface * targetSurf) const
@@ -861,7 +858,7 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn
 			if(isVisible || info->showAllTerrain)
 			{
 				drawTileTerrain(targetSurf, tinfo, tile);
-				if(tinfo.riverType != RIVER_NAMES[0])
+				if(tinfo.riverType->id != River::NO_RIVER)
 					drawRiver(targetSurf, tinfo);
 				drawRoad(targetSurf, tinfo, tinfoUpper);
 			}
@@ -1390,7 +1387,7 @@ void CMapHandler::getTerrainDescr(const int3 & pos, std::string & out, bool isRM
 		}
 	}
 	if(!isTile2Terrain || out.empty())
-		out = CGI->generaltexth->terrainNames[t.terType];
+		out = CGI->generaltexth->terrainNames[t.terType->id];
 
 	if(t.getDiggingStatus(false) == EDiggingStatus::CAN_DIG)
 	{

+ 13 - 13
client/widgets/AdventureMapClasses.cpp

@@ -390,10 +390,11 @@ const SDL_Color & CMinimapInstance::getTileColor(const int3 & pos)
 	}
 
 	// else - use terrain color (blocked version or normal)
+	const auto & colorPair = parent->colors.find(tile->terType->id)->second;
 	if (tile->blocked && (!tile->visitable))
-		return parent->colors.find(tile->terType)->second.second;
+		return colorPair.second;
 	else
-		return parent->colors.find(tile->terType)->second.first;
+		return colorPair.first;
 }
 void CMinimapInstance::tileToPixels (const int3 &tile, int &x, int &y, int toX, int toY)
 {
@@ -494,30 +495,29 @@ void CMinimapInstance::showAll(SDL_Surface * to)
 	}
 }
 
-std::map<Terrain, std::pair<SDL_Color, SDL_Color> > CMinimap::loadColors()
+std::map<TerrainId, std::pair<SDL_Color, SDL_Color> > CMinimap::loadColors()
 {
-	std::map<Terrain, std::pair<SDL_Color, SDL_Color> > ret;
+	std::map<TerrainId, std::pair<SDL_Color, SDL_Color> > ret;
 
-	for(auto & terrain : Terrain::Manager::terrains())
+	for(const auto & terrain : CGI->terrainTypeHandler->terrains())
 	{
-		auto & m = Terrain::Manager::getInfo(terrain);
 		SDL_Color normal =
 		{
-			ui8(m.minimapUnblocked[0]),
-			ui8(m.minimapUnblocked[1]),
-			ui8(m.minimapUnblocked[2]),
+			ui8(terrain.minimapUnblocked[0]),
+			ui8(terrain.minimapUnblocked[1]),
+			ui8(terrain.minimapUnblocked[2]),
 			ui8(255)
 		};
 
 		SDL_Color blocked =
 		{
-			ui8(m.minimapBlocked[0]),
-			ui8(m.minimapBlocked[1]),
-			ui8(m.minimapBlocked[2]),
+			ui8(terrain.minimapBlocked[0]),
+			ui8(terrain.minimapBlocked[1]),
+			ui8(terrain.minimapBlocked[2]),
 			ui8(255)
 		};
 
-		ret[terrain] = std::make_pair(normal, blocked);
+		ret[terrain.id] = std::make_pair(normal, blocked);
 	}
 	return ret;
 }

+ 2 - 2
client/widgets/AdventureMapClasses.h

@@ -222,7 +222,7 @@ protected:
 	int level;
 
 	//to initialize colors
-	std::map<Terrain, std::pair<SDL_Color, SDL_Color> > loadColors();
+	std::map<TerrainId, std::pair<SDL_Color, SDL_Color> > loadColors();
 
 	void clickLeft(tribool down, bool previousState) override;
 	void clickRight(tribool down, bool previousState) override;
@@ -233,7 +233,7 @@ protected:
 
 public:
 	// terrainID -> (normal color, blocked color)
-	const std::map<Terrain, std::pair<SDL_Color, SDL_Color> > colors;
+	const std::map<TerrainId, std::pair<SDL_Color, SDL_Color> > colors;
 
 	CMinimap(const Rect & position);
 

+ 1 - 1
client/windows/CAdvmapInterface.cpp

@@ -1413,7 +1413,7 @@ void CAdvMapInt::select(const CArmedInstance *sel, bool centerView)
 		auto pos = sel->visitablePos();
 		auto tile = LOCPLINT->cb->getTile(pos);
 		if(tile)
-			CCS->musich->playMusicFromSet("terrain", tile->terType, true);
+			CCS->musich->playMusicFromSet("terrain", tile->terType->name, true);
 	}
 	if(centerView)
 		centerOn(sel);

+ 0 - 5
config/randomMap.json

@@ -1,9 +1,4 @@
 {
-  "terrain" :
-  {
-    "undergroundAllow" : ["lava"], //others to be replaced by subterranena
-    "groundProhibit" : ["subterra"] //to be replaced by dirt
-  },
   "waterZone" :
   {
     "treasure" :

+ 30 - 0
config/rivers.json

@@ -0,0 +1,30 @@
+{
+    "waterRiver":
+    {
+        "originalRiverId": 1,
+        "code": "rw", //must be 2 characters
+        "animation": "clrrvr",
+        "delta": "clrdelt"
+    },
+    "iceRiver":
+    {
+        "originalRiverId": 2,
+        "code": "ri",
+        "animation": "icyrvr",
+        "delta": "icedelt"
+    },
+    "mudRiver":
+    {
+        "originalRiverId": 3,
+        "code": "rm",
+        "animation": "mudrvr",
+        "delta": "muddelt"
+    },
+    "lavaRiver":
+    {
+        "originalRiverId": 4,
+        "code": "rl",
+        "animation": "lavrvr",
+        "delta": "lavdelt"
+    }
+}

+ 23 - 0
config/roads.json

@@ -0,0 +1,23 @@
+{
+    "dirtRoad":
+    {
+        "originalRoadId": 1,
+        "code": "pd", //must be 2 characters
+        "animation": "dirtrd",
+        "moveCost": 75
+    },
+    "gravelRoad":
+    {
+        "originalRoadId": 2,
+        "code": "pg",
+        "animation": "gravrd",
+        "moveCost": 65
+    },
+    "cobblestoneRoad":
+    {
+        "originalRoadId": 3,
+        "code": "pc",
+        "animation": "cobbrd",
+        "moveCost": 50
+    }
+}

+ 11 - 0
config/terrains.json

@@ -1,6 +1,7 @@
 {
 	"dirt" :
 	{
+		"originalTerrainId": 0,
 		"moveCost" : 100,
 		"minimapUnblocked" : [ 82, 56, 8 ],
 		"minimapBlocked"   : [ 57, 40, 8 ],
@@ -14,6 +15,7 @@
 	},
 	"sand" :
 	{
+		"originalTerrainId": 1,
 		"moveCost" : 150,
 		"minimapUnblocked" : [ 222, 207, 140 ],
 		"minimapBlocked"   : [ 165, 158, 107 ],
@@ -28,6 +30,7 @@
 	},
 	"grass" :
 	{
+		"originalTerrainId": 2,
 		"moveCost" : 100,
 		"minimapUnblocked" : [ 0, 65, 0 ],
 		"minimapBlocked"   : [ 0, 48, 0 ],
@@ -40,6 +43,7 @@
 	},
 	"snow" :
 	{
+		"originalTerrainId": 3,
 		"moveCost" : 150,
 		"minimapUnblocked" : [ 181, 199, 198 ],
 		"minimapBlocked"   : [ 140, 158, 156 ],
@@ -52,6 +56,7 @@
 	},
 	"swamp" :
 	{
+		"originalTerrainId": 4,
 		"moveCost" : 175,
 		"minimapUnblocked" : [ 74, 134, 107 ],
 		"minimapBlocked"   : [ 33,  89,  66 ],
@@ -64,6 +69,7 @@
 	},
 	"rough" :
 	{
+		"originalTerrainId": 5,
 		"moveCost" : 125,
 		"minimapUnblocked" : [ 132, 113, 49 ],
 		"minimapBlocked"   : [  99,  81, 33 ],
@@ -76,6 +82,7 @@
 	},
 	"subterra" :
 	{
+		"originalTerrainId": 6,
 		"moveCost" : 100,
 		"minimapUnblocked" : [ 132, 48, 0 ],
 		"minimapBlocked"   : [  90,  8, 0 ],
@@ -90,11 +97,13 @@
 	},
 	"lava" :
 	{
+		"originalTerrainId": 7,
 		"moveCost" : 100,
 		"minimapUnblocked" : [ 74, 73, 74 ],
 		"minimapBlocked"   : [ 41, 40, 41 ],
 		"music" : "Lava.mp3",
 		"tiles" : "LAVATL",
+		"type" : ["SUB", "SURFACE"],
 		"code" : "lv",
 		"river" : "rl",
 		"battleFields" : ["lava"],
@@ -103,6 +112,7 @@
 	},
 	"water" :
 	{
+		"originalTerrainId": 8,
 		"moveCost" : 100,
 		"minimapUnblocked" : [ 8, 81, 148 ],
 		"minimapBlocked"   : [ 8, 81, 148 ],
@@ -120,6 +130,7 @@
 	},
 	"rock" :
 	{
+		"originalTerrainId": 9,
 		"moveCost" : -1,
 		"minimapUnblocked" : [ 0, 0, 0 ],
 		"minimapBlocked"   : [ 0, 0, 0 ],

+ 8 - 8
lib/CCreatureHandler.cpp

@@ -285,22 +285,22 @@ std::string CCreature::nodeName() const
 	return "\"" + namePl + "\"";
 }
 
-bool CCreature::isItNativeTerrain(const Terrain & terrain) const
+bool CCreature::isItNativeTerrain(TerrainId terrain) const
 {
 	auto native = getNativeTerrain();
-	return native == terrain || native == Terrain::ANY;
+	return native == terrain || native == Terrain::ANY_TERRAIN;
 }
 
-Terrain CCreature::getNativeTerrain() const
+TerrainId CCreature::getNativeTerrain() const
 {
-	const std::string cachingStringBlocksRetaliation = "type_NO_TERRAIN_PENALTY";
-	static const auto selectorBlocksRetaliation = Selector::type()(Bonus::NO_TERRAIN_PENALTY);
+	const std::string cachingStringNoTerrainPenalty = "type_NO_TERRAIN_PENALTY";
+	static const auto selectorNoTerrainPenalty = Selector::type()(Bonus::NO_TERRAIN_PENALTY);
 
 	//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)
-		? Terrain::ANY
-		: (Terrain)(*VLC->townh)[faction]->nativeTerrain;
+	return hasBonus(selectorNoTerrainPenalty, selectorNoTerrainPenalty)
+		? Terrain::ANY_TERRAIN
+		: (*VLC->townh)[faction]->nativeTerrain;
 }
 
 void CCreature::updateFrom(const JsonNode & data)

+ 2 - 2
lib/CCreatureHandler.h

@@ -121,14 +121,14 @@ public:
 
 	ArtifactID warMachine;
 
-	bool isItNativeTerrain(const Terrain & terrain) const;
+	bool isItNativeTerrain(TerrainId 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.
 	*/
-	Terrain getNativeTerrain() const;
+	TerrainId getNativeTerrain() const;
 	int32_t getIndex() const override;
 	int32_t getIconIndex() const override;
 	const std::string & getName() const override;

+ 1 - 1
lib/CGameInfoCallback.cpp

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

+ 4 - 4
lib/CGameState.cpp

@@ -966,8 +966,8 @@ void CGameState::initGrailPosition()
 					const TerrainTile &t = map->getTile(int3(x, y, z));
 					if(!t.blocked
 						&& !t.visitable
-						&& t.terType.isLand()
-						&& t.terType.isPassable()
+						&& t.terType->isLand()
+						&& t.terType->isPassable()
 						&& (int)map->grailPos.dist2dSQ(int3(x, y, z)) <= (map->grailRadius * map->grailRadius))
 						allowedPos.push_back(int3(x,y,z));
 				}
@@ -1921,7 +1921,7 @@ BattleField CGameState::battleGetBattlefieldType(int3 tile, CRandomGenerator & r
 		return BattleField::fromString("sand_shore");
 	
 	return BattleField::fromString(
-		*RandomGeneratorUtil::nextItem(Terrain::Manager::getInfo(t.terType).battleFields, rand));
+		*RandomGeneratorUtil::nextItem(t.terType->battleFields, rand));
 }
 
 UpgradeInfo CGameState::getUpgradeInfo(const CStackInstance &stack)
@@ -2103,7 +2103,7 @@ void CGameState::updateRumor()
 			rumorId = *RandomGeneratorUtil::nextItem(sRumorTypes, rand);
 			if(rumorId == RumorState::RUMOR_GRAIL)
 			{
-				rumorExtra = getTile(map->grailPos)->terType.id();
+				rumorExtra = getTile(map->grailPos)->terType->id;
 				break;
 			}
 

+ 4 - 4
lib/CGeneralTextHandler.cpp

@@ -338,12 +338,12 @@ CGeneralTextHandler::CGeneralTextHandler()
 	
 	for(int i = 0; i < h3mTerrainNames.size(); ++i)
 	{
-		terrainNames[Terrain::createTerrainTypeH3M(i)] = h3mTerrainNames[i];
+		terrainNames[i] = h3mTerrainNames[i];
 	}
-	for(auto & terrain : Terrain::Manager::terrains())
+	for(const auto & terrain : VLC->terrainTypeHandler->terrains())
 	{
-		if(!Terrain::Manager::getInfo(terrain).terrainText.empty())
-			terrainNames[terrain] = Terrain::Manager::getInfo(terrain).terrainText;
+		if(!terrain.terrainText.empty())
+			terrainNames[terrain.id] = terrain.terrainText;
 	}
 	
 

+ 1 - 1
lib/CGeneralTextHandler.h

@@ -124,7 +124,7 @@ public:
 	std::vector<std::string> advobtxt;
 	std::vector<std::string> xtrainfo;
 	std::vector<std::string> restypes; //names of resources
-	std::map<std::string, std::string> terrainNames;
+	std::map<TerrainId, 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;

+ 4 - 4
lib/CHeroHandler.cpp

@@ -346,9 +346,9 @@ CHeroHandler::~CHeroHandler() = default;
 CHeroHandler::CHeroHandler()
 {
 	loadTerrains();
-	for(int i = 0; i < Terrain::Manager::terrains().size(); ++i)
+	for(const auto & terrain : VLC->terrainTypeHandler->terrains())
 	{
-		VLC->modh->identifiers.registerObject("core", "terrain", Terrain::Manager::terrains()[i], i);
+		VLC->modh->identifiers.registerObject("core", "terrain", terrain.name, terrain.id);
 	}
 	loadBallistics();
 	loadExperience();
@@ -974,9 +974,9 @@ ui64 CHeroHandler::reqExp (ui32 level) const
 
 void CHeroHandler::loadTerrains()
 {
-	for(auto & terrain : Terrain::Manager::terrains())
+	for(const auto & terrain : VLC->terrainTypeHandler->terrains())
 	{
-		terrCosts[terrain] = Terrain::Manager::getInfo(terrain).moveCost;
+		terrCosts[terrain.id] = terrain.moveCost;
 	}
 }
 

+ 1 - 1
lib/CHeroHandler.h

@@ -266,7 +266,7 @@ public:
 	CHeroClassHandler classes;
 
 	//default costs of going through terrains. -1 means terrain is impassable
-	std::map<Terrain, int> terrCosts;
+	std::map<TerrainId, int> terrCosts;
 
 	struct SBallisticsLevelInfo
 	{

+ 11 - 11
lib/CPathfinder.cpp

@@ -47,8 +47,8 @@ void NodeStorage::initialize(const PathfinderOptions & options, const CGameState
 		{
 			for(pos.y=0; pos.y < sizes.y; ++pos.y)
 			{
-				const TerrainTile * tile = &gs->map->getTile(pos);
-				if(tile->terType.isWater())
+				const TerrainTile tile = gs->map->getTile(pos);
+				if(tile.terType->isWater())
 				{
 					resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
 					if(useFlying)
@@ -56,7 +56,7 @@ void NodeStorage::initialize(const PathfinderOptions & options, const CGameState
 					if(useWaterWalking)
 						resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility<ELayer::WATER>(pos, tile, fow, player, gs));
 				}
-				if(tile->terType.isLand())
+				if(tile.terType->isLand())
 				{
 					resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility<ELayer::LAND>(pos, tile, fow, player, gs));
 					if(useFlying)
@@ -1010,10 +1010,10 @@ bool CPathfinderHelper::passOneTurnLimitCheck(const PathNodeInfo & source) const
 
 TurnInfo::BonusCache::BonusCache(TConstBonusListPtr bl)
 {
-	for(int i = 0; i < Terrain::Manager::terrains().size(); ++i)
+	for(const auto & terrain : VLC->terrainTypeHandler->terrains())
 	{
 		noTerrainPenalty.push_back(static_cast<bool>(
-				bl->getFirst(Selector::type()(Bonus::NO_TERRAIN_PENALTY).And(Selector::subtype()(i)))));
+				bl->getFirst(Selector::type()(Bonus::NO_TERRAIN_PENALTY).And(Selector::subtype()(terrain.id)))));
 	}
 
 	freeShipBoarding = static_cast<bool>(bl->getFirst(Selector::type()(Bonus::FREE_SHIP_BOARDING)));
@@ -1180,7 +1180,7 @@ void CPathfinderHelper::getNeighbours(
 			continue;
 
 		const TerrainTile & hlpt = map->getTile(hlp);
-		if(!hlpt.terType.isPassable())
+		if(!hlpt.terType->isPassable())
 			continue;
 
 // 		//we cannot visit things from blocked tiles
@@ -1190,18 +1190,18 @@ void CPathfinderHelper::getNeighbours(
 // 		}
 
 		/// Following condition let us avoid diagonal movement over coast when sailing
-		if(srct.terType.isWater() && limitCoastSailing && hlpt.terType.isWater() && 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.isLand() || map->getTile(hlp2).terType.isLand())
+			if(map->getTile(hlp1).terType->isLand() || map->getTile(hlp2).terType->isLand())
 				continue;
 		}
 
-		if(indeterminate(onLand) || onLand == hlpt.terType.isLand())
+		if(indeterminate(onLand) || onLand == hlpt.terType->isLand())
 		{
 			vec.push_back(hlp);
 		}
@@ -1239,7 +1239,7 @@ int CPathfinderHelper::getMovementCost(
 	{
 		ret = static_cast<int>(ret * (100.0 + ti->valOfBonuses(Bonus::FLYING_MOVEMENT)) / 100.0);
 	}
-	else if(dt->terType.isWater())
+	else if(dt->terType->isWater())
 	{
 		if(hero->boat && ct->hasFavorableWinds() && dt->hasFavorableWinds())
 			ret = static_cast<int>(ret * 0.666);
@@ -1267,7 +1267,7 @@ int CPathfinderHelper::getMovementCost(
 	{
 		std::vector<int3> vec;
 		vec.reserve(8); //optimization
-		getNeighbours(*dt, dst, vec, ct->terType.isLand(), true);
+		getNeighbours(*dt, dst, vec, ct->terType->isLand(), true);
 		for(auto & elem : vec)
 		{
 			int fcost = getMovementCost(dst, elem, nullptr, nullptr, left, false);

+ 1 - 1
lib/CPathfinder.h

@@ -529,7 +529,7 @@ struct DLL_LINKAGE TurnInfo
 	TConstBonusListPtr bonuses;
 	mutable int maxMovePointsLand;
 	mutable int maxMovePointsWater;
-	Terrain nativeTerrain;
+	TerrainId nativeTerrain;
 
 	TurnInfo(const CGHeroInstance * Hero, const int Turn = 0);
 	bool isLayerAvailable(const EPathfindingLayer layer) const;

+ 2 - 2
lib/CStack.cpp

@@ -331,11 +331,11 @@ bool CStack::canBeHealed() const
 bool CStack::isOnNativeTerrain() const
 {
 	//this code is called from CreatureTerrainLimiter::limit on battle start
-	auto res = nativeTerrain == Terrain::ANY || nativeTerrain == battle->getTerrainType();
+	auto res = nativeTerrain == Terrain::ANY_TERRAIN || nativeTerrain == battle->getTerrainType();
 	return res;
 }
 
-bool CStack::isOnTerrain(const Terrain & terrain) const
+bool CStack::isOnTerrain(TerrainId terrain) const
 {
 	return battle->getTerrainType() == terrain;
 }

+ 2 - 2
lib/CStack.h

@@ -31,7 +31,7 @@ public:
 
 	ui32 ID; //unique ID of stack
 	const CCreature * type;
-	Terrain nativeTerrain; //tmp variable to save native terrain value on battle init
+	TerrainId nativeTerrain; //tmp variable to save native terrain value on battle init
 	ui32 baseAmount;
 
 	PlayerColor owner; //owner - player color (255 for neutrals)
@@ -53,7 +53,7 @@ public:
 
 	bool canBeHealed() const; //for first aid tent - only harmed stacks that are not war machines
 	bool isOnNativeTerrain() const;
-	bool isOnTerrain(const Terrain & terrain) const;
+	bool isOnTerrain(TerrainId terrain) const;
 
 	ui32 level() const;
 	si32 magicResistance() const override; //include aura of resistance

+ 6 - 6
lib/CTownHandler.cpp

@@ -28,9 +28,9 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 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 TerrainId CTownHandler::defaultGoodTerrain(Terrain::GRASS);
+const TerrainId CTownHandler::defaultEvilTerrain(Terrain::LAVA);
+const TerrainId CTownHandler::defaultNeutralTerrain(Terrain::ROUGH);
 
 const std::map<std::string, CBuilding::EBuildMode> CBuilding::MODES =
 {
@@ -944,9 +944,9 @@ void CTownHandler::loadPuzzle(CFaction &faction, const JsonNode &source)
 	assert(faction.puzzleMap.size() == GameConstants::PUZZLE_MAP_PIECES);
 }
 
-Terrain CTownHandler::getDefaultTerrainForAlignment(EAlignment::EAlignment alignment) const
+TerrainId CTownHandler::getDefaultTerrainForAlignment(EAlignment::EAlignment alignment) const
 {
-	Terrain terrain = defaultGoodTerrain;
+	TerrainId terrain = defaultGoodTerrain;
 
 	switch(alignment)
 	{
@@ -985,7 +985,7 @@ CFaction * CTownHandler::loadFromJson(const std::string & scope, const JsonNode
 	auto nativeTerrain = source["nativeTerrain"];
 	faction->nativeTerrain = nativeTerrain.isNull()
 		? getDefaultTerrainForAlignment(faction->alignment)
-		: Terrain(nativeTerrain.String());
+		: VLC->terrainTypeHandler->getInfoByName(nativeTerrain.String())->id;
 
 	if (!source["town"].isNull())
 	{

+ 5 - 5
lib/CTownHandler.h

@@ -187,7 +187,7 @@ public:
 
 	TFaction index;
 
-	Terrain nativeTerrain;
+	TerrainId nativeTerrain;
 	EAlignment::EAlignment alignment;
 	bool preferUndergroundPlacement;
 
@@ -360,9 +360,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 Terrain defaultGoodTerrain;
-	const static Terrain defaultEvilTerrain;
-	const static Terrain defaultNeutralTerrain;
+	const static TerrainId defaultGoodTerrain;
+	const static TerrainId defaultEvilTerrain;
+	const static TerrainId defaultNeutralTerrain;
 
 	static TPropagatorPtr & emptyPropagator();
 
@@ -393,7 +393,7 @@ class DLL_LINKAGE CTownHandler : public CHandlerBase<FactionID, Faction, CFactio
 
 	void loadPuzzle(CFaction & faction, const JsonNode & source);
 
-	Terrain getDefaultTerrainForAlignment(EAlignment::EAlignment aligment) const;
+	TerrainId getDefaultTerrainForAlignment(EAlignment::EAlignment aligment) const;
 	void loadRandomFaction();
 
 

+ 53 - 4
lib/GameConstants.h

@@ -681,10 +681,6 @@ enum class ETeleportChannelType
 	MIXED
 };
 
-
-static std::vector<std::string> RIVER_NAMES {"", "rw", "ri", "rm", "rl"};
-static std::vector<std::string> ROAD_NAMES {"", "pd", "pg", "pc"};
-
 class Obj
 {
 public:
@@ -837,6 +833,56 @@ public:
 
 ID_LIKE_OPERATORS(Obj, Obj::EObj)
 
+namespace Terrain
+{
+	enum ETerrain : si8
+	{
+		NATIVE_TERRAIN = -4,
+		ANY_TERRAIN = -3,
+		WRONG = -2,
+		BORDER = -1,
+		FIRST_REGULAR_TERRAIN = 0,
+		DIRT = 0,
+		SAND,
+		GRASS,
+		SNOW,
+		SWAMP,
+		ROUGH,
+		SUBTERRANEAN,
+		LAVA,
+		WATER,
+		ROCK,
+		ORIGINAL_TERRAIN_COUNT
+	};
+}
+
+namespace Road
+{
+	enum ERoad : ui8
+	{
+		NO_ROAD = 0,
+		FIRST_REGULAR_ROAD = 1,
+		DIRT_ROAD = 1,
+		GRAVEL_ROAD = 2,
+		COBBLESTONE_ROAD = 3,
+		ORIGINAL_ROAD_COUNT //+1
+	};
+}
+
+namespace River
+{
+	enum ERiver : ui8
+	{
+		NO_RIVER = 0,
+		FIRST_REGULAR_RIVER = 1,
+		WATER_RIVER = 1,
+		ICY_RIVER = 2,
+		MUD_RIVER = 3,
+		LAVA_RIVER = 4,
+		ORIGINAL_RIVER_COUNT //+1
+	};
+}
+
 namespace SecSkillLevel
 {
 	enum SecSkillLevel
@@ -1188,6 +1234,9 @@ typedef si64 TExpType;
 typedef std::pair<si64, si64> TDmgRange;
 typedef si32 TBonusSubtype;
 typedef si32 TQuantity;
+typedef si8 TerrainId;
+typedef si8 RoadId;
+typedef si8 RiverId;
 
 typedef int TRmgTemplateZoneId;
 

+ 16 - 6
lib/HeroBonus.cpp

@@ -2109,9 +2109,13 @@ bool CPropagatorNodeType::shouldBeAttached(CBonusSystemNode *dest)
 }
 
 CreatureTerrainLimiter::CreatureTerrainLimiter()
-	: terrainType()
+	: terrainType(Terrain::NATIVE_TERRAIN)
 {
+}
 
+CreatureTerrainLimiter::CreatureTerrainLimiter(TerrainId terrain):
+	terrainType(terrain)
+{
 }
 
 int CreatureTerrainLimiter::limit(const BonusLimitationContext &context) const
@@ -2119,9 +2123,14 @@ int CreatureTerrainLimiter::limit(const BonusLimitationContext &context) const
 	const CStack *stack = retrieveStackBattle(&context.node);
 	if(stack)
 	{
-		if(terrainType.isNative())//terrainType not specified = native
+		if (terrainType == Terrain::NATIVE_TERRAIN)//terrainType not specified = native
+		{
 			return !stack->isOnNativeTerrain();
-		return !stack->isOnTerrain(terrainType);
+		}
+		else
+		{
+			return !stack->isOnTerrain(terrainType);
+		}
 	}
 	return true;
 	//TODO neutral creatues
@@ -2130,7 +2139,8 @@ int CreatureTerrainLimiter::limit(const BonusLimitationContext &context) const
 std::string CreatureTerrainLimiter::toString() const
 {
 	boost::format fmt("CreatureTerrainLimiter(terrainType=%s)");
-	fmt % (terrainType.isNative() ? "native" : static_cast<std::string>(terrainType));
+	auto terrainName = VLC->terrainTypeHandler->terrains()[terrainType].name;
+	fmt % (terrainType == Terrain::NATIVE_TERRAIN ? "native" : terrainName);
 	return fmt.str();
 }
 
@@ -2139,8 +2149,8 @@ JsonNode CreatureTerrainLimiter::toJsonNode() const
 	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
 
 	root["type"].String() = "CREATURE_TERRAIN_LIMITER";
-	if(!terrainType.isNative())
-		root["parameters"].Vector().push_back(JsonUtils::stringNode(terrainType));
+	auto terrainName = VLC->terrainTypeHandler->terrains()[terrainType].name;
+	root["parameters"].Vector().push_back(JsonUtils::stringNode(terrainName));
 
 	return root;
 }

+ 2 - 2
lib/HeroBonus.h

@@ -1062,9 +1062,9 @@ public:
 class DLL_LINKAGE CreatureTerrainLimiter : public ILimiter //applies only to creatures that are on specified terrain, default native terrain
 {
 public:
-	Terrain terrainType;
+	TerrainId terrainType;
 	CreatureTerrainLimiter();
-	CreatureTerrainLimiter(const Terrain& terrain);
+	CreatureTerrainLimiter(TerrainId terrain);
 
 	int limit(const BonusLimitationContext &context) const override;
 	virtual std::string toString() const override;

+ 3 - 3
lib/IGameCallback.cpp

@@ -52,7 +52,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.isLand() && tinfo->terType.isPassable() && !tinfo->blocked) //land and free
+				if (tinfo->terType->isLand() && tinfo->terType->isPassable() && !tinfo->blocked) //land and free
 					tiles.push_back (int3 (xd,yd,zd));
 			}
 		}
@@ -119,8 +119,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.isWater() && water)
-					|| (getTile (int3 (xd,yd,zd))->terType.isLand() && land))
+				if ((getTile (int3 (xd,yd,zd))->terType->isWater() && water)
+					|| (getTile (int3 (xd,yd,zd))->terType->isLand() && land))
 					tiles.insert(int3(xd,yd,zd));
 			}
 		}

+ 4 - 4
lib/NetPacksLib.cpp

@@ -703,13 +703,13 @@ DLL_LINKAGE void GiveHero::applyGs(CGameState *gs)
 
 DLL_LINKAGE void NewObject::applyGs(CGameState *gs)
 {
-	Terrain terrainType;
+	TerrainId terrainType = Terrain::BORDER;
 
 	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(Terrain("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));
@@ -718,7 +718,7 @@ DLL_LINKAGE void NewObject::applyGs(CGameState *gs)
 	else
 	{
 		const TerrainTile & t = gs->map->getTile(pos);
-		terrainType = t.terType;
+		terrainType = t.terType->id;
 	}
 
 	CGObjectInstance *o = nullptr;
@@ -726,7 +726,7 @@ DLL_LINKAGE void NewObject::applyGs(CGameState *gs)
 	{
 	case Obj::BOAT:
 		o = new CGBoat();
-		terrainType = Terrain("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/ObstacleHandler.cpp

@@ -68,7 +68,7 @@ std::vector<BattleHex> ObstacleInfo::getBlocked(BattleHex hex) const
 	return ret;
 }
 
-bool ObstacleInfo::isAppropriate(const Terrain & terrainType, const BattleField & battlefield) const
+bool ObstacleInfo::isAppropriate(const TerrainId terrainType, const BattleField & battlefield) const
 {
 	auto bgInfo = battlefield.getInfo();
 	
@@ -86,7 +86,7 @@ ObstacleInfo * ObstacleHandler::loadFromJson(const std::string & scope, const Js
 	info->width = json["width"].Integer();
 	info->height = json["height"].Integer();
 	for(auto & t : json["allowedTerrain"].Vector())
-		info->allowedTerrains.emplace_back(t.String());
+		info->allowedTerrains.emplace_back(VLC->terrainTypeHandler->getInfoByName(t.String())->id);
 	for(auto & t : json["specialBattlefields"].Vector())
 		info->allowedSpecialBfields.emplace_back(t.String());
 	info->blockedTiles = json["blockedTiles"].convertTo<std::vector<si16>>();

+ 2 - 2
lib/ObstacleHandler.h

@@ -33,7 +33,7 @@ public:
 	si32 iconIndex;
 	std::string identifier;
 	std::string appearAnimation, animation, dissapearAnimation;
-	std::vector<Terrain> allowedTerrains;
+	std::vector<TerrainId> allowedTerrains;
 	std::vector<std::string> allowedSpecialBfields;
 	
 	//TODO: here is extra field to implement it's logic in the future but save backward compatibility
@@ -52,7 +52,7 @@ public:
 	
 	std::vector<BattleHex> getBlocked(BattleHex hex) const; //returns vector of hexes blocked by obstacle when it's placed on hex 'hex'
 	
-	bool isAppropriate(const Terrain & terrainType, const BattleField & specialBattlefield) const;
+	bool isAppropriate(const TerrainId terrainType, const BattleField & specialBattlefield) const;
 	
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{

+ 7 - 7
lib/PathfinderUtil.h

@@ -20,7 +20,7 @@ namespace PathfinderUtil
 	using ELayer = EPathfindingLayer;
 
 	template<EPathfindingLayer::EEPathfindingLayer layer>
-	CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo, FoW fow, const PlayerColor player, const CGameState * gs)
+	CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile & tinfo, FoW fow, const PlayerColor player, const CGameState * gs)
 	{
 		if(!(*fow)[pos.z][pos.x][pos.y])
 			return CGPathNode::BLOCKED;
@@ -29,15 +29,15 @@ namespace PathfinderUtil
 		{
 		case ELayer::LAND:
 		case ELayer::SAIL:
-			if(tinfo->visitable)
+			if(tinfo.visitable)
 			{
-				if(tinfo->visitableObjects.front()->ID == Obj::SANCTUARY && tinfo->visitableObjects.back()->ID == Obj::HERO && tinfo->visitableObjects.back()->tempOwner != player) //non-owned hero stands on Sanctuary
+				if(tinfo.visitableObjects.front()->ID == Obj::SANCTUARY && tinfo.visitableObjects.back()->ID == Obj::HERO && tinfo.visitableObjects.back()->tempOwner != player) //non-owned hero stands on Sanctuary
 				{
 					return CGPathNode::BLOCKED;
 				}
 				else
 				{
-					for(const CGObjectInstance * obj : tinfo->visitableObjects)
+					for(const CGObjectInstance * obj : tinfo.visitableObjects)
 					{
 						if(obj->blockVisit)
 							return CGPathNode::BLOCKVIS;
@@ -48,7 +48,7 @@ namespace PathfinderUtil
 					}
 				}
 			}
-			else if(tinfo->blocked)
+			else if(tinfo.blocked)
 			{
 				return CGPathNode::BLOCKED;
 			}
@@ -61,13 +61,13 @@ namespace PathfinderUtil
 			break;
 
 		case ELayer::WATER:
-			if(tinfo->blocked || tinfo->terType.isLand())
+			if(tinfo.blocked || tinfo.terType->isLand())
 				return CGPathNode::BLOCKED;
 
 			break;
 
 		case ELayer::AIR:
-			if(tinfo->blocked || tinfo->terType.isLand())
+			if(tinfo.blocked || tinfo.terType->isLand())
 				return CGPathNode::FLYABLE;
 
 			break;

+ 344 - 96
lib/Terrain.cpp

@@ -19,31 +19,24 @@ VCMI_LIB_NAMESPACE_BEGIN
 //("allowedTerrain"\s*:\s*\[.*)9(.*\],\n)
 //\1"rock"\2
 
-const Terrain Terrain::ANY("ANY");
-
-Terrain Terrain::createTerrainTypeH3M(int tId)
+TerrainTypeHandler::TerrainTypeHandler()
 {
-	static std::array<std::string, 10> terrainsH3M
-	{
-		"dirt", "sand", "grass", "snow", "swamp", "rough", "subterra", "lava", "water", "rock"
-	};
-	return Terrain(terrainsH3M.at(tId));
-}
+	auto allConfigs = VLC->modh->getActiveMods();
+	allConfigs.insert(allConfigs.begin(), "core");
 
-Terrain Terrain::createTerrainByCode(const std::string & typeCode)
-{
-	for(const auto & terrain : Manager::terrains())
-	{
-		if(Manager::getInfo(terrain).typeCode == typeCode)
-			return terrain;
-	}
-	return Terrain::ANY;
+	initRivers(allConfigs);
+	recreateRiverMaps();
+	initRoads(allConfigs);
+	recreateRoadMaps();
+	initTerrains(allConfigs); //maps will be populated inside
 }
 
-Terrain::Manager::Manager()
+void TerrainTypeHandler::initTerrains(const std::vector<std::string> & allConfigs)
 {
-	auto allConfigs = VLC->modh->getActiveMods();
-	allConfigs.insert(allConfigs.begin(), "core");
+	std::vector<std::function<void()>> resolveLater;
+
+	objects.resize(Terrain::ORIGINAL_TERRAIN_COUNT); //make space for original terrains
+
 	for(auto & mod : allConfigs)
 	{
 		if(!CResourceHandler::get(mod)->existsResource(ResourceID("config/terrains.json")))
@@ -52,8 +45,9 @@ Terrain::Manager::Manager()
 		JsonNode terrs(mod, ResourceID("config/terrains.json"));
 		for(auto & terr : terrs.Struct())
 		{
-			Terrain::Info info;
-			info.moveCost = terr.second["moveCost"].Integer();
+			TerrainType info(terr.first); //set name
+
+			info.moveCost = static_cast<int>(terr.second["moveCost"].Integer());
 			const JsonVector &unblockedVec = terr.second["minimapUnblocked"].Vector();
 			info.minimapUnblocked =
 			{
@@ -74,42 +68,47 @@ Terrain::Manager::Manager()
 			
 			if(terr.second["type"].isNull())
 			{
-				info.type = Terrain::Info::Type::Land;
+				info.passabilityType = TerrainType::PassabilityType::LAND | TerrainType::PassabilityType::SURFACE;
 			}
-			else
+			else if (terr.second["type"].getType() == JsonNode::JsonType::DATA_VECTOR)
 			{
-				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 == "ROCK") info.type = Terrain::Info::Type::Rock;
-				if(s == "SUB") info.type = Terrain::Info::Type::Subterranean;
-			}
-			
-			if(terr.second["rockTerrain"].isNull())
-			{
-				info.rockTerrain = "rock";
+				for(const auto& node : terr.second["type"].Vector())
+				{
+					//Set bits
+					auto s = node.String();
+					if (s == "LAND") info.passabilityType |= TerrainType::PassabilityType::LAND;
+					if (s == "WATER") info.passabilityType |= TerrainType::PassabilityType::WATER;
+					if (s == "ROCK") info.passabilityType |= TerrainType::PassabilityType::ROCK;
+					if (s == "SURFACE") info.passabilityType |= TerrainType::PassabilityType::SURFACE;
+					if (s == "SUB") info.passabilityType |= TerrainType::PassabilityType::SUBTERRANEAN;
+				}
 			}
-			else
+			else  //should be string - one option only
 			{
-				info.rockTerrain = terr.second["rockTerrain"].String();
+				auto s = terr.second["type"].String();
+				if (s == "LAND") info.passabilityType = TerrainType::PassabilityType::LAND;
+				if (s == "WATER") info.passabilityType = TerrainType::PassabilityType::WATER;
+				if (s == "ROCK") info.passabilityType = TerrainType::PassabilityType::ROCK;
+				if (s == "SURFACE") info.passabilityType = TerrainType::PassabilityType::SURFACE;
+				if (s == "SUB") info.passabilityType = TerrainType::PassabilityType::SUBTERRANEAN;
 			}
 			
 			if(terr.second["river"].isNull())
 			{
-				info.river = RIVER_NAMES[0];
+				info.river = River::NO_RIVER;
 			}
 			else
 			{
-				info.river = terr.second["river"].String();
+				info.river = getRiverByCode(terr.second["river"].String())->id;
 			}
 			
 			if(terr.second["horseSoundId"].isNull())
 			{
-				info.horseSoundId = 9; //rock sound as default
+				info.horseSoundId = Terrain::ROCK; //rock sound as default
 			}
 			else
 			{
-				info.horseSoundId = terr.second["horseSoundId"].Integer();
+				info.horseSoundId = static_cast<int>(terr.second["horseSoundId"].Float());
 			}
 			
 			if(!terr.second["text"].isNull())
@@ -135,14 +134,6 @@ Terrain::Manager::Manager()
 				}
 			}
 			
-			if(!terr.second["prohibitTransitions"].isNull())
-			{
-				for(auto & t : terr.second["prohibitTransitions"].Vector())
-				{
-					info.prohibitTransitions.emplace_back(t.String());
-				}
-			}
-			
 			info.transitionRequired = false;
 			if(!terr.second["transitionRequired"].isNull())
 			{
@@ -154,104 +145,361 @@ Terrain::Manager::Manager()
 			{
 				info.terrainViewPatterns = terr.second["terrainViewPatterns"].String();
 			}
-			
-			terrainInfo[terr.first] = info;
-			if(!terrainId.count(terr.first))
+
+			if(!terr.second["originalTerrainId"].isNull())
+			{
+				//place in reserved slot
+				info.id = (TerrainId)(terr.second["originalTerrainId"].Float());
+				objects[info.id] = info;
+			}
+			else
+			{
+				//append at the end
+				info.id = static_cast<TerrainId>(objects.size());
+				objects.push_back(info);
+			}
+			TerrainId id = info.id;
+
+			//Update terrain with this id in the future, after all terrain types are populated
+
+			if(!terr.second["prohibitTransitions"].isNull())
+			{
+				for(auto & t : terr.second["prohibitTransitions"].Vector())
+				{
+					std::string prohibitedTerrainName = t.String();
+					resolveLater.push_back([this, prohibitedTerrainName, id]()
+					{
+						//FIXME: is that reference to the element in vector?
+						objects[id].prohibitTransitions.emplace_back(getInfoByName(prohibitedTerrainName)->id);
+					});
+				}
+			}
+
+			if(terr.second["rockTerrain"].isNull())
+			{
+				objects[id].rockTerrain = Terrain::ROCK;
+			}
+			else
+			{
+				auto rockTerrainName = terr.second["rockTerrain"].String();
+				resolveLater.push_back([this, rockTerrainName, id]()
+				{
+					//FIXME: is that reference to the element in vector?
+						objects[id].rockTerrain = getInfoByName(rockTerrainName)->id;
+				});
+			}
+		}
+	}
+
+	for(size_t i = Terrain::FIRST_REGULAR_TERRAIN; i < Terrain::ORIGINAL_TERRAIN_COUNT; i++)
+	{
+		//Make sure that original terrains are loaded
+		assert(objects(i).id != Terrain::WRONG);
+	}
+
+	recreateTerrainMaps();
+
+	for(auto& functor : resolveLater)
+	{
+		functor();
+	}
+}
+
+void TerrainTypeHandler::initRivers(const std::vector<std::string> & allConfigs)
+{
+	riverTypes.resize(River::ORIGINAL_RIVER_COUNT); //make space for original rivers
+	//First object will be default NO_RIVER
+
+	for(auto & mod : allConfigs)
+	{
+		if (!CResourceHandler::get(mod)->existsResource(ResourceID("config/rivers.json")))
+			continue;
+
+		JsonNode rivs(mod, ResourceID("config/rivers.json"));
+		for(auto & river : rivs.Struct())
+		{
+			RiverType info;
+
+			info.fileName = river.second["animation"].String();
+			info.code = river.second["code"].String();
+			info.deltaName = river.second["delta"].String();
+
+			if (!river.second["originalRiverId"].isNull())
+			{
+				info.id = static_cast<RiverId>(river.second["originalRiverId"].Float());
+				riverTypes[info.id] = info;
+			}
+			else
+			{
+				info.id = static_cast<RiverId>(riverTypes.size());
+				riverTypes.push_back(info);
+			}
+		}
+	}
+
+	recreateRiverMaps();
+}
+
+void TerrainTypeHandler::initRoads(const std::vector<std::string> & allConfigs)
+{
+	roadTypes.resize(Road::ORIGINAL_ROAD_COUNT); //make space for original rivers
+	//first object will be default NO_ROAD
+
+	for(auto & mod : allConfigs)
+	{
+		if (!CResourceHandler::get(mod)->existsResource(ResourceID("config/roads.json")))
+			continue;
+
+		JsonNode rds(mod, ResourceID("config/roads.json"));
+		for(auto & road : rds.Struct())
+		{
+			RoadType info;
+
+			info.fileName = road.second["animation"].String();
+			info.code = road.second["code"].String();
+			info.movementCost = static_cast<ui8>(road.second["moveCost"].Float());
+
+			if (!road.second["originalRoadId"].isNull())
 			{
-				terrainId[terr.first] = terrainVault.size();
-				terrainVault.push_back(terr.first);
+				info.id = static_cast<RoadId>(road.second["originalRoadId"].Float());
+				roadTypes[info.id] = info;
+			}
+			else
+			{
+				info.id = static_cast<RoadId>(roadTypes.size());
+				roadTypes.push_back(info);
 			}
 		}
 	}
+
+	recreateRoadMaps();
 }
 
-Terrain::Manager & Terrain::Manager::get()
+void TerrainTypeHandler::recreateTerrainMaps()
 {
-	static Terrain::Manager manager;
-	return manager;
+	//This assumes the vector will never be updated or reallocated in the future
+
+	for(size_t i = 0; i < objects.size(); i++)
+	{
+		const auto * terrainInfo = &objects[i];
+
+		terrainInfoByName[terrainInfo->name] = terrainInfo;
+		terrainInfoByCode[terrainInfo->typeCode] = terrainInfo;
+		terrainInfoById[terrainInfo->id] = terrainInfo;
+	}
 }
 
-const std::vector<Terrain> & Terrain::Manager::terrains()
+void TerrainTypeHandler::recreateRiverMaps()
 {
-	return Terrain::Manager::get().terrainVault;
+	for(size_t i = River::FIRST_REGULAR_RIVER ; i < riverTypes.size(); i++)
+	{
+		const auto * riverInfo = &riverTypes[i];
+
+		riverInfoByName[riverInfo->fileName] = riverInfo;
+		riverInfoByCode[riverInfo->code] = riverInfo;
+		riverInfoById[riverInfo->id] = riverInfo;
+	}
 }
 
-int Terrain::Manager::id(const Terrain & terrain)
+void TerrainTypeHandler::recreateRoadMaps()
 {
-	if(terrain.name == "ANY") return -3;
-	if(terrain.name == "WRONG") return -2;
-	if(terrain.name == "BORDER") return -1;
-	
-	return Terrain::Manager::get().terrainId.at(terrain);
+	for(size_t i = Road::FIRST_REGULAR_ROAD ; i < roadTypes.size(); i++)
+	{
+		const auto * roadInfo = &roadTypes[i];
+
+		roadInfoByName[roadInfo->fileName] = roadInfo;
+		roadInfoByCode[roadInfo->code] = roadInfo;
+		roadInfoById[roadInfo->id] = roadInfo;
+	}
+}
+
+const std::vector<TerrainType> & TerrainTypeHandler::terrains() const
+{
+	//FIXME: somehow make it non-copyable? Pointers must point to original data and not its copy
+	return objects;
+}
+
+const std::vector<RiverType>& TerrainTypeHandler::rivers() const
+{
+	return riverTypes;
+}
+
+const std::vector<RoadType>& TerrainTypeHandler::roads() const
+{
+	return roadTypes;
+}
+
+const TerrainType* TerrainTypeHandler::getInfoByName(const std::string& terrainName) const
+{
+	return terrainInfoByName.at(terrainName);
+}
+
+const TerrainType* TerrainTypeHandler::getInfoByCode(const std::string& terrainCode) const
+{
+	return terrainInfoByCode.at(terrainCode);
+}
+
+const TerrainType* TerrainTypeHandler::getInfoById(TerrainId id) const
+{
+	return terrainInfoById.at(id);
+}
+
+const RiverType* TerrainTypeHandler::getRiverByName(const std::string& riverName) const
+{
+	return riverInfoByName.at(riverName);
 }
 
-const Terrain::Info & Terrain::Manager::getInfo(const Terrain & terrain)
+const RiverType* TerrainTypeHandler::getRiverByCode(const std::string& riverCode) const
 {
-	return Terrain::Manager::get().terrainInfo.at(static_cast<std::string>(terrain));
+	return riverInfoByCode.at(riverCode);
 }
 
-std::ostream & operator<<(std::ostream & os, const Terrain terrainType)
+const RiverType* TerrainTypeHandler::getRiverById(RiverId id) const
+{
+	return riverInfoById.at(id);
+}
+
+const RoadType* TerrainTypeHandler::getRoadByName(const std::string& roadName) const
+{
+	return roadInfoByName.at(roadName);
+}
+
+const RoadType* TerrainTypeHandler::getRoadByCode(const std::string& roadCode) const
+{
+	return roadInfoByCode.at(roadCode);
+}
+
+const RoadType* TerrainTypeHandler::getRoadById(RoadId id) const
+{
+	return roadInfoById.at(id);
+}
+
+std::ostream & operator<<(std::ostream & os, const TerrainType & terrainType)
 {
 	return os << static_cast<const std::string &>(terrainType);
 }
 	
-Terrain::operator std::string() const
+TerrainType::operator std::string() const
 {
 	return name;
 }
-
-Terrain::Terrain(const std::string & _name) : name(_name)
-{}
 	
-Terrain& Terrain::operator=(const std::string & _name)
+TerrainType::TerrainType(const std::string& _name):
+	minimapBlocked({0,0,0}), //black
+	minimapUnblocked({ 128,128,128 }), //grey
+	name(_name),
+	river(River::NO_RIVER),
+	id(Terrain::WRONG),
+	rockTerrain(Terrain::ROCK),
+	moveCost(GameConstants::BASE_MOVEMENT_COST),
+	horseSoundId(0),
+	passabilityType(0),
+	transitionRequired(false)
+{
+}
+
+TerrainType& TerrainType::operator=(const TerrainType & other)
 {
-	name = _name;
+	battleFields = other.battleFields;
+	prohibitTransitions = other.prohibitTransitions;
+	minimapBlocked = other.minimapBlocked;
+	minimapUnblocked = other.minimapUnblocked;
+	name = other.name;
+	musicFilename = other.musicFilename;
+	tilesFilename = other.tilesFilename;
+	terrainText = other.terrainText;
+	typeCode = other.typeCode;
+	terrainViewPatterns = other.terrainViewPatterns;
+	rockTerrain = other.rockTerrain;
+	river = other.river;
+
+	id = other.id;
+	moveCost = other.moveCost;
+	horseSoundId = other.horseSoundId;
+	passabilityType = other.passabilityType;
+	transitionRequired = other.transitionRequired;
+
 	return *this;
 }
+	
+bool TerrainType::operator==(const TerrainType& other)
+{
+	return id == other.id;
+}
 
-bool operator==(const Terrain & l, const Terrain & r)
+bool TerrainType::operator!=(const TerrainType& other)
 {
-	return l.name == r.name;
+	return id != other.id;
 }
 
-bool operator!=(const Terrain & l, const Terrain & r)
+bool TerrainType::operator<(const TerrainType& other)
 {
-	return l.name != r.name;
+	return id < other.id;
 }
 	
-bool operator<(const Terrain & l, const Terrain & r)
+bool TerrainType::isLand() const
 {
-	return l.name < r.name;
+	return !isWater();
 }
-	
-int Terrain::id() const
+
+bool TerrainType::isWater() const
 {
-	return Terrain::Manager::id(*this);
+	return passabilityType & PassabilityType::WATER;
 }
-	
-bool Terrain::isLand() const
+
+bool TerrainType::isPassable() const
 {
-	return !isWater();
+	return !(passabilityType & PassabilityType::ROCK);
 }
-bool Terrain::isWater() const
+
+bool TerrainType::isSurface() const
 {
-	return Terrain::Manager::getInfo(*this).type == Terrain::Info::Type::Water;
+	return passabilityType & PassabilityType::SURFACE;
 }
-bool Terrain::isPassable() const
+
+bool TerrainType::isUnderground() const
 {
-	return Terrain::Manager::getInfo(*this).type != Terrain::Info::Type::Rock;
+	return passabilityType & PassabilityType::SUBTERRANEAN;
 }
-bool Terrain::isUnderground() const
+
+bool TerrainType::isTransitionRequired() const
 {
-	return Terrain::Manager::getInfo(*this).type == Terrain::Info::Type::Subterranean;
+	return transitionRequired;
 }
-bool Terrain::isNative() const
+
+RiverType::RiverType(const std::string & fileName, const std::string & code, RiverId id):
+	fileName(fileName),
+	code(code),
+	id(id)
 {
-	return name.empty();
 }
-bool Terrain::isTransitionRequired() const
+
+RiverType& RiverType::operator=(const RiverType& other)
 {
-	return Terrain::Manager::getInfo(*this).transitionRequired;
+	fileName = other.fileName;
+	code = other.code;
+	deltaName = other.deltaName;
+	id = other.id;
+
+	return *this;
+}
+
+RoadType::RoadType(const std::string& fileName, const std::string& code, RoadId id):
+	fileName(fileName),
+	code(code),
+	id(id),
+	movementCost(GameConstants::BASE_MOVEMENT_COST)
+{
+}
+
+RoadType& RoadType::operator=(const RoadType& other)
+{
+	fileName = other.fileName;
+	code = other.code;
+	id = other.id;
+	movementCost = other.movementCost;
+
+	return *this;
 }
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

+ 157 - 66
lib/Terrain.h

@@ -16,93 +16,184 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class DLL_LINKAGE Terrain
+class DLL_LINKAGE TerrainType
 {
 public:
 	
-	friend class Manager;
-	
-	struct Info
-	{
-		enum class Type
-		{
-			Land, Water, Subterranean, Rock
-		};
-		
-		int moveCost;
-		bool transitionRequired;
-		std::array<int, 3> minimapBlocked;
-		std::array<int, 3> minimapUnblocked;
-		std::string musicFilename;
-		std::string tilesFilename;
-		std::string terrainText;
-		std::string typeCode;
-		std::string terrainViewPatterns;
-		std::string rockTerrain;
-		std::string river;
-		int horseSoundId;
-		Type type;
-		std::vector<std::string> battleFields;
-		std::vector<Terrain> prohibitTransitions;
-	};
-	
-	class DLL_LINKAGE Manager
+	enum PassabilityType : ui8
 	{
-	public:
-		static const std::vector<Terrain> & terrains();
-		static const Info & getInfo(const Terrain &);
-		static int id(const Terrain &);
-		
-	private:
-		static Manager & get();
-		Manager();
-		
-		std::unordered_map<std::string, Info> terrainInfo;
-		std::vector<Terrain> terrainVault;
-		std::map<Terrain, int> terrainId;
+		LAND = 1,
+		WATER = 2,
+		SURFACE = 4,
+		SUBTERRANEAN = 8,
+		ROCK = 16
 	};
 	
-	/*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 std::string & _type);
+	std::vector<std::string> battleFields;
+	std::vector<TerrainId> prohibitTransitions;
+	std::array<int, 3> minimapBlocked;
+	std::array<int, 3> minimapUnblocked;
+	std::string name;
+	std::string musicFilename;
+	std::string tilesFilename;
+	std::string terrainText;
+	std::string typeCode;
+	std::string terrainViewPatterns;
+	RiverId river;
+
+	TerrainId id;
+	TerrainId rockTerrain;
+	int moveCost;
+	int horseSoundId;
+	ui8 passabilityType;
+	bool transitionRequired;
 	
-	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);
+	TerrainType(const std::string & name = "");
+
+	TerrainType& operator=(const TerrainType & other);
 	
-	static const Terrain ANY;
+	bool operator==(const TerrainType & other);
+	bool operator!=(const TerrainType & other);
+	bool operator<(const TerrainType & other);
 	
 	bool isLand() const;
 	bool isWater() const;
-	bool isPassable() const; //ROCK
+	bool isPassable() const;
+	bool isSurface() const;
 	bool isUnderground() const;
-	bool isNative() const;
 	bool isTransitionRequired() const;
-	
 		
 	operator std::string() const;
 	
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & battleFields;
+		h & prohibitTransitions;
+		h & minimapBlocked;
+		h & minimapUnblocked;
 		h & name;
+		h & musicFilename;
+		h & tilesFilename;
+		h & terrainText;
+		h & typeCode;
+		h & terrainViewPatterns;
+		h & rockTerrain;
+		h & river;
+
+		h & id;
+		h & moveCost;
+		h & horseSoundId;
+		h & passabilityType;
+		h & transitionRequired;
 	}
-	
-protected:
-	
-	std::string name;
 };
 
-DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const Terrain terrainType);
+class DLL_LINKAGE RiverType
+{
+public:
+
+	std::string fileName;
+	std::string code;
+	std::string deltaName;
+	RiverId id;
+
+	RiverType(const std::string & fileName = "", const std::string & code = "", RiverId id = River::NO_RIVER);
+
+	RiverType& operator=(const RiverType & other);
+
+	template <typename Handler> void serialize(Handler& h, const int version)
+	{
+		h & fileName;
+		h & code;
+		h & deltaName;
+		h & id;
+	}
+};
+
+class DLL_LINKAGE RoadType
+{
+public:
+	std::string fileName;
+	std::string code;
+	RoadId id;
+	ui8 movementCost;
+
+	RoadType(const std::string & fileName = "", const std::string& code = "", RoadId id = Road::NO_ROAD);
+
+	RoadType& operator=(const RoadType & other);
+
+	template <typename Handler> void serialize(Handler& h, const int version)
+	{
+		h & fileName;
+		h & code;
+		h & id;
+		h & movementCost;
+	}
+};
+
+DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const TerrainType & terrainType);
+
+class DLL_LINKAGE TerrainTypeHandler //TODO: public IHandlerBase ?
+{
+public:
+
+	TerrainTypeHandler();
+	~TerrainTypeHandler() {};
+
+	const std::vector<TerrainType> & terrains() const;
+	const TerrainType * getInfoByName(const std::string & terrainName) const;
+	const TerrainType * getInfoByCode(const std::string & terrainCode) const;
+	const TerrainType * getInfoById(TerrainId id) const;
+
+	const std::vector<RiverType> & rivers() const;
+	const RiverType * getRiverByName(const std::string & riverName) const;
+	const RiverType * getRiverByCode(const std::string & riverCode) const;
+	const RiverType * getRiverById(RiverId id) const;
+
+	const std::vector<RoadType> & roads() const;
+	const RoadType * getRoadByName(const std::string & roadName) const;
+	const RoadType * getRoadByCode(const std::string & roadCode) const;
+	const RoadType * getRoadById(RoadId id) const;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & objects;
+		h & riverTypes;
+		h & roadTypes;
+
+		if (!h.saving)
+		{
+			recreateTerrainMaps();
+			recreateRiverMaps();
+			recreateRoadMaps();
+		}
+	}
+
+private:
+
+	std::vector<TerrainType> objects;
+	std::vector<RiverType> riverTypes;
+	std::vector<RoadType> roadTypes;
+
+	std::unordered_map<std::string, const TerrainType*> terrainInfoByName;
+	std::unordered_map<std::string, const TerrainType*> terrainInfoByCode;
+	std::unordered_map<TerrainId, const TerrainType*> terrainInfoById;
+
+	std::unordered_map<std::string, const RiverType*> riverInfoByName;
+	std::unordered_map<std::string, const RiverType*> riverInfoByCode;
+	std::unordered_map<RiverId, const RiverType*> riverInfoById;
+
+	std::unordered_map<std::string, const RoadType*> roadInfoByName;
+	std::unordered_map<std::string, const RoadType*> roadInfoByCode;
+	std::unordered_map<RoadId, const RoadType*> roadInfoById;
+
+	void initTerrains(const std::vector<std::string> & allConfigs);
+	void initRivers(const std::vector<std::string> & allConfigs);
+	void initRoads(const std::vector<std::string> & allConfigs);
+	void recreateTerrainMaps();
+	void recreateRiverMaps();
+	void recreateRoadMaps();
+
+};
 
 VCMI_LIB_NAMESPACE_END

+ 2 - 0
lib/VCMI_Lib.cpp

@@ -197,6 +197,8 @@ void LibClasses::init(bool onlyEssential)
 
 	createHandler(bth, "Bonus type", pomtime);
 
+	createHandler(terrainTypeHandler, "Terrain", pomtime);
+
 	createHandler(generaltexth, "General text", pomtime);
 
 	createHandler(heroh, "Hero", pomtime);

+ 3 - 0
lib/VCMI_Lib.h

@@ -29,6 +29,7 @@ class CContentHandler;
 class BattleFieldHandler;
 class IBonusTypeHandler;
 class CBonusTypeHandler;
+class TerrainTypeHandler;
 class ObstacleHandler;
 class CTerrainViewPatternConfig;
 class CRmgTemplateStorage;
@@ -85,6 +86,7 @@ public:
 	CTownHandler * townh;
 	CGeneralTextHandler * generaltexth;
 	CModHandler * modh;
+	TerrainTypeHandler * terrainTypeHandler;
 	CTerrainViewPatternConfig * terviewh;
 	CRmgTemplateStorage * tplh;
 	BattleFieldHandler * battlefieldsHandler;
@@ -125,6 +127,7 @@ public:
 		h & skillh;
 		h & battlefieldsHandler;
 		h & obstacleHandler;
+		h & terrainTypeHandler;
 
 		if(!h.saving)
 		{

+ 2 - 2
lib/battle/BattleInfo.cpp

@@ -191,7 +191,7 @@ struct RangeGenerator
 	std::function<int()> myRand;
 };
 
-BattleInfo * BattleInfo::setupBattle(const int3 & tile, const Terrain & terrain, const BattleField & battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town)
+BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const BattleField & battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town)
 {
 	CMP_stack cmpst;
 	auto curB = new BattleInfo();
@@ -565,7 +565,7 @@ BattleField BattleInfo::getBattlefieldType() const
 	return battlefieldType;
 }
 
-Terrain BattleInfo::getTerrainType() const
+TerrainId BattleInfo::getTerrainType() const
 {
 	return terrainType;
 }

+ 3 - 4
lib/battle/BattleInfo.h

@@ -20,7 +20,6 @@ VCMI_LIB_NAMESPACE_BEGIN
 class CStack;
 class CStackInstance;
 class CStackBasicDescriptor;
-class Terrain;
 class BattleField;
 
 class DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallback, public IBattleState
@@ -40,7 +39,7 @@ public:
 	SiegeInfo si;
 
 	BattleField battlefieldType; //like !!BA:B
-	Terrain terrainType; //used for some stack nativity checks (not the bonus limiters though that have their own copy)
+	TerrainId 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)
@@ -76,7 +75,7 @@ public:
 	battle::Units getUnitsIf(battle::UnitFilter predicate) const override;
 
 	BattleField getBattlefieldType() const override;
-	Terrain getTerrainType() const override;
+	TerrainId getTerrainType() const override;
 
 	ObstacleCList getAllObstacles() const override;
 
@@ -141,7 +140,7 @@ public:
 	const CGHeroInstance * getHero(PlayerColor player) const; //returns fighting hero that belongs to given player
 
 	void localInit();
-	static BattleInfo * setupBattle(const int3 & tile, const Terrain & terrain, const BattleField & battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town);
+	static BattleInfo * setupBattle(const int3 & tile, TerrainId, const BattleField & battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town);
 
 	ui8 whatSide(PlayerColor player) const;
 

+ 1 - 1
lib/battle/BattleProxy.cpp

@@ -49,7 +49,7 @@ BattleField BattleProxy::getBattlefieldType() const
 	return subject->battleGetBattlefieldType();
 }
 
-Terrain BattleProxy::getTerrainType() const
+TerrainId BattleProxy::getTerrainType() const
 {
 	return subject->battleTerrainType();
 }

+ 1 - 1
lib/battle/BattleProxy.h

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

+ 2 - 2
lib/battle/CBattleInfoEssentials.cpp

@@ -16,9 +16,9 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-Terrain CBattleInfoEssentials::battleTerrainType() const
+TerrainId CBattleInfoEssentials::battleTerrainType() const
 {
-	RETURN_IF_NOT_BATTLE(Terrain());
+	RETURN_IF_NOT_BATTLE(TerrainId());
 	return getBattle()->getTerrainType();
 }
 

+ 1 - 1
lib/battle/CBattleInfoEssentials.h

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

+ 2 - 2
lib/battle/IBattleInfoCallback.h

@@ -10,13 +10,13 @@
 
 #pragma once
 
+#include "GameConstants.h"
 #include "BattleHex.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
 struct CObstacleInstance;
 class BattleField;
-class Terrain;
 
 namespace battle
 {
@@ -40,7 +40,7 @@ public:
 	virtual scripting::Pool * getContextPool() const = 0;
 #endif
 
-	virtual Terrain battleTerrainType() const = 0;
+	virtual TerrainId battleTerrainType() const = 0;
 	virtual BattleField battleGetBattlefieldType() const = 0;
 
 	///return none if battle is ongoing; otherwise the victorious side (0/1) or 2 if it is a draw

+ 1 - 1
lib/battle/IBattleState.h

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

+ 14 - 29
lib/mapObjects/CGHeroInstance.cpp

@@ -81,32 +81,16 @@ 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 != ROAD_NAMES[0] && from.roadType != ROAD_NAMES[0])
+	if(dest.roadType->id && from.roadType->id)
 	{
-		int roadPos = std::min(vstd::find_pos(ROAD_NAMES, dest.roadType), vstd::find_pos(ROAD_NAMES, from.roadType)); //used road ID
-		switch(roadPos)
-		{
-		case 1:
-			ret = 75;
-			break;
-		case 2:
-			ret = 65;
-			break;
-		case 3:
-			ret = 50;
-			break;
-		default:
-			logGlobal->error("Unknown road type: %d", roadPos);
-			break;
-		}
+		ret = std::max(dest.roadType->movementCost, from.roadType->movementCost);
 	}
-	else if(ti->nativeTerrain != from.terType //the terrain is not native
-		&& ti->nativeTerrain != Terrain::ANY //no special creature bonus
-		&& !ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType.id()) //no special movement bonus
-		)
+	else if(ti->nativeTerrain != from.terType->id &&//the terrain is not native
+			ti->nativeTerrain != Terrain::ANY_TERRAIN && //no special creature bonus
+			!ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType->id)) //no special movement bonus
 	{
 
-		ret = VLC->heroh->terrCosts[from.terType];
+		ret = VLC->heroh->terrCosts[from.terType->id];
 		ret -= ti->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::PATHFINDING);
 		if(ret < GameConstants::BASE_MOVEMENT_COST)
 			ret = GameConstants::BASE_MOVEMENT_COST;
@@ -114,7 +98,7 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile & dest, const TerrainTile & f
 	return (ui32)ret;
 }
 
-Terrain CGHeroInstance::getNativeTerrain() const
+TerrainId 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 +106,18 @@ Terrain 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?
-	Terrain nativeTerrain("BORDER");
+	TerrainId nativeTerrain = Terrain::BORDER;
 
 	for(auto stack : stacks)
 	{
-		Terrain stackNativeTerrain = stack.second->type->getNativeTerrain(); //consider terrain bonuses e.g. Lodestar.
+		TerrainId stackNativeTerrain = stack.second->type->getNativeTerrain(); //consider terrain bonuses e.g. Lodestar.
 
-		if(stackNativeTerrain == Terrain("BORDER"))
+		if(stackNativeTerrain == Terrain::BORDER) //where does this value come from?
 			continue;
-		if(nativeTerrain == Terrain("BORDER"))
+		if(nativeTerrain == Terrain::BORDER)
 			nativeTerrain = stackNativeTerrain;
 		else if(nativeTerrain != stackNativeTerrain)
-			return Terrain("BORDER");
+			return Terrain::BORDER;
 	}
 	return nativeTerrain;
 }
@@ -531,7 +515,8 @@ void CGHeroInstance::initObj(CRandomGenerator & rand)
 
 	if (ID != Obj::PRISON)
 	{
-		auto customApp = VLC->objtypeh->getHandlerFor(ID, type->heroClass->getIndex())->getOverride(cb->gameState()->getTile(visitablePos())->terType, this);
+		auto terrain = cb->gameState()->getTile(visitablePos())->terType->id;
+		auto customApp = VLC->objtypeh->getHandlerFor(ID, type->heroClass->getIndex())->getOverride(terrain, this);
 		if (customApp)
 			appearance = customApp;
 	}

+ 1 - 1
lib/mapObjects/CGHeroInstance.h

@@ -157,7 +157,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
-	Terrain getNativeTerrain() const;
+	TerrainId 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

+ 2 - 1
lib/mapObjects/CGTownInstance.cpp

@@ -1132,8 +1132,9 @@ void CGTownInstance::setType(si32 ID, si32 subID)
 
 void CGTownInstance::updateAppearance()
 {
+	auto terrain = cb->gameState()->getTile(visitablePos())->terType->id;
 	//FIXME: not the best way to do this
-	auto app = VLC->objtypeh->getHandlerFor(ID, subID)->getOverride(cb->gameState()->getTile(visitablePos())->terType, this);
+	auto app = VLC->objtypeh->getHandlerFor(ID, subID)->getOverride(terrain, this);
 	if (app)
 		appearance = app;
 }

+ 11 - 4
lib/mapObjects/CObjectClassesHandler.cpp

@@ -493,8 +493,15 @@ void AObjectTypeHandler::init(const JsonNode & input, boost::optional<std::strin
 		tmpl->id = Obj(type);
 		tmpl->subid = subtype;
 		tmpl->stringID = entry.first; // FIXME: create "fullID" - type.object.template?
-		tmpl->readJson(entry.second);
-		templates.push_back(std::shared_ptr<const ObjectTemplate>(tmpl));
+		try
+		{
+			tmpl->readJson(entry.second);
+			templates.push_back(std::shared_ptr<const ObjectTemplate>(tmpl));
+		}
+		catch (const std::exception & e)
+		{
+			logGlobal->warn("Failed to load terrains for object %s: %s", entry.first, e.what());
+		}
 	}
 
 	if (input["name"].isNull())
@@ -583,7 +590,7 @@ BattleField AObjectTypeHandler::getBattlefield() const
 	return battlefield ? BattleField::fromString(battlefield.get()) : BattleField::NONE;
 }
 
-std::vector<std::shared_ptr<const ObjectTemplate>>AObjectTypeHandler::getTemplates(const Terrain & terrainType) const
+std::vector<std::shared_ptr<const ObjectTemplate>>AObjectTypeHandler::getTemplates(TerrainId terrainType) const
 {
 	std::vector<std::shared_ptr<const ObjectTemplate>> templates = getTemplates();
 	std::vector<std::shared_ptr<const ObjectTemplate>> filtered;
@@ -600,7 +607,7 @@ std::vector<std::shared_ptr<const ObjectTemplate>>AObjectTypeHandler::getTemplat
 		return filtered;
 }
 
-std::shared_ptr<const ObjectTemplate> AObjectTypeHandler::getOverride(const Terrain & terrainType, const CGObjectInstance * object) const
+std::shared_ptr<const ObjectTemplate> AObjectTypeHandler::getOverride(TerrainId terrainType, const CGObjectInstance * object) const
 {
 	std::vector<std::shared_ptr<const ObjectTemplate>> ret = getTemplates(terrainType);
 	for (const auto & tmpl: ret)

+ 2 - 2
lib/mapObjects/CObjectClassesHandler.h

@@ -184,11 +184,11 @@ public:
 
 	/// returns all templates matching parameters
 	std::vector<std::shared_ptr<const ObjectTemplate>> getTemplates() const;
-	std::vector<std::shared_ptr<const ObjectTemplate>> getTemplates(const Terrain & terrainType) const;
+	std::vector<std::shared_ptr<const ObjectTemplate>> getTemplates(const TerrainId 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)
-	std::shared_ptr<const ObjectTemplate> getOverride(const Terrain & terrainType, const CGObjectInstance * object) const;
+	std::shared_ptr<const ObjectTemplate> getOverride(TerrainId terrainType, const CGObjectInstance * object) const;
 
 	BattleField getBattlefield() const;
 

+ 3 - 3
lib/mapObjects/CObjectHandler.cpp

@@ -208,8 +208,8 @@ void CGObjectInstance::setType(si32 ID, si32 subID)
 		logGlobal->error("Unknown object type %d:%d at %s", ID, subID, visitablePos().toString());
 		return;
 	}
-	if(!handler->getTemplates(tile.terType).empty())
-		appearance = handler->getTemplates(tile.terType)[0];
+	if(!handler->getTemplates(tile.terType->id).empty())
+		appearance = handler->getTemplates(tile.terType->id)[0];
 	else
 		appearance = handler->getTemplates()[0]; // get at least some appearance since alternative is crash
 	if (ID == Obj::HERO)
@@ -436,7 +436,7 @@ int3 IBoatGenerator::bestLocation() const
 	{
 		if(const TerrainTile *tile = IObjectInterface::cb->getTile(o->pos + offset, false)) //tile is in the map
 		{
-			if(tile->terType.isWater()  &&  (!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;
 		}
 	}

+ 1 - 1
lib/mapObjects/CommonConstructors.cpp

@@ -80,7 +80,7 @@ CGObjectInstance * CTownInstanceConstructor::create(std::shared_ptr<const Object
 
 void CTownInstanceConstructor::configureObject(CGObjectInstance * object, CRandomGenerator & rng) const
 {
-	auto templ = getOverride(object->cb->getTile(object->pos)->terType, object);
+	auto templ = getOverride(object->cb->getTile(object->pos)->terType->id, object);
 	if(templ)
 		object->appearance = templ;
 }

+ 19 - 20
lib/mapObjects/ObjectTemplate.cpp

@@ -157,20 +157,20 @@ void ObjectTemplate::readTxt(CLegacyConfigParser & parser)
 	// so these two fields can be interpreted as "strong affinity" and "weak affinity" towards terrains
 	std::string & terrStr = strings[4]; // allowed terrains, 1 = object can be placed on this terrain
 
-	assert(terrStr.size() == 9); // all terrains but rock
-	for(size_t i = 0; i < 9; i++)
+	assert(terrStr.size() == Terrain::ROCK - 1); // all terrains but rock
+	for(TerrainId i = Terrain::FIRST_REGULAR_TERRAIN; i < Terrain::ROCK; i++)
 	{
 		if (terrStr[8-i] == '1')
-			allowedTerrains.insert(Terrain::createTerrainTypeH3M(i));
+			allowedTerrains.insert(i);
 	}
 	
 	//assuming that object can be placed on other land terrains
-	if(allowedTerrains.size() >= 8 && !allowedTerrains.count(Terrain("water")))
+	if(allowedTerrains.size() >= 8 && !allowedTerrains.count(Terrain::WATER))
 	{
-		for(auto & terrain : Terrain::Manager::terrains())
+		for(const auto & terrain : VLC->terrainTypeHandler->terrains())
 		{
 			if(terrain.isLand() && terrain.isPassable())
-				allowedTerrains.insert(terrain);
+				allowedTerrains.insert(terrain.id);
 		}
 	}
 
@@ -231,19 +231,19 @@ void ObjectTemplate::readMap(CBinaryReader & reader)
 
 	reader.readUInt16();
 	ui16 terrMask = reader.readUInt16();
-	for(size_t i = 0; i < 9; i++)
+	for(size_t i = Terrain::FIRST_REGULAR_TERRAIN; i < Terrain::ROCK; i++)
 	{
 		if (((terrMask >> i) & 1 ) != 0)
-			allowedTerrains.insert(Terrain::createTerrainTypeH3M(i));
+			allowedTerrains.insert(i);
 	}
 	
 	//assuming that object can be placed on other land terrains
-	if(allowedTerrains.size() >= 8 && !allowedTerrains.count(Terrain("water")))
+	if(allowedTerrains.size() >= 8 && !allowedTerrains.count(Terrain::WATER))
 	{
-		for(auto & terrain : Terrain::Manager::terrains())
+		for(const auto & terrain : VLC->terrainTypeHandler->terrains())
 		{
 			if(terrain.isLand() && terrain.isPassable())
-				allowedTerrains.insert(terrain);
+				allowedTerrains.insert(terrain.id);
 		}
 	}
 
@@ -286,23 +286,22 @@ void ObjectTemplate::readJson(const JsonNode &node, const bool withTerrain)
 
 	if(withTerrain && !node["allowedTerrains"].isNull())
 	{
-		for (auto & entry : node["allowedTerrains"].Vector())
-			allowedTerrains.insert(entry.String());
+		for(auto& entry : node["allowedTerrains"].Vector())
+			allowedTerrains.insert(VLC->terrainTypeHandler->getInfoByName(entry.String())->id);
 	}
 	else
 	{
-		for(auto & i : Terrain::Manager::terrains())
+		for(const auto & terrain : VLC->terrainTypeHandler->terrains())
 		{
-			if(!i.isPassable() || i.isWater())
+			if(!terrain.isPassable() || terrain.isWater())
 				continue;
-			allowedTerrains.insert(i);
+			allowedTerrains.insert(terrain.id);
 		}
 	}
 
 	if(withTerrain && allowedTerrains.empty())
 		logGlobal->warn("Loaded template without allowed terrains!");
 
-
 	auto charToTile = [&](const char & ch) -> ui8
 	{
 		switch (ch)
@@ -371,7 +370,7 @@ void ObjectTemplate::writeJson(JsonNode & node, const bool withTerrain) const
 	if(withTerrain)
 	{
 		//assumed that ROCK and WATER terrains are not included
-		if(allowedTerrains.size() < (Terrain::Manager::terrains().size() - 2))
+		if(allowedTerrains.size() < (VLC->terrainTypeHandler->terrains().size() - 2))
 		{
 			JsonVector & data = node["allowedTerrains"].Vector();
 
@@ -558,9 +557,9 @@ void ObjectTemplate::calculateVisitableOffset()
 	visitableOffset = int3(0, 0, 0);
 }
 
-bool ObjectTemplate::canBePlacedAt(Terrain terrain) const
+bool ObjectTemplate::canBePlacedAt(TerrainId terrain) const
 {
-	return allowedTerrains.count(terrain) != 0;
+	return vstd::contains(allowedTerrains, terrain);
 }
 
 void ObjectTemplate::recalculate()

+ 2 - 3
lib/mapObjects/ObjectTemplate.h

@@ -18,7 +18,6 @@ class CBinaryReader;
 class CLegacyConfigParser;
 class JsonNode;
 class int3;
-class Terrain;
 
 class DLL_LINKAGE ObjectTemplate
 {
@@ -34,7 +33,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<Terrain> allowedTerrains;
+	std::set<TerrainId> allowedTerrains;
 
 	void afterLoadFixup();
 
@@ -101,7 +100,7 @@ public:
 	};
 
 	// Checks if object can be placed on specific terrain
-	bool canBePlacedAt(Terrain terrain) const;
+	bool canBePlacedAt(TerrainId terrain) const;
 
 	ObjectTemplate();
 	//custom copy constructor is required

+ 8 - 8
lib/mapping/CDrawRoadsOperation.cpp

@@ -158,14 +158,14 @@ CDrawLinesOperation::CDrawLinesOperation(CMap * map, const CTerrainSelection & t
 }
 
 ///CDrawRoadsOperation
-CDrawRoadsOperation::CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, const std::string & roadType, CRandomGenerator * gen):
+CDrawRoadsOperation::CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, RoadId roadType, CRandomGenerator * gen):
 	CDrawLinesOperation(map, terrainSel,gen),
 	roadType(roadType)
 {
 }
 
 ///CDrawRiversOperation
-CDrawRiversOperation::CDrawRiversOperation(CMap * map, const CTerrainSelection & terrainSel, const std::string & riverType, CRandomGenerator * gen):
+CDrawRiversOperation::CDrawRiversOperation(CMap * map, const CTerrainSelection & terrainSel, RiverId riverType, CRandomGenerator * gen):
 	CDrawLinesOperation(map, terrainSel, gen),
 	riverType(riverType)
 {
@@ -338,12 +338,12 @@ std::string CDrawRiversOperation::getLabel() const
 
 void CDrawRoadsOperation::executeTile(TerrainTile & tile)
 {
-	tile.roadType = roadType;
+	tile.roadType = const_cast<RoadType*>(&VLC->terrainTypeHandler->roads()[roadType]);
 }
 
 void CDrawRiversOperation::executeTile(TerrainTile & tile)
 {
-	tile.riverType = riverType;
+	tile.riverType = const_cast<RiverType*>(&VLC->terrainTypeHandler->rivers()[riverType]);
 }
 
 bool CDrawRoadsOperation::canApplyPattern(const LinePattern & pattern) const
@@ -358,22 +358,22 @@ bool CDrawRiversOperation::canApplyPattern(const LinePattern & pattern) const
 
 bool CDrawRoadsOperation::needUpdateTile(const TerrainTile & tile) const
 {
-	return tile.roadType != ROAD_NAMES[0];
+	return tile.roadType->id != Road::NO_ROAD;
 }
 
 bool CDrawRiversOperation::needUpdateTile(const TerrainTile & tile) const
 {
-	return tile.riverType != RIVER_NAMES[0];
+	return tile.riverType->id != River::NO_RIVER;
 }
 
 bool CDrawRoadsOperation::tileHasSomething(const int3& pos) const
 {
-	return map->getTile(pos).roadType != ROAD_NAMES[0];
+	return map->getTile(pos).roadType->id != Road::NO_ROAD;
 }
 
 bool CDrawRiversOperation::tileHasSomething(const int3& pos) const
 {
-	return map->getTile(pos).riverType != RIVER_NAMES[0];
+	return map->getTile(pos).riverType->id != River::NO_RIVER;
 }
 
 void CDrawRoadsOperation::updateTile(TerrainTile & tile, const LinePattern & pattern, const int flip)

+ 4 - 4
lib/mapping/CDrawRoadsOperation.h

@@ -63,7 +63,7 @@ protected:
 class CDrawRoadsOperation : public CDrawLinesOperation
 {
 public:
-	CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, const std::string & roadType, CRandomGenerator * gen);
+	CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, RoadId roadType, CRandomGenerator * gen);
 	std::string getLabel() const override;
 	
 protected:
@@ -74,13 +74,13 @@ protected:
 	void updateTile(TerrainTile & tile, const CDrawLinesOperation::LinePattern & pattern, const int flip) override;
 	
 private:
-	std::string roadType;
+	RoadId roadType;
 };
 
 class CDrawRiversOperation : public CDrawLinesOperation
 {
 public:
-	CDrawRiversOperation(CMap * map, const CTerrainSelection & terrainSel, const std::string & roadType, CRandomGenerator * gen);
+	CDrawRiversOperation(CMap * map, const CTerrainSelection & terrainSel, RoadId roadType, CRandomGenerator * gen);
 	std::string getLabel() const override;
 	
 protected:
@@ -91,7 +91,7 @@ protected:
 	void updateTile(TerrainTile & tile, const CDrawLinesOperation::LinePattern & pattern, const int flip) override;
 	
 private:
-	std::string riverType;
+	RiverId riverType;
 };
 
 VCMI_LIB_NAMESPACE_END

+ 14 - 8
lib/mapping/CMap.cpp

@@ -125,22 +125,28 @@ CCastleEvent::CCastleEvent() : town(nullptr)
 
 }
 
-TerrainTile::TerrainTile() : terType("BORDER"), terView(0), riverType(RIVER_NAMES[0]),
-	riverDir(0), roadType(ROAD_NAMES[0]), roadDir(0), extTileFlags(0), visitable(false),
+TerrainTile::TerrainTile():
+	terType(nullptr),
+	terView(0),
+	riverType(const_cast<RiverType*>(&VLC->terrainTypeHandler->rivers()[River::NO_RIVER])),
+	riverDir(0),
+	roadType(const_cast<RoadType*>(&VLC->terrainTypeHandler->roads()[Road::NO_ROAD])),
+	roadDir(0),
+	extTileFlags(0),
+	visitable(false),
 	blocked(false)
 {
-
 }
 
 bool TerrainTile::entrableTerrain(const TerrainTile * from) const
 {
-	return entrableTerrain(from ? from->terType.isLand() : true, from ? from->terType.isWater() : true);
+	return entrableTerrain(from ? from->terType->isLand() : true, from ? from->terType->isWater() : true);
 }
 
 bool TerrainTile::entrableTerrain(bool allowLand, bool allowSea) const
 {
-	return terType.isPassable()
-			&& ((allowSea && terType.isWater())  ||  (allowLand && terType.isLand()));
+	return terType->isPassable()
+			&& ((allowSea && terType->isWater())  ||  (allowLand && terType->isLand()));
 }
 
 bool TerrainTile::isClear(const TerrainTile * from) const
@@ -166,7 +172,7 @@ CGObjectInstance * TerrainTile::topVisitableObj(bool excludeTop) const
 
 EDiggingStatus TerrainTile::getDiggingStatus(const bool excludeTop) const
 {
-	if(terType.isWater() || !terType.isPassable())
+	if(terType->isWater() || !terType->isPassable())
 		return EDiggingStatus::WRONG_TERRAIN;
 
 	int allowedBlocked = excludeTop ? 1 : 0;
@@ -183,7 +189,7 @@ bool TerrainTile::hasFavorableWinds() const
 
 bool TerrainTile::isWater() const
 {
-	return terType.isWater();
+	return terType->isWater();
 }
 
 void CMapHeader::setupEvents()

+ 3 - 3
lib/mapping/CMapDefines.h

@@ -82,11 +82,11 @@ struct DLL_LINKAGE TerrainTile
 	EDiggingStatus getDiggingStatus(const bool excludeTop = true) const;
 	bool hasFavorableWinds() const;
 
-	Terrain terType;
+	TerrainType * terType;
 	ui8 terView;
-	std::string riverType;
+	RiverType * riverType;
 	ui8 riverDir;
-	std::string roadType; //TODO: switch to ui8
+	RoadType * 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

+ 3 - 3
lib/mapping/CMapEditManager.cpp

@@ -126,19 +126,19 @@ void CMapEditManager::clearTerrain(CRandomGenerator * gen)
 	execute(make_unique<CClearTerrainOperation>(map, gen ? gen : &(this->gen)));
 }
 
-void CMapEditManager::drawTerrain(Terrain terType, CRandomGenerator * gen)
+void CMapEditManager::drawTerrain(TerrainId terType, CRandomGenerator * gen)
 {
 	execute(make_unique<CDrawTerrainOperation>(map, terrainSel, terType, gen ? gen : &(this->gen)));
 	terrainSel.clearSelection();
 }
 
-void CMapEditManager::drawRoad(const std::string & roadType, CRandomGenerator* gen)
+void CMapEditManager::drawRoad(RoadId roadType, CRandomGenerator* gen)
 {
 	execute(make_unique<CDrawRoadsOperation>(map, terrainSel, roadType, gen ? gen : &(this->gen)));
 	terrainSel.clearSelection();
 }
 
-void CMapEditManager::drawRiver(const std::string & riverType, CRandomGenerator* gen)
+void CMapEditManager::drawRiver(RiverId riverType, CRandomGenerator* gen)
 {
 	execute(make_unique<CDrawRiversOperation>(map, terrainSel, riverType, gen ? gen : &(this->gen)));
 	terrainSel.clearSelection();

+ 3 - 3
lib/mapping/CMapEditManager.h

@@ -72,13 +72,13 @@ public:
 	void clearTerrain(CRandomGenerator * gen = nullptr);
 
 	/// Draws terrain at the current terrain selection. The selection will be cleared automatically.
-	void drawTerrain(Terrain terType, CRandomGenerator * gen = nullptr);
+	void drawTerrain(TerrainId terType, CRandomGenerator * gen = nullptr);
 
 	/// Draws roads at the current terrain selection. The selection will be cleared automatically.
-	void drawRoad(const std::string & roadType, CRandomGenerator * gen = nullptr);
+	void drawRoad(RoadId roadType, CRandomGenerator * gen = nullptr);
 	
 	/// Draws rivers at the current terrain selection. The selection will be cleared automatically.
-	void drawRiver(const std::string & riverType, CRandomGenerator * gen = nullptr);
+	void drawRiver(RiverId riverType, CRandomGenerator * gen = nullptr);
 
 	void insertObject(CGObjectInstance * obj);
 	void insertObjects(std::set<CGObjectInstance *> & objects);

+ 23 - 20
lib/mapping/CMapOperation.cpp

@@ -83,8 +83,11 @@ void CComposedOperation::addOperation(std::unique_ptr<CMapOperation>&& operation
 	operations.push_back(std::move(operation));
 }
 
-CDrawTerrainOperation::CDrawTerrainOperation(CMap* map, const CTerrainSelection& terrainSel, Terrain terType, CRandomGenerator* gen)
-	: CMapOperation(map), terrainSel(terrainSel), terType(terType), gen(gen)
+CDrawTerrainOperation::CDrawTerrainOperation(CMap* map, const CTerrainSelection& terrainSel, TerrainId terType, CRandomGenerator* gen):
+	CMapOperation(map),
+	terrainSel(terrainSel),
+	terType(terType),
+	gen(gen)
 {
 
 }
@@ -94,7 +97,7 @@ void CDrawTerrainOperation::execute()
 	for(const auto & pos : terrainSel.getSelectedItems())
 	{
 		auto & tile = map->getTile(pos);
-		tile.terType = terType;
+		tile.terType = const_cast<TerrainType*>(&VLC->terrainTypeHandler->terrains()[terType]);
 		invalidateTerrainViews(pos);
 	}
 
@@ -151,7 +154,7 @@ void CDrawTerrainOperation::updateTerrainTypes()
 			rect.forEach([&](const int3& posToTest)
 				{
 					auto & terrainTile = map->getTile(posToTest);
-					if(centerTile.terType != terrainTile.terType)
+					if(centerTile.terType->id != terrainTile.terType->id)
 					{
 						auto formerTerType = terrainTile.terType;
 						terrainTile.terType = centerTile.terType;
@@ -254,7 +257,7 @@ void CDrawTerrainOperation::updateTerrainViews()
 {
 	for(const auto & pos : invalidatedTerViews)
 	{
-		const auto & patterns = VLC->terviewh->getTerrainViewPatterns(map->getTile(pos).terType);
+		const auto & patterns = VLC->terviewh->getTerrainViewPatterns(map->getTile(pos).terType->id);
 
 		// Detect a pattern which fits best
 		int bestPattern = -1;
@@ -342,7 +345,7 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi
 		int cy = pos.y + (i / 3) - 1;
 		int3 currentPos(cx, cy, pos.z);
 		bool isAlien = false;
-		Terrain terType;
+		TerrainType * terType = nullptr;
 		if(!map->isInTheMap(currentPos))
 		{
 			// position is not in the map, so take the ter type from the neighbor tile
@@ -375,7 +378,7 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi
 		else
 		{
 			terType = map->getTile(currentPos).terType;
-			if(terType != centerTerType && (terType.isPassable() || centerTerType.isPassable()))
+			if(terType != centerTerType && (terType->isPassable() || centerTerType->isPassable()))
 			{
 				isAlien = true;
 			}
@@ -390,9 +393,9 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi
 			{
 				if(recDepth == 0 && map->isInTheMap(currentPos))
 				{
-					if(terType == centerTerType)
+					if(terType->id == centerTerType->id)
 					{
-						const auto & patternForRule = VLC->terviewh->getTerrainViewPatternsById(centerTerType, rule.name);
+						const auto & patternForRule = VLC->terviewh->getTerrainViewPatternsById(centerTerType->id, rule.name);
 						if(auto p = patternForRule)
 						{
 							auto rslt = validateTerrainView(currentPos, &(*p), 1);
@@ -419,18 +422,18 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi
 			bool nativeTestOk, nativeTestStrongOk;
 			nativeTestOk = nativeTestStrongOk = (rule.isNativeStrong() || rule.isNativeRule()) && !isAlien;
 
-			if(centerTerType == Terrain("dirt"))
+			if(centerTerType->id == Terrain::DIRT)
 			{
-				nativeTestOk = rule.isNativeRule() && !terType.isTransitionRequired();
+				nativeTestOk = rule.isNativeRule() && !terType->isTransitionRequired();
 				bool sandTestOk = (rule.isSandRule() || rule.isTransition())
-					&& terType.isTransitionRequired();
+					&& terType->isTransitionRequired();
 				applyValidationRslt(rule.isAnyRule() || sandTestOk || nativeTestOk || nativeTestStrongOk);
 			}
-			else if(centerTerType == Terrain("sand"))
+			else if(centerTerType->id == Terrain::SAND)
 			{
 				applyValidationRslt(true);
 			}
-			else if(centerTerType.isTransitionRequired()) //water, rock and some special terrains require sand transition
+			else if(centerTerType->isTransitionRequired()) //water, rock and some special terrains require sand transition
 			{
 				bool sandTestOk = (rule.isSandRule() || rule.isTransition())
 					&& isAlien;
@@ -439,9 +442,9 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi
 			else
 			{
 				bool dirtTestOk = (rule.isDirtRule() || rule.isTransition())
-					&& isAlien && !terType.isTransitionRequired();
+					&& isAlien && !terType->isTransitionRequired();
 				bool sandTestOk = (rule.isSandRule() || rule.isTransition())
-					&& terType.isTransitionRequired();
+					&& terType->isTransitionRequired();
 
 				if(transitionReplacement.empty() && rule.isTransition()
 					&& (dirtTestOk || sandTestOk))
@@ -504,7 +507,7 @@ CDrawTerrainOperation::InvalidTiles CDrawTerrainOperation::getInvalidTiles(const
 				auto valid = validateTerrainView(pos, ptrConfig->getTerrainTypePatternById("n1")).result;
 
 				// Special validity check for rock & water
-				if(valid && (terType.isWater() || !terType.isPassable()))
+				if(valid && (terType->isWater() || !terType->isPassable()))
 				{
 					static const std::string patternIds[] = { "s1", "s2" };
 					for(auto & patternId : patternIds)
@@ -514,7 +517,7 @@ CDrawTerrainOperation::InvalidTiles CDrawTerrainOperation::getInvalidTiles(const
 					}
 				}
 				// Additional validity check for non rock OR water
-				else if(!valid && (terType.isLand() && terType.isPassable()))
+				else if(!valid && (terType->isLand() && terType->isPassable()))
 				{
 					static const std::string patternIds[] = { "n2", "n3" };
 					for (auto & patternId : patternIds)
@@ -548,12 +551,12 @@ CClearTerrainOperation::CClearTerrainOperation(CMap* map, CRandomGenerator* gen)
 {
 	CTerrainSelection terrainSel(map);
 	terrainSel.selectRange(MapRect(int3(0, 0, 0), map->width, map->height));
-	addOperation(make_unique<CDrawTerrainOperation>(map, terrainSel, Terrain("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, Terrain("rock"), gen));
+		addOperation(make_unique<CDrawTerrainOperation>(map, terrainSel, Terrain::ROCK, gen));
 	}
 }
 

+ 2 - 2
lib/mapping/CMapOperation.h

@@ -63,7 +63,7 @@ private:
 class CDrawTerrainOperation : public CMapOperation
 {
 public:
-	CDrawTerrainOperation(CMap * map, const CTerrainSelection & terrainSel, Terrain terType, CRandomGenerator * gen);
+	CDrawTerrainOperation(CMap * map, const CTerrainSelection & terrainSel, TerrainId terType, CRandomGenerator * gen);
 
 	void execute() override;
 	void undo() override;
@@ -100,7 +100,7 @@ private:
 	ValidationResult validateTerrainViewInner(const int3 & pos, const TerrainViewPattern & pattern, int recDepth = 0) const;
 
 	CTerrainSelection terrainSel;
-	Terrain terType;
+	TerrainId terType;
 	CRandomGenerator* gen;
 	std::set<int3> invalidatedTerViews;
 };

+ 4 - 4
lib/mapping/MapEditUtils.cpp

@@ -270,9 +270,9 @@ CTerrainViewPatternConfig::~CTerrainViewPatternConfig()
 
 }
 
-const std::vector<CTerrainViewPatternConfig::TVPVector> & CTerrainViewPatternConfig::getTerrainViewPatterns(const Terrain & terrain) const
+const std::vector<CTerrainViewPatternConfig::TVPVector> & CTerrainViewPatternConfig::getTerrainViewPatterns(TerrainId terrain) const
 {
-	auto iter = terrainViewPatterns.find(Terrain::Manager::getInfo(terrain).terrainViewPatterns);
+	auto iter = terrainViewPatterns.find(VLC->terrainTypeHandler->terrains()[terrain].terrainViewPatterns);
 	if (iter == terrainViewPatterns.end())
 		return terrainViewPatterns.at("normal");
 	return iter->second;
@@ -295,7 +295,7 @@ boost::optional<const TerrainViewPattern &> CTerrainViewPatternConfig::getTerrai
 	return boost::optional<const TerrainViewPattern&>();
 }
 
-boost::optional<const CTerrainViewPatternConfig::TVPVector &> CTerrainViewPatternConfig::getTerrainViewPatternsById(const Terrain & terrain, const std::string & id) const
+boost::optional<const CTerrainViewPatternConfig::TVPVector &> CTerrainViewPatternConfig::getTerrainViewPatternsById(TerrainId terrain, const std::string & id) const
 {
 	const std::vector<TVPVector> & groupPatterns = getTerrainViewPatterns(terrain);
 	for (const TVPVector & patternFlips : groupPatterns)
@@ -357,7 +357,7 @@ void CTerrainViewPatternUtils::printDebuggingInfoAboutTile(const CMap * map, int
 			{
 				auto debugTile = map->getTile(debugPos);
 
-				std::string terType = static_cast<std::string>(debugTile.terType).substr(0, 6);
+				std::string terType = debugTile.terType->name.substr(0, 6);
 				line += terType;
 				line.insert(line.end(), PADDED_LENGTH - terType.size(), ' ');
 			}

+ 2 - 2
lib/mapping/MapEditUtils.h

@@ -217,9 +217,9 @@ public:
 	CTerrainViewPatternConfig();
 	~CTerrainViewPatternConfig();
 
-	const std::vector<TVPVector> & getTerrainViewPatterns(const Terrain & terrain) const;
+	const std::vector<TVPVector> & getTerrainViewPatterns(TerrainId terrain) const;
 	boost::optional<const TerrainViewPattern &> getTerrainViewPatternById(std::string patternId, const std::string & id) const;
-	boost::optional<const TVPVector &> getTerrainViewPatternsById(const Terrain & terrain, const std::string & id) const;
+	boost::optional<const TVPVector &> getTerrainViewPatternsById(TerrainId terrain, const std::string & id) const;
 	const TVPVector * getTerrainTypePatternById(const std::string & id) const;
 	void flipPattern(TerrainViewPattern & pattern, int flip) const;
 

+ 8 - 10
lib/mapping/MapFormatH3M.cpp

@@ -923,30 +923,28 @@ bool CMapLoaderH3M::loadArtifactToSlot(CGHeroInstance * hero, int slot)
 void CMapLoaderH3M::readTerrain()
 {
 	map->initTerrain();
+	const auto & terrains = VLC->terrainTypeHandler->terrains();
+	const auto & rivers = VLC->terrainTypeHandler->rivers();
+	const auto & roads = VLC->terrainTypeHandler->roads();
 
 	// Read terrain
 	int3 pos;
-	for(pos.z = 0; pos.z < 2; ++pos.z)
+	for(pos.z = 0; pos.z < map->levels(); ++pos.z)
 	{
-		if(pos.z == 1 && !map->twoLevel)
-		{
-			break;
-		}
-
 		//OH3 format is [z][y][x]
 		for(pos.y = 0; pos.y < map->height; pos.y++)
 		{
 			for(pos.x = 0; pos.x < map->width; pos.x++)
 			{
 				auto & tile = map->getTile(pos);
-				tile.terType = Terrain::createTerrainTypeH3M(reader.readUInt8());
+				tile.terType = const_cast<TerrainType*>(&terrains[reader.readUInt8()]);
 				tile.terView = reader.readUInt8();
-				tile.riverType = RIVER_NAMES[reader.readUInt8()];
+				tile.riverType = const_cast<RiverType*>(&rivers[reader.readUInt8()]);
 				tile.riverDir = reader.readUInt8();
-				tile.roadType = ROAD_NAMES[reader.readUInt8()];
+				tile.roadType = const_cast<RoadType*>(&roads[reader.readUInt8()]);
 				tile.roadDir = reader.readUInt8();
 				tile.extTileFlags = reader.readUInt8();
-				tile.blocked = ((!tile.terType.isPassable() || tile.terType == Terrain("BORDER") ) ? true : false); //underground tiles are always blocked
+				tile.blocked = ((!tile.terType->isPassable() || tile.terType->id == Terrain::BORDER ) ? true : false); //underground tiles are always blocked
 				tile.visitable = 0;
 			}
 		}

+ 99 - 88
lib/mapping/MapFormatJson.cpp

@@ -944,96 +944,107 @@ void CMapLoaderJson::readHeader(const bool complete)
 
 void CMapLoaderJson::readTerrainTile(const std::string & src, TerrainTile & tile)
 {
-	using namespace TerrainDetail;
-	{//terrain type
-		const std::string typeCode = src.substr(0, 2);
-		tile.terType = Terrain::createTerrainByCode(typeCode);
-	}
-	int startPos = 2; //0+typeCode fixed length
-	{//terrain view
-		int pos = startPos;
-		while(isdigit(src.at(pos)))
-			pos++;
-		int len = pos - startPos;
-		if(len<=0)
-			throw std::runtime_error("Invalid terrain view in "+src);
-		const std::string rawCode = src.substr(startPos, len);
-		tile.terView = atoi(rawCode.c_str());
-		startPos+=len;
-	}
-	{//terrain flip
-		int terrainFlip = vstd::find_pos(flipCodes, src.at(startPos++));
-		if(terrainFlip < 0)
-			throw std::runtime_error("Invalid terrain flip in "+src);
-		else
-			tile.extTileFlags = terrainFlip;
-	}
-	if(startPos >= src.size())
-		return;
-	bool hasRoad = true;
-	{//road type
-		const std::string typeCode = src.substr(startPos, 2);
-		startPos+=2;
-		if(vstd::find_pos(ROAD_NAMES, typeCode) < 0)
-		{
-			if(vstd::find_pos(RIVER_NAMES, typeCode) < 0)
-				throw std::runtime_error("Invalid river type in "+src);
+	try
+	{
+		using namespace TerrainDetail;
+		{//terrain type
+			const std::string typeCode = src.substr(0, 2);
+			tile.terType = const_cast<TerrainType*>(VLC->terrainTypeHandler->getInfoByCode(typeCode));
+		}
+		int startPos = 2; //0+typeCode fixed length
+		{//terrain view
+			int pos = startPos;
+			while (isdigit(src.at(pos)))
+				pos++;
+			int len = pos - startPos;
+			if (len <= 0)
+				throw std::runtime_error("Invalid terrain view in " + src);
+			const std::string rawCode = src.substr(startPos, len);
+			tile.terView = atoi(rawCode.c_str());
+			startPos += len;
+		}
+		{//terrain flip
+			int terrainFlip = vstd::find_pos(flipCodes, src.at(startPos++));
+			if (terrainFlip < 0)
+				throw std::runtime_error("Invalid terrain flip in " + src);
 			else
+				tile.extTileFlags = terrainFlip;
+		}
+		if (startPos >= src.size())
+			return;
+		bool hasRoad = true;
+		//FIXME: check without exceptions?
+		{//road type
+			const std::string typeCode = src.substr(startPos, 2);
+			startPos += 2;
+			try
 			{
-				tile.riverType = typeCode;
-				hasRoad = false;
+				tile.roadType = const_cast<RoadType*>(VLC->terrainTypeHandler->getRoadByCode(typeCode));
 			}
+			catch (const std::exception& e) //it's not a road, it's a river
+			{
+				try
+				{
+					tile.riverType = const_cast<RiverType*>(VLC->terrainTypeHandler->getRiverByCode(typeCode));
+					hasRoad = false;
+				}
+				catch (const std::exception& e)
+				{
+					throw std::runtime_error("Invalid river type in " + src);
+				}
+
+			}	
+		}
+		if (hasRoad)
+		{//road dir
+			int pos = startPos;
+			while (isdigit(src.at(pos)))
+				pos++;
+			int len = pos - startPos;
+			if (len <= 0)
+				throw std::runtime_error("Invalid road dir in " + src);
+			const std::string rawCode = src.substr(startPos, len);
+			tile.roadDir = atoi(rawCode.c_str());
+			startPos += len;
+		}
+		if (hasRoad)
+		{//road flip
+			int flip = vstd::find_pos(flipCodes, src.at(startPos++));
+			if (flip < 0)
+				throw std::runtime_error("Invalid road flip in " + src);
+			else
+				tile.extTileFlags |= (flip << 4);
+		}
+		if (startPos >= src.size())
+			return;
+		if (hasRoad)
+		{//river type
+			const std::string typeCode = src.substr(startPos, 2);
+			startPos += 2;
+			tile.riverType = const_cast<RiverType*>(VLC->terrainTypeHandler->getRiverByCode(typeCode));
+		}
+		{//river dir
+			int pos = startPos;
+			while (isdigit(src.at(pos)))
+				pos++;
+			int len = pos - startPos;
+			if (len <= 0)
+				throw std::runtime_error("Invalid river dir in " + src);
+			const std::string rawCode = src.substr(startPos, len);
+			tile.riverDir = atoi(rawCode.c_str());
+			startPos += len;
+		}
+		{//river flip
+			int flip = vstd::find_pos(flipCodes, src.at(startPos++));
+			if (flip < 0)
+				throw std::runtime_error("Invalid road flip in " + src);
+			else
+				tile.extTileFlags |= (flip << 2);
 		}
-		else
-			tile.roadType = typeCode;
-	}
-	if(hasRoad)
-	{//road dir
-		int pos = startPos;
-		while(isdigit(src.at(pos)))
-			pos++;
-		int len = pos - startPos;
-		if(len<=0)
-			throw std::runtime_error("Invalid road dir in "+src);
-		const std::string rawCode = src.substr(startPos, len);
-		tile.roadDir = atoi(rawCode.c_str());
-		startPos+=len;
-	}
-	if(hasRoad)
-	{//road flip
-		int flip = vstd::find_pos(flipCodes, src.at(startPos++));
-		if(flip < 0)
-			throw std::runtime_error("Invalid road flip in "+src);
-		else
-			tile.extTileFlags |= (flip<<4);
-	}
-	if(startPos >= src.size())
-		return;
-	if(hasRoad)
-	{//river type
-		const std::string typeCode = src.substr(startPos, 2);
-		startPos+=2;
-		if(vstd::find_pos(RIVER_NAMES, typeCode) < 0)
-			throw std::runtime_error("Invalid river type in "+src);
-		tile.riverType = typeCode;
-	}
-	{//river dir
-		int pos = startPos;
-		while(isdigit(src.at(pos)))
-			pos++;
-		int len = pos - startPos;
-		if(len<=0)
-			throw std::runtime_error("Invalid river dir in "+src);
-		const std::string rawCode = src.substr(startPos, len);
-		tile.riverDir = atoi(rawCode.c_str());
-		startPos+=len;
 	}
-	{//river flip
-		int flip = vstd::find_pos(flipCodes, src.at(startPos++));
-		if(flip < 0)
-			throw std::runtime_error("Invalid road flip in "+src);
-		else
-			tile.extTileFlags |= (flip<<2);
+	catch (const std::exception & e)
+	{
+		logGlobal->error("Failed to read terrain tile: %s");
 	}
 }
 
@@ -1278,12 +1289,12 @@ std::string CMapSaverJson::writeTerrainTile(const TerrainTile & tile)
 	out.setf(std::ios::dec, std::ios::basefield);
 	out.unsetf(std::ios::showbase);
 
-	out << Terrain::Manager::getInfo(tile.terType).typeCode << (int)tile.terView << flipCodes[tile.extTileFlags % 4];
+	out << tile.terType->typeCode << (int)tile.terView << flipCodes[tile.extTileFlags % 4];
 
-	if(tile.roadType != ROAD_NAMES[0])
+	if(tile.roadType->id != Road::NO_ROAD)
 		out << tile.roadType << (int)tile.roadDir << flipCodes[(tile.extTileFlags >> 4) % 4];
 
-	if(tile.riverType != RIVER_NAMES[0])
+	if(tile.riverType->id != River::NO_RIVER)
 		out << tile.riverType << (int)tile.riverDir << flipCodes[(tile.extTileFlags >> 2) % 4];
 
 	return out.str();

+ 1 - 10
lib/rmg/CMapGenerator.cpp

@@ -48,16 +48,7 @@ void CMapGenerator::loadConfig()
 {
 	static const ResourceID path("config/randomMap.json");
 	JsonNode randomMapJson(path);
-	for(auto& s : randomMapJson["terrain"]["undergroundAllow"].Vector())
-	{
-		if(!s.isNull())
-			config.terrainUndergroundAllowed.emplace_back(s.String());
-	}
-	for(auto& s : randomMapJson["terrain"]["groundProhibit"].Vector())
-	{
-		if(!s.isNull())
-			config.terrainGroundProhibit.emplace_back(s.String());
-	}
+
 	config.shipyardGuard = randomMapJson["waterZone"]["shipyard"]["value"].Integer();
 	for(auto & treasure : randomMapJson["waterZone"]["treasure"].Vector())
 	{

+ 0 - 2
lib/rmg/CMapGenerator.h

@@ -34,8 +34,6 @@ class DLL_LINKAGE CMapGenerator: public Load::Progress
 public:
 	struct Config
 	{
-		std::vector<Terrain> terrainUndergroundAllowed;
-		std::vector<Terrain> terrainGroundProhibit;
 		std::vector<CTreasureInfo> waterTreasure;
 		int shipyardGuard;
 		int mineExtraResources;

+ 8 - 7
lib/rmg/CRmgTemplate.cpp

@@ -69,12 +69,13 @@ class TerrainEncoder
 public:
 	static si32 decode(const std::string & identifier)
 	{
-		return vstd::find_pos(Terrain::Manager::terrains(), identifier);
+		return VLC->terrainTypeHandler->getInfoByCode(identifier)->id;
 	}
 
 	static std::string encode(const si32 index)
 	{
-		return (index >=0 && index < Terrain::Manager::terrains().size()) ? static_cast<std::string>(Terrain::Manager::terrains()[index]) : "<INVALID TERRAIN>";
+		const auto& terrains = VLC->terrainTypeHandler->terrains();
+		return (index >=0 && index < terrains.size()) ? terrains[index].name : "<INVALID TERRAIN>";
 	}
 };
 
@@ -151,9 +152,9 @@ ZoneOptions::ZoneOptions()
 	terrainTypeLikeZone(NO_ZONE),
 	treasureLikeZone(NO_ZONE)
 {
-	for(auto & terr : Terrain::Manager::terrains())
+	for(const auto & terr : VLC->terrainTypeHandler->terrains())
 		if(terr.isLand() && terr.isPassable())
-			terrainTypes.insert(terr);
+			terrainTypes.insert(terr.id);
 }
 
 ZoneOptions & ZoneOptions::operator=(const ZoneOptions & other)
@@ -216,12 +217,12 @@ boost::optional<int> ZoneOptions::getOwner() const
 	return owner;
 }
 
-const std::set<Terrain> & ZoneOptions::getTerrainTypes() const
+const std::set<TerrainId> & ZoneOptions::getTerrainTypes() const
 {
 	return terrainTypes;
 }
 
-void ZoneOptions::setTerrainTypes(const std::set<Terrain> & value)
+void ZoneOptions::setTerrainTypes(const std::set<TerrainId> & 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());
@@ -376,7 +377,7 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler)
 				terrainTypes.clear();
 				for(auto ttype : node.Vector())
 				{
-					terrainTypes.emplace(ttype.String());
+					terrainTypes.emplace(VLC->terrainTypeHandler->getInfoByName(ttype.String())->id);
 				}
 			}
 		}

+ 3 - 3
lib/rmg/CRmgTemplate.h

@@ -105,8 +105,8 @@ public:
 	void setSize(int value);
 	boost::optional<int> getOwner() const;
 
-	const std::set<Terrain> & getTerrainTypes() const;
-	void setTerrainTypes(const std::set<Terrain> & value);
+	const std::set<TerrainId> & getTerrainTypes() const;
+	void setTerrainTypes(const std::set<TerrainId> & value);
 
 	const CTownInfo & getPlayerTowns() const;
 	const CTownInfo & getNeutralTowns() const;
@@ -146,7 +146,7 @@ protected:
 	CTownInfo playerTowns;
 	CTownInfo neutralTowns;
 	bool matchTerrainToTown;
-	std::set<Terrain> terrainTypes;
+	std::set<TerrainId> terrainTypes;
 	bool townsAreSameType;
 
 	std::set<TFaction> townTypes;

+ 5 - 4
lib/rmg/CZonePlacer.cpp

@@ -193,16 +193,17 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const
 				else
 				{
 					auto & tt = (*VLC->townh)[faction]->nativeTerrain;
-					if(tt == Terrain("dirt"))
+					if(tt == Terrain::DIRT)
 					{
 						//any / random
 						zonesToPlace.push_back(zone);
 					}
 					else
 					{
-						if(tt.isUnderground())
+						const auto & terrainType = VLC->terrainTypeHandler->terrains()[tt];
+						if(terrainType.isUnderground() && !terrainType.isSurface())
 						{
-							//underground
+							//underground only
 							zonesOnLevel[1]++;
 							levels[zone.first] = 1;
 						}
@@ -573,7 +574,7 @@ void CZonePlacer::assignZones(CRandomGenerator * rand)
 
 			//make sure that terrain inside zone is not a rock
 			//FIXME: reorder actions?
-			paintZoneTerrain(*zone.second, *rand, map, Terrain("subterra"));
+			paintZoneTerrain(*zone.second, *rand, map, Terrain::SUBTERRANEAN);
 		}
 	}
 	logGlobal->info("Finished zone colouring");

+ 3 - 2
lib/rmg/ConnectionsPlacer.cpp

@@ -84,8 +84,9 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
 	
 	//1. Try to make direct connection
 	//Do if it's not prohibited by terrain settings
-	bool directProhibited = vstd::contains(Terrain::Manager::getInfo(zone.getTerrainType()).prohibitTransitions, otherZone->getTerrainType())
-						 || vstd::contains(Terrain::Manager::getInfo(otherZone->getTerrainType()).prohibitTransitions, zone.getTerrainType());
+	const auto& terrains = VLC->terrainTypeHandler->terrains();
+	bool directProhibited = vstd::contains(terrains[zone.getTerrainType()].prohibitTransitions, otherZone->getTerrainType())
+						 || vstd::contains(terrains[otherZone->getTerrainType()].prohibitTransitions, zone.getTerrainType());
 	auto directConnectionIterator = dNeighbourZones.find(otherZoneId);
 	if(!directProhibited && directConnectionIterator != dNeighbourZones.end())
 	{

+ 18 - 22
lib/rmg/Functions.cpp

@@ -95,14 +95,14 @@ void createBorder(RmgMap & gen, Zone & zone)
 	}
 }
 
-void paintZoneTerrain(const Zone & zone, CRandomGenerator & generator, RmgMap & map, const Terrain & terrainType)
+void paintZoneTerrain(const Zone & zone, CRandomGenerator & generator, RmgMap & map, TerrainId terrain)
 {
 	auto v = zone.getArea().getTilesVector();
 	map.getEditManager()->getTerrainSelection().setSelection(v);
-	map.getEditManager()->drawTerrain(terrainType, &generator);
+	map.getEditManager()->drawTerrain(terrain, &generator);
 }
 
-int chooseRandomAppearance(CRandomGenerator & generator, si32 ObjID, const Terrain & terrain)
+int chooseRandomAppearance(CRandomGenerator & generator, si32 ObjID, TerrainId terrain)
 {
 	auto factories = VLC->objtypeh->knownSubObjects(ObjID);
 	vstd::erase_if(factories, [ObjID, &terrain](si32 f)
@@ -118,10 +118,10 @@ void initTerrainType(Zone & zone, CMapGenerator & gen)
 	if(zone.getType()==ETemplateZoneType::WATER)
 	{
 		//collect all water terrain types
-		std::vector<Terrain> waterTerrains;
-		for(auto & terrain : Terrain::Manager::terrains())
+		std::vector<TerrainId> waterTerrains;
+		for(const auto & terrain : VLC->terrainTypeHandler->terrains())
 			if(terrain.isWater())
-				waterTerrains.push_back(terrain);
+				waterTerrains.push_back(terrain.id);
 		
 		zone.setTerrainType(*RandomGeneratorUtil::nextItem(waterTerrains, gen.rand));
 	}
@@ -136,25 +136,21 @@ void initTerrainType(Zone & zone, CMapGenerator & gen)
 			zone.setTerrainType(*RandomGeneratorUtil::nextItem(zone.getTerrainTypes(), gen.rand));
 		}
 		
-		//TODO: allow new types of terrain?
+		//Now, replace disallowed terrains on surface and in the underground
+		const auto & terrainType = VLC->terrainTypeHandler->terrains()[zone.getTerrainType()];
+
+		if(zone.isUnderground())
 		{
-			if(zone.isUnderground())
+			if(!terrainType.isUnderground())
 			{
-				if(!vstd::contains(gen.getConfig().terrainUndergroundAllowed, zone.getTerrainType()))
-				{
-					//collect all underground terrain types
-					std::vector<Terrain> undegroundTerrains;
-					for(auto & terrain : Terrain::Manager::terrains())
-						if(terrain.isUnderground())
-							undegroundTerrains.push_back(terrain);
-					
-					zone.setTerrainType(*RandomGeneratorUtil::nextItem(undegroundTerrains, gen.rand));
-				}
+				zone.setTerrainType(Terrain::SUBTERRANEAN);
 			}
-			else
+		}
+		else
+		{
+			if (!terrainType.isSurface())
 			{
-				if(vstd::contains(gen.getConfig().terrainGroundProhibit, zone.getTerrainType()) || zone.getTerrainType().isUnderground())
-					zone.setTerrainType(Terrain("dirt"));
+				zone.setTerrainType(Terrain::DIRT);
 			}
 		}
 	}
@@ -170,7 +166,7 @@ void createObstaclesCommon2(RmgMap & map, CRandomGenerator & generator)
 			for(int y = 0; y < map.map().height; y++)
 			{
 				int3 tile(x, y, 1);
-				if(!map.map().getTile(tile).terType.isPassable())
+				if(!map.map().getTile(tile).terType->isPassable())
 				{
 					map.setOccupied(tile, ETileType::USED);
 				}

+ 2 - 2
lib/rmg/Functions.h

@@ -42,11 +42,11 @@ rmg::Tileset collectDistantTiles(const Zone & zone, int distance);
 
 void createBorder(RmgMap & gen, Zone & zone);
 
-void paintZoneTerrain(const Zone & zone, CRandomGenerator & generator, RmgMap & map, const Terrain & terrainType);
+void paintZoneTerrain(const Zone & zone, CRandomGenerator & generator, RmgMap & map, TerrainId terrainType);
 
 void initTerrainType(Zone & zone, CMapGenerator & gen);
 
-int chooseRandomAppearance(CRandomGenerator & generator, si32 ObjID, const Terrain & terrain);
+int chooseRandomAppearance(CRandomGenerator & generator, si32 ObjID, TerrainId terrain);
 
 
 VCMI_LIB_NAMESPACE_END

+ 1 - 1
lib/rmg/ObstaclePlacer.cpp

@@ -26,7 +26,7 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-void ObstacleProxy::collectPossibleObstacles(const Terrain & terrain)
+void ObstacleProxy::collectPossibleObstacles(TerrainId terrain)
 {
 	//get all possible obstacles for this terrain
 	for(auto primaryID : VLC->objtypeh->knownObjects())

+ 1 - 1
lib/rmg/ObstaclePlacer.h

@@ -26,7 +26,7 @@ public:
 
 	rmg::Area blockedArea;
 
-	void collectPossibleObstacles(const Terrain & terrain);
+	void collectPossibleObstacles(TerrainId terrain);
 
 	void placeObstacles(CMap * map, CRandomGenerator & rand);
 

+ 10 - 15
lib/rmg/RiverPlacer.cpp

@@ -26,13 +26,6 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 const int RIVER_DELTA_ID = 143;
 const int RIVER_DELTA_SUBTYPE = 0;
-const std::map<std::string, std::string> RIVER_DELTA_TEMPLATE_NAME
-{
-	{RIVER_NAMES[1], "clrdelt"},
-	{RIVER_NAMES[2], "icedelt"},
-	{RIVER_NAMES[3], "muddelt"},
-	{RIVER_NAMES[4], "lavdelt"}
-};
 
 const std::array<std::array<int, 25>, 4> deltaTemplates
 {
@@ -93,7 +86,7 @@ void RiverPlacer::init()
 void RiverPlacer::drawRivers()
 {
 	map.getEditManager()->getTerrainSelection().setSelection(rivers.getTilesVector());
-	map.getEditManager()->drawRiver(Terrain::Manager::getInfo(zone.getTerrainType()).river, &generator.rand);
+	map.getEditManager()->drawRiver(VLC->terrainTypeHandler->terrains()[zone.getTerrainType()].river, &generator.rand);
 }
 
 char RiverPlacer::dump(const int3 & t)
@@ -202,7 +195,7 @@ void RiverPlacer::preprocess()
 	//calculate delta positions
 	if(connectedToWaterZoneId > -1)
 	{
-		auto river = Terrain::Manager::getInfo(zone.getTerrainType()).river;
+		auto river = VLC->terrainTypeHandler->terrains()[zone.getTerrainType()].river;
 		auto & a = neighbourZonesTiles[connectedToWaterZoneId];
 		auto availableArea = zone.areaPossible() + zone.freePaths();
 		for(auto & tileToProcess : availableArea.getTilesVector())
@@ -240,7 +233,7 @@ void RiverPlacer::preprocess()
 						deltaOrientations[p] = templateId + 1;
 						
 						//specific case: deltas for ice rivers amd mud rivers are messed :(
-						if(river == RIVER_NAMES[2])
+						if(river == River::ICY_RIVER)
 						{
 							switch(deltaOrientations[p])
 							{
@@ -258,7 +251,7 @@ void RiverPlacer::preprocess()
 									break;
 							}
 						}
-						if(river == RIVER_NAMES[3])
+						if(river == River::MUD_RIVER)
 						{
 							switch(deltaOrientations[p])
 							{
@@ -328,8 +321,9 @@ void RiverPlacer::preprocess()
 
 void RiverPlacer::connectRiver(const int3 & tile)
 {
-	auto river = Terrain::Manager::getInfo(zone.getTerrainType()).river;
-	if(river.empty() || river == RIVER_NAMES[0])
+	auto riverType = VLC->terrainTypeHandler->terrains()[zone.getTerrainType()].river;
+	const auto & river = VLC->terrainTypeHandler->rivers()[riverType];
+	if(river.id == River::NO_RIVER)
 		return;
 	
 	rmg::Area roads;
@@ -386,9 +380,10 @@ void RiverPlacer::connectRiver(const int3 & tile)
 		if(tmplates.size() > 3)
 		{
 			if(tmplates.size() % 4 != 0)
-				throw rmgException(boost::to_string(boost::format("River templates for (%d,%d) at terrain %s, river %s are incorrect") % RIVER_DELTA_ID % RIVER_DELTA_SUBTYPE % zone.getTerrainType() % river));
+				throw rmgException(boost::to_string(boost::format("River templates for (%d,%d) at terrain %s, river %s are incorrect") %
+					RIVER_DELTA_ID % RIVER_DELTA_SUBTYPE % zone.getTerrainType() % river.code));
 			
-			std::string targetTemplateName = RIVER_DELTA_TEMPLATE_NAME.at(river) + std::to_string(deltaOrientations[pos]) + ".def";
+			std::string targetTemplateName = river.deltaName + std::to_string(deltaOrientations[pos]) + ".def";
 			for(auto & templ : tmplates)
 			{
 				if(templ->animationFile == targetTemplateName)

+ 2 - 2
lib/rmg/RmgMap.cpp

@@ -84,7 +84,7 @@ void RmgMap::initTiles(CMapGenerator & generator)
 	
 	getEditManager()->clearTerrain(&generator.rand);
 	getEditManager()->getTerrainSelection().selectRange(MapRect(int3(0, 0, 0), mapGenOptions.getWidth(), mapGenOptions.getHeight()));
-	getEditManager()->drawTerrain(Terrain("grass"), &generator.rand);
+	getEditManager()->drawTerrain(Terrain::GRASS, &generator.rand);
 	
 	auto tmpl = mapGenOptions.getMapTemplate();
 	zones.clear();
@@ -231,7 +231,7 @@ void RmgMap::setOccupied(const int3 &tile, ETileType::ETileType state)
 	tiles[tile.x][tile.y][tile.z].setOccupied(state);
 }
 
-void RmgMap::setRoad(const int3& tile, const std::string & roadType)
+void RmgMap::setRoad(const int3& tile, RoadId roadType)
 {
 	assertOnMap(tile);
 	

+ 1 - 1
lib/rmg/RmgMap.h

@@ -46,7 +46,7 @@ public:
 	bool isOnMap(const int3 & tile) const;
 	
 	void setOccupied(const int3 &tile, ETileType::ETileType state);
-	void setRoad(const int3 &tile, const std::string & roadType);
+	void setRoad(const int3 &tile, RoadId roadType);
 	
 	TileInfo getTile(const int3 & tile) const;
 		

+ 12 - 7
lib/rmg/RmgObject.cpp

@@ -105,13 +105,17 @@ void Object::Instance::setPositionRaw(const int3 & position)
 	dObject.pos = dPosition + dParent.getPosition();
 }
 
-void Object::Instance::setTemplate(const Terrain & terrain)
+void Object::Instance::setTemplate(const TerrainId & terrain)
 {
 	if(dObject.appearance->id == Obj::NO_OBJ)
 	{
 		auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrain);
-		if(templates.empty())
-			throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s") % dObject.ID % dObject.subID % static_cast<std::string>(terrain)));
+		auto terrainName = VLC->terrainTypeHandler->terrains()[terrain].name;
+		if (templates.empty())
+		{
+			throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s") %
+				dObject.ID % dObject.subID % terrainName));
+		}
 		
 		dObject.appearance = templates.front();
 	}
@@ -255,7 +259,7 @@ void Object::setPosition(const int3 & position)
 		i.setPositionRaw(i.getPosition());
 }
 
-void Object::setTemplate(const Terrain & terrain)
+void Object::setTemplate(const TerrainId & terrain)
 {
 	for(auto& i : dInstances)
 		i.setTemplate(terrain);
@@ -291,11 +295,12 @@ void Object::Instance::finalize(RmgMap & map)
 	if (dObject.appearance->id == Obj::NO_OBJ)
 	{
 		auto terrainType = map.map().getTile(getPosition(true)).terType;
-		auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrainType);
+		auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrainType->id);
 		if (templates.empty())
-			throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s (terrain %d)") % dObject.ID % dObject.subID % getPosition(true).toString() % terrainType));
+			throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s (terrain %d)") %
+				dObject.ID % dObject.subID % getPosition(true).toString() % terrainType->name));
 		
-		setTemplate(terrainType);
+		setTemplate(terrainType->id);
 	}
 	
 	for(auto & tile : getBlockedArea().getTilesVector())

+ 2 - 3
lib/rmg/RmgObject.h

@@ -18,7 +18,6 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 class CGObjectInstance;
 class RmgMap;
-class Terrain;
 
 namespace rmg {
 class Object
@@ -36,7 +35,7 @@ public:
 		int3 getVisitablePosition() const;
 		bool isVisitableFrom(const int3 & tile) const;
 		const Area & getAccessibleArea() const;
-		void setTemplate(const Terrain & terrain); //cache invalidation
+		void setTemplate(const TerrainId & terrain); //cache invalidation
 		
 		int3 getPosition(bool isAbsolute = false) const;
 		void setPosition(const int3 & position); //cache invalidation
@@ -72,7 +71,7 @@ public:
 	
 	const int3 & getPosition() const;
 	void setPosition(const int3 & position);
-	void setTemplate(const Terrain & terrain);
+	void setTemplate(const TerrainId & terrain);
 	
 	const Area & getArea() const;  //lazy cache invalidation
 	

+ 2 - 1
lib/rmg/RoadPlacer.cpp

@@ -71,7 +71,8 @@ void RoadPlacer::drawRoads(bool secondary)
 	zone.areaPossible().subtract(roads);
 	zone.freePaths().unite(roads);
 	map.getEditManager()->getTerrainSelection().setSelection(roads.getTilesVector());
-	std::string roadType = (secondary ? generator.getConfig().secondaryRoadType : generator.getConfig().defaultRoadType);
+	std::string roadCode = (secondary ? generator.getConfig().secondaryRoadType : generator.getConfig().defaultRoadType);
+	RoadId roadType = VLC->terrainTypeHandler->getRoadByCode(roadCode)->id;
 	map.getEditManager()->drawRoad(roadType, &generator.rand);
 }
 

+ 4 - 4
lib/rmg/RockPlacer.cpp

@@ -24,8 +24,8 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 void RockPlacer::process()
 {
-	rockTerrain = Terrain::Manager::getInfo(zone.getTerrainType()).rockTerrain;
-	assert(!rockTerrain.isPassable());
+	rockTerrain = VLC->terrainTypeHandler->terrains()[zone.getTerrainType()].rockTerrain;
+	assert(!VLC->terrainTypeHandler->terrains()[rockTerrain].isPassable());
 	
 	accessibleArea = zone.freePaths() + zone.areaUsed();
 	if(auto * m = zone.getModificator<ObjectManager>())
@@ -78,7 +78,7 @@ void RockPlacer::postProcess()
 	//finally mark rock tiles as occupied, spawn no obstacles there
 	rockArea = zone.area().getSubarea([this](const int3 & t)
 	{
-		return !map.map().getTile(t).terType.isPassable();
+		return !map.map().getTile(t).terType->isPassable();
 	});
 	
 	zone.areaUsed().unite(rockArea);
@@ -97,7 +97,7 @@ void RockPlacer::init()
 
 char RockPlacer::dump(const int3 & t)
 {
-	if(!map.map().getTile(t).terType.isPassable())
+	if(!map.map().getTile(t).terType->isPassable())
 	{
 		return zone.area().contains(t) ? 'R' : 'E';
 	}

+ 1 - 1
lib/rmg/RockPlacer.h

@@ -28,7 +28,7 @@ public:
 protected:
 	
 	rmg::Area rockArea, accessibleArea;
-	Terrain rockTerrain;
+	TerrainId rockTerrain;
 };
 
 VCMI_LIB_NAMESPACE_END

+ 6 - 6
lib/rmg/TileInfo.cpp

@@ -46,7 +46,7 @@ bool TileInfo::isFree() const
 
 bool TileInfo::isRoad() const
 {
-	return roadType != ROAD_NAMES[0];
+	return roadType != Road::NO_ROAD;
 }
 
 bool TileInfo::isUsed() const
@@ -63,19 +63,19 @@ ETileType::ETileType TileInfo::getTileType() const
 	return occupied;
 }
 
-Terrain TileInfo::getTerrainType() const
+TerrainId TileInfo::getTerrainType() const
 {
 	return terrain;
 }
 
-void TileInfo::setTerrainType(Terrain value)
+void TileInfo::setTerrainType(TerrainId type)
 {
-	terrain = value;
+	terrain = type;
 }
 
-void TileInfo::setRoadType(const std::string & value)
+void TileInfo::setRoadType(RoadId type)
 {
-	roadType = value;
+	roadType = type;
 	//	setOccupied(ETileType::FREE);
 }
 

+ 5 - 5
lib/rmg/TileInfo.h

@@ -30,16 +30,16 @@ public:
 	bool isUsed() const;
 	bool isRoad() const;
 	void setOccupied(ETileType::ETileType value);
-	Terrain getTerrainType() const;
+	TerrainId getTerrainType() const;
 	ETileType::ETileType getTileType() const;
-	void setTerrainType(Terrain value);
+	void setTerrainType(TerrainId value);
 	
-	void setRoadType(const std::string & value);
+	void setRoadType(RoadId type);
 private:
 	float nearestObjectDistance;
 	ETileType::ETileType occupied;
-	Terrain terrain;
-	std::string roadType;
+	TerrainId terrain;
+	RoadId roadType;
 };
 
 VCMI_LIB_NAMESPACE_END

+ 1 - 1
lib/rmg/TreasurePlacer.cpp

@@ -813,7 +813,7 @@ ObjectInfo::ObjectInfo()
 	
 }
 
-void ObjectInfo::setTemplate(si32 type, si32 subtype, Terrain terrainType)
+void ObjectInfo::setTemplate(si32 type, si32 subtype, TerrainId terrainType)
 {
 	auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype);
 	if(!templHandler)

+ 1 - 1
lib/rmg/TreasurePlacer.h

@@ -28,7 +28,7 @@ struct ObjectInfo
 	//ui32 maxPerMap; //unused
 	std::function<CGObjectInstance *()> generateObject;
 	
-	void setTemplate(si32 type, si32 subtype, Terrain terrain);
+	void setTemplate(si32 type, si32 subtype, TerrainId terrain);
 	
 	ObjectInfo();
 	

+ 1 - 1
lib/rmg/WaterProxy.cpp

@@ -53,7 +53,7 @@ void WaterProxy::process()
 		
 		for(auto & t : z.second->area().getTilesVector())
 		{
-			if(map.map().getTile(t).terType == zone.getTerrainType())
+			if(map.map().getTile(t).terType->id == zone.getTerrainType())
 			{
 				z.second->areaPossible().erase(t);
 				z.second->area().erase(t);

+ 3 - 3
lib/rmg/Zone.cpp

@@ -28,7 +28,7 @@ std::function<bool(const int3 &)> AREA_NO_FILTER = [](const int3 & t)
 Zone::Zone(RmgMap & map, CMapGenerator & generator)
 					: ZoneOptions(),
 					townType(ETownType::NEUTRAL),
-					terrainType(Terrain("grass")),
+					terrainType(Terrain::GRASS),
 					map(map),
 					generator(generator)
 {
@@ -134,12 +134,12 @@ void Zone::setTownType(si32 town)
 	townType = town;
 }
 
-const Terrain & Zone::getTerrainType() const
+TerrainId Zone::getTerrainType() const
 {
 	return terrainType;
 }
 
-void Zone::setTerrainType(const Terrain & terrain)
+void Zone::setTerrainType(TerrainId terrain)
 {
 	terrainType = terrain;
 }

+ 3 - 3
lib/rmg/Zone.h

@@ -100,8 +100,8 @@ public:
 	
 	si32 getTownType() const;
 	void setTownType(si32 town);
-	const Terrain & getTerrainType() const;
-	void setTerrainType(const Terrain & terrain);
+	TerrainId getTerrainType() const;
+	void setTerrainType(TerrainId terrain);
 		
 	void connectPath(const rmg::Path & path);
 	rmg::Path searchPath(const rmg::Area & src, bool onlyStraight, std::function<bool(const int3 &)> areafilter = AREA_NO_FILTER) const;
@@ -140,7 +140,7 @@ protected:
 	
 	//template info
 	si32 townType;
-	Terrain terrainType;
+	TerrainId terrainType;
 	
 };
 

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

@@ -88,7 +88,7 @@ int BattleCbProxy::getTerrainType(lua_State * L)
 	if(!S.tryGet(1, object))
 		return S.retVoid();
 
-	return LuaStack::quickRetStr(L, object->battleTerrainType());
+	return LuaStack::quickRetInt(L, object->battleTerrainType());
 }
 
 int BattleCbProxy::getUnitByPos(lua_State * L)

+ 8 - 8
server/CGameHandler.cpp

@@ -1933,7 +1933,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.isLand(), ti.get());
+			hth.move = h->maxMovePointsCached(gs->map->getTile(h->getPosition(false)).terType->isLand(), ti.get());
 			hth.mana = h->getManaNewTurn();
 
 			n.heroes.insert(hth);
@@ -2240,10 +2240,10 @@ void CGameHandler::setupBattle(int3 tile, const CArmedInstance *armies[2], const
 {
 	battleResult.set(nullptr);
 
-	const auto t = getTile(tile);
-	Terrain terrain = t->terType;
+	const auto & t = *getTile(tile);
+	TerrainId terrain = t.terType->id;
 	if (gs->map->isCoastalTile(tile)) //coastal tile is always ground
-		terrain = Terrain("sand");
+		terrain = Terrain::SAND;
 
 	BattleField terType = gs->battleGetBattlefieldType(tile, getRandomGenerator());
 	if (heroes[0] && heroes[0]->boat && heroes[1] && heroes[1]->boat)
@@ -2340,7 +2340,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.isLand() && !t.blocked;
+	const bool disembarking = h->boat && t.terType->isLand() && !t.blocked;
 
 	//result structure for start - movement failed, no move points used
 	TryMoveHero tmh;
@@ -2362,11 +2362,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.isPassable()  ||  (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.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)
+		|| ((!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.isLand() && 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!"))

+ 2 - 2
test/game/CGameStateTest.cpp

@@ -191,9 +191,9 @@ public:
 
 		int3 tile(4,4,0);
 
-		const auto t = gameCallback->getTile(tile);
+		const auto & t = *gameCallback->getTile(tile);
 
-		Terrain terrain = t->terType;
+		TerrainId terrain = t.terType->id;
 		BattleField terType = BattleField::fromString("grass_hills");
 
 		//send info about battles

+ 17 - 17
test/map/CMapEditManagerTest.cpp

@@ -33,30 +33,30 @@ TEST(MapManager, DrawTerrain_Type)
 
 		// 1x1 Blow up
 		editManager->getTerrainSelection().select(int3(5, 5, 0));
-		editManager->drawTerrain(Terrain("grass"));
+		editManager->drawTerrain(Terrain::GRASS);
 		static const int3 squareCheck[] = { int3(5,5,0), int3(5,4,0), int3(4,4,0), int3(4,5,0) };
 		for(int i = 0; i < ARRAY_COUNT(squareCheck); ++i)
 		{
-			EXPECT_EQ(map->getTile(squareCheck[i]).terType, Terrain("grass"));
+			EXPECT_EQ(map->getTile(squareCheck[i]).terType->id, Terrain::GRASS);
 		}
 
 		// Concat to square
 		editManager->getTerrainSelection().select(int3(6, 5, 0));
-		editManager->drawTerrain(Terrain("grass"));
-		EXPECT_EQ(map->getTile(int3(6, 4, 0)).terType, Terrain("grass"));
+		editManager->drawTerrain(Terrain::GRASS);
+		EXPECT_EQ(map->getTile(int3(6, 4, 0)).terType->id, Terrain::GRASS);
 		editManager->getTerrainSelection().select(int3(6, 5, 0));
-		editManager->drawTerrain(Terrain("lava"));
-		EXPECT_EQ(map->getTile(int3(4, 4, 0)).terType, Terrain("grass"));
-		EXPECT_EQ(map->getTile(int3(7, 4, 0)).terType, Terrain("lava"));
+		editManager->drawTerrain(Terrain::LAVA);
+		EXPECT_EQ(map->getTile(int3(4, 4, 0)).terType->id, Terrain::GRASS);
+		EXPECT_EQ(map->getTile(int3(7, 4, 0)).terType->id, Terrain::LAVA);
 
 		// Special case water,rock
 		editManager->getTerrainSelection().selectRange(MapRect(int3(10, 10, 0), 10, 5));
-		editManager->drawTerrain(Terrain("grass"));
+		editManager->drawTerrain(Terrain::GRASS);
 		editManager->getTerrainSelection().selectRange(MapRect(int3(15, 17, 0), 10, 5));
-		editManager->drawTerrain(Terrain("grass"));
+		editManager->drawTerrain(Terrain::GRASS);
 		editManager->getTerrainSelection().select(int3(21, 16, 0));
-		editManager->drawTerrain(Terrain("grass"));
-		EXPECT_EQ(map->getTile(int3(20, 15, 0)).terType, Terrain("grass"));
+		editManager->drawTerrain(Terrain::GRASS);
+		EXPECT_EQ(map->getTile(int3(20, 15, 0)).terType->id, Terrain::GRASS);
 
 		// Special case non water,rock
 		static const int3 diagonalCheck[] = { int3(31,42,0), int3(32,42,0), int3(32,43,0), int3(33,43,0), int3(33,44,0),
@@ -66,17 +66,17 @@ TEST(MapManager, DrawTerrain_Type)
 		{
 			editManager->getTerrainSelection().select(diagonalCheck[i]);
 		}
-		editManager->drawTerrain(Terrain("grass"));
-		EXPECT_EQ(map->getTile(int3(35, 44, 0)).terType, Terrain("water"));
+		editManager->drawTerrain(Terrain::GRASS);
+		EXPECT_EQ(map->getTile(int3(35, 44, 0)).terType->id, Terrain::WATER);
 
 		// Rock case
 		editManager->getTerrainSelection().selectRange(MapRect(int3(1, 1, 1), 15, 15));
-		editManager->drawTerrain(Terrain("subterra"));
+		editManager->drawTerrain(Terrain::SUBTERRANEAN);
 		std::vector<int3> vec({ int3(6, 6, 1), int3(7, 6, 1), int3(8, 6, 1), int3(5, 7, 1), int3(6, 7, 1), int3(7, 7, 1),
 								int3(8, 7, 1), int3(4, 8, 1), int3(5, 8, 1), int3(6, 8, 1)});
 		editManager->getTerrainSelection().setSelection(vec);
-		editManager->drawTerrain(Terrain("rock"));
-		EXPECT_TRUE(!map->getTile(int3(5, 6, 1)).terType.isPassable() || !map->getTile(int3(7, 8, 1)).terType.isPassable());
+		editManager->drawTerrain(Terrain::ROCK);
+		EXPECT_TRUE(!map->getTile(int3(5, 6, 1)).terType->isPassable() || !map->getTile(int3(7, 8, 1)).terType->isPassable());
 
 		//todo: add checks here and enable, also use smaller size
 		#if 0
@@ -143,7 +143,7 @@ TEST(MapManager, DrawTerrain_View)
 				int3 pos((si32)posVector[0].Float(), (si32)posVector[1].Float(), (si32)posVector[2].Float());
 				const auto & originalTile = originalMap->getTile(pos);
 				editManager->getTerrainSelection().selectRange(MapRect(pos, 1, 1));
-				editManager->drawTerrain(originalTile.terType, &gen);
+				editManager->drawTerrain(originalTile.terType->id, &gen);
 				const auto & tile = map->getTile(pos);
 				bool isInRange = false;
 				for(const auto & range : mapping)

Некоторые файлы не были показаны из-за большого количества измененных файлов