Browse Source

(somewhat) configurable adventure map spells

- All adventure map spells have most of their parameters in json.
- Parameters of adventure map spells can now be defined separately per
each mastery level.
- It is now possible to add a new spell that will have effect similar to
H3 adventure map spell
Ivan Savenko 5 months ago
parent
commit
79f3126c63

+ 254 - 91
config/spells/adventure.json

@@ -6,9 +6,25 @@
 		"sounds": {
 			"cast": "SUMMBOAT"
 		},
-		"levels" :{
+		"levels" : {
 			"base":{
-				"range" : "X"
+				"range" : "X",
+				"adventureEffect" : {
+					"type" : "summonBoat",
+					"castsPerDay" : 0,
+					"useExistingBoat" : true,
+					"createNewBoat" : false
+				}
+			},
+			"advanced":{
+				"adventureEffect" : {
+					"createNewBoat" : true
+				}
+			},
+			"expert":{
+				"adventureEffect" : {
+					"createNewBoat" : true
+				}
 			}
 		},
 		"flags" : {
@@ -25,7 +41,25 @@
 		},
 		"levels" : {
 			"base":{
-				"range" : "X"
+				"range" : "X",
+				"adventureEffect" : {
+					"type" : "removeObject",
+					"castsPerDay" : 0,
+					"rangeX" : 9,
+					"rangeY" : 8,
+					"ignoreFow" : false,
+					"objects" : {
+						"boat" : true
+					}
+				}
+			},
+			"advanced":{
+				"adventureEffect" : {
+				}
+			},
+			"expert":{
+				"adventureEffect" : {
+				}
 			}
 		},
 		"flags" : {
@@ -43,51 +77,58 @@
 		"levels" : {
 			"base":{
 				"range" : "0",
-				"effects" : {
-					"visionsMonsters" : {
-						"type" : "VISIONS",
-						"subtype" : "visionsMonsters",
-						"duration" : "ONE_DAY",
-						"val" : 1,
-						"valueType" : "INDEPENDENT_MAX"
+				"adventureEffect" : {
+					"type" : "generic",
+					"castsPerDay" : 0,
+					"bonuses" : {
+						"visionsMonsters" : {
+							"type" : "VISIONS",
+							"subtype" : "visionsMonsters",
+							"duration" : "ONE_DAY",
+							"val" : 1,
+							"valueType" : "INDEPENDENT_MAX"
+						}
 					}
-				}				
+				}
 			},
 			"advanced":{
-				"effects" : {
-					"visionsMonsters" : {
-						"val" : 2
-					},
-					"visionsHeroes" :{
-						"type" : "VISIONS",
-						"subtype" : "visionsHeroes",
-						"duration" : "ONE_DAY",
-						"val" : 2,
-						"valueType" : "INDEPENDENT_MAX"
+				"adventureEffect" : {
+					"bonuses" : {
+						"visionsMonsters" : {
+							"val" : 2
+						},
+						"visionsHeroes" :{
+							"type" : "VISIONS",
+							"subtype" : "visionsHeroes",
+							"duration" : "ONE_DAY",
+							"val" : 2,
+							"valueType" : "INDEPENDENT_MAX"
+						}
 					}
-					
-				}			
+				}
 			},
 			"expert":{
-				"effects" : {
-					"visionsMonsters" : {
-						"val" : 3
-					},
-					"visionsHeroes" :{
-						"type" : "VISIONS",
-						"subtype" : "visionsHeroes",
-						"duration" : "ONE_DAY",
-						"val" : 3,
-						"valueType" : "INDEPENDENT_MAX"
-					},
-					"visionsTowns" :{
-						"type" : "VISIONS",
-						"subtype" : "visionsTowns",
-						"duration" : "ONE_DAY",
-						"val" : 3,
-						"valueType" : "INDEPENDENT_MAX"
+				"adventureEffect" : {
+					"bonuses" : {
+						"visionsMonsters" : {
+							"val" : 3
+						},
+						"visionsHeroes" :{
+							"type" : "VISIONS",
+							"subtype" : "visionsHeroes",
+							"duration" : "ONE_DAY",
+							"val" : 3,
+							"valueType" : "INDEPENDENT_MAX"
+						},
+						"visionsTowns" :{
+							"type" : "VISIONS",
+							"subtype" : "visionsTowns",
+							"duration" : "ONE_DAY",
+							"val" : 3,
+							"valueType" : "INDEPENDENT_MAX"
+						}
 					}
-				}			
+				}
 			}
 		},
 		"flags" : {
@@ -103,7 +144,33 @@
 		},
 		"levels" : {
 			"base":{
-				"range" : "X"
+				"range" : "X",
+				"adventureEffect" : {
+					"type" : "viewWorld",
+					"castsPerDay" : 0,
+					"objects" : {
+						"resource" : true
+					}
+				}
+			},
+			"advanced":{
+				"adventureEffect" : {
+					"objects" : {
+						"resource" : true,
+						"mine" : true,
+						"abandonedMine" : true
+					}
+				}
+			},
+			"expert":{
+				"adventureEffect" : {
+					"objects" : {
+						"resource" : true,
+						"mine" : true,
+						"abandonedMine" : true
+					},
+					"terrain" : true
+				}
 			}
 		},
 		"flags" : {
@@ -120,28 +187,36 @@
 		"levels" : {
 			"base":{
 				"range" : "0",
-				"effects" : {
-					"stealth" : {
-						"type" : "DISGUISED",
-						"duration" : "ONE_DAY",
-						"val" : 1,
-						"valueType" : "INDEPENDENT_MAX"
+				"adventureEffect" : {
+					"type" : "generic",
+					"castsPerDay" : 0,
+					"bonuses" : {
+						"stealth" : {
+							"type" : "DISGUISED",
+							"duration" : "ONE_DAY",
+							"val" : 1,
+							"valueType" : "INDEPENDENT_MAX"
+						}
 					}
-				}				
+				}
 			},
 			"advanced":{
-				"effects" : {
-					"stealth" : {
-						"val" : 2
-					}					
-				}			
+				"adventureEffect" : {
+					"bonuses" : {
+						"stealth" : {
+							"val" : 2
+						}
+					}
+				}
 			},
 			"expert":{
-				"effects" : {
-					"stealth" : {
-						"val" : 3
+				"adventureEffect" : {
+					"bonuses" : {
+						"stealth" : {
+							"val" : 3
+						}
 					}
-				}			
+				}
 			}
 		},
 		"flags" : {
@@ -157,7 +232,31 @@
 		},
 		"levels" : {
 			"base":{
-				"range" : "X"
+				"range" : "X",
+				"adventureEffect" : {
+					"type" : "viewWorld",
+					"castsPerDay" : 0,
+					"objects" : {
+						"artifact" : true
+					}
+				}
+			},
+			"advanced":{
+				"adventureEffect" : {
+					"objects" : {
+						"artifact" : true,
+						"hero" : true
+					}
+				}
+			},
+			"expert":{
+				"adventureEffect" : {
+					"objects" : {
+						"artifact" : true,
+						"hero" : true,
+						"town" : true
+					}
+				}
 			}
 		},
 		"flags" : {
@@ -174,28 +273,38 @@
 		"levels" : {
 			"base":{
 				"range" : "0",
-				"effects" : {
-					"fly" : {
-						"type" : "FLYING_MOVEMENT",
-						"duration" : "ONE_DAY",
-						"val" : 40,
-						"valueType" : "INDEPENDENT_MIN"
+				"adventureEffect" : {
+					"type" : "generic",
+					"castsPerDay" : 0,
+					"bonuses" : {
+						"fly" : {
+							"type" : "FLYING_MOVEMENT",
+							"duration" : "ONE_DAY",
+							"val" : 40,
+							"valueType" : "INDEPENDENT_MIN"
+						}
 					}
-				}				
+				}
 			},
 			"advanced":{
-				"effects" : {
-					"fly" : {
-						"val" : 20
+				"adventureEffect" : {
+					"type" : "generic",
+					"bonuses" : {
+						"fly" : {
+							"val" : 20
+						}
 					}
-				}			
+				}
 			},
 			"expert":{
-				"effects" : {
-					"fly" : {
-						"val" : 0
+				"adventureEffect" : {
+					"type" : "generic",
+					"bonuses" : {
+						"fly" : {
+							"val" : 0
+						}
 					}
-				}			
+				}
 			}
 		},
 		"flags" : {
@@ -212,28 +321,38 @@
 		"levels" : {
 			"base":{
 				"range" : "0",
-				"effects" : {
-					"waterWalk" : {
-						"type" : "WATER_WALKING",
-						"duration" : "ONE_DAY",
-						"val" : 40,
-						"valueType" : "INDEPENDENT_MIN"
+				"adventureEffect" : {
+					"type" : "generic",
+					"castsPerDay" : 0,
+					"bonuses" : {
+						"waterWalk" : {
+							"type" : "WATER_WALKING",
+							"duration" : "ONE_DAY",
+							"val" : 40,
+							"valueType" : "INDEPENDENT_MIN"
+						}
 					}
-				}				
+				}
 			},
 			"advanced":{
-				"effects" : {
-					"waterWalk" : {
-						"val" : 20
+				"adventureEffect" : {
+					"type" : "generic",
+					"bonuses" : {
+						"waterWalk" : {
+							"val" : 20
+						}
 					}
-				}			
+				}
 			},
 			"expert":{
-				"effects" : {
-					"waterWalk" : {
-						"val" : 0
+				"adventureEffect" : {
+					"type" : "generic",
+					"bonuses" : {
+						"waterWalk" : {
+							"val" : 0
+						}
 					}
-				}			
+				}
 			}
 		},
 		"flags" : {
@@ -250,7 +369,29 @@
 		},
 		"levels" : {
 			"base":{
-				"range" : "X"
+				"range" : "X",
+				"adventureEffect" : {
+					"type" : "dimensionDoor",
+					"movementPointsRequired" : 0,
+					"movementPointsTaken" : 300,
+					"waterLandFailureTakesPoints" : true,
+					"castsPerDay" : 2,
+					"rangeX" : 9,
+					"rangeY" : 8,
+					"ignoreFow" : true,
+					"exposeFow" : true
+				}
+			},
+			"advanced":{
+				"adventureEffect" : {
+					"castsPerDay" : 3
+				}
+			},
+			"expert":{
+				"adventureEffect" : {
+					"castsPerDay" : 4,
+					"movementPointsTaken" : 200
+				}
 			}
 		},
 		"flags" : {
@@ -266,7 +407,29 @@
 		},
 		"levels" : {
 			"base":{
-				"range" : "X"
+				"range" : "X",
+				"adventureEffect" : {
+					"type" : "townPortal",
+					"castsPerDay" : 2,
+					"allowTownSelection" : false,
+					"skipOccupiedTowns" : false,
+					"movementPointsRequired" : 300,
+					"movementPointsTaken" : 300
+				}
+			},
+			"advanced":{
+				"adventureEffect" : {
+					"allowTownSelection" : true,
+					"movementPointsRequired" : 200,
+					"movementPointsTaken" : 200
+				}
+			},
+			"expert":{
+				"adventureEffect" : {
+					"allowTownSelection" : true,
+					"movementPointsRequired" : 200,
+					"movementPointsTaken" : 200
+				}
 			}
 		},
 		"flags" : {

+ 12 - 10
lib/CMakeLists.txt

@@ -268,12 +268,13 @@ set(lib_MAIN_SRCS
 	spells/TargetCondition.cpp
 	spells/ViewSpellInt.cpp
 
+	spells/adventure/AdventureSpellEffect.cpp
 	spells/adventure/AdventureSpellMechanics.cpp
-	spells/adventure/DimensionDoorMechanics.cpp
-	spells/adventure/ScuttleBoatMechanics.cpp
-	spells/adventure/SummonBoatMechanics.cpp
-	spells/adventure/TownPortalMechanics.cpp
-	spells/adventure/ViewWorldMechanics.cpp
+	spells/adventure/DimensionDoorEffect.cpp
+	spells/adventure/RemoveObjectEffect.cpp
+	spells/adventure/SummonBoatEffect.cpp
+	spells/adventure/TownPortalEffect.cpp
+	spells/adventure/ViewWorldEffect.cpp
 
 	spells/effects/Catapult.cpp
 	spells/effects/Clone.cpp
@@ -731,11 +732,12 @@ set(lib_MAIN_HEADERS
 	spells/ViewSpellInt.h
 
 	spells/adventure/AdventureSpellMechanics.h
-	spells/adventure/DimensionDoorMechanics.h
-	spells/adventure/ScuttleBoatMechanics.h
-	spells/adventure/SummonBoatMechanics.h
-	spells/adventure/TownPortalMechanics.h
-	spells/adventure/ViewWorldMechanics.h
+	spells/adventure/AdventureSpellEffect.h
+	spells/adventure/DimensionDoorEffect.h
+	spells/adventure/RemoveObjectEffect.h
+	spells/adventure/SummonBoatEffect.h
+	spells/adventure/TownPortalEffect.h
+	spells/adventure/ViewWorldEffect.h
 
 	spells/effects/Catapult.h
 	spells/effects/Clone.h

+ 0 - 4
lib/GameSettings.cpp

@@ -68,10 +68,6 @@ const std::vector<GameSettings::SettingOption> GameSettings::settingProperties =
 		{EGameSettings::CREATURES_JOINING_PERCENTAGE,                     "creatures", "joiningPercentage"                    },
 		{EGameSettings::CREATURES_WEEKLY_GROWTH_CAP,                      "creatures", "weeklyGrowthCap"                      },
 		{EGameSettings::CREATURES_WEEKLY_GROWTH_PERCENT,                  "creatures", "weeklyGrowthPercent"                  },
-		{EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE,              "spells",    "dimensionDoorExposesTerrainType"      },
-		{EGameSettings::DIMENSION_DOOR_FAILURE_SPENDS_POINTS,             "spells",    "dimensionDoorFailureSpendsPoints"     },
-		{EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES,           "spells",    "dimensionDoorOnlyToUncoveredTiles"    },
-		{EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT,            "spells",    "dimensionDoorTournamentRulesLimit"    },
 		{EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS,                   "spells",    "dimensionDoorTriggersGuards"          },
 		{EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL,                "dwellings", "accumulateWhenNeutral"                },
 		{EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED,                  "dwellings", "accumulateWhenOwned"                  },

+ 0 - 4
lib/IGameSettings.h

@@ -41,10 +41,6 @@ enum class EGameSettings
 	CREATURES_JOINING_PERCENTAGE,
 	CREATURES_WEEKLY_GROWTH_CAP,
 	CREATURES_WEEKLY_GROWTH_PERCENT,
-	DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE,
-	DIMENSION_DOOR_FAILURE_SPENDS_POINTS,
-	DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES,
-	DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT,
 	DIMENSION_DOOR_TRIGGERS_GUARDS,
 	DWELLINGS_ACCUMULATE_WHEN_NEUTRAL,
 	DWELLINGS_ACCUMULATE_WHEN_OWNED,

+ 2 - 0
lib/spells/CSpellHandler.cpp

@@ -997,6 +997,8 @@ std::shared_ptr<CSpell> CSpellHandler::loadFromJson(const std::string & scope, c
 			levelObject.cumulativeEffects.push_back(b);
 		}
 
+		levelObject.adventureEffect = levelNode["adventureEffect"];
+
 		if(!levelNode["battleEffects"].Struct().empty())
 		{
 			levelObject.battleEffects = levelNode["battleEffects"];

+ 1 - 0
lib/spells/CSpellHandler.h

@@ -101,6 +101,7 @@ public:
 		std::vector<std::shared_ptr<Bonus>> cumulativeEffects; //deprecated
 
 		JsonNode battleEffects;
+		JsonNode adventureEffect;
 	};
 
 	/** \brief Low level accessor. Don`t use it if absolutely necessary

+ 4 - 27
lib/spells/ISpellMechanics.cpp

@@ -27,11 +27,6 @@
 #include "Problem.h"
 
 #include "adventure/AdventureSpellMechanics.h"
-#include "adventure/DimensionDoorMechanics.h"
-#include "adventure/ScuttleBoatMechanics.h"
-#include "adventure/SummonBoatMechanics.h"
-#include "adventure/TownPortalMechanics.h"
-#include "adventure/ViewWorldMechanics.h"
 
 #include "BattleSpellMechanics.h"
 
@@ -648,28 +643,10 @@ IAdventureSpellMechanics::IAdventureSpellMechanics(const CSpell * s)
 
 std::unique_ptr<IAdventureSpellMechanics> IAdventureSpellMechanics::createMechanics(const CSpell * s)
 {
-	switch(s->id.toEnum())
-	{
-	case SpellID::SUMMON_BOAT:
-		return std::make_unique<SummonBoatMechanics>(s);
-	case SpellID::SCUTTLE_BOAT:
-		return std::make_unique<ScuttleBoatMechanics>(s);
-	case SpellID::DIMENSION_DOOR:
-		return std::make_unique<DimensionDoorMechanics>(s);
-	case SpellID::FLY:
-	case SpellID::WATER_WALK:
-	case SpellID::VISIONS:
-	case SpellID::DISGUISE:
-		return std::make_unique<AdventureSpellMechanics>(s); //implemented using bonus system
-	case SpellID::TOWN_PORTAL:
-		return std::make_unique<TownPortalMechanics>(s);
-	case SpellID::VIEW_EARTH:
-		return std::make_unique<ViewEarthMechanics>(s);
-	case SpellID::VIEW_AIR:
-		return std::make_unique<ViewAirMechanics>(s);
-	default:
-		return s->isCombat() ? std::unique_ptr<IAdventureSpellMechanics>() : std::make_unique<AdventureSpellMechanics>(s);
-	}
+	if (s->isCombat())
+		return nullptr;
+
+	return std::make_unique<AdventureSpellMechanics>(s);
 }
 
 VCMI_LIB_NAMESPACE_END

+ 52 - 0
lib/spells/adventure/AdventureSpellEffect.cpp

@@ -0,0 +1,52 @@
+/*
+ * AdventureSpellEffect.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "AdventureSpellEffect.h"
+
+#include "../../json/JsonNode.h"
+#include "../../mapObjects/CGHeroInstance.h"
+#include "../../callback/IGameInfoCallback.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+AdventureSpellRangedEffect::AdventureSpellRangedEffect(const JsonNode & config)
+	: rangeX(config["rangeX"].Integer())
+	, rangeY(config["rangeY"].Integer())
+	, ignoreFow(config["ignoreFow"].Bool())
+{
+}
+
+bool AdventureSpellRangedEffect::isTargetInRange(const int3 & pos, const int3 & center) const
+{
+	int3 diff = pos - center;
+	return diff.x >= -rangeX && diff.x <= rangeX && diff.y >= -rangeY && diff.y <= rangeY;
+}
+
+bool AdventureSpellRangedEffect::canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
+{
+	if(!cb->isInTheMap(pos))
+		return false;
+
+	if(caster->getHeroCaster())
+	{
+		int3 casterPosition = caster->getHeroCaster()->getSightCenter();
+
+		if(!isTargetInRange(casterPosition, pos))
+			return false;
+	}
+
+	if(!ignoreFow && !cb->isVisibleFor(pos, caster->getCasterOwner()))
+		return false;
+
+	return true;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 58 - 0
lib/spells/adventure/AdventureSpellEffect.h

@@ -0,0 +1,58 @@
+/*
+ * AdventureSpellEffect.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 "../ISpellMechanics.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+enum class ESpellCastResult : int8_t
+{
+	OK, // cast successful
+	CANCEL, // cast failed but it is not an error, no mana has been spent
+	PENDING,
+	ERROR // error occurred, for example invalid request from player
+};
+
+class AdventureSpellMechanics;
+
+class IAdventureSpellEffect
+{
+public:
+	virtual ~IAdventureSpellEffect() = default;
+
+	virtual ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const {return ESpellCastResult::OK;};
+	virtual ESpellCastResult beginCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters, const AdventureSpellMechanics & mechanics) const {return ESpellCastResult::OK;};
+	virtual void endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const {};
+	virtual bool canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const {return true;};
+	virtual bool canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const {return true;};
+};
+
+class AdventureSpellEffect final : public IAdventureSpellEffect
+{
+public:
+	AdventureSpellEffect() = default;
+};
+
+class AdventureSpellRangedEffect : public IAdventureSpellEffect
+{
+	int rangeX;
+	int rangeY;
+	bool ignoreFow;
+
+	bool isTargetInRange(const int3 & pos, const int3 & center) const;
+public:
+	AdventureSpellRangedEffect(const JsonNode & config);
+
+	bool canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const override;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 90 - 54
lib/spells/adventure/AdventureSpellMechanics.cpp

@@ -11,17 +11,72 @@
 #include "StdInc.h"
 #include "AdventureSpellMechanics.h"
 
+#include "AdventureSpellEffect.h"
+#include "DimensionDoorEffect.h"
+#include "RemoveObjectEffect.h"
+#include "SummonBoatEffect.h"
+#include "TownPortalEffect.h"
+#include "ViewWorldEffect.h"
+
 #include "../CSpellHandler.h"
 #include "../Problem.h"
 
+#include "../../json/JsonBonus.h"
 #include "../../mapObjects/CGHeroInstance.h"
 #include "../../networkPacks/PacksForClient.h"
+#include "../../callback/IGameInfoCallback.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
+static std::unique_ptr<IAdventureSpellEffect> createAdventureEffect(const CSpell * s, const JsonNode & node)
+{
+	const std::string & typeID = node["type"].String();
+
+	if(typeID == "generic")
+		return std::make_unique<AdventureSpellEffect>();
+	if(typeID == "dimensionDoor")
+		return std::make_unique<DimensionDoorEffect>(s, node);
+	if(typeID == "removeObject")
+		return std::make_unique<RemoveObjectEffect>(s, node);
+	if(typeID == "summonBoat")
+		return std::make_unique<SummonBoatEffect>(s, node);
+	if(typeID == "townPortal")
+		return std::make_unique<TownPortalEffect>(s, node);
+	if(typeID == "viewWorld")
+		return std::make_unique<ViewWorldEffect>(s, node);
+
+	return std::make_unique<AdventureSpellEffect>();
+}
+
 AdventureSpellMechanics::AdventureSpellMechanics(const CSpell * s)
 	: IAdventureSpellMechanics(s)
 {
+	for(int level = 0; level < GameConstants::SPELL_SCHOOL_LEVELS; level++)
+	{
+		const JsonNode & config = s->getLevelInfo(level).adventureEffect;
+
+		levelOptions[level].effect = createAdventureEffect(s, config);
+		levelOptions[level].castsPerDay = config["castsPerDay"].Integer();
+		levelOptions[level].castsPerDayXL = config["castsPerDayXL"].Integer();
+
+		levelOptions[level].bonuses = s->getLevelInfo(level).effects;
+
+		for(const auto & elem : config["bonuses"].Struct())
+		{
+			auto b = JsonUtils::parseBonus(elem.second);
+			b->sid = BonusSourceID(s->id);
+			b->source = BonusSource::SPELL_EFFECT;
+			levelOptions[level].bonuses.push_back(b);
+		}
+	}
+}
+
+AdventureSpellMechanics::~AdventureSpellMechanics() = default;
+
+const AdventureSpellMechanics::LevelOptions & AdventureSpellMechanics::getLevel(const spells::Caster * caster) const
+{
+	int schoolLevel = caster->getSpellSchoolLevel(owner);
+	return levelOptions.at(schoolLevel);
 }
 
 bool AdventureSpellMechanics::canBeCast(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const
@@ -44,24 +99,30 @@ bool AdventureSpellMechanics::canBeCast(spells::Problem & problem, const IGameIn
 
 		if(heroCaster->mana < cost)
 			return false;
-	}
 
-	return canBeCastImpl(problem, cb, caster);
-}
+		std::stringstream cachingStr;
+		cachingStr << "source_" << vstd::to_underlying(BonusSource::SPELL_EFFECT) << "id_" << owner->id.num;
+		int castsAlreadyPerformedThisTurn = caster->getHeroCaster()->getBonuses(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(owner->id)), cachingStr.str())->size();
+		int3 mapSize = cb->getMapSize();
+		bool mapSizeIsAtLeastXL = mapSize.x * mapSize.y * mapSize.z >= GameConstants::TOURNAMENT_RULES_DD_MAP_TILES_THRESHOLD;
+		bool useAlternativeLimit = mapSizeIsAtLeastXL && getLevel(caster).castsPerDayXL != 0;
+		int castsLimit = useAlternativeLimit ? getLevel(caster).castsPerDayXL : getLevel(caster).castsPerDay;
 
-bool AdventureSpellMechanics::canBeCastAt(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
-{
-	return canBeCast(problem, cb, caster) && canBeCastAtImpl(problem, cb, caster, pos);
-}
+		if(castsLimit > 0 && castsLimit <= castsAlreadyPerformedThisTurn ) //limit casts per turn
+		{
+			MetaString message = MetaString::createFromTextID("core.genrltxt.338");
+			caster->getCasterName(message);
+			problem.add(std::move(message));
+			return false;
+		}
+	}
 
-bool AdventureSpellMechanics::canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const
-{
-	return true;
+	return getLevel(caster).effect->canBeCastImpl(problem, cb, caster);
 }
 
-bool AdventureSpellMechanics::canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
+bool AdventureSpellMechanics::canBeCastAt(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
 {
-	return true;
+	return canBeCast(problem, cb, caster) && getLevel(caster).effect->canBeCastAtImpl(problem, cb, caster, pos);
 }
 
 bool AdventureSpellMechanics::adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
@@ -71,7 +132,7 @@ bool AdventureSpellMechanics::adventureCast(SpellCastEnvironment * env, const Ad
 	if(!canBeCastAt(problem, env->getCb(), parameters.caster, parameters.pos))
 		return false;
 
-	ESpellCastResult result = beginCast(env, parameters);
+	ESpellCastResult result = getLevel(parameters.caster).effect->beginCast(env, parameters, *this);
 
 	if(result == ESpellCastResult::OK)
 		performCast(env, parameters);
@@ -79,43 +140,21 @@ bool AdventureSpellMechanics::adventureCast(SpellCastEnvironment * env, const Ad
 	return result != ESpellCastResult::ERROR;
 }
 
-ESpellCastResult AdventureSpellMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+void AdventureSpellMechanics::giveBonuses(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
 {
-	if(owner->hasEffects())
+	for(const auto & b : getLevel(parameters.caster).bonuses)
 	{
-		//todo: cumulative effects support
-		const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
-
-		std::vector<Bonus> bonuses;
-
-		owner->getEffects(bonuses, schoolLevel, false, parameters.caster->getEnchantPower(owner));
-
-		for(const Bonus & b : bonuses)
-		{
-			GiveBonus gb;
-			gb.id = ObjectInstanceID(parameters.caster->getCasterUnitId());
-			gb.bonus = b;
-			env->apply(gb);
-		}
-
-		return ESpellCastResult::OK;
+		GiveBonus gb;
+		gb.id = ObjectInstanceID(parameters.caster->getCasterUnitId());
+		gb.bonus = *b;
+		gb.bonus.duration = parameters.caster->getEnchantPower(owner);
+		env->apply(gb);
 	}
-	else
-	{
-		//There is no generic algorithm of adventure cast
-		env->complain("Unimplemented adventure spell");
-		return ESpellCastResult::ERROR;
-	}
-}
 
-ESpellCastResult AdventureSpellMechanics::beginCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
-{
-	return ESpellCastResult::OK;
-}
-
-void AdventureSpellMechanics::endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
-{
-	// no-op, only for implementation in derived classes
+	GiveBonus gb;
+	gb.id = ObjectInstanceID(parameters.caster->getCasterUnitId());
+	gb.bonus = Bonus(BonusDuration::ONE_DAY, BonusType::NONE, BonusSource::SPELL_EFFECT, 0, BonusSourceID(owner->id));
+	env->apply(gb);
 }
 
 void AdventureSpellMechanics::performCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
@@ -128,16 +167,13 @@ void AdventureSpellMechanics::performCast(SpellCastEnvironment * env, const Adve
 	asc.spellID = owner->id;
 	env->apply(asc);
 
-	ESpellCastResult result = applyAdventureEffects(env, parameters);
+	ESpellCastResult result = getLevel(parameters.caster).effect->applyAdventureEffects(env, parameters);
 
-	switch(result)
+	if (result == ESpellCastResult::OK)
 	{
-		case ESpellCastResult::OK:
-			parameters.caster->spendMana(env, cost);
-			endCast(env, parameters);
-			break;
-		default:
-			break;
+		giveBonuses(env, parameters);
+		parameters.caster->spendMana(env, cost);
+		getLevel(parameters.caster).effect->endCast(env, parameters);
 	}
 }
 

+ 17 - 18
lib/spells/adventure/AdventureSpellMechanics.h

@@ -14,32 +14,31 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-enum class ESpellCastResult
-{
-	OK, // cast successful
-	CANCEL, // cast failed but it is not an error, no mana has been spent
-	PENDING,
-	ERROR // error occurred, for example invalid request from player
-};
+class IAdventureSpellEffect;
 
-class AdventureSpellMechanics : public IAdventureSpellMechanics
+class AdventureSpellMechanics final : public IAdventureSpellMechanics, boost::noncopyable
 {
+	struct LevelOptions
+	{
+		std::unique_ptr<IAdventureSpellEffect> effect;
+		std::vector<std::shared_ptr<Bonus>> bonuses;
+		int castsPerDay;
+		int castsPerDayXL;
+	};
+
+	std::array<LevelOptions, GameConstants::SPELL_SCHOOL_LEVELS> levelOptions;
+
 public:
 	AdventureSpellMechanics(const CSpell * s);
+	~AdventureSpellMechanics();
+
+	const LevelOptions & getLevel(const spells::Caster * caster) const;
 
 	bool canBeCast(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const final;
 	bool canBeCastAt(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const final;
+	bool adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const final;
 
-	bool adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override final;
-
-protected:
-	///actual adventure cast implementation
-	virtual ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
-	virtual ESpellCastResult beginCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
-	virtual void endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
-	virtual bool canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const;
-	virtual bool canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const;
-
+	void giveBonuses(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
 	void performCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
 };
 

+ 125 - 0
lib/spells/adventure/DimensionDoorEffect.cpp

@@ -0,0 +1,125 @@
+/*
+ * DimensionDoorEffect.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "DimensionDoorEffect.h"
+
+#include "../CSpellHandler.h"
+
+#include "../../IGameSettings.h"
+#include "../../callback/IGameInfoCallback.h"
+#include "../../mapObjects/CGHeroInstance.h"
+#include "../../mapping/TerrainTile.h"
+#include "../../networkPacks/PacksForClient.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+DimensionDoorEffect::DimensionDoorEffect(const CSpell * s, const JsonNode & config)
+	: AdventureSpellRangedEffect(config)
+	, movementPointsRequired(config["movementPointsRequired"].Integer())
+	, movementPointsTaken(config["movementPointsTaken"].Integer())
+	, waterLandFailureTakesPoints(config["waterLandFailureTakesPoints"].Bool())
+	, exposeFow(config["exposeFow"].Bool())
+{
+}
+
+bool DimensionDoorEffect::canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const
+{
+	if(!caster->getHeroCaster())
+		return false;
+
+	if(caster->getHeroCaster()->movementPointsRemaining() <= movementPointsRequired)
+	{
+		problem.add(MetaString::createFromTextID("core.genrltxt.125"));
+		return false;
+	}
+
+	return true;
+}
+
+bool DimensionDoorEffect::canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
+{
+	if (!AdventureSpellRangedEffect::canBeCastAtImpl(problem, cb, caster, pos))
+		return false;
+
+	int3 casterPosition = caster->getHeroCaster()->getSightCenter();
+	const TerrainTile * dest = cb->getTileUnchecked(pos);
+	const TerrainTile * curr = cb->getTileUnchecked(casterPosition);
+
+	if(!dest)
+		return false;
+
+	if(!curr)
+		return false;
+
+	if(exposeFow)
+	{
+		if(!dest->isClear(curr))
+			return false;
+	}
+	else
+	{
+		if(dest->blocked())
+			return false;
+	}
+
+	return true;
+}
+
+ESpellCastResult DimensionDoorEffect::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+	int3 casterPosition = parameters.caster->getHeroCaster()->getSightCenter();
+	const TerrainTile * dest = env->getCb()->getTile(parameters.pos);
+	const TerrainTile * curr = env->getCb()->getTile(casterPosition);
+
+	if(!dest->isClear(curr))
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->getCasterOwner();
+
+		// tile is either blocked or not possible to move (e.g. water <-> land)
+		if(waterLandFailureTakesPoints)
+		{
+			// SOD: DD to such "wrong" terrain results in mana and move points spending, but fails to move hero
+			iw.text = MetaString::createFromTextID("core.genrltxt.70"); // Dimension Door failed!
+			env->apply(iw);
+			// no return - resources will be spent
+		}
+		else
+		{
+			// HotA: game will show error message without taking mana or move points, even when DD into terra incognita
+			iw.text = MetaString::createFromTextID("vcmi.dimensionDoor.seaToLandError");
+			env->apply(iw);
+			return ESpellCastResult::CANCEL;
+		}
+	}
+
+	SetMovePoints smp;
+	smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId());
+	if(movementPointsTaken < static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()))
+		smp.val = parameters.caster->getHeroCaster()->movementPointsRemaining() - movementPointsTaken;
+	else
+		smp.val = 0;
+	env->apply(smp);
+
+	return ESpellCastResult::OK;
+}
+
+void DimensionDoorEffect::endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+	int3 casterPosition = parameters.caster->getHeroCaster()->getSightCenter();
+	const TerrainTile * dest = env->getCb()->getTile(parameters.pos);
+	const TerrainTile * curr = env->getCb()->getTile(casterPosition);
+
+	if(dest->isClear(curr))
+		env->moveHero(ObjectInstanceID(parameters.caster->getCasterUnitId()), parameters.caster->getHeroCaster()->convertFromVisitablePos(parameters.pos), EMovementMode::DIMENSION_DOOR);
+}
+
+VCMI_LIB_NAMESPACE_END

+ 9 - 4
lib/spells/adventure/DimensionDoorMechanics.h → lib/spells/adventure/DimensionDoorEffect.h

@@ -1,5 +1,5 @@
 /*
- * DimensionDoorMechanics.h, part of VCMI engine
+ * DimensionDoorEffect.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -10,14 +10,19 @@
 
 #pragma once
 
-#include "AdventureSpellMechanics.h"
+#include "AdventureSpellEffect.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class DimensionDoorMechanics final : public AdventureSpellMechanics
+class DimensionDoorEffect final : public AdventureSpellRangedEffect
 {
+	int movementPointsRequired;
+	int movementPointsTaken;
+	bool waterLandFailureTakesPoints;
+	bool exposeFow;
+
 public:
-	DimensionDoorMechanics(const CSpell * s);
+	DimensionDoorEffect(const CSpell * s, const JsonNode & config);
 
 protected:
 	bool canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const override;

+ 0 - 166
lib/spells/adventure/DimensionDoorMechanics.cpp

@@ -1,166 +0,0 @@
-/*
- * DimensionDoorMechanics.cpp, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-
-#include "StdInc.h"
-#include "DimensionDoorMechanics.h"
-
-#include "../CSpellHandler.h"
-
-#include "../../IGameSettings.h"
-#include "../../callback/IGameInfoCallback.h"
-#include "../../mapObjects/CGHeroInstance.h"
-#include "../../mapping/TerrainTile.h"
-#include "../../networkPacks/PacksForClient.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-DimensionDoorMechanics::DimensionDoorMechanics(const CSpell * s)
-	: AdventureSpellMechanics(s)
-{
-}
-
-bool DimensionDoorMechanics::canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const
-{
-	if(!caster->getHeroCaster())
-		return false;
-
-	if(caster->getHeroCaster()->movementPointsRemaining() <= 0) //unlike town portal non-zero MP is enough
-	{
-		problem.add(MetaString::createFromTextID("core.genrltxt.125"));
-		return false;
-	}
-
-	const auto schoolLevel = caster->getSpellSchoolLevel(owner);
-
-	std::stringstream cachingStr;
-	cachingStr << "source_" << vstd::to_underlying(BonusSource::SPELL_EFFECT) << "id_" << owner->id.num;
-
-	int castsAlreadyPerformedThisTurn = caster->getHeroCaster()->getBonuses(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(owner->id)), cachingStr.str())->size();
-	int castsLimit = owner->getLevelPower(schoolLevel);
-
-	bool isTournamentRulesLimitEnabled = cb->getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT);
-	if(isTournamentRulesLimitEnabled)
-	{
-		int3 mapSize = cb->getMapSize();
-
-		bool meetsTournamentRulesTwoCastsRequirements =  mapSize.x * mapSize.y * mapSize.z >= GameConstants::TOURNAMENT_RULES_DD_MAP_TILES_THRESHOLD && schoolLevel == MasteryLevel::EXPERT;
-
-		castsLimit = meetsTournamentRulesTwoCastsRequirements ? 2 : 1;
-	}
-
-	if(castsAlreadyPerformedThisTurn >= castsLimit) //limit casts per turn
-	{
-		MetaString message = MetaString::createFromTextID("core.genrltxt.338");
-		caster->getCasterName(message);
-		problem.add(std::move(message));
-		return false;
-	}
-
-	return true;
-}
-
-bool DimensionDoorMechanics::canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
-{
-	if(!cb->isInTheMap(pos))
-		return false;
-
-	if(cb->getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES))
-	{
-		if(!cb->isVisibleFor(pos, caster->getCasterOwner()))
-			return false;
-	}
-
-	int3 casterPosition = caster->getHeroCaster()->getSightCenter();
-
-	const TerrainTile * dest = cb->getTileUnchecked(pos);
-	const TerrainTile * curr = cb->getTileUnchecked(casterPosition);
-
-	if(!dest)
-		return false;
-
-	if(!curr)
-		return false;
-
-	if(!isInScreenRange(casterPosition, pos))
-		return false;
-
-	if(cb->getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE))
-	{
-		if(!dest->isClear(curr))
-			return false;
-	}
-	else
-	{
-		if(dest->blocked())
-			return false;
-	}
-
-	return true;
-}
-
-ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
-{
-	const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
-	const int baseCost = env->getCb()->getSettings().getInteger(EGameSettings::HEROES_MOVEMENT_COST_BASE);
-	const int movementCost = baseCost * ((schoolLevel >= 3) ? 2 : 3);
-
-	int3 casterPosition = parameters.caster->getHeroCaster()->getSightCenter();
-	const TerrainTile * dest = env->getCb()->getTile(parameters.pos);
-	const TerrainTile * curr = env->getCb()->getTile(casterPosition);
-
-	if(!dest->isClear(curr))
-	{
-		InfoWindow iw;
-		iw.player = parameters.caster->getCasterOwner();
-
-		// tile is either blocked or not possible to move (e.g. water <-> land)
-		if(env->getCb()->getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_FAILURE_SPENDS_POINTS))
-		{
-			// SOD: DD to such "wrong" terrain results in mana and move points spending, but fails to move hero
-			iw.text = MetaString::createFromTextID("core.genrltxt.70"); // Dimension Door failed!
-			env->apply(iw);
-			// no return - resources will be spent
-		}
-		else
-		{
-			// HotA: game will show error message without taking mana or move points, even when DD into terra incognita
-			iw.text = MetaString::createFromTextID("vcmi.dimensionDoor.seaToLandError");
-			env->apply(iw);
-			return ESpellCastResult::CANCEL;
-		}
-	}
-
-	GiveBonus gb;
-	gb.id = ObjectInstanceID(parameters.caster->getCasterUnitId());
-	gb.bonus = Bonus(BonusDuration::ONE_DAY, BonusType::NONE, BonusSource::SPELL_EFFECT, 0, BonusSourceID(owner->id));
-	env->apply(gb);
-
-	SetMovePoints smp;
-	smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId());
-	if(movementCost < static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()))
-		smp.val = parameters.caster->getHeroCaster()->movementPointsRemaining() - movementCost;
-	else
-		smp.val = 0;
-	env->apply(smp);
-
-	return ESpellCastResult::OK;
-}
-
-void DimensionDoorMechanics::endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
-{
-	int3 casterPosition = parameters.caster->getHeroCaster()->getSightCenter();
-	const TerrainTile * dest = env->getCb()->getTile(parameters.pos);
-	const TerrainTile * curr = env->getCb()->getTile(casterPosition);
-
-	if(dest->isClear(curr))
-		env->moveHero(ObjectInstanceID(parameters.caster->getCasterUnitId()), parameters.caster->getHeroCaster()->convertFromVisitablePos(parameters.pos), EMovementMode::DIMENSION_DOOR);
-}
-
-VCMI_LIB_NAMESPACE_END

+ 22 - 22
lib/spells/adventure/ScuttleBoatMechanics.cpp → lib/spells/adventure/RemoveObjectEffect.cpp

@@ -1,5 +1,5 @@
 /*
- * ScuttleBoatMechanics.cpp, part of VCMI engine
+ * RemoveObjectEffect.cpp, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -9,7 +9,7 @@
  */
 
 #include "StdInc.h"
-#include "ScuttleBoatMechanics.h"
+#include "RemoveObjectEffect.h"
 
 #include "../CSpellHandler.h"
 
@@ -17,28 +17,30 @@
 #include "../../mapObjects/CGHeroInstance.h"
 #include "../../mapping/CMap.h"
 #include "../../networkPacks/PacksForClient.h"
+#include "../../modding/IdentifierStorage.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-ScuttleBoatMechanics::ScuttleBoatMechanics(const CSpell * s)
-	: AdventureSpellMechanics(s)
+RemoveObjectEffect::RemoveObjectEffect(const CSpell * s, const JsonNode & config)
+	: AdventureSpellRangedEffect(config)
+	, owner(s)
+	, failMessage(MetaString::createFromTextID("core.genrltxt.337")) //%s tried to scuttle the boat, but failed
 {
-}
-
-bool ScuttleBoatMechanics::canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
-{
-	if(!cb->isInTheMap(pos))
-		return false;
-
-	if(caster->getHeroCaster())
+	for(const auto & objectNode : config["objects"].Struct())
 	{
-		int3 casterPosition = caster->getHeroCaster()->getSightCenter();
-
-		if(!isInScreenRange(casterPosition, pos))
-			return false;
+		if(objectNode.second.Bool())
+		{
+			LIBRARY->identifiers()->requestIdentifier(objectNode.second.getModScope(), "object", objectNode.first, [this](si32 index)
+			{
+				removedObjects.push_back(MapObjectID(index));
+			});
+		}
 	}
+}
 
-	if(!cb->isVisibleFor(pos, caster->getCasterOwner()))
+bool RemoveObjectEffect::canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
+{
+	if (!AdventureSpellRangedEffect::canBeCastAtImpl(problem, cb, caster, pos))
 		return false;
 
 	const TerrainTile * t = cb->getTile(pos);
@@ -46,13 +48,11 @@ bool ScuttleBoatMechanics::canBeCastAtImpl(spells::Problem & problem, const IGam
 		return false;
 
 	const CGObjectInstance * topObject = cb->getObj(t->visitableObjects.back());
-	if(topObject->ID != Obj::BOAT)
-		return false;
 
-	return true;
+	return vstd::contains(removedObjects, topObject->ID);
 }
 
-ESpellCastResult ScuttleBoatMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+ESpellCastResult RemoveObjectEffect::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
 {
 	const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
 	//check if spell works at all
@@ -60,7 +60,7 @@ ESpellCastResult ScuttleBoatMechanics::applyAdventureEffects(SpellCastEnvironmen
 	{
 		InfoWindow iw;
 		iw.player = parameters.caster->getCasterOwner();
-		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed
+		iw.text = failMessage;
 		parameters.caster->getCasterName(iw.text);
 		env->apply(iw);
 		return ESpellCastResult::OK;

+ 8 - 6
lib/spells/adventure/ScuttleBoatMechanics.h → lib/spells/adventure/RemoveObjectEffect.h

@@ -1,5 +1,5 @@
 /*
- * ScuttleBoatMechanics.h, part of VCMI engine
+ * RemoveObjectEffect.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -10,18 +10,20 @@
 
 #pragma once
 
-#include "AdventureSpellMechanics.h"
+#include "AdventureSpellEffect.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class ScuttleBoatMechanics final : public AdventureSpellMechanics
+class RemoveObjectEffect final : public AdventureSpellRangedEffect
 {
+	const CSpell * owner;
+	std::vector<MapObjectID> removedObjects;
+	MetaString failMessage;
+
 public:
-	ScuttleBoatMechanics(const CSpell * s);
+	RemoveObjectEffect(const CSpell * s, const JsonNode & config);
 
-protected:
 	bool canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const override;
-
 	ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
 };
 

+ 22 - 16
lib/spells/adventure/SummonBoatMechanics.cpp → lib/spells/adventure/SummonBoatEffect.cpp

@@ -1,5 +1,5 @@
 /*
- * SummonBoatMechanics.cpp, part of VCMI engine
+ * SummonBoatEffect.cpp, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -10,7 +10,7 @@
 
 #include "StdInc.h"
 
-#include "SummonBoatMechanics.h"
+#include "SummonBoatEffect.h"
 
 #include "../CSpellHandler.h"
 
@@ -21,12 +21,14 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-SummonBoatMechanics::SummonBoatMechanics(const CSpell * s)
-	: AdventureSpellMechanics(s)
+SummonBoatEffect::SummonBoatEffect(const CSpell * s, const JsonNode & config)
+	: owner(s)
+	, useExistingBoat(config["useExistingBoat"].Bool())
+	, createNewBoat(config["createNewBoat"].Bool())
 {
 }
 
-bool SummonBoatMechanics::canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const
+bool SummonBoatEffect::canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const
 {
 	if(!caster->getHeroCaster())
 		return false;
@@ -52,7 +54,7 @@ bool SummonBoatMechanics::canBeCastImpl(spells::Problem & problem, const IGameIn
 	return true;
 }
 
-ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+ESpellCastResult SummonBoatEffect::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
 {
 	const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
 
@@ -69,17 +71,21 @@ ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment
 
 	//try to find unoccupied boat to summon
 	const CGBoat * nearest = nullptr;
-	double dist = 0;
-	for(const auto & b : env->getMap()->getObjects<CGBoat>())
-	{
-		if(b->getBoardedHero() || b->layer != EPathfindingLayer::SAIL)
-			continue; //we're looking for unoccupied boat
 
-		double nDist = b->visitablePos().dist2d(parameters.caster->getHeroCaster()->visitablePos());
-		if(!nearest || nDist < dist) //it's first boat or closer than previous
+	if (useExistingBoat)
+	{
+		double dist = 0;
+		for(const auto & b : env->getMap()->getObjects<CGBoat>())
 		{
-			nearest = b;
-			dist = nDist;
+			if(b->getBoardedHero() || b->layer != EPathfindingLayer::SAIL)
+				continue; //we're looking for unoccupied boat
+
+			double nDist = b->visitablePos().dist2d(parameters.caster->getHeroCaster()->visitablePos());
+			if(!nearest || nDist < dist) //it's first boat or closer than previous
+			{
+				nearest = b;
+				dist = nDist;
+			}
 		}
 	}
 
@@ -93,7 +99,7 @@ ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment
 		cop.initiator = parameters.caster->getCasterOwner();
 		env->apply(cop);
 	}
-	else if(schoolLevel < 2) //none or basic level -> cannot create boat :(
+	else if(!createNewBoat) //none or basic level -> cannot create boat :(
 	{
 		InfoWindow iw;
 		iw.player = parameters.caster->getCasterOwner();

+ 8 - 4
lib/spells/adventure/SummonBoatMechanics.h → lib/spells/adventure/SummonBoatEffect.h

@@ -1,5 +1,5 @@
 /*
- * SummonBoatMechanics.h, part of VCMI engine
+ * SummonBoatEffect.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -10,14 +10,18 @@
 
 #pragma once
 
-#include "AdventureSpellMechanics.h"
+#include "AdventureSpellEffect.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class SummonBoatMechanics final : public AdventureSpellMechanics
+class SummonBoatEffect final : public IAdventureSpellEffect
 {
+	const CSpell * owner;
+	bool useExistingBoat;
+	bool createNewBoat;
+
 public:
-	SummonBoatMechanics(const CSpell * s);
+	SummonBoatEffect(const CSpell * s, const JsonNode & config);
 
 protected:
 	bool canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const override;

+ 27 - 30
lib/spells/adventure/TownPortalMechanics.cpp → lib/spells/adventure/TownPortalEffect.cpp

@@ -1,5 +1,5 @@
 /*
- * TownPortalMechanics.cpp, part of VCMI engine
+ * TownPortalEffect.cpp, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -9,7 +9,9 @@
  */
 
 #include "StdInc.h"
-#include "TownPortalMechanics.h"
+#include "TownPortalEffect.h"
+
+#include "AdventureSpellMechanics.h"
 
 #include "../CSpellHandler.h"
 
@@ -23,15 +25,18 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-TownPortalMechanics::TownPortalMechanics(const CSpell * s):
-	AdventureSpellMechanics(s)
+TownPortalEffect::TownPortalEffect(const CSpell * s, const JsonNode & config)
+	: owner(s)
+	, movementPointsRequired(config["movementPointsRequired"].Integer())
+	, movementPointsTaken(config["movementPointsTaken"].Integer())
+	, allowTownSelection(config["allowTownSelection"].Bool())
+	, skipOccupiedTowns(config["skipOccupiedTowns"].Bool())
 {
 }
 
-ESpellCastResult TownPortalMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+ESpellCastResult TownPortalEffect::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
 {
 	const CGTownInstance * destination = nullptr;
-	const int moveCost = movementCost(env, parameters);
 
 	if(!parameters.caster->getHeroCaster())
 	{
@@ -39,7 +44,7 @@ ESpellCastResult TownPortalMechanics::applyAdventureEffects(SpellCastEnvironment
 		return ESpellCastResult::ERROR;
 	}
 
-	if(parameters.caster->getSpellSchoolLevel(owner) < 2)
+	if(!allowTownSelection)
 	{
 		std::vector<const CGTownInstance *> pool = getPossibleTowns(env, parameters);
 		destination = findNearestTown(env, parameters, pool);
@@ -47,7 +52,7 @@ ESpellCastResult TownPortalMechanics::applyAdventureEffects(SpellCastEnvironment
 		if(nullptr == destination)
 			return ESpellCastResult::ERROR;
 
-		if(static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()) < moveCost)
+		if(static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()) < movementPointsRequired)
 			return ESpellCastResult::ERROR;
 
 		if(destination->getVisitingHero())
@@ -98,7 +103,7 @@ ESpellCastResult TownPortalMechanics::applyAdventureEffects(SpellCastEnvironment
 			return ESpellCastResult::ERROR;
 		}
 
-		if(static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()) < moveCost)
+		if(static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()) < movementPointsRequired)
 		{
 			env->complain("This hero has not enough movement points!");
 			return ESpellCastResult::ERROR;
@@ -131,9 +136,8 @@ ESpellCastResult TownPortalMechanics::applyAdventureEffects(SpellCastEnvironment
 	return ESpellCastResult::OK;
 }
 
-void TownPortalMechanics::endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+void TownPortalEffect::endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
 {
-	const int moveCost = movementCost(env, parameters);
 	const CGTownInstance * destination = nullptr;
 
 	if(parameters.caster->getSpellSchoolLevel(owner) < 2)
@@ -154,12 +158,15 @@ void TownPortalMechanics::endCast(SpellCastEnvironment * env, const AdventureSpe
 	{
 		SetMovePoints smp;
 		smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId());
-		smp.val = std::max<ui32>(0, parameters.caster->getHeroCaster()->movementPointsRemaining() - moveCost);
+		if(movementPointsTaken < static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()))
+			smp.val = parameters.caster->getHeroCaster()->movementPointsRemaining() - movementPointsTaken;
+		else
+			smp.val = 0;
 		env->apply(smp);
 	}
 }
 
-ESpellCastResult TownPortalMechanics::beginCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+ESpellCastResult TownPortalEffect::beginCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters, const AdventureSpellMechanics & mechanics) const
 {
 	std::vector<const CGTownInstance *> towns = getPossibleTowns(env, parameters);
 
@@ -178,9 +185,7 @@ ESpellCastResult TownPortalMechanics::beginCast(SpellCastEnvironment * env, cons
 		return ESpellCastResult::CANCEL;
 	}
 
-	const int moveCost = movementCost(env, parameters);
-
-	if(static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()) < moveCost)
+	if(static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()) < movementPointsTaken)
 	{
 		InfoWindow iw;
 		iw.player = parameters.caster->getCasterOwner();
@@ -191,7 +196,7 @@ ESpellCastResult TownPortalMechanics::beginCast(SpellCastEnvironment * env, cons
 
 	if(!parameters.pos.isValid() && parameters.caster->getSpellSchoolLevel(owner) >= 2)
 	{
-		auto queryCallback = [this, env, parameters](std::optional<int32_t> reply) -> void
+		auto queryCallback = [&mechanics, env, parameters](std::optional<int32_t> reply) -> void
 		{
 			if(reply.has_value())
 			{
@@ -213,7 +218,7 @@ ESpellCastResult TownPortalMechanics::beginCast(SpellCastEnvironment * env, cons
 				AdventureSpellCastParameters p;
 				p.caster = parameters.caster;
 				p.pos = o->visitablePos();
-				performCast(env, p);
+				mechanics.performCast(env, p);
 			}
 		};
 
@@ -247,7 +252,7 @@ ESpellCastResult TownPortalMechanics::beginCast(SpellCastEnvironment * env, cons
 	return ESpellCastResult::OK;
 }
 
-const CGTownInstance * TownPortalMechanics::findNearestTown(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters, const std::vector <const CGTownInstance *> & pool) const
+const CGTownInstance * TownPortalEffect::findNearestTown(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters, const std::vector <const CGTownInstance *> & pool) const
 {
 	if(pool.empty())
 		return nullptr;
@@ -271,7 +276,7 @@ const CGTownInstance * TownPortalMechanics::findNearestTown(SpellCastEnvironment
 	return *nearest;
 }
 
-std::vector<const CGTownInstance *> TownPortalMechanics::getPossibleTowns(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+std::vector<const CGTownInstance *> TownPortalEffect::getPossibleTowns(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
 {
 	std::vector<const CGTownInstance *> ret;
 
@@ -281,19 +286,11 @@ std::vector<const CGTownInstance *> TownPortalMechanics::getPossibleTowns(SpellC
 	{
 		for(auto currTown : env->getCb()->getPlayerState(color)->getTowns())
 		{
-			ret.push_back(currTown);
+			if (!skipOccupiedTowns || currTown->getVisitingHero() == nullptr)
+				ret.push_back(currTown);
 		}
 	}
 	return ret;
 }
 
-int32_t TownPortalMechanics::movementCost(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
-{
-	if(parameters.caster != parameters.caster->getHeroCaster()) //if caster is not hero
-		return 0;
-
-	int baseMovementCost = env->getCb()->getSettings().getInteger(EGameSettings::HEROES_MOVEMENT_COST_BASE);
-	return baseMovementCost * ((parameters.caster->getSpellSchoolLevel(owner) >= 3) ? 2 : 3);
-}
-
 VCMI_LIB_NAMESPACE_END

+ 11 - 6
lib/spells/adventure/TownPortalMechanics.h → lib/spells/adventure/TownPortalEffect.h

@@ -1,5 +1,5 @@
 /*
- * TownPortalMechanics.h, part of VCMI engine
+ * TownPortalEffect.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -10,25 +10,30 @@
 
 #pragma once
 
-#include "AdventureSpellMechanics.h"
+#include "AdventureSpellEffect.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
 class CGTownInstance;
 
-class TownPortalMechanics final : public AdventureSpellMechanics
+class TownPortalEffect final : public IAdventureSpellEffect
 {
+	const CSpell * owner;
+	int movementPointsRequired;
+	int movementPointsTaken;
+	bool allowTownSelection;
+	bool skipOccupiedTowns;
+
 public:
-	TownPortalMechanics(const CSpell * s);
+	TownPortalEffect(const CSpell * s, const JsonNode & config);
 
 protected:
 	ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
-	ESpellCastResult beginCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
+	ESpellCastResult beginCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters, const AdventureSpellMechanics & mechanics) const override;
 	void endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
 
 private:
 	const CGTownInstance * findNearestTown(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters, const std::vector<const CGTownInstance *> & pool) const;
-	int32_t movementCost(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
 	std::vector<const CGTownInstance *> getPossibleTowns(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
 };
 

+ 68 - 0
lib/spells/adventure/ViewWorldEffect.cpp

@@ -0,0 +1,68 @@
+/*
+ * ViewWorldEffect.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+
+#include "ViewWorldEffect.h"
+
+#include "../CSpellHandler.h"
+
+#include "../../CPlayerState.h"
+#include "../../callback/IGameInfoCallback.h"
+#include "../../mapObjects/CGHeroInstance.h"
+#include "../../mapping/CMap.h"
+#include "../../modding/IdentifierStorage.h"
+#include "../../networkPacks/PacksForClient.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+ViewWorldEffect::ViewWorldEffect(const CSpell * s, const JsonNode & config)
+{
+	showTerrain = config["showTerrain"].Bool();
+
+	for(const auto & objectNode : config["objects"].Struct())
+	{
+		if(objectNode.second.Bool())
+		{
+			LIBRARY->identifiers()->requestIdentifier(objectNode.second.getModScope(), "object", objectNode.first, [this](si32 index)
+			{
+				filteredObjects.push_back(MapObjectID(index));
+			});
+		}
+	}
+}
+
+ESpellCastResult ViewWorldEffect::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+	ShowWorldViewEx pack;
+
+	pack.player = parameters.caster->getCasterOwner();
+	pack.showTerrain = showTerrain;
+
+	const auto & fowMap = env->getCb()->getPlayerTeam(parameters.caster->getCasterOwner())->fogOfWarMap;
+
+	for(const auto & obj : env->getMap()->getObjects())
+	{
+		//deleted object remain as empty pointer
+		if(obj && vstd::contains(filteredObjects, obj->ID))
+		{
+			ObjectPosInfo posInfo(obj);
+
+			if(fowMap[posInfo.pos.z][posInfo.pos.x][posInfo.pos.y] == 0)
+				pack.objectPositions.push_back(posInfo);
+		}
+	}
+
+	env->apply(pack);
+
+	return ESpellCastResult::OK;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 28 - 0
lib/spells/adventure/ViewWorldEffect.h

@@ -0,0 +1,28 @@
+/*
+ * ViewWorldEffect.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 "AdventureSpellEffect.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class ViewWorldEffect final : public IAdventureSpellEffect
+{
+	std::vector<MapObjectID> filteredObjects;
+	bool showTerrain = false;
+
+public:
+	ViewWorldEffect(const CSpell * s, const JsonNode & config);
+
+	ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 0 - 90
lib/spells/adventure/ViewWorldMechanics.cpp

@@ -1,90 +0,0 @@
-/*
- * ViewWorldMechanics.cpp, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-
-#include "StdInc.h"
-
-#include "ViewWorldMechanics.h"
-
-#include "../CSpellHandler.h"
-
-#include "../../CPlayerState.h"
-#include "../../callback/IGameInfoCallback.h"
-#include "../../mapObjects/CGHeroInstance.h"
-#include "../../mapping/CMap.h"
-#include "../../networkPacks/PacksForClient.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-ViewMechanics::ViewMechanics(const CSpell * s):
-	AdventureSpellMechanics(s)
-{
-}
-
-ESpellCastResult ViewMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
-{
-	ShowWorldViewEx pack;
-
-	pack.player = parameters.caster->getCasterOwner();
-
-	const auto spellLevel = parameters.caster->getSpellSchoolLevel(owner);
-
-	const auto & fowMap = env->getCb()->getPlayerTeam(parameters.caster->getCasterOwner())->fogOfWarMap;
-
-	for(const auto & obj : env->getMap()->getObjects())
-	{
-		//deleted object remain as empty pointer
-		if(obj && filterObject(obj, spellLevel))
-		{
-			ObjectPosInfo posInfo(obj);
-
-			if(fowMap[posInfo.pos.z][posInfo.pos.x][posInfo.pos.y] == 0)
-				pack.objectPositions.push_back(posInfo);
-		}
-	}
-	pack.showTerrain = showTerrain(spellLevel);
-
-	env->apply(pack);
-
-	return ESpellCastResult::OK;
-}
-
-///ViewAirMechanics
-ViewAirMechanics::ViewAirMechanics(const CSpell * s)
-	: ViewMechanics(s)
-{
-}
-
-bool ViewAirMechanics::filterObject(const CGObjectInstance * obj, const int32_t spellLevel) const
-{
-	return (obj->ID == Obj::ARTIFACT) || (spellLevel > 1 && obj->ID == Obj::HERO) || (spellLevel > 2 && obj->ID == Obj::TOWN);
-}
-
-bool ViewAirMechanics::showTerrain(const int32_t spellLevel) const
-{
-	return false;
-}
-
-///ViewEarthMechanics
-ViewEarthMechanics::ViewEarthMechanics(const CSpell * s)
-	: ViewMechanics(s)
-{
-}
-
-bool ViewEarthMechanics::filterObject(const CGObjectInstance * obj, const int32_t spellLevel) const
-{
-	return (obj->ID == Obj::RESOURCE) || (spellLevel > 1 && obj->ID == Obj::MINE);
-}
-
-bool ViewEarthMechanics::showTerrain(const int32_t spellLevel) const
-{
-	return spellLevel > 2;
-}
-
-VCMI_LIB_NAMESPACE_END

+ 0 - 48
lib/spells/adventure/ViewWorldMechanics.h

@@ -1,48 +0,0 @@
-/*
- * ViewWorldMechanics.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 "AdventureSpellMechanics.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-class ViewMechanics : public AdventureSpellMechanics
-{
-public:
-	ViewMechanics(const CSpell * s);
-
-protected:
-	ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
-	virtual bool filterObject(const CGObjectInstance * obj, const int32_t spellLevel) const = 0;
-	virtual bool showTerrain(const int32_t spellLevel) const = 0;
-};
-
-class ViewAirMechanics final : public ViewMechanics
-{
-public:
-	ViewAirMechanics(const CSpell * s);
-
-protected:
-	bool filterObject(const CGObjectInstance * obj, const int32_t spellLevel) const override;
-	bool showTerrain(const int32_t spellLevel) const override;
-};
-
-class ViewEarthMechanics final : public ViewMechanics
-{
-public:
-	ViewEarthMechanics(const CSpell * s);
-
-protected:
-	bool filterObject(const CGObjectInstance * obj, const int32_t spellLevel) const override;
-	bool showTerrain(const int32_t spellLevel) const override;
-};
-
-VCMI_LIB_NAMESPACE_END