diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index 7914ac217..5d6a52332 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/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); diff --git a/config/schemas/template.json b/config/schemas/template.json index d139f257b..d17fa48b6 100644 --- a/config/schemas/template.json +++ b/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" diff --git a/docs/modders/Entities_Format/Faction_Format.md b/docs/modders/Entities_Format/Faction_Format.md index 78e80ab3d..255342d95 100644 --- a/docs/modders/Entities_Format/Faction_Format.md +++ b/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) diff --git a/docs/modders/Entities_Format/Hero_Class_Format.md b/docs/modders/Entities_Format/Hero_Class_Format.md index 1a25cc01c..b1cb0e939 100644 --- a/docs/modders/Entities_Format/Hero_Class_Format.md +++ b/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 } } ``` diff --git a/docs/modders/Entities_Format/Secondary_Skill_Format.md b/docs/modders/Entities_Format/Secondary_Skill_Format.md index eb9dc38db..ab04bfd51 100644 --- a/docs/modders/Entities_Format/Secondary_Skill_Format.md +++ b/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 diff --git a/docs/modders/Entities_Format/Spell_Format.md b/docs/modders/Entities_Format/Spell_Format.md index f66b332ed..dbb1aeb39 100644 --- a/docs/modders/Entities_Format/Spell_Format.md +++ b/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":{}, diff --git a/docs/modders/Random_Map_Template.md b/docs/modders/Random_Map_Template.md index 1750e636b..a4166ed63 100644 --- a/docs/modders/Random_Map_Template.md +++ b/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" : diff --git a/lib/CSkillHandler.cpp b/lib/CSkillHandler.cpp index 4eddd9b3e..543c547bd 100644 --- a/lib/CSkillHandler.cpp +++ b/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 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(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: diff --git a/lib/entities/faction/CTownHandler.cpp b/lib/entities/faction/CTownHandler.cpp index 20a99cb3b..17c81dc47 100644 --- a/lib/entities/faction/CTownHandler.cpp +++ b/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(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(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; }); diff --git a/lib/entities/hero/CHeroClassHandler.cpp b/lib/entities/hero/CHeroClassHandler.cpp index 05be20e0b..9a8f7a6ab 100644 --- a/lib/entities/hero/CHeroClassHandler.cpp +++ b/lib/entities/hero/CHeroClassHandler.cpp @@ -97,45 +97,37 @@ std::shared_ptr CHeroClassHandler::loadFromJson(const std::string & for(auto skillPair : node["secondarySkills"].Struct()) { int probability = static_cast(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(node["defaultTavern"].Float()); for(const auto & tavern : node["tavern"].Struct()) { int value = static_cast(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("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(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; } diff --git a/lib/mapObjectConstructors/CommonConstructors.cpp b/lib/mapObjectConstructors/CommonConstructors.cpp index 6d33d85c4..f055f654a 100644 --- a/lib/mapObjectConstructors/CommonConstructors.cpp +++ b/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); }); diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 145f17d4d..8f9a9def4 100644 --- a/lib/mapping/CMap.cpp +++ b/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; }); } diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index 8544c82cb..dc8b5bf05 100644 --- a/lib/mapping/CMap.h +++ b/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(); diff --git a/lib/mapping/CMapHeader.h b/lib/mapping/CMapHeader.h index 8bd828eb9..5391877b0 100644 --- a/lib/mapping/CMapHeader.h +++ b/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 diff --git a/lib/modding/IdentifierStorage.cpp b/lib/modding/IdentifierStorage.cpp index 894221bef..d71afbe98 100644 --- a/lib/modding/IdentifierStorage.cpp +++ b/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 & callback, bool optional) +CIdentifierStorage::ObjectCallback CIdentifierStorage::ObjectCallback::fromNameAndType(const std::string & scope, const std::string & type, const std::string & fullName, const std::function & 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 & 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 & 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 & 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 & 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 & callback) const +void CIdentifierStorage::requestIdentifierIfNotNull(const std::string & type, const JsonNode & name, const std::function & callback) const { if (!name.isNull()) requestIdentifier(type, name, callback); } +void CIdentifierStorage::requestIdentifierIfFound(const std::string & type, const JsonNode & name, const std::function & 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 & 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 & 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 & 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 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(), silent); + auto options = ObjectCallback::fromNameAndType(scope, type, name, std::function(), silent, false); return getIdentifierImpl(options, silent); } @@ -200,7 +211,7 @@ std::optional CIdentifierStorage::getIdentifier(const std::string & type, { assert(state != ELoadingState::LOADING); - auto options = ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), std::function(), silent); + auto options = ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), std::function(), 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::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); diff --git a/lib/modding/IdentifierStorage.h b/lib/modding/IdentifierStorage.h index 76b67bc76..51ab94c1f 100644 --- a/lib/modding/IdentifierStorage.h +++ b/lib/modding/IdentifierStorage.h @@ -32,13 +32,14 @@ class DLL_LINKAGE CIdentifierStorage std::string name; /// string ID std::function 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 & 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 & callback, bool optional); + static ObjectCallback fromNameAndType(const std::string & scope, const std::string & type, const std::string & fullName, const std::function & 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 & callback) const; void requestIdentifier(const JsonNode & name, const std::function & callback) const; - void requestIdentifierOptional(const std::string & type, const JsonNode & name, const std::function & callback) const; + void requestIdentifierIfNotNull(const std::string & type, const JsonNode & name, const std::function & callback) const; + void requestIdentifierIfFound(const std::string & type, const JsonNode & name, const std::function & callback) const; + void requestIdentifierIfFound(const std::string & scope, const std::string & type, const std::string & name, const std::function & 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 & callback) const; diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 86c2e53c1..7c1fcb1b7 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/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() diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index 26860fd22..3ecc2a35e 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/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"]; { diff --git a/lib/rmg/CRmgTemplate.h b/lib/rmg/CRmgTemplate.h index fc9fb8ac0..16cc0a5ef 100644 --- a/lib/rmg/CRmgTemplate.h +++ b/lib/rmg/CRmgTemplate.h @@ -338,6 +338,11 @@ public: const JsonNode & getMapSettings() const; const std::vector & getConnectedZoneIds() const; + const std::set & getBannedSpells() const { return bannedSpells; } + const std::set & getBannedArtifacts() const { return bannedArtifacts; } + const std::set & getBannedSkills() const { return bannedSkills; } + const std::set & 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 allowedWaterContent; std::unique_ptr mapSettings; + std::set bannedSpells; + std::set bannedArtifacts; + std::set bannedSkills; + std::set bannedHeroes; + std::set inheritTerrainType(std::shared_ptr zone, uint32_t iteration = 0); std::map inheritMineTypes(std::shared_ptr zone, uint32_t iteration = 0); std::vector inheritTreasureInfo(std::shared_ptr zone, uint32_t iteration = 0); @@ -375,4 +385,4 @@ private: }; -VCMI_LIB_NAMESPACE_END \ No newline at end of file +VCMI_LIB_NAMESPACE_END diff --git a/lib/serializer/JsonSerializeFormat.h b/lib/serializer/JsonSerializeFormat.h index b8267fe6d..3ec30f37c 100644 --- a/lib/serializer/JsonSerializeFormat.h +++ b/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 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 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)); }); } diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index 7f55193d2..1ce14fcae 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -767,7 +767,7 @@ std::shared_ptr CSpellHandler::loadFromJson(const std::string & scope, c { const int chance = static_cast(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; });