浏览代码

battlefields in VLC and custom bonuses for terrain patches

Andrii Danylchenko 3 年之前
父节点
当前提交
4b4cc3cf4b
共有 47 个文件被更改,包括 645 次插入221 次删除
  1. 6 0
      client/CGameInfo.cpp
  2. 3 0
      client/CGameInfo.h
  3. 0 6
      client/Graphics.cpp
  4. 0 1
      client/Graphics.h
  5. 5 3
      client/battle/CBattleInterface.cpp
  6. 173 0
      config/battlefields.json
  7. 0 29
      config/battles_graphics.json
  8. 4 0
      config/gameConfig.json
  9. 35 0
      config/schemas/battlefield.json
  10. 6 0
      config/schemas/mod.json
  11. 2 0
      include/vcmi/Services.h
  12. 108 0
      lib/BattleFieldHandler.cpp
  13. 85 0
      lib/BattleFieldHandler.h
  14. 3 2
      lib/CGameState.cpp
  15. 5 2
      lib/CHeroHandler.cpp
  16. 1 1
      lib/CHeroHandler.h
  17. 3 1
      lib/CMakeLists.txt
  18. 2 0
      lib/CModHandler.cpp
  19. 38 0
      lib/GameConstants.cpp
  20. 17 0
      lib/GameConstants.h
  21. 1 0
      lib/IGameCallback.cpp
  22. 0 26
      lib/Terrain.cpp
  23. 2 40
      lib/Terrain.h
  24. 10 0
      lib/VCMI_Lib.cpp
  25. 5 0
      lib/VCMI_Lib.h
  26. 4 55
      lib/battle/BattleInfo.cpp
  27. 8 16
      lib/battle/CBattleInfoCallback.cpp
  28. 4 4
      lib/mapObjects/CObjectClassesHandler.cpp
  29. 2 1
      lib/mapObjects/CObjectClassesHandler.h
  30. 5 2
      lib/mapping/CMapEditManager.cpp
  31. 1 1
      lib/mapping/CMapEditManager.h
  32. 2 0
      lib/registerTypes/RegisterTypes.h
  33. 11 1
      lib/rmg/CMapGenOptions.cpp
  34. 4 0
      lib/rmg/CRmgTemplateZone.cpp
  35. 3 1
      lib/spells/ISpellMechanics.cpp
  36. 3 1
      scripting/lua/api/BattleCb.cpp
  37. 10 10
      scripts/lib/erm/BU.lua
  38. 1 1
      server/CGameHandler.cpp
  39. 35 8
      test/battle/CBattleInfoCallbackTest.cpp
  40. 5 3
      test/erm/ERM_BU.cpp
  41. 1 1
      test/game/CGameStateTest.cpp
  42. 1 1
      test/map/CMapEditManagerTest.cpp
  43. 4 1
      test/map/CMapFormatTest.cpp
  44. 1 1
      test/mock/BattleFake.cpp
  45. 23 0
      test/mock/ZoneOptionsFake.h
  46. 2 1
      test/mock/mock_Services.h
  47. 1 1
      test/spells/effects/CloneTest.cpp

+ 6 - 0
client/CGameInfo.cpp

@@ -37,6 +37,7 @@ void CGameInfo::setFromLib()
 	spellh = VLC->spellh;
 	skillh = VLC->skillh;
 	objtypeh = VLC->objtypeh;
+	battleFieldHandler = VLC->battlefieldsHandler;
 }
 
 const ArtifactService * CGameInfo::artifacts() const
@@ -44,6 +45,11 @@ const ArtifactService * CGameInfo::artifacts() const
 	return globalServices->artifacts();
 }
 
+const BattleFieldService * CGameInfo::battlefields() const
+{
+	return globalServices->battlefields();
+}
+
 const CreatureService * CGameInfo::creatures() const
 {
 	return globalServices->creatures();

+ 3 - 0
client/CGameInfo.h

@@ -31,6 +31,7 @@ class CCursorHandler;
 class CGameState;
 class IMainVideoPlayer;
 class CServerHandler;
+class BattleFieldHandler;
 
 class CMap;
 
@@ -60,6 +61,7 @@ public:
 	const scripting::Service * scripts() const override;
 	const spells::Service * spells() const override;
 	const SkillService * skills() const override;
+	const BattleFieldService * battlefields() const override;
 
 	void updateEntity(Metatype metatype, int32_t index, const JsonNode & data) override;
 
@@ -68,6 +70,7 @@ public:
 
 
 	ConstTransitivePtr<CModHandler> modh; //public?
+	ConstTransitivePtr<BattleFieldHandler> battleFieldHandler;
 	ConstTransitivePtr<CHeroHandler> heroh;
 	ConstTransitivePtr<CCreatureHandler> creh;
 	ConstTransitivePtr<CSpellHandler> spellh;

+ 0 - 6
client/Graphics.cpp

@@ -109,12 +109,6 @@ void Graphics::initializeBattleGraphics()
 			
 		const JsonNode config(mod, ResourceID("config/battles_graphics.json"));
 
-		if(!config["backgrounds"].isNull())
-		for(auto & t : config["backgrounds"].Struct())
-		{
-			battleBacks[t.first] = t.second.String();
-		}
-
 		//initialization of AC->def name mapping
 		if(!config["ac_mapping"].isNull())
 		for(const JsonNode &ac : config["ac_mapping"].Vector())

+ 0 - 1
client/Graphics.h

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

+ 5 - 3
client/battle/CBattleInterface.cpp

@@ -41,6 +41,7 @@
 #include "../../lib/spells/ISpellMechanics.h"
 #include "../../lib/spells/Problem.h"
 #include "../../lib/CTownHandler.h"
+#include "../../lib/BattleFieldHandler.h"
 #include "../../lib/CGameState.h"
 #include "../../lib/mapping/CMap.h"
 #include "../../lib/NetPacks.h"
@@ -201,13 +202,14 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 	else
 	{
 		auto bfieldType = curInt->cb->battleGetBattlefieldType();
-		if(!vstd::contains(graphics->battleBacks, bfieldType))
+
+		if(bfieldType == BattleField::NONE)
 		{
-			logGlobal->error("%s is not valid battlefield type!", static_cast<std::string>(bfieldType));
+			logGlobal->error("Invalid battlefield returned for current battle");
 		}
 		else
 		{
-			background = BitmapHandler::loadBitmap(graphics->battleBacks[bfieldType], false);
+			background = BitmapHandler::loadBitmap(bfieldType.getInfo()->graphics, false);
 		}
 	}
 

+ 173 - 0
config/battlefields.json

@@ -0,0 +1,173 @@
+{
+	"sand_shore": { "graphics" : "CMBKBCH.BMP" },
+	"sand_mesas": { "graphics" : "CMBKDES.BMP" },
+	"dirt_birches": { "graphics" : "CMBKDRTR.BMP" },
+	"dirt_hills": { "graphics" : "CMBKDRMT.BMP" },
+	"dirt_pines": { "graphics" : "CMBKDRDD.BMP" },
+	"grass_hills": { "graphics" : "CMBKGRMT.BMP" },
+	"grass_pines": { "graphics" : "CMBKGRTR.BMP" },
+	"lava":  { "graphics" :"CMBKLAVA.BMP" },
+	"magic_plains": {
+		"graphics": "CMBKMAG.BMP",
+		"isSpecial" : true,
+		"bonuses": [
+			{
+				"type" : "MAGIC_SCHOOL_SKILL",
+				"subtype" : 0,
+				"val" : 3,
+				"valueType" : "BASE_NUMBER"
+			}
+		]
+	},
+	"snow_mountains": { "graphics" : "CMBKSNMT.BMP" },
+	"snow_trees": { "graphics" : "CMBKSNTR.BMP" },
+	"subterranean": { "graphics" : "CMBKSUB.BMP", "isSpecial" : true },
+	"swamp_trees": { "graphics" : "CMBKSWMP.BMP" },
+	"fiery_fields": {
+		"graphics": "CMBKFF.BMP",
+		"isSpecial" : true,
+		"bonuses": [
+			{
+				"type" : "MAGIC_SCHOOL_SKILL",
+				"subtype" : 2,
+				"val" : 3,
+				"valueType" : "BASE_NUMBER"
+			}
+		]
+	},
+	"rocklands": {
+		"graphics": "CMBKRK.BMP",
+		"isSpecial" : true,
+		"bonuses": [
+			{
+				"type" : "MAGIC_SCHOOL_SKILL",
+				"subtype" : 8,
+				"val" : 3,
+				"valueType" : "BASE_NUMBER"
+			}
+		]
+	},
+	"magic_clouds": {
+		"graphics": "CMBKMC.BMP",
+		"isSpecial" : true,
+		"bonuses": [
+			{
+				"type" : "MAGIC_SCHOOL_SKILL",
+				"subtype" : 1,
+				"val" : 3,
+				"valueType" : "BASE_NUMBER"
+			}
+		]
+	},
+	"lucid_pools": {
+		"graphics": "CMBKLP.BMP",
+		"isSpecial" : true,
+		"bonuses": [
+			{
+				"type" : "MAGIC_SCHOOL_SKILL",
+				"subtype" : 4,
+				"val" : 3,
+				"valueType" : "BASE_NUMBER"
+			}
+		]
+	},
+	"holy_ground": {
+		"graphics": "CMBKHG.BMP",
+		"isSpecial" : true,
+		"bonuses": [
+			{
+				"type" : "MORALE",
+				"val" : 1,
+				"valueType" : "BASE_NUMBER",
+				"description" : "Creatures of good town alignment on Holly Ground",
+				"limiters": [{ "type" : "CREATURE_ALIGNMENT_LIMITER", "parameters" : ["good"] }]
+			},
+			{
+				"type" : "MORALE",
+				"val" : -1,
+				"valueType" : "BASE_NUMBER",
+				"description" : "Creatures of evil town alignment on Holly Ground",
+				"limiters": [{ "type" : "CREATURE_ALIGNMENT_LIMITER", "parameters" : ["evil"] }]
+			}
+		]
+	},
+	"clover_field": {
+		"graphics": "CMBKCF.BMP",
+		"isSpecial" : true,
+		"bonuses": [
+			{
+				"type" : "LUCK",
+				"val" : 2,
+				"valueType" : "BASE_NUMBER",
+				"description" : "Creatures of neutral town alignment on Clover Field",
+				"limiters": [{ "type" : "CREATURE_ALIGNMENT_LIMITER", "parameters" : ["neutral"] }]
+			}
+		]
+	},
+	"evil_fog": {
+		"graphics": "CMBKEF.BMP",
+		"isSpecial" : true,
+		"bonuses": [
+			{
+				"type" : "MORALE",
+				"val" : -1,
+				"valueType" : "BASE_NUMBER",
+				"description" : "Creatures of good town alignment on Evil Fog",
+				"limiters": [{ "type" : "CREATURE_ALIGNMENT_LIMITER", "parameters" : ["good"] }]
+			},
+			{
+				"type" : "MORALE",
+				"val" : 1,
+				"valueType" : "BASE_NUMBER",
+				"description" : "Creatures of evil town alignment on Evil Fog",
+				"limiters": [{ "type" : "CREATURE_ALIGNMENT_LIMITER", "parameters" : ["evil"] }]
+			}
+		]
+	},
+	"favorable_winds": {
+		"graphics": "CMBKFW.BMP",
+		"isSpecial" : true
+	},
+	"cursed_ground": {
+		"graphics": "CMBKCUR.BMP",
+		"isSpecial" : true,
+		"bonuses": [
+			{
+				"type" : "NO_MORALE",
+				"subtype" : 0,
+				"val" : 0,
+				"valueType" : "INDEPENDENT_MIN",
+				"description" : "Creatures on Cursed Ground"
+			},
+			{
+				"type" : "NO_LUCK",
+				"subtype" : 0,
+				"val" : 0,
+				"valueType" : "INDEPENDENT_MIN",
+				"description" : "Creatures on Cursed Ground"
+			},
+			{
+				"type" : "BLOCK_MAGIC_ABOVE",
+				"subtype" : 0,
+				"val" : 1,
+				"valueType" : "INDEPENDENT_MIN"
+			}
+		]
+	},
+	"rough": { "graphics" : "CMBKRGH.BMP" },
+	"ship_to_ship": 
+	{
+		"graphics" : "CMBKBOAT.BMP"
+		"impassableHexes" : [
+			6, 7, 8, 9,
+			24, 25, 26,
+			58, 59, 60,
+			75, 76, 77,
+			92, 93, 94,
+			109, 110, 111,
+			126, 127, 128,
+			159, 160, 161, 162, 163,
+			176, 177, 178, 179, 180]
+	},
+	"ship": { "graphics" : "CMBKDECK.BMP" }
+}

+ 0 - 29
config/battles_graphics.json

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

+ 4 - 0
config/gameConfig.json

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

+ 35 - 0
config/schemas/battlefield.json

@@ -0,0 +1,35 @@
+{
+	"type":"object",
+	"$schema": "http://json-schema.org/draft-04/schema",
+	"title" : "VCMI battlefield format",
+	"description" : "Format used to define new artifacts in VCMI",
+	"required" : [ "graphics" ],
+
+	"additionalProperties" : false,
+	"properties":{
+		"bonuses": {
+			"type":"array",
+			"description": "Bonuses provided by this battleground using bonus system",
+			"items": { "$ref" : "bonus.json" }
+		},
+		"name": {
+			"type":"string",
+			"description": "Name of the battleground"
+		},
+		"graphics": {
+			"type":"string",
+			"description": "BMP battleground resource"
+		},
+		"isSpecial": {
+			"type":"boolean",
+			"description": "Shows if this battleground has own obstacles"
+		},
+		"impassableHexes": {
+			"type":"array",
+			"description": "Battle hexes always impassable for this type of battlefield (ship to ship for instance)",
+			"items": {
+				"type":"number"
+			}
+		}
+	}
+}

+ 6 - 0
config/schemas/mod.json

@@ -112,6 +112,12 @@
 			"description": "List of configuration files for RMG templates",
 			"items": { "type":"string", "format" : "textFile" }
 
+		},
+		"battlefields":{
+			"type":"array",
+			"description": "List of configuration files for battlefields",
+			"items": { "type":"string", "format" : "textFile" }
+
 		},
 
 		"changelog" : {

+ 2 - 0
include/vcmi/Services.h

@@ -19,6 +19,7 @@ class HeroClassService;
 class HeroTypeService;
 class SkillService;
 class JsonNode;
+class BattleFieldService;
 
 namespace spells
 {
@@ -48,6 +49,7 @@ public:
 	virtual const scripting::Service * scripts() const = 0;
 	virtual const spells::Service * spells() const = 0;
 	virtual const SkillService * skills() const = 0;
+	virtual const BattleFieldService * battlefields() const = 0;
 
 	virtual void updateEntity(Metatype metatype, int32_t index, const JsonNode & data) = 0;
 

+ 108 - 0
lib/BattleFieldHandler.cpp

@@ -0,0 +1,108 @@
+/*
+ * BattleFieldHandler.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+
+#include <vcmi/Entity.h>
+#include "BattleFieldHandler.h"
+
+BattleFieldInfo * BattleFieldHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index)
+{
+	BattleFieldInfo * info = new BattleFieldInfo(BattleField(index), identifier);
+
+	if(json["graphics"].getType() == JsonNode::JsonType::DATA_STRING)
+	{
+		info->graphics = json["graphics"].String();
+	}
+
+	if(json["icon"].getType() == JsonNode::JsonType::DATA_STRING)
+	{
+		info->icon = json["icon"].String();
+	}
+
+	if(json["name"].getType() == JsonNode::JsonType::DATA_STRING)
+	{
+		info->name = json["name"].String();
+	}
+
+	if(json["bonuses"].getType() == JsonNode::JsonType::DATA_VECTOR)
+	{
+		for(auto b : json["bonuses"].Vector())
+		{
+			auto bonus = JsonUtils::parseBonus(b);
+
+			bonus->source = Bonus::TERRAIN_OVERLAY;
+			bonus->sid = info->getIndex();
+			bonus->duration = Bonus::ONE_BATTLE;
+
+			info->bonuses.push_back(bonus);
+		}
+	}
+
+	if(json["isSpecial"].getType() == JsonNode::JsonType::DATA_BOOL)
+	{
+		info->isSpecial = json["isSpecial"].Bool();
+	}
+
+	if(json["impassableHexes"].getType() == JsonNode::JsonType::DATA_VECTOR)
+	{
+		for(auto node : json["impassableHexes"].Vector())
+			info->impassableHexes.push_back(BattleHex(node.Integer()));
+	}
+
+
+	return info;
+}
+
+std::vector<JsonNode> BattleFieldHandler::loadLegacyData(size_t dataSize)
+{
+	return std::vector<JsonNode>();
+}
+
+const std::vector<std::string> & BattleFieldHandler::getTypeNames() const
+{
+	static const  std::vector<std::string> types = std::vector<std::string> { "battlefield" };
+
+	return types;
+}
+
+std::vector<bool> BattleFieldHandler::getDefaultAllowed() const
+{
+	return std::vector<bool>();
+}
+
+int32_t BattleFieldInfo::getIndex() const
+{
+	return battlefield.getNum();
+}
+
+int32_t BattleFieldInfo::getIconIndex() const
+{
+	return iconIndex;
+}
+
+const std::string & BattleFieldInfo::getName() const
+{
+	return name;
+}
+
+const std::string & BattleFieldInfo::getJsonKey() const
+{
+	return identifier;
+}
+
+void BattleFieldInfo::registerIcons(const IconRegistar & cb) const
+{
+	//cb(getIconIndex(), "BATTLEFIELD", icon);
+}
+
+BattleField BattleFieldInfo::getId() const
+{
+	return battlefield;
+}

+ 85 - 0
lib/BattleFieldHandler.h

@@ -0,0 +1,85 @@
+/*
+ * BattleFieldHandler.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include <vcmi/EntityService.h>
+#include "HeroBonus.h"
+#include "GameConstants.h"
+#include "IHandlerBase.h"
+#include "Terrain.h"
+#include "battle/BattleHex.h"
+
+class BattleFieldInfo : public EntityT<BattleField>
+{
+public:
+	BattleField battlefield;
+	std::vector<std::shared_ptr<Bonus>> bonuses;
+	bool isSpecial;
+	std::string graphics;
+	std::string name;
+	std::string identifier;
+	std::string icon;
+	si32 iconIndex;
+	std::vector<BattleHex> impassableHexes;
+
+	BattleFieldInfo() 
+		: BattleFieldInfo(BattleField::NONE, "")
+	{
+	}
+
+	BattleFieldInfo(BattleField battlefield, std::string identifier)
+		:bonuses(), isSpecial(false), battlefield(battlefield), identifier(identifier), graphics(), icon(), iconIndex(battlefield.getNum()), impassableHexes(), name(identifier)
+	{
+	}
+
+	int32_t getIndex() const override;
+	int32_t getIconIndex() const override;
+	const std::string & getName() const override;
+	const std::string & getJsonKey() const override;
+	void registerIcons(const IconRegistar & cb) const override;
+	BattleField getId() const override;
+
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+		h & name;
+		h & identifier;
+		h & isSpecial;
+		h & graphics;
+		h & icon;
+		h & iconIndex;
+		h & battlefield;
+		h & impassableHexes;
+
+	}
+};
+
+class DLL_LINKAGE BattleFieldService : public EntityServiceT<BattleField, BattleFieldInfo>
+{
+public:
+};
+
+class BattleFieldHandler : public CHandlerBase<BattleField, BattleFieldInfo, BattleFieldInfo, BattleFieldService>
+{
+public:
+	virtual BattleFieldInfo * loadFromJson(
+		const std::string & scope,
+		const JsonNode & json,
+		const std::string & identifier,
+		size_t index) override;
+
+	virtual const std::vector<std::string> & getTypeNames() const override;
+	virtual std::vector<JsonNode> loadLegacyData(size_t dataSize) override;
+	virtual std::vector<bool> getDefaultAllowed() const override;
+
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+		h & objects;
+	}
+};

+ 3 - 2
lib/CGameState.cpp

@@ -1922,9 +1922,10 @@ BattleField CGameState::battleGetBattlefieldType(int3 tile, CRandomGenerator & r
 	}
 
 	if(map->isCoastalTile(tile)) //coastal tile is always ground
-		return BattleField("sand_shore");
+		return BattleField::fromString("sand_shore");
 	
-	return *RandomGeneratorUtil::nextItem(Terrain::Manager::getInfo(t.terType).battleFields, rand);
+	return BattleField::fromString(
+		*RandomGeneratorUtil::nextItem(Terrain::Manager::getInfo(t.terType).battleFields, rand));
 }
 
 UpgradeInfo CGameState::getUpgradeInfo(const CStackInstance &stack)

+ 5 - 2
lib/CHeroHandler.cpp

@@ -25,6 +25,7 @@
 #include <math.h>
 
 #include "mapObjects/CObjectClassesHandler.h"
+#include "BattleFieldHandler.h"
 
 CHero::CHero() = default;
 CHero::~CHero() = default;
@@ -179,8 +180,10 @@ std::vector<BattleHex> CObstacleInfo::getBlocked(BattleHex hex) const
 
 bool CObstacleInfo::isAppropriate(const Terrain & terrainType, const BattleField & battlefield) const
 {
-	if(battlefield.isSpecial())
-		return vstd::contains(allowedSpecialBfields, battlefield);
+	auto bgInfo = battlefield.getInfo();
+
+	if(bgInfo->isSpecial)
+		return vstd::contains(allowedSpecialBfields, bgInfo->identifier);
 
 	return vstd::contains(allowedTerrains, terrainType);
 }

+ 1 - 1
lib/CHeroHandler.h

@@ -227,7 +227,7 @@ struct DLL_LINKAGE CObstacleInfo
 {
 	std::string defName;
 	std::vector<Terrain> allowedTerrains;
-	std::vector<BattleField> allowedSpecialBfields;
+	std::vector<std::string> allowedSpecialBfields;
 
 	ui8 isAbsoluteObstacle; //there may only one such obstacle in battle and its position is always the same
 	si32 width, height; //how much space to the right and up is needed to place obstacle (affects only placement algorithm)

+ 3 - 1
lib/CMakeLists.txt

@@ -129,7 +129,8 @@ set(lib_SRCS
 		spells/effects/Sacrifice.cpp
 
 		vstd/StringUtils.cpp
-
+		
+		BattleFieldHandler.cpp
 		CAndroidVMHelper.cpp
 		CArtHandler.cpp
 		CBonusTypeHandler.cpp
@@ -338,6 +339,7 @@ set(lib_HEADERS
 		spells/effects/Sacrifice.h
 
 		AI_Base.h
+		BattleFieldHandler.h
 		CAndroidVMHelper.h
 		CArtHandler.h
 		CBonusTypeHandler.h

+ 2 - 0
lib/CModHandler.cpp

@@ -25,6 +25,7 @@
 #include "spells/CSpellHandler.h"
 #include "CSkillHandler.h"
 #include "ScriptHandler.h"
+#include "BattleFieldHandler.h"
 
 #include <vstd/StringUtils.h>
 
@@ -433,6 +434,7 @@ void CContentHandler::init()
 	handlers.insert(std::make_pair("skills", ContentTypeHandler(VLC->skillh, "skill")));
 	handlers.insert(std::make_pair("templates", ContentTypeHandler((IHandlerBase *)VLC->tplh, "template")));
 	handlers.insert(std::make_pair("scripts", ContentTypeHandler(VLC->scriptHandler, "script")));
+	handlers.insert(std::make_pair("battlefields", ContentTypeHandler(VLC->battlefieldsHandler, "battlefield")));
 	//TODO: any other types of moddables?
 }
 

+ 38 - 0
lib/GameConstants.cpp

@@ -34,6 +34,7 @@
 #include "StringConstants.h"
 #include "CGeneralTextHandler.h"
 #include "CModHandler.h"//todo: remove
+#include "BattleFieldHandler.h"
 
 const SlotID SlotID::COMMANDER_SLOT_PLACEHOLDER = SlotID(-2);
 const SlotID SlotID::SUMMONED_SLOT_PLACEHOLDER = SlotID(-3);
@@ -255,3 +256,40 @@ std::ostream & operator<<(std::ostream & os, const EPathfindingLayer pathfinding
 	if (it == pathfinderLayerToString.end()) return os << "<Unknown type>";
 	else return os << it->second;
 }
+
+const BattleField BattleField::NONE;
+
+bool operator==(const BattleField & l, const BattleField & r)
+{
+	return l.num == r.num;
+}
+
+bool operator!=(const BattleField & l, const BattleField & r)
+{
+	return l.num != r.num;
+}
+
+bool operator<(const BattleField & l, const BattleField & r)
+{
+	return l.num < r.num;
+}
+
+BattleField::operator std::string() const
+{
+	return getInfo()->identifier;
+}
+
+const BattleFieldInfo * BattleField::getInfo() const
+{
+	return VLC->battlefields()->getById(*this);
+}
+
+BattleField BattleField::fromString(std::string identifier)
+{
+	auto rawId = VLC->modh->identifiers.getIdentifier("core", "battlefield", identifier);
+
+	if(rawId)
+		return BattleField(rawId.get());
+	else
+		return BattleField::NONE;
+}

+ 17 - 0
lib/GameConstants.h

@@ -1109,6 +1109,23 @@ public:
 
 ID_LIKE_OPERATORS(SpellID, SpellID::ESpellID)
 
+class BattleFieldInfo;
+class BattleField : public BaseForID<BattleField, si32>
+{
+	INSTID_LIKE_CLASS_COMMON(BattleField, si32)
+
+	DLL_LINKAGE static const BattleField NONE;
+
+	DLL_LINKAGE friend bool operator==(const BattleField & l, const BattleField & r);
+	DLL_LINKAGE friend bool operator!=(const BattleField & l, const BattleField & r);
+	DLL_LINKAGE friend bool operator<(const BattleField & l, const BattleField & r);
+
+	DLL_LINKAGE operator std::string() const;
+	DLL_LINKAGE const BattleFieldInfo * getInfo() const;
+
+	DLL_LINKAGE static BattleField fromString(std::string identifier);
+};
+
 enum class ESpellSchool: ui8
 {
 	AIR 	= 0,

+ 1 - 0
lib/IGameCallback.cpp

@@ -16,6 +16,7 @@
 #include "NetPacks.h"
 #include "CBonusTypeHandler.h"
 #include "CModHandler.h"
+#include "BattleFieldHandler.h"
 
 #include "serializer/CSerializer.h" // for SAVEGAME_MAGIC
 #include "serializer/BinaryDeserializer.h"

+ 0 - 26
lib/Terrain.cpp

@@ -18,8 +18,6 @@
 
 const Terrain Terrain::ANY("ANY");
 
-const BattleField BattleField::NONE("");
-
 Terrain Terrain::createTerrainTypeH3M(int tId)
 {
 	static std::array<std::string, 10> terrainsH3M
@@ -227,27 +225,3 @@ bool Terrain::isTransitionRequired() const
 {
 	return Terrain::Manager::getInfo(*this).transitionRequired;
 }
-
-bool operator==(const BattleField & l, const BattleField & r)
-{
-	return l.name == r.name;
-}
-
-bool operator!=(const BattleField & l, const BattleField & r)
-{
-	return l.name != r.name;
-}
-
-bool operator<(const BattleField & l, const BattleField & r)
-{
-	return l.name < r.name;
-}
-BattleField::operator std::string() const
-{
-	return name;
-}
-
-int BattleField::hash() const
-{
-	return std::hash<std::string>{}(name);
-}

+ 2 - 40
lib/Terrain.h

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

+ 10 - 0
lib/VCMI_Lib.cpp

@@ -32,6 +32,7 @@
 #include "rmg/CRmgTemplateStorage.h"
 #include "mapping/CMapEditManager.h"
 #include "ScriptHandler.h"
+#include "BattleFieldHandler.h"
 
 LibClasses * VLC = nullptr;
 
@@ -110,6 +111,11 @@ spells::effects::Registry * LibClasses::spellEffects()
 	return spells::effects::GlobalRegistry::get();
 }
 
+const BattleFieldService * LibClasses::battlefields() const
+{
+	return battlefieldsHandler;
+}
+
 void LibClasses::updateEntity(Metatype metatype, int32_t index, const JsonNode & data)
 {
 	switch(metatype)
@@ -205,6 +211,8 @@ void LibClasses::init(bool onlyEssential)
 
 	createHandler(scriptHandler, "Script", pomtime);
 
+	createHandler(battlefieldsHandler, "Battlefields", pomtime);
+
 	logGlobal->info("\tInitializing handlers: %d ms", totalTime.getDiff());
 
 	modh->load();
@@ -231,6 +239,7 @@ void LibClasses::clear()
 	delete tplh;
 	delete terviewh;
 	delete scriptHandler;
+	delete battlefieldsHandler;
 	makeNull();
 }
 
@@ -250,6 +259,7 @@ void LibClasses::makeNull()
 	tplh = nullptr;
 	terviewh = nullptr;
 	scriptHandler = nullptr;
+	battlefieldsHandler = nullptr;
 }
 
 LibClasses::LibClasses()

+ 5 - 0
lib/VCMI_Lib.h

@@ -24,6 +24,7 @@ class CTownHandler;
 class CGeneralTextHandler;
 class CModHandler;
 class CContentHandler;
+class BattleFieldHandler;
 class IBonusTypeHandler;
 class CBonusTypeHandler;
 class CTerrainViewPatternConfig;
@@ -56,6 +57,7 @@ public:
 	const scripting::Service * scripts() const override;
 	const spells::Service * spells() const override;
 	const SkillService * skills() const override;
+	const BattleFieldService * battlefields() const override;
 
 	void updateEntity(Metatype metatype, int32_t index, const JsonNode & data) override;
 
@@ -76,6 +78,7 @@ public:
 	CModHandler * modh;
 	CTerrainViewPatternConfig * terviewh;
 	CRmgTemplateStorage * tplh;
+	BattleFieldHandler * battlefieldsHandler;
 	scripting::ScriptHandler * scriptHandler;
 
 	LibClasses(); //c-tor, loads .lods and NULLs handlers
@@ -104,6 +107,8 @@ public:
 		h & objtypeh;
 		h & spellh;
 		h & skillh;
+		h & battlefieldsHandler;
+
 		if(!h.saving)
 		{
 			//modh will be changed and modh->content will be empty after deserialization

+ 4 - 55
lib/battle/BattleInfo.cpp

@@ -16,6 +16,7 @@
 #include "../mapObjects/CGTownInstance.h"
 #include "../CGeneralTextHandler.h"
 #include "../Terrain.h"
+#include "../BattleFieldHandler.h"
 
 //TODO: remove
 #include "../IGameCallback.h"
@@ -458,64 +459,12 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, const Terrain & terrain,
 	auto good = std::make_shared<CreatureAlignmentLimiter>(EAlignment::GOOD);
 	auto evil = std::make_shared<CreatureAlignmentLimiter>(EAlignment::EVIL);
 
-	//giving terrain overlay premies
-	int bonusSubtype = -1;
+	auto bgInfo = VLC->battlefields()->getById(battlefieldType);
 
-	if(battlefieldType == BattleField("magic_plains"))
+	for(const std::shared_ptr<Bonus> & bonus : bgInfo->bonuses)
 	{
-		bonusSubtype = 0;
+		curB->addNewBonus(bonus);
 	}
-	if(battlefieldType == BattleField("fiery_fields"))
-	{
-		if(bonusSubtype == -1) bonusSubtype = 2;
-	}
-	if(battlefieldType == BattleField("rocklands"))
-	{
-		if(bonusSubtype == -1) bonusSubtype = 8;
-	}
-	if(battlefieldType == BattleField("magic_clouds"))
-	{
-		if(bonusSubtype == -1) bonusSubtype = 1;
-	}
-	if(battlefieldType == BattleField("lucid_pools"))
-	{
-		if(bonusSubtype == -1) bonusSubtype = 4;
-	}
-	if(bonusSubtype != -1)
-	{ //common part for cases 9, 14, 15, 16, 17
-		curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::MAGIC_SCHOOL_SKILL,Bonus::TERRAIN_OVERLAY, 3, battlefieldType.hash(), bonusSubtype));
-	}
-	else if(battlefieldType == BattleField("holy_ground"))
-	{
-		std::string goodArmyDesc = VLC->generaltexth->arraytxt[123];
-		goodArmyDesc.erase(goodArmyDesc.size() - 2, 2); //omitting hardcoded +1 in description
-		std::string evilArmyDesc = VLC->generaltexth->arraytxt[124];
-		evilArmyDesc.erase(evilArmyDesc.size() - 2, 2);
-		curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, +1, battlefieldType.hash(), goodArmyDesc, 0)->addLimiter(good));
-		curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, -1, battlefieldType.hash(), evilArmyDesc, 0)->addLimiter(evil));
-	}
-	else if(battlefieldType == BattleField("clover_field"))
-	{ //+2 luck bonus for neutral creatures
-		std::string desc = VLC->generaltexth->arraytxt[83];
-		desc.erase(desc.size() - 2, 2);
-		curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::LUCK, Bonus::TERRAIN_OVERLAY, +2, battlefieldType.hash(), desc, 0)->addLimiter(neutral));
-	}
-	else if(battlefieldType == BattleField("evil_fog"))
-	{
-		std::string goodArmyDesc = VLC->generaltexth->arraytxt[126];
-		goodArmyDesc.erase(goodArmyDesc.size() - 2, 2);
-		std::string evilArmyDesc = VLC->generaltexth->arraytxt[125];
-		evilArmyDesc.erase(evilArmyDesc.size() - 2, 2);
-		curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, -1, battlefieldType.hash(), goodArmyDesc, 0)->addLimiter(good));
-		curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::TERRAIN_OVERLAY, +1, battlefieldType.hash(), evilArmyDesc, 0)->addLimiter(evil));
-	}
-	else if(battlefieldType == BattleField("cursed_ground"))
-	{
-		curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::NO_MORALE, Bonus::TERRAIN_OVERLAY, 0, battlefieldType.hash(), VLC->generaltexth->arraytxt[112], 0));
-		curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::NO_LUCK, Bonus::TERRAIN_OVERLAY, 0, battlefieldType.hash(), VLC->generaltexth->arraytxt[81], 0));
-		curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::BLOCK_MAGIC_ABOVE, Bonus::TERRAIN_OVERLAY, 1, battlefieldType.hash(), 0, Bonus::INDEPENDENT_MIN));
-	}
-	//overlay premies given
 
 	//native terrain bonuses
 	static auto nativeTerrain = std::make_shared<CreatureTerrainLimiter>();

+ 8 - 16
lib/battle/CBattleInfoCallback.cpp

@@ -17,6 +17,7 @@
 #include "../NetPacks.h"
 #include "../spells/CSpellHandler.h"
 #include "../mapObjects/CGTownInstance.h"
+#include "../BattleFieldHandler.h"
 
 namespace SiegeStuffThatShouldBeMovedToHandlers // <=== TODO
 {
@@ -1058,24 +1059,15 @@ AccessibilityInfo CBattleInfoCallback::getAccesibility() const
 	}
 
 	//special battlefields with logically unavailable tiles
-	std::vector<BattleHex> impassableHexes;
-	if(battleGetBattlefieldType() == BattleField("ship_to_ship"))
+	auto bFieldType = battleGetBattlefieldType();
+
+	if(bFieldType != BattleField::NONE)
 	{
-		impassableHexes =
-		{
-			6, 7, 8, 9,
-			24, 25, 26,
-			58, 59, 60,
-			75, 76, 77,
-			92, 93, 94,
-			109, 110, 111,
-			126, 127, 128,
-			159, 160, 161, 162, 163,
-			176, 177, 178, 179, 180
-		};
+		std::vector<BattleHex> impassableHexes = bFieldType.getInfo()->impassableHexes;
+
+		for(auto hex : impassableHexes)
+			ret[hex] = EAccessibility::UNAVAILABLE;
 	}
-	for(auto hex : impassableHexes)
-		ret[hex] = EAccessibility::UNAVAILABLE;
 
 	//gate -> should be before stacks
 	if(battleGetSiegeLevel() > 0)

+ 4 - 4
lib/mapObjects/CObjectClassesHandler.cpp

@@ -515,10 +515,10 @@ void AObjectTypeHandler::init(const JsonNode & input, boost::optional<std::strin
 	else
 		aiValue = static_cast<boost::optional<si32>>(input["aiValue"].Integer());
 
-	if(input["battleground"].isNull())
-		battlefield = BattleField::NONE;
+	if(input["battleground"].getType() == JsonNode::JsonType::DATA_STRING)
+		battlefield = input["battleground"].String();
 	else
-		battlefield = BattleField(input["battleground"].String());
+		battlefield = boost::none;
 
 	initTypeData(input);
 }
@@ -577,7 +577,7 @@ std::vector<ObjectTemplate> AObjectTypeHandler::getTemplates() const
 
 BattleField AObjectTypeHandler::getBattlefield() const
 {
-	return battlefield;
+	return battlefield ? BattleField::fromString(battlefield.get()) : BattleField::NONE;
 }
 
 std::vector<ObjectTemplate> AObjectTypeHandler::getTemplates(const Terrain & terrainType) const

+ 2 - 1
lib/mapObjects/CObjectClassesHandler.h

@@ -150,7 +150,7 @@ class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable
 
 	boost::optional<si32> aiValue;
 
-	BattleField battlefield;
+	boost::optional<std::string> battlefield;
 
 protected:
 	void preInitObject(CGObjectInstance * obj) const;
@@ -219,6 +219,7 @@ public:
 		h & subTypeName;
 		h & sounds;
 		h & aiValue;
+		h & battlefield;
 	}
 };
 

+ 5 - 2
lib/mapping/CMapEditManager.cpp

@@ -461,12 +461,15 @@ const std::vector<CTerrainViewPatternConfig::TVPVector> & CTerrainViewPatternCon
 	return iter->second;
 }
 
-boost::optional<const TerrainViewPattern &> CTerrainViewPatternConfig::getTerrainViewPatternById(const Terrain & terrain, const std::string & id) const
+boost::optional<const TerrainViewPattern &> CTerrainViewPatternConfig::getTerrainViewPatternById(std::string patternId, const std::string & id) const
 {
-	const std::vector<TVPVector> & groupPatterns = getTerrainViewPatterns(terrain);
+	auto iter = terrainViewPatterns.find(patternId);
+	const std::vector<TVPVector> & groupPatterns = (iter == terrainViewPatterns.end()) ? terrainViewPatterns.at("normal") : iter->second;
+
 	for (const TVPVector & patternFlips : groupPatterns)
 	{
 		const TerrainViewPattern & pattern = patternFlips.front();
+
 		if(id == pattern.id)
 		{
 			return boost::optional<const TerrainViewPattern &>(pattern);

+ 1 - 1
lib/mapping/CMapEditManager.h

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

+ 2 - 0
lib/registerTypes/RegisterTypes.h

@@ -55,6 +55,7 @@ void registerTypesMapObjects1(Serializer &s)
 	s.template registerType<CGObjectInstance, CGShipyard>(); s.template registerType<IShipyard, CGShipyard>();
 	s.template registerType<CGObjectInstance, CGDenOfthieves>();
 	s.template registerType<CGObjectInstance, CGLighthouse>();
+	s.template registerType<CGObjectInstance, CGTerrainPatch>();
 	s.template registerType<CGObjectInstance, CGMarket>(); s.template registerType<IMarket, CGMarket>();
 		s.template registerType<CGMarket, CGBlackMarket>();
 		s.template registerType<CGMarket, CGUniversity>();
@@ -108,6 +109,7 @@ void registerTypesMapObjectTypes(Serializer &s)
 	REGISTER_GENERIC_HANDLER(CGHeroInstance);
 	REGISTER_GENERIC_HANDLER(CGKeymasterTent);
 	REGISTER_GENERIC_HANDLER(CGLighthouse);
+	REGISTER_GENERIC_HANDLER(CGTerrainPatch);
 	REGISTER_GENERIC_HANDLER(CGMagi);
 	REGISTER_GENERIC_HANDLER(CGMagicSpring);
 	REGISTER_GENERIC_HANDLER(CGMagicWell);

+ 11 - 1
lib/rmg/CMapGenOptions.cpp

@@ -246,8 +246,18 @@ void CMapGenOptions::finalize(CRandomGenerator & rand)
 
 	if(waterContent == EWaterContent::RANDOM)
 	{
-		waterContent = *RandomGeneratorUtil::nextItem(mapTemplate->getWaterContentAllowed(), rand);
+		auto allowedContent = mapTemplate->getWaterContentAllowed();
+
+		if(allowedContent.size())
+		{
+			waterContent = *RandomGeneratorUtil::nextItem(mapTemplate->getWaterContentAllowed(), rand);
+		}
+		else
+		{
+			waterContent = EWaterContent::NONE;
+		}
 	}
+
 	if(monsterStrength == EMonsterStrength::RANDOM)
 	{
 		monsterStrength = static_cast<EMonsterStrength::EMonsterStrength>(rand.nextInt(EMonsterStrength::GLOBAL_WEAK, EMonsterStrength::GLOBAL_STRONG));

+ 4 - 0
lib/rmg/CRmgTemplateZone.cpp

@@ -2019,6 +2019,10 @@ int3 CRmgTemplateZone::makeBoat(TRmgTemplateZoneId land, const std::set<int3> &
 {
 	std::set<int3> lakeCoast;
 	std::set_intersection(gen->getZones()[land]->getCoastTiles().begin(), gen->getZones()[land]->getCoastTiles().end(), lake.begin(), lake.end(), std::inserter(lakeCoast, lakeCoast.begin()));
+
+	if(lakeCoast.empty())
+		return int3(-1, -1, -1);
+
 	for(int randomAttempts = 0; randomAttempts<5; ++randomAttempts)
 	{
 		auto coastTile = *RandomGeneratorUtil::nextItem(lakeCoast, gen->rand);

+ 3 - 1
lib/spells/ISpellMechanics.cpp

@@ -12,6 +12,7 @@
 #include "ISpellMechanics.h"
 
 #include "../CRandomGenerator.h"
+#include "../VCMI_Lib.h"
 
 #include "../HeroBonus.h"
 #include "../battle/CBattleInfoCallback.h"
@@ -39,6 +40,7 @@
 
 #include "../CHeroHandler.h"//todo: remove
 #include "../IGameCallback.h"//todo: remove
+#include "../BattleFieldHandler.h"
 
 namespace spells
 {
@@ -520,7 +522,7 @@ bool BaseMechanics::adaptProblem(ESpellCastProblem::ESpellCastProblem source, Pr
 				caster->getCasterName(text);
 				target.add(std::move(text), spells::Problem::NORMAL);
 			}
-			else if(b && b->source == Bonus::TERRAIN_OVERLAY && b->sid == BattleField("cursed_ground").hash())
+			else if(b && b->source == Bonus::TERRAIN_OVERLAY && VLC->battlefields()->getByIndex(b->sid)->identifier == "cursed_ground")
 			{
 				text.addTxt(MetaString::GENERAL_TXT, 537);
 				target.add(std::move(text), spells::Problem::NORMAL);

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

@@ -10,12 +10,14 @@
 #include "StdInc.h"
 
 #include "BattleCb.h"
+#include <vcmi/Entity.h>
 
 #include "../LuaStack.h"
 #include "../LuaCallWrapper.h"
 
 #include "../../../lib/GameConstants.h"
 #include "../../../lib/battle/Unit.h"
+#include "../../../lib/BattleFieldHandler.h"
 
 namespace scripting
 {
@@ -73,7 +75,7 @@ int BattleCbProxy::getBattlefieldType(lua_State * L)
 
 	auto ret = object->battleGetBattlefieldType();
 
-	return LuaStack::quickRetStr(L, ret);
+	return LuaStack::quickRetStr(L, ret.getInfo()->identifier);
 }
 
 int BattleCbProxy::getTerrainType(lua_State * L)

+ 10 - 10
scripts/lib/erm/BU.lua

@@ -55,16 +55,16 @@ end
 
 local SPECIAL_FIELDS = {}
 
-SPECIAL_FIELDS[0] = 0
-SPECIAL_FIELDS[22] = 1
-SPECIAL_FIELDS[9] = 2
-SPECIAL_FIELDS[18] = 3
-SPECIAL_FIELDS[20] = 4
-SPECIAL_FIELDS[19] = 5
-SPECIAL_FIELDS[17] = 6
-SPECIAL_FIELDS[14] = 7
-SPECIAL_FIELDS[15] = 8
-SPECIAL_FIELDS[16] = 9
+SPECIAL_FIELDS['sand_shore'] = 0
+SPECIAL_FIELDS['cursed_ground'] = 1
+SPECIAL_FIELDS['magic_plains'] = 2
+SPECIAL_FIELDS['holy_ground'] = 3
+SPECIAL_FIELDS['evil_fog'] = 4
+SPECIAL_FIELDS['clover_field'] = 5
+SPECIAL_FIELDS['lucid_pools'] = 6
+SPECIAL_FIELDS['fiery_fields'] = 7
+SPECIAL_FIELDS['rocklands'] = 8
+SPECIAL_FIELDS['magic_clouds'] = 9
 
 
 function BU:G(x, p1)

+ 1 - 1
server/CGameHandler.cpp

@@ -2221,7 +2221,7 @@ void CGameHandler::setupBattle(int3 tile, const CArmedInstance *armies[2], const
 
 	BattleField terType = gs->battleGetBattlefieldType(tile, getRandomGenerator());
 	if (heroes[0] && heroes[0]->boat && heroes[1] && heroes[1]->boat)
-		terType = BattleField("ship_to_ship");
+		terType = BattleField::fromString("ship_to_ship");
 
 	//send info about battles
 	BattleStart bs;

+ 35 - 8
test/battle/CBattleInfoCallbackTest.cpp

@@ -10,6 +10,7 @@
 #include "StdInc.h"
 
 #include "../../lib/battle/CBattleInfoCallback.h"
+#include "../../lib/battle/CUnitState.h"
 
 #include <vstd/RNG.h>
 
@@ -25,7 +26,15 @@ using namespace testing;
 
 class UnitFake : public UnitMock
 {
+private:
+	std::shared_ptr<CUnitState> state;
+
 public:
+	UnitFake()
+	{
+		state.reset(new CUnitStateDetached(this, this));
+	}
+
 	void addNewBonus(const std::shared_ptr<Bonus> & b)
 	{
 		bonusFake.addNewBonus(b);
@@ -58,6 +67,13 @@ public:
 		EXPECT_CALL(*this, unitSlot()).WillRepeatedly(Return(SlotID(0)));
 		EXPECT_CALL(*this, creatureIndex()).WillRepeatedly(Return(0));
 	}
+
+	void setDefaultState()
+	{
+		EXPECT_CALL(*this, isClone()).WillRepeatedly(Return(false));
+		EXPECT_CALL(*this, isCaster()).WillRepeatedly(Return(false));
+		EXPECT_CALL(*this, acquireState()).WillRepeatedly(Return(state));
+	}
 private:
 	BonusBearerMock bonusFake;
 };
@@ -207,6 +223,7 @@ TEST_F(BattleFinishedTest, LastAliveUnitWins)
 {
 	UnitFake & unit = unitsFake.add(1);
 	unit.makeAlive();
+	unit.setDefaultState();
 
 	setDefaultExpectations();
 	startBattle();
@@ -232,6 +249,7 @@ TEST_F(BattleFinishedTest, LastWarMachineNotWins)
 	UnitFake & unit = unitsFake.add(0);
 	unit.makeAlive();
 	unit.makeWarMachine();
+	unit.setDefaultState();
 
 	setDefaultExpectations();
 	startBattle();
@@ -241,17 +259,26 @@ TEST_F(BattleFinishedTest, LastWarMachineNotWins)
 
 TEST_F(BattleFinishedTest, LastWarMachineLoose)
 {
-	UnitFake & unit1 = unitsFake.add(0);
-	unit1.makeAlive();
+	try
+	{
+		UnitFake & unit1 = unitsFake.add(0);
+		unit1.makeAlive();
+		unit1.setDefaultState();
 
-	UnitFake & unit2 = unitsFake.add(1);
-	unit2.makeAlive();
-	unit2.makeWarMachine();
+		UnitFake & unit2 = unitsFake.add(1);
+		unit2.makeAlive();
+		unit2.makeWarMachine();
+		unit2.setDefaultState();
 
-	setDefaultExpectations();
-	startBattle();
+		setDefaultExpectations();
+		startBattle();
 
-	expectBattleWinner(0);
+		expectBattleWinner(0);
+	}
+	catch(std::exception e)
+	{
+		logGlobal->error(e.what());
+	}
 }
 
 class BattleMatchOwnerTest : public CBattleInfoCallbackTest

+ 5 - 3
test/erm/ERM_BU.cpp

@@ -155,7 +155,7 @@ TEST_F(ERM_BU_G, Get)
 	source << "!?PI;" << std::endl;
 	source << "!!BU:G?v1;" << std::endl;
 
-	EXPECT_CALL(binfoMock, battleGetBattlefieldType()).WillOnce(Return(BattleField("snow_trees")));
+	EXPECT_CALL(binfoMock, battleGetBattlefieldType()).WillOnce(Return(BattleField::fromString("snow_trees")));
 
 
 	loadScript(VLC->scriptHandler->erm, source.str());
@@ -169,12 +169,14 @@ TEST_F(ERM_BU_G, Get)
 
 TEST_F(ERM_BU_G, Get2)
 {
+	const int EXPECTED_ERM_FOG_CODE = 4;
+
 	std::stringstream source;
 	source << "VERM" << std::endl;
 	source << "!?PI;" << std::endl;
 	source << "!!BU:G?v1;" << std::endl;
 
-	EXPECT_CALL(binfoMock, battleGetBattlefieldType()).WillOnce(Return(BattleField("evil_fog")));
+	EXPECT_CALL(binfoMock, battleGetBattlefieldType()).WillOnce(Return(BattleField::fromString("evil_fog")));
 
 	loadScript(VLC->scriptHandler->erm, source.str());
 	runServer();
@@ -182,7 +184,7 @@ TEST_F(ERM_BU_G, Get2)
 	JsonNode actualState = context->saveState();
 
 
-	EXPECT_EQ(actualState["ERM"]["v"]["1"], JsonUtils::floatNode(4)) << actualState.toJson(true);
+	EXPECT_EQ(actualState["ERM"]["v"]["1"], JsonUtils::floatNode(EXPECTED_ERM_FOG_CODE)) << actualState.toJson(true);
 }
 
 //TODO: ERM_BU_G Set

+ 1 - 1
test/game/CGameStateTest.cpp

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

+ 1 - 1
test/map/CMapEditManagerTest.cpp

@@ -132,7 +132,7 @@ TEST(MapManager, DrawTerrain_View)
 			const auto & id = patternParts[1];
 
 			// Get mapping range
-			const auto & pattern = VLC->terviewh->getTerrainViewPatternById(groupStr, id);
+			const auto & pattern = VLC->terviewh->getTerrainViewPatternById(groupStr, id); 
 			const auto & mapping = (*pattern).mapping;
 
 			const auto & positionsNode = node["pos"].Vector();

+ 4 - 1
test/map/CMapFormatTest.cpp

@@ -23,6 +23,7 @@
 
 #include "MapComparer.h"
 #include "../JsonComparer.h"
+#include "mock/ZoneOptionsFake.h"
 
 static const int TEST_RANDOM_SEED = 1337;
 
@@ -43,10 +44,12 @@ TEST(MapFormat, Random)
 
 	CMapGenOptions opt;
 	CRmgTemplate tmpl;
+	std::shared_ptr<ZoneOptionsFake> zoneOptions = std::make_shared<ZoneOptionsFake>();
 
 	const_cast<CRmgTemplate::CPlayerCountRange &>(tmpl.getCpuPlayers()).addRange(1, 4);
-	const_cast<CRmgTemplate::Zones &>(tmpl.getZones())[0] = std::make_shared<rmg::ZoneOptions>();
+	const_cast<CRmgTemplate::Zones &>(tmpl.getZones())[0] = zoneOptions;
 
+	zoneOptions->setOwner(1);
 	opt.setMapTemplate(&tmpl);
 
 	opt.setHeight(CMapHeader::MAP_SIZE_MIDDLE);

+ 1 - 1
test/mock/BattleFake.cpp

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

+ 23 - 0
test/mock/ZoneOptionsFake.h

@@ -0,0 +1,23 @@
+/*
+ * BattleFake.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "../../../lib/mapping/CMap.h"
+#include "../../../lib/rmg/CMapGenOptions.h"
+#include "../../../lib/rmg/CMapGenerator.h"
+
+#pragma once
+
+class ZoneOptionsFake : public rmg::ZoneOptions
+{
+public:
+	void setOwner(int ow)
+	{
+		this->owner = ow;
+	}
+};

+ 2 - 1
test/mock/mock_Services.h

@@ -23,7 +23,8 @@ public:
 	MOCK_CONST_METHOD0(heroTypes, const HeroTypeService *());
 	MOCK_CONST_METHOD0(scripts, const scripting::Service *());
 	MOCK_CONST_METHOD0(spells, const spells::Service *());
-	MOCK_CONST_METHOD0(skills, const SkillService *());
+	MOCK_CONST_METHOD0(skills, const SkillService * ());
+	MOCK_CONST_METHOD0(battlefields, const BattleFieldService *());
 
 	MOCK_METHOD3(updateEntity, void(Metatype, int32_t, const JsonNode &));
 

+ 1 - 1
test/spells/effects/CloneTest.cpp

@@ -232,7 +232,7 @@ TEST_F(CloneApplyTest, SetsLifetimeMarker)
 {
 	setDefaultExpectations();
 
-	EXPECT_CALL(*battleFake, addUnitBonus(_,_)).WillOnce(Invoke(this, &CloneApplyTest::checkCloneLifetimeMarker));
+	EXPECT_CALL(*battleFake, addUnitBonus(_, _)).WillOnce(Invoke(this, &CloneApplyTest::checkCloneLifetimeMarker));
 
 	subject->apply(&serverMock, &mechanicsMock, target);
 }