mirror of
https://github.com/vcmi/vcmi.git
synced 2025-08-13 19:54:17 +02:00
Unified Json randomization logic for all entity types
This commit is contained in:
@@ -56,31 +56,156 @@ namespace JsonRandom
|
|||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string loadKey(const JsonNode & value, CRandomGenerator & rng, const std::set<std::string> & valuesSet)
|
template<typename IdentifierType>
|
||||||
|
IdentifierType decodeKey(const JsonNode & value)
|
||||||
|
{
|
||||||
|
return IdentifierType(*VLC->identifiers()->getIdentifier(IdentifierType::entityType(), value));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
PrimarySkill decodeKey(const JsonNode & value)
|
||||||
|
{
|
||||||
|
return PrimarySkill(*VLC->identifiers()->getIdentifier("primarySkill", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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)
|
||||||
|
{
|
||||||
|
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<SpellID> filterKeysTyped(const JsonNode & value, const std::set<SpellID> & valuesSet)
|
||||||
|
{
|
||||||
|
std::set<SpellID> result = valuesSet;
|
||||||
|
|
||||||
|
if (!value["level"].isNull())
|
||||||
|
{
|
||||||
|
int32_t spellLevel = value["level"].Float();
|
||||||
|
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
if(value.isString())
|
if(value.isString())
|
||||||
return value.String();
|
return { decodeKey<IdentifierType>(value) };
|
||||||
|
|
||||||
|
assert(value.isStruct());
|
||||||
|
|
||||||
if(value.isStruct())
|
if(value.isStruct())
|
||||||
{
|
{
|
||||||
if(!value["type"].isNull())
|
if(!value["type"].isNull())
|
||||||
return value["type"].String();
|
return filterKeys(value["type"], valuesSet);
|
||||||
|
|
||||||
|
std::set<IdentifierType> filteredTypes = filterKeysTyped(value, valuesSet);
|
||||||
|
|
||||||
if(!value["anyOf"].isNull())
|
if(!value["anyOf"].isNull())
|
||||||
return RandomGeneratorUtil::nextItem(value["anyOf"].Vector(), rng)->String();
|
{
|
||||||
|
std::set<IdentifierType> filteredAnyOf;
|
||||||
|
for (auto const & entry : value["anyOf"].Vector())
|
||||||
|
{
|
||||||
|
std::set<IdentifierType> subset = filterKeys(entry, valuesSet);
|
||||||
|
filteredAnyOf.insert(subset.begin(), subset.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
vstd::erase_if(filteredTypes, [&](const IdentifierType & value)
|
||||||
|
{
|
||||||
|
return filteredAnyOf.count(value) == 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if(!value["noneOf"].isNull())
|
if(!value["noneOf"].isNull())
|
||||||
{
|
{
|
||||||
auto copyValuesSet = valuesSet;
|
for (auto const & entry : value["noneOf"].Vector())
|
||||||
for(auto & s : value["noneOf"].Vector())
|
{
|
||||||
copyValuesSet.erase(s.String());
|
std::set<IdentifierType> subset = filterKeys(entry, valuesSet);
|
||||||
|
for (auto bannedEntry : subset )
|
||||||
if(!copyValuesSet.empty())
|
filteredTypes.erase(bannedEntry);
|
||||||
return *RandomGeneratorUtil::nextItem(copyValuesSet, rng);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return filteredTypes;
|
||||||
}
|
}
|
||||||
|
return valuesSet;
|
||||||
return valuesSet.empty() ? "" : *RandomGeneratorUtil::nextItem(valuesSet, rng);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TResources loadResources(const JsonNode & value, CRandomGenerator & rng)
|
TResources loadResources(const JsonNode & value, CRandomGenerator & rng)
|
||||||
@@ -103,11 +228,19 @@ namespace JsonRandom
|
|||||||
|
|
||||||
TResources loadResource(const JsonNode & value, CRandomGenerator & rng)
|
TResources loadResource(const JsonNode & value, CRandomGenerator & rng)
|
||||||
{
|
{
|
||||||
std::set<std::string> defaultResources(std::begin(GameConstants::RESOURCE_NAMES), std::end(GameConstants::RESOURCE_NAMES) - 1); //except mithril
|
std::set<GameResID> defaultResources{
|
||||||
|
GameResID::WOOD,
|
||||||
std::string resourceName = loadKey(value, rng, defaultResources);
|
GameResID::MERCURY,
|
||||||
|
GameResID::ORE,
|
||||||
|
GameResID::SULFUR,
|
||||||
|
GameResID::CRYSTAL,
|
||||||
|
GameResID::GEMS,
|
||||||
|
GameResID::GOLD
|
||||||
|
};
|
||||||
|
|
||||||
|
std::set<GameResID> potentialPicks = filterKeys(value, defaultResources);
|
||||||
|
GameResID resourceID = *RandomGeneratorUtil::nextItem(potentialPicks, rng);
|
||||||
si32 resourceAmount = loadValue(value, rng, 0);
|
si32 resourceAmount = loadValue(value, rng, 0);
|
||||||
si32 resourceID(VLC->identifiers()->getIdentifier(value.meta, "resource", resourceName).value());
|
|
||||||
|
|
||||||
TResources ret;
|
TResources ret;
|
||||||
ret[resourceID] = resourceAmount;
|
ret[resourceID] = resourceAmount;
|
||||||
@@ -127,15 +260,22 @@ namespace JsonRandom
|
|||||||
}
|
}
|
||||||
if(value.isVector())
|
if(value.isVector())
|
||||||
{
|
{
|
||||||
|
std::set<PrimarySkill> defaultSkills{
|
||||||
|
PrimarySkill::ATTACK,
|
||||||
|
PrimarySkill::DEFENSE,
|
||||||
|
PrimarySkill::SPELL_POWER,
|
||||||
|
PrimarySkill::KNOWLEDGE
|
||||||
|
};
|
||||||
|
|
||||||
ret.resize(GameConstants::PRIMARY_SKILLS, 0);
|
ret.resize(GameConstants::PRIMARY_SKILLS, 0);
|
||||||
std::set<std::string> defaultStats(std::begin(NPrimarySkill::names), std::end(NPrimarySkill::names));
|
|
||||||
for(const auto & element : value.Vector())
|
for(const auto & element : value.Vector())
|
||||||
{
|
{
|
||||||
auto key = loadKey(element, rng, defaultStats);
|
std::set<PrimarySkill> potentialPicks = filterKeys(element, defaultSkills);
|
||||||
defaultStats.erase(key);
|
PrimarySkill skillID = *RandomGeneratorUtil::nextItem(potentialPicks, rng);
|
||||||
int id = vstd::find_pos(NPrimarySkill::names, key);
|
|
||||||
if(id != -1)
|
defaultSkills.erase(skillID);
|
||||||
ret[id] += loadValue(element, rng);
|
ret[static_cast<int>(skillID)] += loadValue(element, rng);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
@@ -154,26 +294,18 @@ namespace JsonRandom
|
|||||||
}
|
}
|
||||||
if(value.isVector())
|
if(value.isVector())
|
||||||
{
|
{
|
||||||
std::set<std::string> defaultSkills;
|
std::set<SecondarySkill> defaultSkills;
|
||||||
for(const auto & skill : VLC->skillh->objects)
|
for(const auto & skill : VLC->skillh->objects)
|
||||||
{
|
if (IObjectInterface::cb->isAllowed(2, skill->getIndex()))
|
||||||
IObjectInterface::cb->isAllowed(2, skill->getIndex());
|
defaultSkills.insert(skill->getId());
|
||||||
auto scopeAndName = vstd::splitStringToPair(skill->getJsonKey(), ':');
|
|
||||||
if(scopeAndName.first == ModScope::scopeBuiltin() || scopeAndName.first == value.meta)
|
|
||||||
defaultSkills.insert(scopeAndName.second);
|
|
||||||
else
|
|
||||||
defaultSkills.insert(skill->getJsonKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
for(const auto & element : value.Vector())
|
for(const auto & element : value.Vector())
|
||||||
{
|
{
|
||||||
auto key = loadKey(element, rng, defaultSkills);
|
std::set<SecondarySkill> potentialPicks = filterKeys(element, defaultSkills);
|
||||||
defaultSkills.erase(key); //avoid dupicates
|
SecondarySkill skillID = *RandomGeneratorUtil::nextItem(potentialPicks, rng);
|
||||||
if(auto identifier = VLC->identifiers()->getIdentifier(ModScope::scopeGame(), "skill", key))
|
|
||||||
{
|
defaultSkills.erase(skillID); //avoid dupicates
|
||||||
SecondarySkill id(identifier.value());
|
ret[skillID] = loadValue(element, rng);
|
||||||
ret[id] = loadValue(element, rng);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
@@ -181,53 +313,13 @@ namespace JsonRandom
|
|||||||
|
|
||||||
ArtifactID loadArtifact(const JsonNode & value, CRandomGenerator & rng)
|
ArtifactID loadArtifact(const JsonNode & value, CRandomGenerator & rng)
|
||||||
{
|
{
|
||||||
if (value.getType() == JsonNode::JsonType::DATA_STRING)
|
std::set<ArtifactID> allowedArts;
|
||||||
return ArtifactID(VLC->identifiers()->getIdentifier("artifact", value).value());
|
for (auto const * artifact : VLC->arth->allowedArtifacts)
|
||||||
|
allowedArts.insert(artifact->getId());
|
||||||
|
|
||||||
std::set<CArtifact::EartClass> allowedClasses;
|
std::set<ArtifactID> potentialPicks = filterKeys(value, allowedArts);
|
||||||
std::set<ArtifactPosition> allowedPositions;
|
|
||||||
ui32 minValue = 0;
|
|
||||||
ui32 maxValue = std::numeric_limits<ui32>::max();
|
|
||||||
|
|
||||||
if (value["class"].getType() == JsonNode::JsonType::DATA_STRING)
|
return VLC->arth->pickRandomArtifact(rng, potentialPicks);
|
||||||
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());
|
|
||||||
|
|
||||||
return VLC->arth->pickRandomArtifact(rng, [=](const ArtifactID & artID) -> bool
|
|
||||||
{
|
|
||||||
CArtifact * art = VLC->arth->objects[artID];
|
|
||||||
|
|
||||||
if(!vstd::iswithin(art->getPrice(), minValue, maxValue))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if(!allowedClasses.empty() && !allowedClasses.count(art->aClass))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if(!IObjectInterface::cb->isAllowed(1, art->getIndex()))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if(!allowedPositions.empty())
|
|
||||||
{
|
|
||||||
for(const auto & pos : art->getPossibleSlots().at(ArtBearer::HERO))
|
|
||||||
{
|
|
||||||
if(allowedPositions.count(pos))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<ArtifactID> loadArtifacts(const JsonNode & value, CRandomGenerator & rng)
|
std::vector<ArtifactID> loadArtifacts(const JsonNode & value, CRandomGenerator & rng)
|
||||||
@@ -242,35 +334,19 @@ namespace JsonRandom
|
|||||||
|
|
||||||
SpellID loadSpell(const JsonNode & value, CRandomGenerator & rng, std::vector<SpellID> spells)
|
SpellID loadSpell(const JsonNode & value, CRandomGenerator & rng, std::vector<SpellID> spells)
|
||||||
{
|
{
|
||||||
if (value.getType() == JsonNode::JsonType::DATA_STRING)
|
std::set<SpellID> defaultSpells;
|
||||||
return SpellID(VLC->identifiers()->getIdentifier("spell", value).value());
|
for(const auto & spell : VLC->spellh->objects)
|
||||||
|
if (IObjectInterface::cb->isAllowed(0, spell->getIndex()))
|
||||||
|
defaultSpells.insert(spell->getId());
|
||||||
|
|
||||||
if (!value["level"].isNull())
|
std::set<SpellID> potentialPicks = filterKeys(value, defaultSpells);
|
||||||
{
|
|
||||||
int32_t spellLevel = value["level"].Float();
|
|
||||||
|
|
||||||
vstd::erase_if(spells, [=](const SpellID & spell)
|
if (potentialPicks.empty())
|
||||||
{
|
|
||||||
return VLC->spellh->getById(spell)->getLevel() != spellLevel;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!value["school"].isNull())
|
|
||||||
{
|
|
||||||
int32_t schoolID = VLC->identifiers()->getIdentifier("spellSchool", value["school"]).value();
|
|
||||||
|
|
||||||
vstd::erase_if(spells, [=](const SpellID & spell)
|
|
||||||
{
|
|
||||||
return !VLC->spellh->getById(spell)->hasSchool(SpellSchool(schoolID));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (spells.empty())
|
|
||||||
{
|
{
|
||||||
logMod->warn("Failed to select suitable random spell!");
|
logMod->warn("Failed to select suitable random spell!");
|
||||||
return SpellID::NONE;
|
return SpellID::NONE;
|
||||||
}
|
}
|
||||||
return SpellID(*RandomGeneratorUtil::nextItem(spells, rng));
|
return *RandomGeneratorUtil::nextItem(potentialPicks, rng);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<SpellID> loadSpells(const JsonNode & value, CRandomGenerator & rng, const std::vector<SpellID> & spells)
|
std::vector<SpellID> loadSpells(const JsonNode & value, CRandomGenerator & rng, const std::vector<SpellID> & spells)
|
||||||
@@ -326,7 +402,21 @@ namespace JsonRandom
|
|||||||
CStackBasicDescriptor loadCreature(const JsonNode & value, CRandomGenerator & rng)
|
CStackBasicDescriptor loadCreature(const JsonNode & value, CRandomGenerator & rng)
|
||||||
{
|
{
|
||||||
CStackBasicDescriptor stack;
|
CStackBasicDescriptor stack;
|
||||||
stack.type = VLC->creh->objects[VLC->identifiers()->getIdentifier("creature", value["type"]).value()];
|
|
||||||
|
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);
|
||||||
|
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);
|
stack.count = loadValue(value, rng);
|
||||||
if (!value["upgradeChance"].isNull() && !stack.type->upgrades.empty())
|
if (!value["upgradeChance"].isNull() && !stack.type->upgrades.empty())
|
||||||
{
|
{
|
||||||
|
@@ -32,7 +32,7 @@ namespace JsonRandom
|
|||||||
};
|
};
|
||||||
|
|
||||||
DLL_LINKAGE si32 loadValue(const JsonNode & value, CRandomGenerator & rng, si32 defaultValue = 0);
|
DLL_LINKAGE si32 loadValue(const JsonNode & value, CRandomGenerator & rng, si32 defaultValue = 0);
|
||||||
DLL_LINKAGE std::string loadKey(const JsonNode & value, CRandomGenerator & rng, const std::set<std::string> & valuesSet = {});
|
|
||||||
DLL_LINKAGE TResources loadResources(const JsonNode & value, CRandomGenerator & rng);
|
DLL_LINKAGE TResources loadResources(const JsonNode & value, CRandomGenerator & rng);
|
||||||
DLL_LINKAGE TResources loadResource(const JsonNode & value, CRandomGenerator & rng);
|
DLL_LINKAGE TResources loadResource(const JsonNode & value, CRandomGenerator & rng);
|
||||||
DLL_LINKAGE std::vector<si32> loadPrimary(const JsonNode & value, CRandomGenerator & rng);
|
DLL_LINKAGE std::vector<si32> loadPrimary(const JsonNode & value, CRandomGenerator & rng);
|
||||||
|
@@ -323,4 +323,14 @@ const ObstacleInfo * Obstacle::getInfo() const
|
|||||||
return VLC->obstacles()->getById(*this);
|
return VLC->obstacles()->getById(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string GameResID::entityType()
|
||||||
|
{
|
||||||
|
return "resource";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SecondarySkill::entityType()
|
||||||
|
{
|
||||||
|
return "secondarySkill";
|
||||||
|
}
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_END
|
VCMI_LIB_NAMESPACE_END
|
||||||
|
@@ -347,6 +347,7 @@ class SecondarySkill : public IdentifierWithEnum<SecondarySkill, SecondarySkillB
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using IdentifierWithEnum<SecondarySkill, SecondarySkillBase>::IdentifierWithEnum;
|
using IdentifierWithEnum<SecondarySkill, SecondarySkillBase>::IdentifierWithEnum;
|
||||||
|
static std::string entityType();
|
||||||
};
|
};
|
||||||
|
|
||||||
class DLL_LINKAGE FactionID : public Identifier<FactionID>
|
class DLL_LINKAGE FactionID : public Identifier<FactionID>
|
||||||
@@ -945,6 +946,8 @@ class GameResID : public IdentifierWithEnum<GameResID, GameResIDBase>
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using IdentifierWithEnum<GameResID, GameResIDBase>::IdentifierWithEnum;
|
using IdentifierWithEnum<GameResID, GameResIDBase>::IdentifierWithEnum;
|
||||||
|
|
||||||
|
static std::string entityType();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
|
Reference in New Issue
Block a user