浏览代码

Merge pull request #5919 from IvanSavenko/rmg_banned_entities

Support for banned game entities in random map templates
Ivan Savenko 3 月之前
父节点
当前提交
6720289682

+ 9 - 1
client/lobby/RandomMapTab.cpp

@@ -182,6 +182,9 @@ void RandomMapTab::updateMapInfoByHost()
 	mapInfo->mapHeader->name.appendLocalString(EMetaText::GENERAL_TXT, 740);
 	mapInfo->mapHeader->description.appendLocalString(EMetaText::GENERAL_TXT, 741);
 
+	if(mapGenOptions->getWaterContent() != EWaterContent::RANDOM)
+		mapInfo->mapHeader->banWaterHeroes(mapGenOptions->getWaterContent() != EWaterContent::NONE);
+
 	const auto * temp = mapGenOptions->getMapTemplate();
 	if (temp)
 	{
@@ -191,6 +194,9 @@ void RandomMapTab::updateMapInfoByHost()
 			auto description = std::string("\n\n") + randomTemplateDescription;
 			mapInfo->mapHeader->description.appendRawString(description);
 		}
+
+		for (const auto & hero : temp->getBannedHeroes())
+			mapInfo->mapHeader->allowedHeroes.erase(hero);
 	}
 
 	mapInfo->mapHeader->difficulty = EMapDifficulty::NORMAL;
@@ -590,9 +596,11 @@ void RandomMapTab::saveOptions(const CMapGenOptions & options)
 
 void RandomMapTab::loadOptions()
 {
-	auto rmgSettings = persistentStorage["rmg"]["rmg"];
+	JsonNode rmgSettings = persistentStorage["rmg"]["rmg"];
+
 	if (!rmgSettings.Struct().empty())
 	{
+		rmgSettings.setModScope(ModScope::scopeGame());
 		mapGenOptions.reset(new CMapGenOptions());
 		JsonDeserializer handler(nullptr, rmgSettings);
 		handler.serializeStruct("lastSettings", *mapGenOptions);

+ 21 - 0
config/schemas/template.json

@@ -206,6 +206,27 @@
 			"type" : "object",
 			"$ref" : "gameSettings.json"
 		},
+		
+		"bannedSpells": {
+			"description" : "List of spells that are banned on this template",
+			"$ref" : "#/definitions/stringArray"
+		},
+		
+		"bannedArtifacts": {
+			"description" : "List of artifacts that are banned on this template",
+			"$ref" : "#/definitions/stringArray"
+		},
+		
+		"bannedSkills": {
+			"description" : "List of skills that are banned on this template",
+			"$ref" : "#/definitions/stringArray"
+		},
+		
+		"bannedHeroes": {
+			"description" : "List of heroes that are banned on this template",
+			"$ref" : "#/definitions/stringArray"
+		},
+		
 		"name" : {
 			"description" : "Optional name - useful to have several template variations with same name",
 			"type": "string"

+ 9 - 2
docs/modders/Entities_Format/Faction_Format.md

@@ -228,19 +228,26 @@ Each town requires a set of buildings (Around 30-45 buildings)
 
 	// Chance of specific hero class to appear in this town
 	// Mirrored version of field "tavern" from hero class format
+	/// Identifier without modID specifier MUST exist in base game or in one of dependencies
+	/// Identifier with explicit modID specifier will be silently skipped if corresponding mod is not loaded
 	"tavern" :
 	{
 		"knight" : 5,
-		"druid"  : 6
+		"druid"  : 6,
+		"modID:classFromMod" : 4
 	},
 
 	// Chance of specific spell to appear in mages guild of this town
 	// If spell is missing or set to 0 it will not appear unless set as "always present" in editor
 	// Spells from unavailable levels are not required to be in this list
+	/// Identifier without modID specifier MUST exist in base game or in one of dependencies
+	/// Identifier with explicit modID specifier will be silently skipped if corresponding mod is not loaded
+
 	"guildSpells" :
 	{
 		"magicArrow" : 30,
-		"bless"  : 10
+		"bless"  : 10,
+		"modID:spellFromMod" : 20
 	},
 
 	// Which tiers in this town have creature hordes. Set to -1 to disable horde(s)

+ 8 - 2
docs/modders/Entities_Format/Hero_Class_Format.md

@@ -81,13 +81,16 @@ In order to make functional hero class you also need:
 
 	// Chance to get specific secondary skill on level-up
 	// All missing skills are considered to be banned, including universities
+	/// Identifier without modID specifier MUST exist in base game or in one of dependencies
+	/// Identifier with explicit modID specifier will be silently skipped if corresponding mod is not loaded
 	"secondarySkills" :
 	{
 		"pathfinding"  : 3.
 		"archery"      : 6.
 		 ...
 		"resistance"   : 5,
-		"firstAid"     : 4
+		"firstAid"     : 4,
+		"modName:skillName" : 9
 	},
 
 	// Chance for a this hero class to appear in a town, creates pair with same field in town format 
@@ -99,11 +102,14 @@ In order to make functional hero class you also need:
 	// Reversed version of field "tavern" from town format
 	// If faction-class pair is not listed in any of them
 	// chance set to 0 and the class won't appear in tavern of this town
+	/// Identifier without modID specifier MUST exist in base game or in one of dependencies
+	/// Identifier with explicit modID specifier will be silently skipped if corresponding mod is not loaded
 	"tavern" :
 	{
 		"castle"     : 4,
 		 ...
-		"conflux"    : 6
+		"conflux"    : 6,
+		"modID:factionFromMod" : 5
 	}
 }
 ```

+ 3 - 0
docs/modders/Entities_Format/Secondary_Skill_Format.md

@@ -32,6 +32,8 @@
 		],
 		
 		// Chance for the skill to be offered on level-up (heroClass may override)
+		/// Identifier without modID specifier MUST exist in base game or in one of dependencies
+		/// Identifier with explicit modID specifier will be silently skipped if corresponding mod is not loaded
 		"gainChance" : {
 			// Chance for hero classes with might affinity
 			"might" : 4,
@@ -41,6 +43,7 @@
 			"knight" : 2,
 			"cleric" : 8,
 			...
+			"modName:heroClassName" : 5
 		},
 		
 		// This skill is major obligatory (like H3 Wisdom) and is guaranteed to be offered once per specific number of levels

+ 4 - 1
docs/modders/Entities_Format/Spell_Format.md

@@ -39,9 +39,12 @@
 	
 		// Chance for this spell to appear in Mage Guild of a specific faction
 		// Symmetric property of "guildSpells" property in towns
+		/// Identifier without modID specifier MUST exist in base game or in one of dependencies
+		/// Identifier with explicit modID specifier will be silently skipped if corresponding mod is not loaded
 		"gainChance":
 		{
-			"factionName" : 3
+			"factionName" : 3,
+			"modID:anotherFactionName" : 5
 		},
 
 		"animation":{<Animation format>},

+ 48 - 0
docs/modders/Random_Map_Template.md

@@ -35,6 +35,38 @@
 			"perPlayerOnMapCap" : 1
 		}
 	},
+	
+	/// List of spells that are banned on this map. 
+	/// Identifier without modID specifier MUST exist in base game or in one of dependencies
+	/// Identifier with explicit modID specifier will be silently skipped if corresponding mod is not loaded
+	"bannedSpells": [
+		"townPortal",
+		"modID:spellFromMod"
+	],
+
+	/// List of artifacts that are banned on this map. 
+	/// Identifier without modID specifier MUST exist in base game or in one of dependencies
+	/// Identifier with explicit modID specifier will be silently skipped if corresponding mod is not loaded
+	"bannedArtifacts": [
+		"armageddonsBlade",
+		"modID:artifactFromMod"
+	],
+
+	/// List of secondary skills that are banned on this map. 
+	/// Identifier without modID specifier MUST exist in base game or in one of dependencies
+	/// Identifier with explicit modID specifier will be silently skipped if corresponding mod is not loaded
+	"bannedSkills": [
+		"diplomacy",
+		"modID:secondarySkillFromMod"
+	],
+
+	/// List of heroes that are banned on this map. 
+	/// Identifier without modID specifier MUST exist in base game or in one of dependencies
+	/// Identifier with explicit modID specifier will be silently skipped if corresponding mod is not loaded
+	"bannedHeroes": [
+		"lordHaart",
+		"modID:heroFromMod"
+	]
 
 	/// List of named zones, see below for format description
 	"zones" :
@@ -92,6 +124,8 @@
 	"monsters" : "normal", 
 
 	//possible terrain types. All terrains will be available if not specified
+	/// Identifier without modID specifier MUST exist in base game or in one of dependencies
+	/// Identifier with explicit modID specifier will be silently skipped if corresponding mod is not loaded
 	"terrainTypes" : [ "sand" ], 
 
 	// List of type hints for every town, in the order of placement. First present hint if used for each town
@@ -111,6 +145,8 @@
 	],
 	
 	//optional, list of explicitly banned terrain types
+	/// Identifier without modID specifier MUST exist in base game or in one of dependencies
+	/// Identifier with explicit modID specifier will be silently skipped if corresponding mod is not loaded
 	"bannedTerrains" : ["lava", "asphalt"] 
 
 	// if true, terrain for this zone will match native terrain of player faction. Used only in owned zones
@@ -129,15 +165,27 @@
 	"customObjectsLikeZone" : 1,
 
 	// factions of monsters allowed on this zone
+	/// Identifier without modID specifier MUST exist in base game or in one of dependencies
+	/// Identifier with explicit modID specifier will be silently skipped if corresponding mod is not loaded
+
 	"allowedMonsters" : ["inferno", "necropolis"] 
 	
 	// These monsers will never appear in the zone
+	/// Identifier without modID specifier MUST exist in base game or in one of dependencies
+	/// Identifier with explicit modID specifier will be silently skipped if corresponding mod is not loaded
+
 	"bannedMonsters" : ["fortress", "stronghold", "conflux"]
 	
 	// towns allowed on this terrain
+	/// Identifier without modID specifier MUST exist in base game or in one of dependencies
+	/// Identifier with explicit modID specifier will be silently skipped if corresponding mod is not loaded
+
 	"allowedTowns" : ["castle", "tower", "rampart"] 
 	
 	// towns will never spawn on this terrain
+	/// Identifier without modID specifier MUST exist in base game or in one of dependencies
+	/// Identifier with explicit modID specifier will be silently skipped if corresponding mod is not loaded
+
 	"bannedTowns" : ["necropolis"] 
 
 	// List of mines that will be added to this zone

+ 24 - 0
lib/CSkillHandler.cpp

@@ -16,6 +16,7 @@
 #include "GameLibrary.h"
 #include "bonuses/Updaters.h"
 #include "constants/StringConstants.h"
+#include "entities/hero/CHeroClassHandler.h"
 #include "filesystem/Filesystem.h"
 #include "modding/IdentifierStorage.h"
 #include "texts/CGeneralTextHandler.h"
@@ -221,6 +222,29 @@ std::shared_ptr<CSkill> CSkillHandler::loadFromJson(const std::string & scope, c
 	skill->special = json["special"].Bool();
 
 	LIBRARY->generaltexth->registerString(scope, skill->getNameTextID(), json["name"]);
+
+	for(auto skillPair : json["gainChance"].Struct())
+	{
+		int probability = static_cast<int>(skillPair.second.Integer());
+
+		if (skillPair.first == "might")
+		{
+			skill->gainChance[0] = probability;
+			continue;
+		}
+
+		if (skillPair.first == "magic")
+		{
+			skill->gainChance[1] = probability;
+			continue;
+		}
+
+		LIBRARY->identifiers()->requestIdentifierIfFound(skillPair.second.getModScope(), "heroClass", skillPair.first, [skill, probability](si32 classID)
+		{
+			LIBRARY->heroclassesh->objects[classID]->secSkillProbability[skill->id] = probability;
+		});
+	}
+
 	switch(json["gainChance"].getType())
 	{
 	case JsonNode::JsonType::DATA_INTEGER:

+ 6 - 6
lib/entities/faction/CTownHandler.cpp

@@ -300,17 +300,17 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
 	const JsonNode & fortifications = source["fortifications"];
 	if (!fortifications.isNull())
 	{
-		LIBRARY->identifiers()->requestIdentifierOptional("creature", fortifications["citadelShooter"], [=](si32 identifier)
+		LIBRARY->identifiers()->requestIdentifierIfNotNull("creature", fortifications["citadelShooter"], [=](si32 identifier)
 		{
 			ret->fortifications.citadelShooter = CreatureID(identifier);
 		});
 
-		LIBRARY->identifiers()->requestIdentifierOptional("creature", fortifications["upperTowerShooter"], [=](si32 identifier)
+		LIBRARY->identifiers()->requestIdentifierIfNotNull("creature", fortifications["upperTowerShooter"], [=](si32 identifier)
 		{
 			ret->fortifications.upperTowerShooter = CreatureID(identifier);
 		});
 
-		LIBRARY->identifiers()->requestIdentifierOptional("creature", fortifications["lowerTowerShooter"], [=](si32 identifier)
+		LIBRARY->identifiers()->requestIdentifierIfNotNull("creature", fortifications["lowerTowerShooter"], [=](si32 identifier)
 		{
 			ret->fortifications.lowerTowerShooter = CreatureID(identifier);
 		});
@@ -326,7 +326,7 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
 
 	if(!source["mapObjectLikeBonuses"].isNull())
 	{
-		LIBRARY->identifiers()->requestIdentifierOptional("object", source["mapObjectLikeBonuses"], [ret](si32 identifier)
+		LIBRARY->identifiers()->requestIdentifierIfNotNull("object", source["mapObjectLikeBonuses"], [ret](si32 identifier)
 		{
 			ret->mapObjectLikeBonuses = MapObjectID(identifier);
 		});
@@ -684,7 +684,7 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source)
 	{
 		int chance = static_cast<int>(node.second.Float());
 
-		LIBRARY->identifiers()->requestIdentifier(node.second.getModScope(), "heroClass",node.first, [=](si32 classID)
+		LIBRARY->identifiers()->requestIdentifierIfFound(node.second.getModScope(), "heroClass", node.first, [=](si32 classID)
 		{
 			LIBRARY->heroclassesh->objects[classID]->selectionProbability[town->faction->getId()] = chance;
 		});
@@ -694,7 +694,7 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source)
 	{
 		int chance = static_cast<int>(node.second.Float());
 
-		LIBRARY->identifiers()->requestIdentifier(node.second.getModScope(), "spell", node.first, [=](si32 spellID)
+		LIBRARY->identifiers()->requestIdentifierIfFound(node.second.getModScope(), "spell", node.first, [=](si32 spellID)
 		{
 			LIBRARY->spellh->objects.at(spellID)->probabilities[town->faction->getId()] = chance;
 		});

+ 21 - 29
lib/entities/hero/CHeroClassHandler.cpp

@@ -97,45 +97,37 @@ std::shared_ptr<CHeroClass> CHeroClassHandler::loadFromJson(const std::string &
 	for(auto skillPair : node["secondarySkills"].Struct())
 	{
 		int probability = static_cast<int>(skillPair.second.Integer());
-		LIBRARY->identifiers()->requestIdentifier(skillPair.second.getModScope(), "skill", skillPair.first, [heroClass, probability](si32 skillID)
-											  {
-												  heroClass->secSkillProbability[skillID] = probability;
-											  });
+		LIBRARY->identifiers()->requestIdentifierIfFound(skillPair.second.getModScope(), "skill", skillPair.first, [heroClass, probability](si32 skillID) {
+			heroClass->secSkillProbability[skillID] = probability;
+		});
 	}
 
-	LIBRARY->identifiers()->requestIdentifier ("creature", node["commander"],
-										  [=](si32 commanderID)
-										  {
-											  heroClass->commander = CreatureID(commanderID);
-										  });
+	LIBRARY->identifiers()->requestIdentifier ("creature", node["commander"], [=](si32 commanderID) {
+			heroClass->commander = CreatureID(commanderID);
+		});
 
 	heroClass->defaultTavernChance = static_cast<ui32>(node["defaultTavern"].Float());
 	for(const auto & tavern : node["tavern"].Struct())
 	{
 		int value = static_cast<int>(tavern.second.Float());
 
-		LIBRARY->identifiers()->requestIdentifier(tavern.second.getModScope(), "faction", tavern.first,
-											  [=](si32 factionID)
-											  {
-												  heroClass->selectionProbability[FactionID(factionID)] = value;
-											  });
+		LIBRARY->identifiers()->requestIdentifierIfFound(tavern.second.getModScope(), "faction", tavern.first, [=](si32 factionID) {
+			heroClass->selectionProbability[FactionID(factionID)] = value;
+		});
 	}
 
-	LIBRARY->identifiers()->requestIdentifier("faction", node["faction"],
-										  [=](si32 factionID)
-										  {
-											  heroClass->faction.setNum(factionID);
-										  });
-
-	LIBRARY->identifiers()->requestIdentifier(scope, "object", "hero", [=](si32 index)
-										  {
-											  JsonNode classConf = node["mapObject"];
-											  classConf["heroClass"].String() = identifier;
-											  if (!node["compatibilityIdentifiers"].isNull())
-												  classConf["compatibilityIdentifiers"] = node["compatibilityIdentifiers"];
-											  classConf.setModScope(scope);
-											  LIBRARY->objtypeh->loadSubObject(identifier, classConf, index, heroClass->getIndex());
-										  });
+	LIBRARY->identifiers()->requestIdentifier("faction", node["faction"], [=](si32 factionID) {
+		heroClass->faction.setNum(factionID);
+	});
+
+	LIBRARY->identifiers()->requestIdentifier(scope, "object", "hero", [=](si32 index) {
+		JsonNode classConf = node["mapObject"];
+		classConf["heroClass"].String() = identifier;
+		if (!node["compatibilityIdentifiers"].isNull())
+			classConf["compatibilityIdentifiers"] = node["compatibilityIdentifiers"];
+		classConf.setModScope(scope);
+		LIBRARY->objtypeh->loadSubObject(identifier, classConf, index, heroClass->getIndex());
+	});
 
 	return heroClass;
 }

+ 1 - 1
lib/mapObjectConstructors/CommonConstructors.cpp

@@ -51,7 +51,7 @@ void ResourceInstanceConstructor::initTypeData(const JsonNode & input)
 	config = input;
 
 	resourceType = GameResID::GOLD; //set up fallback
-	LIBRARY->identifiers()->requestIdentifierOptional("resource", input["resource"], [&](si32 index)
+	LIBRARY->identifiers()->requestIdentifierIfNotNull("resource", input["resource"], [&](si32 index)
 	{
 		resourceType = GameResID(index);
 	});

+ 4 - 4
lib/mapping/CMap.cpp

@@ -687,7 +687,7 @@ bool CMap::calculateWaterContent()
 
 void CMap::banWaterContent()
 {
-	banWaterHeroes();
+	banWaterHeroes(isWaterMap());
 	banWaterArtifacts();
 	banWaterSpells();
 	banWaterSkills();
@@ -717,16 +717,16 @@ void CMap::banWaterSkills()
 	});
 }
 
-void CMap::banWaterHeroes()
+void CMapHeader::banWaterHeroes(bool isWaterMap)
 {
 	vstd::erase_if(allowedHeroes, [&](HeroTypeID hero)
 	{
-		return hero.toHeroType()->onlyOnWaterMap && !isWaterMap();
+		return hero.toHeroType()->onlyOnWaterMap && !isWaterMap;
 	});
 
 	vstd::erase_if(allowedHeroes, [&](HeroTypeID hero)
 	{
-		return hero.toHeroType()->onlyOnMapWithoutWater && isWaterMap();
+		return hero.toHeroType()->onlyOnMapWithoutWater && isWaterMap;
 	});
 }
 

+ 0 - 1
lib/mapping/CMap.h

@@ -222,7 +222,6 @@ public:
 	bool isWaterMap() const;
 	bool calculateWaterContent();
 	void banWaterArtifacts();
-	void banWaterHeroes();
 	void banHero(const HeroTypeID& id);
 	void unbanHero(const HeroTypeID & id);
 	void banWaterSpells();

+ 2 - 0
lib/mapping/CMapHeader.h

@@ -239,6 +239,8 @@ public:
 
 	ui8 levels() const;
 
+	void banWaterHeroes(bool isWaterMap);
+
 	EMapFormat version; /// The default value is EMapFormat::SOD.
 	ModCompatibilityInfo mods; /// set of mods required to play a map
 

+ 42 - 15
lib/modding/IdentifierStorage.cpp

@@ -123,7 +123,7 @@ CIdentifierStorage::ObjectCallback CIdentifierStorage::ObjectCallback::fromNameW
 	return result;
 }
 
-CIdentifierStorage::ObjectCallback CIdentifierStorage::ObjectCallback::fromNameAndType(const std::string & scope, const std::string & type, const std::string & fullName, const std::function<void(si32)> & callback, bool optional)
+CIdentifierStorage::ObjectCallback CIdentifierStorage::ObjectCallback::fromNameAndType(const std::string & scope, const std::string & type, const std::string & fullName, const std::function<void(si32)> & callback, bool optional, bool bypassDependenciesCheck)
 {
 	assert(!scope.empty());
 
@@ -148,13 +148,14 @@ CIdentifierStorage::ObjectCallback CIdentifierStorage::ObjectCallback::fromNameA
 	result.name = typeAndName.second;
 	result.callback = callback;
 	result.optional = optional;
+	result.bypassDependenciesCheck = bypassDependenciesCheck;
 	result.dynamicType = false;
 	return result;
 }
 
 void CIdentifierStorage::requestIdentifier(const std::string & scope, const std::string & type, const std::string & name, const std::function<void(si32)> & callback) const
 {
-	requestIdentifier(ObjectCallback::fromNameAndType(scope, type, name, callback, false));
+	requestIdentifier(ObjectCallback::fromNameAndType(scope, type, name, callback, false, false));
 }
 
 void CIdentifierStorage::requestIdentifier(const std::string & scope, const std::string & fullName, const std::function<void(si32)> & callback) const
@@ -164,7 +165,7 @@ void CIdentifierStorage::requestIdentifier(const std::string & scope, const std:
 
 void CIdentifierStorage::requestIdentifier(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback) const
 {
-	requestIdentifier(ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), callback, false));
+	requestIdentifier(ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), callback, false, false));
 }
 
 void CIdentifierStorage::requestIdentifier(const JsonNode & name, const std::function<void(si32)> & callback) const
@@ -172,27 +173,37 @@ void CIdentifierStorage::requestIdentifier(const JsonNode & name, const std::fun
 	requestIdentifier(ObjectCallback::fromNameWithType(name.getModScope(), name.String(), callback, false));
 }
 
-void CIdentifierStorage::requestIdentifierOptional(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback) const
+void CIdentifierStorage::requestIdentifierIfNotNull(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback) const
 {
 	if (!name.isNull())
 		requestIdentifier(type, name, callback);
 }
 
+void CIdentifierStorage::requestIdentifierIfFound(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback) const
+{
+	requestIdentifier(ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), callback, false, true));
+}
+
+void CIdentifierStorage::requestIdentifierIfFound(const std::string & scope, const std::string & type, const std::string & name, const std::function<void(si32)> & callback) const
+{
+	requestIdentifier(ObjectCallback::fromNameAndType(scope, type, name, callback, false, true));
+}
+
 void CIdentifierStorage::tryRequestIdentifier(const std::string & scope, const std::string & type, const std::string & name, const std::function<void(si32)> & callback) const
 {
-	requestIdentifier(ObjectCallback::fromNameAndType(scope, type, name, callback, true));
+	requestIdentifier(ObjectCallback::fromNameAndType(scope, type, name, callback, true, false));
 }
 
 void CIdentifierStorage::tryRequestIdentifier(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback) const
 {
-	requestIdentifier(ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), callback, true));
+	requestIdentifier(ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), callback, true, false));
 }
 
 std::optional<si32> CIdentifierStorage::getIdentifier(const std::string & scope, const std::string & type, const std::string & name, bool silent) const
 {
 	//assert(state != ELoadingState::LOADING);
 
-	auto options = ObjectCallback::fromNameAndType(scope, type, name, std::function<void(si32)>(), silent);
+	auto options = ObjectCallback::fromNameAndType(scope, type, name, std::function<void(si32)>(), silent, false);
 	return getIdentifierImpl(options, silent);
 }
 
@@ -200,7 +211,7 @@ std::optional<si32> CIdentifierStorage::getIdentifier(const std::string & type,
 {
 	assert(state != ELoadingState::LOADING);
 
-	auto options = ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), std::function<void(si32)>(), silent);
+	auto options = ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), std::function<void(si32)>(), silent, false);
 
 	return getIdentifierImpl(options, silent);
 }
@@ -269,26 +280,28 @@ void CIdentifierStorage::showIdentifierResolutionErrorDetails(const ObjectCallba
 		}
 		else
 		{
-			// such identifiers exists, but were not picked for some reason
-			if (options.remoteScope.empty())
+			// such identifier(s) exists, but were not picked for some reason
+			for (auto const & testOption : testList)
 			{
 				// attempt to access identifier from mods that is not dependency
-				for (auto const & testOption : testList)
+				bool isValidScope = true;
+				const auto & dependencies = LIBRARY->modh->getModDependencies(options.localScope, isValidScope);
+				if (!vstd::contains(dependencies, testOption.scope))
 				{
 					logMod->error("Identifier '%s' exists in mod %s", options.name, testOption.scope);
 					logMod->error("Please add mod '%s' as dependency of mod '%s' to access this identifier", testOption.scope, options.localScope);
+					continue;
 				}
-			}
-			else
-			{
+
 				// attempt to access identifier in form 'modName:object', but identifier is only present in different mod
-				for (auto const & testOption : testList)
+				if (options.remoteScope.empty())
 				{
 					logMod->error("Identifier '%s' exists in mod '%s' but identifier was explicitly requested from mod '%s'!", options.name, testOption.scope, options.remoteScope);
 					if (options.dynamicType)
 						logMod->error("Please use form '%s.%s' or '%s:%s.%s' to access this identifier", options.type, options.name, testOption.scope, options.type, options.name);
 					else
 						logMod->error("Please use form '%s' or '%s:%s' to access this identifier", options.name, testOption.scope, options.name);
+					continue;
 				}
 			}
 		}
@@ -377,6 +390,11 @@ std::vector<CIdentifierStorage::ObjectData> CIdentifierStorage::getPossibleIdent
 			// allow self-access
 			allowedScopes.insert(request.remoteScope);
 		}
+		else if (request.bypassDependenciesCheck)
+		{
+			// this is request for an identifier that bypasses mod dependencies check
+			allowedScopes.insert(request.remoteScope);
+		}
 		else
 		{
 			// allow access only if mod is in our dependencies
@@ -423,6 +441,15 @@ bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request) const
 		return true;
 	}
 
+	if (request.bypassDependenciesCheck)
+	{
+		if (!vstd::contains(LIBRARY->modh->getActiveMods(), request.remoteScope))
+		{
+			logMod->debug("Mod '%s' requested identifier '%s' from not loaded mod '%s'. Ignoring.", request.localScope, request.remoteScope, request.name);
+			return true; // mod was not loaded - ignore
+		}
+	}
+
 	// error found. Try to generate some debug info
 	failedRequests.push_back(request);
 	showIdentifierResolutionErrorDetails(request);

+ 5 - 2
lib/modding/IdentifierStorage.h

@@ -32,13 +32,14 @@ class DLL_LINKAGE CIdentifierStorage
 		std::string name;        /// string ID
 		std::function<void(si32)> callback;
 		bool optional;
+		bool bypassDependenciesCheck;
 		bool dynamicType;
 
 		/// Builds callback from identifier in form "targetMod:type.name"
 		static ObjectCallback fromNameWithType(const std::string & scope, const std::string & fullName, const std::function<void(si32)> & callback, bool optional);
 
 		/// Builds callback from identifier in form "targetMod:name"
-		static ObjectCallback fromNameAndType(const std::string & scope, const std::string & type, const std::string & fullName, const std::function<void(si32)> & callback, bool optional);
+		static ObjectCallback fromNameAndType(const std::string & scope, const std::string & type, const std::string & fullName, const std::function<void(si32)> & callback, bool optional, bool bypassDependenciesCheck);
 
 	private:
 		ObjectCallback() = default;
@@ -87,7 +88,9 @@ public:
 	void requestIdentifier(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback) const;
 	void requestIdentifier(const JsonNode & name, const std::function<void(si32)> & callback) const;
 
-	void requestIdentifierOptional(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback) const;
+	void requestIdentifierIfNotNull(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback) const;
+	void requestIdentifierIfFound(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback) const;
+	void requestIdentifierIfFound(const std::string & scope, const std::string & type, const std::string & name, const std::function<void(si32)> & callback) const;
 
 	/// try to request ID. If ID with such name won't be loaded, callback function will not be called
 	void tryRequestIdentifier(const std::string & scope, const std::string & type, const std::string & name, const std::function<void(si32)> & callback) const;

+ 12 - 0
lib/rmg/CMapGenerator.cpp

@@ -466,6 +466,18 @@ void CMapGenerator::addHeaderInfo()
 	m.waterMap = (mapGenOptions.getWaterContent() != EWaterContent::EWaterContent::NONE);
 	m.banWaterContent();
 	m.overrideGameSettings(mapGenOptions.getMapTemplate()->getMapSettings());
+
+	for (const auto & spell : mapGenOptions.getMapTemplate()->getBannedSpells())
+		m.allowedSpells.erase(spell);
+
+	for (const auto & artifact : mapGenOptions.getMapTemplate()->getBannedArtifacts())
+		m.allowedArtifact.erase(artifact);
+
+	for (const auto & skill : mapGenOptions.getMapTemplate()->getBannedSkills())
+		m.allowedAbilities.erase(skill);
+
+	for (const auto & hero : mapGenOptions.getMapTemplate()->getBannedHeroes())
+		m.allowedHeroes.erase(hero);
 }
 
 int CMapGenerator::getNextMonlithIndex()

+ 5 - 0
lib/rmg/CRmgTemplate.cpp

@@ -835,6 +835,11 @@ void CRmgTemplate::serializeJson(JsonSerializeFormat & handler)
 	serializePlayers(handler, players, "players");
 	serializePlayers(handler, humanPlayers, "humans"); // TODO: Rename this parameter
 
+	handler.serializeIdArray("bannedSpells", bannedSpells);
+	handler.serializeIdArray("bannedArtifacts", bannedArtifacts);
+	handler.serializeIdArray("bannedSkills", bannedSkills);
+	handler.serializeIdArray("bannedHeroes", bannedHeroes);
+
 	*mapSettings = handler.getCurrent()["settings"];
 
 	{

+ 11 - 1
lib/rmg/CRmgTemplate.h

@@ -338,6 +338,11 @@ public:
 	const JsonNode & getMapSettings() const;
 	const std::vector<rmg::ZoneConnection> & getConnectedZoneIds() const;
 
+	const std::set<SpellID> & getBannedSpells() const { return bannedSpells; }
+	const std::set<ArtifactID> & getBannedArtifacts() const { return bannedArtifacts; }
+	const std::set<SecondarySkill> & getBannedSkills() const { return bannedSkills; }
+	const std::set<HeroTypeID> & getBannedHeroes() const { return bannedHeroes; }
+
 	void validate() const; /// Tests template on validity and throws exception on failure
 
 	void serializeJson(JsonSerializeFormat & handler);
@@ -356,6 +361,11 @@ private:
 	std::set<EWaterContent::EWaterContent> allowedWaterContent;
 	std::unique_ptr<JsonNode> mapSettings;
 
+	std::set<SpellID> bannedSpells;
+	std::set<ArtifactID> bannedArtifacts;
+	std::set<SecondarySkill> bannedSkills;
+	std::set<HeroTypeID> bannedHeroes;
+
 	std::set<TerrainId> inheritTerrainType(std::shared_ptr<rmg::ZoneOptions> zone, uint32_t iteration = 0);
 	std::map<TResource, ui16> inheritMineTypes(std::shared_ptr<rmg::ZoneOptions> zone, uint32_t iteration = 0);
 	std::vector<CTreasureInfo> inheritTreasureInfo(std::shared_ptr<rmg::ZoneOptions> zone, uint32_t iteration = 0);
@@ -375,4 +385,4 @@ private:
 
 };
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

+ 7 - 13
lib/serializer/JsonSerializeFormat.h

@@ -316,12 +316,11 @@ public:
 		}
 		else
 		{
-			std::string fieldValue;
-			serializeString(fieldName, fieldValue);
+			const JsonNode & fieldValue = getCurrent()[fieldName];
 
-			if (!fieldValue.empty())
+			if (!fieldValue.String().empty())
 			{
-				LIBRARY->identifiers()->requestIdentifier(ModScope::scopeGame(), IdentifierType::entityType(), fieldValue, [&value](int32_t index){
+				LIBRARY->identifiers()->requestIdentifier(IdentifierType::entityType(), fieldValue, [&value](int32_t index){
 					value = IdentifierType(index);
 				});
 			}
@@ -347,14 +346,12 @@ public:
 		}
 		else
 		{
-			std::vector<std::string> fieldValue;
-			serializeInternal(fieldName, fieldValue);
-
+			const JsonVector & fieldValue = getCurrent()[fieldName].Vector();
 			value.resize(fieldValue.size());
 
 			for(size_t i = 0; i < fieldValue.size(); ++i)
 			{
-				LIBRARY->identifiers()->requestIdentifier(ModScope::scopeGame(), E::entityType(), fieldValue[i], [&value, i](int32_t index){
+				LIBRARY->identifiers()->requestIdentifier(E::entityType(), fieldValue[i], [&value, i](int32_t index){
 					value[i] = T(index);
 				});
 			}
@@ -376,12 +373,9 @@ public:
 		}
 		else
 		{
-			std::vector<std::string> fieldValue;
-			serializeInternal(fieldName, fieldValue);
-
-			for(size_t i = 0; i < fieldValue.size(); ++i)
+			for (const auto & element : getCurrent()[fieldName].Vector())
 			{
-				LIBRARY->identifiers()->requestIdentifier(ModScope::scopeGame(), U::entityType(), fieldValue[i], [&value](int32_t index){
+				LIBRARY->identifiers()->requestIdentifierIfFound(U::entityType(), element, [&value](int32_t index){
 					value.insert(T(index));
 				});
 			}

+ 1 - 1
lib/spells/CSpellHandler.cpp

@@ -761,7 +761,7 @@ std::shared_ptr<CSpell> CSpellHandler::loadFromJson(const std::string & scope, c
 	{
 		const int chance = static_cast<int>(node.second.Integer());
 
-		LIBRARY->identifiers()->requestIdentifier(node.second.getModScope(), "faction", node.first, [=](si32 factionID)
+		LIBRARY->identifiers()->requestIdentifierIfFound(node.second.getModScope(), "faction", node.first, [=](si32 factionID)
 		{
 			spell->probabilities[FactionID(factionID)] = chance;
 		});