mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-24 22:14:36 +02:00
Enable Limiter nesting with AllOf, AnyOf, NoneOf (#439)
* Renamed LimiterList to AllOfLimiter and added AnyOfLimiter, NoneOfLimiter * Updated bonus schema to new limiter format
This commit is contained in:
parent
6ddcb079a4
commit
82f334b503
@ -28,6 +28,7 @@ MODS:
|
||||
* Added support for modding of original secondary skills and creation of new ones.
|
||||
* Map object sounds can now be configured via json
|
||||
* Added bonus updaters for hero specialties
|
||||
* Added allOf, anyOf and noneOf qualifiers for bonus limiters
|
||||
|
||||
SOUND:
|
||||
* Fixed many mising or wrong pickup and visit sounds for map objects
|
||||
|
@ -5,6 +5,40 @@
|
||||
"description" : "Subsection of several formats, used to add generic bonuses to objects",
|
||||
"required": ["type"],
|
||||
|
||||
"definitions" :
|
||||
{
|
||||
"nestedLimiter" : {
|
||||
"anyOf" : [
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "parameterless limiter or boolean operator at start of array"
|
||||
},
|
||||
{
|
||||
"type" : "object",
|
||||
"additionalProperties" : false,
|
||||
"properties" : {
|
||||
"type" : {
|
||||
"type" : "string",
|
||||
"description" : "type"
|
||||
},
|
||||
"parameters" : {
|
||||
"type" : "array",
|
||||
"description" : "parameters",
|
||||
"additionalItems" : true
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
"type" : "array",
|
||||
"additionalItems" : {
|
||||
"$ref" : "#/definitions/nestedLimiter",
|
||||
"description" : "nested limiters optionally prefixed with boolean operator"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties" : false,
|
||||
"properties":{
|
||||
"addInfo": {
|
||||
@ -38,31 +72,9 @@
|
||||
"type":"string",
|
||||
"description": "effectRange"
|
||||
},
|
||||
"limiters": {
|
||||
"type":"array",
|
||||
"description": "limiters",
|
||||
"items": {
|
||||
"oneOf" : [
|
||||
{
|
||||
"type":"object",
|
||||
"additionalProperties" : false,
|
||||
"properties" : {
|
||||
"parameters": {
|
||||
"type":"array",
|
||||
"description" : "parameters",
|
||||
"additionalItems": true
|
||||
},
|
||||
"type": {
|
||||
"type":"string",
|
||||
"description": "type"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type" : "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
"limiters" : {
|
||||
"$ref" : "#/definitions/nestedLimiter",
|
||||
"description" : "limiter"
|
||||
},
|
||||
"propagator": {
|
||||
"description": "propagator",
|
||||
|
@ -1171,7 +1171,7 @@ void CBonusSystemNode::limitBonuses(const BonusList &allBonuses, BonusList &out)
|
||||
for(int i = 0; i < undecided.size(); i++)
|
||||
{
|
||||
auto b = undecided[i];
|
||||
BonusLimitationContext context = {b, *this, out};
|
||||
BonusLimitationContext context = {b, *this, out, undecided};
|
||||
int decision = b->limiter ? b->limiter->limit(context) : ILimiter::ACCEPT; //bonuses without limiters will be accepted by default
|
||||
if(decision == ILimiter::DISCARD)
|
||||
{
|
||||
@ -1347,7 +1347,7 @@ JsonNode Bonus::toJsonNode() const
|
||||
if(turnsRemain)
|
||||
root["turns"].Integer() = turnsRemain;
|
||||
if(limiter)
|
||||
root["limiters"].Vector().push_back(limiter->toJsonNode());
|
||||
root["limiters"] = limiter->toJsonNode();
|
||||
if(updater)
|
||||
root["updater"] = updater->toJsonNode();
|
||||
if(propagator)
|
||||
@ -1557,11 +1557,11 @@ std::shared_ptr<Bonus> Bonus::addLimiter(TLimiterPtr Limiter)
|
||||
if (limiter)
|
||||
{
|
||||
//If we already have limiter list, retrieve it
|
||||
auto limiterList = std::dynamic_pointer_cast<LimiterList>(limiter);
|
||||
auto limiterList = std::dynamic_pointer_cast<AllOfLimiter>(limiter);
|
||||
if(!limiterList)
|
||||
{
|
||||
//Create a new limiter list with old limiter and the new one will be pushed later
|
||||
limiterList = std::make_shared<LimiterList>();
|
||||
limiterList = std::make_shared<AllOfLimiter>();
|
||||
limiterList->add(limiter);
|
||||
limiter = limiterList;
|
||||
}
|
||||
@ -1661,6 +1661,10 @@ int HasAnotherBonusLimiter::limit(const BonusLimitationContext &context) const
|
||||
if(context.alreadyAccepted.getFirst(mySelector))
|
||||
return ACCEPT;
|
||||
|
||||
//if there are no matching bonuses pending, we can (and must) reject right away
|
||||
if(!context.stillUndecided.getFirst(mySelector))
|
||||
return DISCARD;
|
||||
|
||||
//do not accept for now but it may change if more bonuses gets included
|
||||
return NOT_SURE;
|
||||
}
|
||||
@ -1824,7 +1828,30 @@ StackOwnerLimiter::StackOwnerLimiter(PlayerColor Owner)
|
||||
{
|
||||
}
|
||||
|
||||
int LimiterList::limit( const BonusLimitationContext &context ) const
|
||||
// Aggregate/Boolean Limiters
|
||||
|
||||
void AggregateLimiter::add(TLimiterPtr limiter)
|
||||
{
|
||||
if(limiter)
|
||||
limiters.push_back(limiter);
|
||||
}
|
||||
|
||||
JsonNode AggregateLimiter::toJsonNode() const
|
||||
{
|
||||
JsonNode result(JsonNode::JsonType::DATA_VECTOR);
|
||||
result.Vector().push_back(JsonUtils::stringNode(getAggregator()));
|
||||
for(auto l : limiters)
|
||||
result.Vector().push_back(l->toJsonNode());
|
||||
return result;
|
||||
}
|
||||
|
||||
const std::string AllOfLimiter::aggregator = "allOf";
|
||||
const std::string & AllOfLimiter::getAggregator() const
|
||||
{
|
||||
return aggregator;
|
||||
}
|
||||
|
||||
int AllOfLimiter::limit(const BonusLimitationContext & context) const
|
||||
{
|
||||
bool wasntSure = false;
|
||||
|
||||
@ -1840,9 +1867,48 @@ int LimiterList::limit( const BonusLimitationContext &context ) const
|
||||
return wasntSure ? ILimiter::NOT_SURE : ILimiter::ACCEPT;
|
||||
}
|
||||
|
||||
void LimiterList::add( TLimiterPtr limiter )
|
||||
const std::string AnyOfLimiter::aggregator = "anyOf";
|
||||
const std::string & AnyOfLimiter::getAggregator() const
|
||||
{
|
||||
limiters.push_back(limiter);
|
||||
return aggregator;
|
||||
}
|
||||
|
||||
int AnyOfLimiter::limit(const BonusLimitationContext & context) const
|
||||
{
|
||||
bool wasntSure = false;
|
||||
|
||||
for(auto limiter : limiters)
|
||||
{
|
||||
auto result = limiter->limit(context);
|
||||
if(result == ILimiter::ACCEPT)
|
||||
return result;
|
||||
if(result == ILimiter::NOT_SURE)
|
||||
wasntSure = true;
|
||||
}
|
||||
|
||||
return wasntSure ? ILimiter::NOT_SURE : ILimiter::DISCARD;
|
||||
}
|
||||
|
||||
const std::string NoneOfLimiter::aggregator = "noneOf";
|
||||
const std::string & NoneOfLimiter::getAggregator() const
|
||||
{
|
||||
return aggregator;
|
||||
}
|
||||
|
||||
int NoneOfLimiter::limit(const BonusLimitationContext & context) const
|
||||
{
|
||||
bool wasntSure = false;
|
||||
|
||||
for(auto limiter : limiters)
|
||||
{
|
||||
auto result = limiter->limit(context);
|
||||
if(result == ILimiter::ACCEPT)
|
||||
return ILimiter::DISCARD;
|
||||
if(result == ILimiter::NOT_SURE)
|
||||
wasntSure = true;
|
||||
}
|
||||
|
||||
return wasntSure ? ILimiter::NOT_SURE : ILimiter::ACCEPT;
|
||||
}
|
||||
|
||||
// Updaters
|
||||
|
@ -615,8 +615,9 @@ public:
|
||||
struct BonusLimitationContext
|
||||
{
|
||||
const std::shared_ptr<Bonus> b;
|
||||
const CBonusSystemNode &node;
|
||||
const BonusList &alreadyAccepted;
|
||||
const CBonusSystemNode & node;
|
||||
const BonusList & alreadyAccepted;
|
||||
const BonusList & stillUndecided;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE ILimiter
|
||||
@ -873,14 +874,50 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
//Stores multiple limiters. If any of them fails -> bonus is dropped.
|
||||
class DLL_LINKAGE LimiterList : public ILimiter
|
||||
class DLL_LINKAGE AggregateLimiter : public ILimiter
|
||||
{
|
||||
protected:
|
||||
std::vector<TLimiterPtr> limiters;
|
||||
|
||||
virtual const std::string & getAggregator() const = 0;
|
||||
public:
|
||||
int limit(const BonusLimitationContext &context) const override;
|
||||
void add(TLimiterPtr limiter);
|
||||
JsonNode toJsonNode() const override;
|
||||
|
||||
template <typename Handler> void serialize(Handler & h, const int version)
|
||||
{
|
||||
h & static_cast<ILimiter&>(*this);
|
||||
if(version >= 786)
|
||||
{
|
||||
h & limiters;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class DLL_LINKAGE AllOfLimiter : public AggregateLimiter
|
||||
{
|
||||
protected:
|
||||
const std::string & getAggregator() const override;
|
||||
public:
|
||||
static const std::string aggregator;
|
||||
int limit(const BonusLimitationContext & context) const override;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE AnyOfLimiter : public AggregateLimiter
|
||||
{
|
||||
protected:
|
||||
const std::string & getAggregator() const override;
|
||||
public:
|
||||
static const std::string aggregator;
|
||||
int limit(const BonusLimitationContext & context) const override;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE NoneOfLimiter : public AggregateLimiter
|
||||
{
|
||||
protected:
|
||||
const std::string & getAggregator() const override;
|
||||
public:
|
||||
static const std::string aggregator;
|
||||
int limit(const BonusLimitationContext & context) const override;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE CCreatureTypeLimiter : public ILimiter //affect only stacks of given creature (and optionally it's upgrades)
|
||||
|
146
lib/JsonNode.cpp
146
lib/JsonNode.cpp
@ -567,6 +567,96 @@ void JsonUtils::resolveIdentifier(const JsonNode &node, si32 &var)
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<ILimiter> JsonUtils::parseLimiter(const JsonNode & limiter)
|
||||
{
|
||||
switch(limiter.getType())
|
||||
{
|
||||
case JsonNode::JsonType::DATA_VECTOR:
|
||||
{
|
||||
const JsonVector & subLimiters = limiter.Vector();
|
||||
if(subLimiters.size() == 0)
|
||||
{
|
||||
logMod->warn("Warning: empty limiter list");
|
||||
return std::make_shared<AllOfLimiter>();
|
||||
}
|
||||
std::shared_ptr<AggregateLimiter> result;
|
||||
int offset = 1;
|
||||
// determine limiter type and offset for sub-limiters
|
||||
if(subLimiters[0].getType() == JsonNode::JsonType::DATA_STRING)
|
||||
{
|
||||
const std::string & aggregator = subLimiters[0].String();
|
||||
if(aggregator == AllOfLimiter::aggregator)
|
||||
result = std::make_shared<AllOfLimiter>();
|
||||
else if(aggregator == AnyOfLimiter::aggregator)
|
||||
result = std::make_shared<AnyOfLimiter>();
|
||||
else if(aggregator == NoneOfLimiter::aggregator)
|
||||
result = std::make_shared<NoneOfLimiter>();
|
||||
}
|
||||
if(!result)
|
||||
{
|
||||
// collapse for single limiter without explicit aggregate operator
|
||||
if(subLimiters.size() == 1)
|
||||
return parseLimiter(subLimiters[0]);
|
||||
// implicit aggregator must be allOf
|
||||
result = std::make_shared<AllOfLimiter>();
|
||||
offset = 0;
|
||||
}
|
||||
if(subLimiters.size() == offset)
|
||||
logMod->warn("Warning: empty sub-limiter list");
|
||||
for(int sl = offset; sl < subLimiters.size(); ++sl)
|
||||
result->add(parseLimiter(subLimiters[sl]));
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
case JsonNode::JsonType::DATA_STRING: //pre-defined limiters
|
||||
return parseByMap(bonusLimiterMap, &limiter, "limiter type ");
|
||||
break;
|
||||
case JsonNode::JsonType::DATA_STRUCT: //customizable limiters
|
||||
{
|
||||
std::string limiterType = limiter["type"].String();
|
||||
const JsonVector & parameters = limiter["parameters"].Vector();
|
||||
if(limiterType == "CREATURE_TYPE_LIMITER")
|
||||
{
|
||||
std::shared_ptr<CCreatureTypeLimiter> creatureLimiter = std::make_shared<CCreatureTypeLimiter>();
|
||||
VLC->modh->identifiers.requestIdentifier("creature", parameters[0], [=](si32 creature)
|
||||
{
|
||||
creatureLimiter->setCreature(CreatureID(creature));
|
||||
});
|
||||
creatureLimiter->includeUpgrades = parameters.size() > 1 ? parameters[1].Bool() : false;
|
||||
return creatureLimiter;
|
||||
}
|
||||
else if(limiterType == "HAS_ANOTHER_BONUS_LIMITER")
|
||||
{
|
||||
std::string anotherBonusType = parameters[0].String();
|
||||
auto it = bonusNameMap.find(anotherBonusType);
|
||||
if(it == bonusNameMap.end())
|
||||
{
|
||||
logMod->error("Error: invalid ability type %s.", anotherBonusType);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::shared_ptr<HasAnotherBonusLimiter> bonusLimiter = std::make_shared<HasAnotherBonusLimiter>();
|
||||
bonusLimiter->type = it->second;
|
||||
if(parameters.size() > 1)
|
||||
{
|
||||
resolveIdentifier(parameters[1], bonusLimiter->subtype);
|
||||
bonusLimiter->isSubtypeRelevant = true;
|
||||
}
|
||||
return bonusLimiter;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logMod->error("Error: invalid customizable limiter type %s.", limiterType);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<Bonus> JsonUtils::parseBonus(const JsonNode &ability)
|
||||
{
|
||||
auto b = std::make_shared<Bonus>();
|
||||
@ -641,61 +731,7 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
|
||||
|
||||
value = &ability["limiters"];
|
||||
if (!value->isNull())
|
||||
{
|
||||
for (const JsonNode & limiter : value->Vector())
|
||||
{
|
||||
switch (limiter.getType())
|
||||
{
|
||||
case JsonNode::JsonType::DATA_STRING: //pre-defined limiters
|
||||
b->limiter = parseByMap(bonusLimiterMap, &limiter, "limiter type ");
|
||||
break;
|
||||
case JsonNode::JsonType::DATA_STRUCT: //customizable limiters
|
||||
{
|
||||
std::shared_ptr<ILimiter> l;
|
||||
if (limiter["type"].String() == "CREATURE_TYPE_LIMITER")
|
||||
{
|
||||
std::shared_ptr<CCreatureTypeLimiter> l2 = std::make_shared<CCreatureTypeLimiter>(); //TODO: How the hell resolve pointer to creature?
|
||||
const JsonVector vec = limiter["parameters"].Vector();
|
||||
VLC->modh->identifiers.requestIdentifier("creature", vec[0], [=](si32 creature)
|
||||
{
|
||||
l2->setCreature(CreatureID(creature));
|
||||
});
|
||||
if (vec.size() > 1)
|
||||
{
|
||||
l2->includeUpgrades = vec[1].Bool();
|
||||
}
|
||||
else
|
||||
l2->includeUpgrades = false;
|
||||
|
||||
l = l2;
|
||||
}
|
||||
if (limiter["type"].String() == "HAS_ANOTHER_BONUS_LIMITER")
|
||||
{
|
||||
std::shared_ptr<HasAnotherBonusLimiter> l2 = std::make_shared<HasAnotherBonusLimiter>();
|
||||
const JsonVector vec = limiter["parameters"].Vector();
|
||||
std::string anotherBonusType = vec[0].String();
|
||||
|
||||
auto it = bonusNameMap.find(anotherBonusType);
|
||||
if (it == bonusNameMap.end())
|
||||
{
|
||||
logMod->error("Error: invalid ability type %s.", anotherBonusType);
|
||||
continue;
|
||||
}
|
||||
l2->type = it->second;
|
||||
|
||||
if (vec.size() > 1 )
|
||||
{
|
||||
resolveIdentifier(vec[1], l2->subtype);
|
||||
l2->isSubtypeRelevant = true;
|
||||
}
|
||||
l = l2;
|
||||
}
|
||||
b->addLimiter(l);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
b->limiter = parseLimiter(*value);
|
||||
|
||||
value = &ability["propagator"];
|
||||
if (!value->isNull())
|
||||
|
@ -16,6 +16,7 @@ typedef std::vector <JsonNode> JsonVector;
|
||||
struct Bonus;
|
||||
class ResourceID;
|
||||
class CAddInfo;
|
||||
class ILimiter;
|
||||
|
||||
class DLL_LINKAGE JsonNode
|
||||
{
|
||||
@ -168,6 +169,7 @@ namespace JsonUtils
|
||||
DLL_LINKAGE std::shared_ptr<Bonus> parseBonus(const JsonVector &ability_vec);
|
||||
DLL_LINKAGE std::shared_ptr<Bonus> parseBonus(const JsonNode &ability);
|
||||
DLL_LINKAGE bool parseBonus(const JsonNode &ability, Bonus *placement);
|
||||
DLL_LINKAGE std::shared_ptr<ILimiter> parseLimiter(const JsonNode & limiter);
|
||||
DLL_LINKAGE void resolveIdentifier(si32 &var, const JsonNode &node, std::string name);
|
||||
DLL_LINKAGE void resolveIdentifier(const JsonNode &node, si32 &var);
|
||||
DLL_LINKAGE void resolveAddInfo(CAddInfo & var, const JsonNode & node);
|
||||
|
@ -139,6 +139,9 @@ void registerTypesMapObjectTypes(Serializer &s)
|
||||
s.template registerType<IUpdater, GrowsWithLevelUpdater>();
|
||||
s.template registerType<IUpdater, TimesHeroLevelUpdater>();
|
||||
s.template registerType<IUpdater, TimesStackLevelUpdater>();
|
||||
|
||||
s.template registerType<ILimiter, AnyOfLimiter>();
|
||||
s.template registerType<ILimiter, NoneOfLimiter>();
|
||||
//new types (other than netpacks) must register here
|
||||
//order of type registration is critical for loading old savegames
|
||||
}
|
||||
@ -178,7 +181,7 @@ void registerTypesMapObjects2(Serializer &s)
|
||||
|
||||
// Limiters
|
||||
//s.template registerType<ILimiter>();
|
||||
s.template registerType<ILimiter, LimiterList>();
|
||||
s.template registerType<ILimiter, AllOfLimiter>();
|
||||
s.template registerType<ILimiter, CCreatureTypeLimiter>();
|
||||
s.template registerType<ILimiter, HasAnotherBonusLimiter>();
|
||||
s.template registerType<ILimiter, CreatureNativeTerrainLimiter>();
|
||||
|
@ -12,7 +12,7 @@
|
||||
#include "../ConstTransitivePtr.h"
|
||||
#include "../GameConstants.h"
|
||||
|
||||
const ui32 SERIALIZATION_VERSION = 785;
|
||||
const ui32 SERIALIZATION_VERSION = 786;
|
||||
const ui32 MINIMAL_SERIALIZATION_VERSION = 753;
|
||||
const std::string SAVEGAME_MAGIC = "VCMISVG";
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user