/* * JsonRandom.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 "JsonRandom.h" #include #include "JsonNode.h" #include "CRandomGenerator.h" #include "constants/StringConstants.h" #include "VCMI_Lib.h" #include "CArtHandler.h" #include "CCreatureHandler.h" #include "CCreatureSet.h" #include "spells/CSpellHandler.h" #include "CSkillHandler.h" #include "CHeroHandler.h" #include "IGameCallback.h" #include "mapObjects/IObjectInterface.h" #include "modding/IdentifierStorage.h" #include "modding/ModScope.h" VCMI_LIB_NAMESPACE_BEGIN namespace JsonRandom { si32 loadVariable(std::string variableGroup, const std::string & value, const Variables & variables, si32 defaultValue) { if (value.empty() || value[0] != '@') { logMod->warn("Invalid syntax in load value! Can not load value from '%s'", value); return defaultValue; } std::string variableID = variableGroup + value; if (variables.count(variableID) == 0) { logMod->warn("Invalid syntax in load value! Unknown variable '%s'", value); return defaultValue; } return variables.at(variableID); } si32 loadValue(const JsonNode & value, CRandomGenerator & rng, const Variables & variables, si32 defaultValue) { if(value.isNull()) return defaultValue; if(value.isNumber()) return static_cast(value.Float()); if (value.isString()) return loadVariable("number", value.String(), variables, defaultValue); if(value.isVector()) { const auto & vector = value.Vector(); size_t index= rng.getIntRange(0, vector.size()-1)(); return loadValue(vector[index], rng, variables, 0); } if(value.isStruct()) { if (!value["amount"].isNull()) return static_cast(loadValue(value["amount"], rng, variables, defaultValue)); si32 min = static_cast(loadValue(value["min"], rng, variables, 0)); si32 max = static_cast(loadValue(value["max"], rng, variables, 0)); return rng.getIntRange(min, max)(); } return defaultValue; } template IdentifierType decodeKey(const std::string & modScope, const std::string & value, const Variables & variables) { if (value.empty() || value[0] != '@') return IdentifierType(*VLC->identifiers()->getIdentifier(modScope, IdentifierType::entityType(), value)); else return loadVariable(IdentifierType::entityType(), value, variables, IdentifierType::NONE); } template IdentifierType decodeKey(const JsonNode & value, const Variables & variables) { if (value.String().empty() || value.String()[0] != '@') return IdentifierType(*VLC->identifiers()->getIdentifier(IdentifierType::entityType(), value)); else return loadVariable(IdentifierType::entityType(), value.String(), variables, IdentifierType::NONE); } template<> PlayerColor decodeKey(const JsonNode & value, const Variables & variables) { return PlayerColor(*VLC->identifiers()->getIdentifier("playerColor", value)); } template<> PrimarySkill decodeKey(const JsonNode & value, const Variables & variables) { return PrimarySkill(*VLC->identifiers()->getIdentifier("primarySkill", value)); } template<> PrimarySkill decodeKey(const std::string & modScope, const std::string & value, const Variables & variables) { if (value.empty() || value[0] != '@') return PrimarySkill(*VLC->identifiers()->getIdentifier(modScope, "primarySkill", value)); else return PrimarySkill(loadVariable("primarySkill", value, variables, PrimarySkill::NONE.getNum())); } /// Method that allows type-specific object filtering /// Default implementation is to accept all input objects template std::set filterKeysTyped(const JsonNode & value, const std::set & valuesSet) { return valuesSet; } template<> std::set filterKeysTyped(const JsonNode & value, const std::set & valuesSet) { assert(value.isStruct()); std::set allowedClasses; std::set allowedPositions; ui32 minValue = 0; ui32 maxValue = std::numeric_limits::max(); if (value["class"].getType() == JsonNode::JsonType::DATA_STRING) allowedClasses.insert(CArtHandler::stringToClass(value["class"].String())); else for(const auto & entry : value["class"].Vector()) allowedClasses.insert(CArtHandler::stringToClass(entry.String())); if (value["slot"].getType() == JsonNode::JsonType::DATA_STRING) allowedPositions.insert(ArtifactPosition::decode(value["class"].String())); else for(const auto & entry : value["slot"].Vector()) allowedPositions.insert(ArtifactPosition::decode(entry.String())); if (!value["minValue"].isNull()) minValue = static_cast(value["minValue"].Float()); if (!value["maxValue"].isNull()) maxValue = static_cast(value["maxValue"].Float()); std::set result; for (auto const & artID : valuesSet) { CArtifact * art = VLC->arth->objects[artID]; if(!vstd::iswithin(art->getPrice(), minValue, maxValue)) continue; if(!allowedClasses.empty() && !allowedClasses.count(art->aClass)) continue; if(!IObjectInterface::cb->isAllowed(1, art->getIndex())) continue; if(!allowedPositions.empty()) { bool positionAllowed = false; for(const auto & pos : art->getPossibleSlots().at(ArtBearer::HERO)) { if(allowedPositions.count(pos)) positionAllowed = true; } if (!positionAllowed) continue; } result.insert(artID); } return result; } template<> std::set filterKeysTyped(const JsonNode & value, const std::set & valuesSet) { std::set result = valuesSet; if (!value["level"].isNull()) { int32_t spellLevel = value["level"].Integer(); vstd::erase_if(result, [=](const SpellID & spell) { return VLC->spellh->getById(spell)->getLevel() != spellLevel; }); } if (!value["school"].isNull()) { int32_t schoolID = VLC->identifiers()->getIdentifier("spellSchool", value["school"]).value(); vstd::erase_if(result, [=](const SpellID & spell) { return !VLC->spellh->getById(spell)->hasSchool(SpellSchool(schoolID)); }); } return result; } template std::set filterKeys(const JsonNode & value, const std::set & valuesSet, const Variables & variables) { if(value.isString()) return { decodeKey(value, variables) }; assert(value.isStruct()); if(value.isStruct()) { if(!value["type"].isNull()) return filterKeys(value["type"], valuesSet, variables); std::set filteredTypes = filterKeysTyped(value, valuesSet); if(!value["anyOf"].isNull()) { std::set filteredAnyOf; for (auto const & entry : value["anyOf"].Vector()) { std::set subset = filterKeys(entry, valuesSet, variables); filteredAnyOf.insert(subset.begin(), subset.end()); } vstd::erase_if(filteredTypes, [&](const IdentifierType & value) { return filteredAnyOf.count(value) == 0; }); } if(!value["noneOf"].isNull()) { for (auto const & entry : value["noneOf"].Vector()) { std::set subset = filterKeys(entry, valuesSet, variables); for (auto bannedEntry : subset ) filteredTypes.erase(bannedEntry); } } return filteredTypes; } return valuesSet; } TResources loadResources(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) { TResources ret; if (value.isVector()) { for (const auto & entry : value.Vector()) ret += loadResource(entry, rng, variables); return ret; } for (size_t i=0; i defaultResources{ GameResID::WOOD, GameResID::MERCURY, GameResID::ORE, GameResID::SULFUR, GameResID::CRYSTAL, GameResID::GEMS, GameResID::GOLD }; std::set potentialPicks = filterKeys(value, defaultResources, variables); GameResID resourceID = *RandomGeneratorUtil::nextItem(potentialPicks, rng); si32 resourceAmount = loadValue(value, rng, variables, 0); TResources ret; ret[resourceID] = resourceAmount; return ret; } PrimarySkill loadPrimary(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) { std::set defaultSkills{ PrimarySkill::ATTACK, PrimarySkill::DEFENSE, PrimarySkill::SPELL_POWER, PrimarySkill::KNOWLEDGE }; std::set potentialPicks = filterKeys(value, defaultSkills, variables); return *RandomGeneratorUtil::nextItem(potentialPicks, rng); } std::vector loadPrimaries(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) { std::vector ret(GameConstants::PRIMARY_SKILLS, 0); std::set defaultSkills{ PrimarySkill::ATTACK, PrimarySkill::DEFENSE, PrimarySkill::SPELL_POWER, PrimarySkill::KNOWLEDGE }; if(value.isStruct()) { for(const auto & pair : value.Struct()) { PrimarySkill id = decodeKey(pair.second.meta, pair.first, variables); ret[id.getNum()] += loadValue(pair.second, rng, variables); } } if(value.isVector()) { for(const auto & element : value.Vector()) { std::set potentialPicks = filterKeys(element, defaultSkills, variables); PrimarySkill skillID = *RandomGeneratorUtil::nextItem(potentialPicks, rng); defaultSkills.erase(skillID); ret[skillID.getNum()] += loadValue(element, rng, variables); } } return ret; } SecondarySkill loadSecondary(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) { std::set defaultSkills; for(const auto & skill : VLC->skillh->objects) if (IObjectInterface::cb->isAllowed(2, skill->getIndex())) defaultSkills.insert(skill->getId()); std::set potentialPicks = filterKeys(value, defaultSkills, variables); return *RandomGeneratorUtil::nextItem(potentialPicks, rng); } std::map loadSecondaries(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) { std::map ret; if(value.isStruct()) { for(const auto & pair : value.Struct()) { SecondarySkill id = decodeKey(pair.second.meta, pair.first, variables); ret[id] = loadValue(pair.second, rng, variables); } } if(value.isVector()) { std::set defaultSkills; for(const auto & skill : VLC->skillh->objects) if (IObjectInterface::cb->isAllowed(2, skill->getIndex())) defaultSkills.insert(skill->getId()); for(const auto & element : value.Vector()) { std::set potentialPicks = filterKeys(element, defaultSkills, variables); SecondarySkill skillID = *RandomGeneratorUtil::nextItem(potentialPicks, rng); defaultSkills.erase(skillID); //avoid dupicates ret[skillID] = loadValue(element, rng, variables); } } return ret; } ArtifactID loadArtifact(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) { std::set allowedArts; for (auto const * artifact : VLC->arth->allowedArtifacts) allowedArts.insert(artifact->getId()); std::set potentialPicks = filterKeys(value, allowedArts, variables); return VLC->arth->pickRandomArtifact(rng, potentialPicks); } std::vector loadArtifacts(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) { std::vector ret; for (const JsonNode & entry : value.Vector()) { ret.push_back(loadArtifact(entry, rng, variables)); } return ret; } SpellID loadSpell(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) { std::set defaultSpells; for(const auto & spell : VLC->spellh->objects) if (IObjectInterface::cb->isAllowed(0, spell->getIndex())) defaultSpells.insert(spell->getId()); std::set potentialPicks = filterKeys(value, defaultSpells, variables); if (potentialPicks.empty()) { logMod->warn("Failed to select suitable random spell!"); return SpellID::NONE; } return *RandomGeneratorUtil::nextItem(potentialPicks, rng); } std::vector loadSpells(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) { std::vector ret; for (const JsonNode & entry : value.Vector()) { ret.push_back(loadSpell(entry, rng, variables)); } return ret; } std::vector loadColors(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) { std::vector ret; std::set defaultPlayers; for(size_t i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i) defaultPlayers.insert(PlayerColor(i)); for(auto & entry : value.Vector()) { std::set potentialPicks = filterKeys(entry, defaultPlayers, variables); ret.push_back(*RandomGeneratorUtil::nextItem(potentialPicks, rng)); } return ret; } std::vector loadHeroes(const JsonNode & value, CRandomGenerator & rng) { std::vector ret; for(auto & entry : value.Vector()) { ret.push_back(VLC->heroTypes()->getByIndex(VLC->identifiers()->getIdentifier("hero", entry.String()).value())->getId()); } return ret; } std::vector loadHeroClasses(const JsonNode & value, CRandomGenerator & rng) { std::vector ret; for(auto & entry : value.Vector()) { ret.push_back(VLC->heroClasses()->getByIndex(VLC->identifiers()->getIdentifier("heroClass", entry.String()).value())->getId()); } return ret; } CStackBasicDescriptor loadCreature(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) { CStackBasicDescriptor stack; std::set defaultCreatures; for(const auto & creature : VLC->creh->objects) if (!creature->special) defaultCreatures.insert(creature->getId()); std::set potentialPicks = filterKeys(value, defaultCreatures, variables); CreatureID pickedCreature; if (!potentialPicks.empty()) pickedCreature = *RandomGeneratorUtil::nextItem(potentialPicks, rng); else logMod->warn("Failed to select suitable random creature!"); stack.type = VLC->creh->objects[pickedCreature]; stack.count = loadValue(value, rng, variables); if (!value["upgradeChance"].isNull() && !stack.type->upgrades.empty()) { if (int(value["upgradeChance"].Float()) > rng.nextInt(99)) // select random upgrade { stack.type = VLC->creh->objects[*RandomGeneratorUtil::nextItem(stack.type->upgrades, rng)]; } } return stack; } std::vector loadCreatures(const JsonNode & value, CRandomGenerator & rng, const Variables & variables) { std::vector ret; for (const JsonNode & node : value.Vector()) { ret.push_back(loadCreature(node, rng, variables)); } return ret; } std::vector evaluateCreatures(const JsonNode & value, const Variables & variables) { std::vector ret; for (const JsonNode & node : value.Vector()) { RandomStackInfo info; if (!node["amount"].isNull()) info.minAmount = info.maxAmount = static_cast(node["amount"].Float()); else { info.minAmount = static_cast(node["min"].Float()); info.maxAmount = static_cast(node["max"].Float()); } const CCreature * crea = VLC->creh->objects[VLC->identifiers()->getIdentifier("creature", node["type"]).value()]; info.allowedCreatures.push_back(crea); if (node["upgradeChance"].Float() > 0) { for(const auto & creaID : crea->upgrades) info.allowedCreatures.push_back(VLC->creh->objects[creaID]); } ret.push_back(info); } return ret; } std::vector DLL_LINKAGE loadBonuses(const JsonNode & value) { std::vector ret; for (const JsonNode & entry : value.Vector()) { if(auto bonus = JsonUtils::parseBonus(entry)) ret.push_back(*bonus); } return ret; } } VCMI_LIB_NAMESPACE_END