mirror of
https://github.com/vcmi/vcmi.git
synced 2024-11-24 08:32:34 +02:00
7ffe014d6b
1. Handlers should not contain non-const game state data 2. This field was duplicating same field in CMap 3. Due to removal of VLC serialization, this field is not updated on map load leading to issues with artifact randomization
548 lines
16 KiB
C++
548 lines
16 KiB
C++
/*
|
|
* 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 <vstd/StringUtils.h>
|
|
|
|
#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 "gameState/CGameState.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<si32>(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<si32>(loadValue(value["amount"], rng, variables, defaultValue));
|
|
si32 min = static_cast<si32>(loadValue(value["min"], rng, variables, 0));
|
|
si32 max = static_cast<si32>(loadValue(value["max"], rng, variables, 0));
|
|
return rng.getIntRange(min, max)();
|
|
}
|
|
return defaultValue;
|
|
}
|
|
|
|
template<typename IdentifierType>
|
|
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<typename IdentifierType>
|
|
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<typename IdentifierType>
|
|
std::set<IdentifierType> filterKeysTyped(const JsonNode & value, const std::set<IdentifierType> & valuesSet)
|
|
{
|
|
return valuesSet;
|
|
}
|
|
|
|
template<>
|
|
std::set<ArtifactID> filterKeysTyped(const JsonNode & value, const std::set<ArtifactID> & valuesSet)
|
|
{
|
|
assert(value.isStruct());
|
|
|
|
std::set<CArtifact::EartClass> allowedClasses;
|
|
std::set<ArtifactPosition> allowedPositions;
|
|
ui32 minValue = 0;
|
|
ui32 maxValue = std::numeric_limits<ui32>::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<ui32>(value["minValue"].Float());
|
|
if (!value["maxValue"].isNull())
|
|
maxValue = static_cast<ui32>(value["maxValue"].Float());
|
|
|
|
std::set<ArtifactID> result;
|
|
|
|
for (auto const & artID : valuesSet)
|
|
{
|
|
const CArtifact * art = artID.toArtifact();
|
|
|
|
if(!vstd::iswithin(art->getPrice(), minValue, maxValue))
|
|
continue;
|
|
|
|
if(!allowedClasses.empty() && !allowedClasses.count(art->aClass))
|
|
continue;
|
|
|
|
if(!IObjectInterface::cb->isAllowed(art->getId()))
|
|
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<SpellID> filterKeysTyped(const JsonNode & value, const std::set<SpellID> & valuesSet)
|
|
{
|
|
std::set<SpellID> 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<typename IdentifierType>
|
|
std::set<IdentifierType> filterKeys(const JsonNode & value, const std::set<IdentifierType> & valuesSet, const Variables & variables)
|
|
{
|
|
if(value.isString())
|
|
return { decodeKey<IdentifierType>(value, variables) };
|
|
|
|
assert(value.isStruct());
|
|
|
|
if(value.isStruct())
|
|
{
|
|
if(!value["type"].isNull())
|
|
return filterKeys(value["type"], valuesSet, variables);
|
|
|
|
std::set<IdentifierType> filteredTypes = filterKeysTyped(value, valuesSet);
|
|
|
|
if(!value["anyOf"].isNull())
|
|
{
|
|
std::set<IdentifierType> filteredAnyOf;
|
|
for (auto const & entry : value["anyOf"].Vector())
|
|
{
|
|
std::set<IdentifierType> 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<IdentifierType> 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<GameConstants::RESOURCE_QUANTITY; i++)
|
|
{
|
|
ret[i] = loadValue(value[GameConstants::RESOURCE_NAMES[i]], rng, variables);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
TResources loadResource(const JsonNode & value, CRandomGenerator & rng, const Variables & variables)
|
|
{
|
|
std::set<GameResID> defaultResources{
|
|
GameResID::WOOD,
|
|
GameResID::MERCURY,
|
|
GameResID::ORE,
|
|
GameResID::SULFUR,
|
|
GameResID::CRYSTAL,
|
|
GameResID::GEMS,
|
|
GameResID::GOLD
|
|
};
|
|
|
|
std::set<GameResID> 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<PrimarySkill> defaultSkills{
|
|
PrimarySkill::ATTACK,
|
|
PrimarySkill::DEFENSE,
|
|
PrimarySkill::SPELL_POWER,
|
|
PrimarySkill::KNOWLEDGE
|
|
};
|
|
std::set<PrimarySkill> potentialPicks = filterKeys(value, defaultSkills, variables);
|
|
return *RandomGeneratorUtil::nextItem(potentialPicks, rng);
|
|
}
|
|
|
|
std::vector<si32> loadPrimaries(const JsonNode & value, CRandomGenerator & rng, const Variables & variables)
|
|
{
|
|
std::vector<si32> ret(GameConstants::PRIMARY_SKILLS, 0);
|
|
std::set<PrimarySkill> defaultSkills{
|
|
PrimarySkill::ATTACK,
|
|
PrimarySkill::DEFENSE,
|
|
PrimarySkill::SPELL_POWER,
|
|
PrimarySkill::KNOWLEDGE
|
|
};
|
|
|
|
if(value.isStruct())
|
|
{
|
|
for(const auto & pair : value.Struct())
|
|
{
|
|
PrimarySkill id = decodeKey<PrimarySkill>(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<PrimarySkill> 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<SecondarySkill> defaultSkills;
|
|
for(const auto & skill : VLC->skillh->objects)
|
|
if (IObjectInterface::cb->isAllowed(skill->getId()))
|
|
defaultSkills.insert(skill->getId());
|
|
|
|
std::set<SecondarySkill> potentialPicks = filterKeys(value, defaultSkills, variables);
|
|
return *RandomGeneratorUtil::nextItem(potentialPicks, rng);
|
|
}
|
|
|
|
std::map<SecondarySkill, si32> loadSecondaries(const JsonNode & value, CRandomGenerator & rng, const Variables & variables)
|
|
{
|
|
std::map<SecondarySkill, si32> ret;
|
|
if(value.isStruct())
|
|
{
|
|
for(const auto & pair : value.Struct())
|
|
{
|
|
SecondarySkill id = decodeKey<SecondarySkill>(pair.second.meta, pair.first, variables);
|
|
ret[id] = loadValue(pair.second, rng, variables);
|
|
}
|
|
}
|
|
if(value.isVector())
|
|
{
|
|
std::set<SecondarySkill> defaultSkills;
|
|
for(const auto & skill : VLC->skillh->objects)
|
|
if (IObjectInterface::cb->isAllowed(skill->getId()))
|
|
defaultSkills.insert(skill->getId());
|
|
|
|
for(const auto & element : value.Vector())
|
|
{
|
|
std::set<SecondarySkill> 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<ArtifactID> allowedArts;
|
|
for(const auto & artifact : VLC->arth->objects)
|
|
if (IObjectInterface::cb->isAllowed(artifact->getId()) && VLC->arth->legalArtifact(artifact->getId()))
|
|
allowedArts.insert(artifact->getId());
|
|
|
|
std::set<ArtifactID> potentialPicks = filterKeys(value, allowedArts, variables);
|
|
|
|
return IObjectInterface::cb->gameState()->pickRandomArtifact(rng, potentialPicks);
|
|
}
|
|
|
|
std::vector<ArtifactID> loadArtifacts(const JsonNode & value, CRandomGenerator & rng, const Variables & variables)
|
|
{
|
|
std::vector<ArtifactID> 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<SpellID> defaultSpells;
|
|
for(const auto & spell : VLC->spellh->objects)
|
|
if (IObjectInterface::cb->isAllowed(spell->getId()) && !spell->isSpecial())
|
|
defaultSpells.insert(spell->getId());
|
|
|
|
std::set<SpellID> 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<SpellID> loadSpells(const JsonNode & value, CRandomGenerator & rng, const Variables & variables)
|
|
{
|
|
std::vector<SpellID> ret;
|
|
for (const JsonNode & entry : value.Vector())
|
|
{
|
|
ret.push_back(loadSpell(entry, rng, variables));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
std::vector<PlayerColor> loadColors(const JsonNode & value, CRandomGenerator & rng, const Variables & variables)
|
|
{
|
|
std::vector<PlayerColor> ret;
|
|
std::set<PlayerColor> defaultPlayers;
|
|
for(size_t i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i)
|
|
defaultPlayers.insert(PlayerColor(i));
|
|
|
|
for(auto & entry : value.Vector())
|
|
{
|
|
std::set<PlayerColor> potentialPicks = filterKeys(entry, defaultPlayers, variables);
|
|
ret.push_back(*RandomGeneratorUtil::nextItem(potentialPicks, rng));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
std::vector<HeroTypeID> loadHeroes(const JsonNode & value, CRandomGenerator & rng)
|
|
{
|
|
std::vector<HeroTypeID> ret;
|
|
for(auto & entry : value.Vector())
|
|
{
|
|
ret.push_back(VLC->heroTypes()->getByIndex(VLC->identifiers()->getIdentifier("hero", entry.String()).value())->getId());
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
std::vector<HeroClassID> loadHeroClasses(const JsonNode & value, CRandomGenerator & rng)
|
|
{
|
|
std::vector<HeroClassID> 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<CreatureID> defaultCreatures;
|
|
for(const auto & creature : VLC->creh->objects)
|
|
if (!creature->special)
|
|
defaultCreatures.insert(creature->getId());
|
|
|
|
std::set<CreatureID> 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 = pickedCreature.toCreature();
|
|
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 = RandomGeneratorUtil::nextItem(stack.type->upgrades, rng)->toCreature();
|
|
}
|
|
}
|
|
return stack;
|
|
}
|
|
|
|
std::vector<CStackBasicDescriptor> loadCreatures(const JsonNode & value, CRandomGenerator & rng, const Variables & variables)
|
|
{
|
|
std::vector<CStackBasicDescriptor> ret;
|
|
for (const JsonNode & node : value.Vector())
|
|
{
|
|
ret.push_back(loadCreature(node, rng, variables));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
std::vector<RandomStackInfo> evaluateCreatures(const JsonNode & value, const Variables & variables)
|
|
{
|
|
std::vector<RandomStackInfo> ret;
|
|
for (const JsonNode & node : value.Vector())
|
|
{
|
|
RandomStackInfo info;
|
|
|
|
if (!node["amount"].isNull())
|
|
info.minAmount = info.maxAmount = static_cast<si32>(node["amount"].Float());
|
|
else
|
|
{
|
|
info.minAmount = static_cast<si32>(node["min"].Float());
|
|
info.maxAmount = static_cast<si32>(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(creaID.toCreature());
|
|
}
|
|
ret.push_back(info);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
std::vector<Bonus> DLL_LINKAGE loadBonuses(const JsonNode & value)
|
|
{
|
|
std::vector<Bonus> ret;
|
|
for (const JsonNode & entry : value.Vector())
|
|
{
|
|
if(auto bonus = JsonUtils::parseBonus(entry))
|
|
ret.push_back(*bonus);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
}
|
|
|
|
VCMI_LIB_NAMESPACE_END
|