1
0
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:
Henning Koehler 2018-04-01 23:17:34 +12:00 committed by ArseniyShestakov
parent 6ddcb079a4
commit 82f334b503
8 changed files with 252 additions and 95 deletions

View File

@ -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

View File

@ -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",

View File

@ -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

View File

@ -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)

View File

@ -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())

View File

@ -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);

View File

@ -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>();

View File

@ -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";