mirror of
https://github.com/vcmi/vcmi.git
synced 2025-10-08 23:22:25 +02:00
Fixes Adela specialty that was apparently broken back in #1518 and replaced with logic that was clearly not tested - it was neither functional, nor it was following H3 behavior. - `HAS_ANOTHER_BONUS_LIMITER` now accepts `null` in place of bonus type, for cases when limiting is needed by bonus source or bonus subtype. This allows Adela Bless specialty to always work, irregardless of which bonuses are provided by Bless. - Implemented `DIVIDE_STACK_LEVEL` updater that functions same as `TIMES_STACK_LEVEL`, but it divides bonus value, instead of multiplying it (to make Adela specialty weaker for high-tier units, as in H3) - Implemented `TIMES_HERO_LEVEL_DIVIDE_STACK_LEVEL` updater that combines two existing updaters, to implement `val * heroLevel / unitLevel` formula needed for Adela specialty - Removed deprecated `ARMY_MOVEMENT` updater. Its functionality has already been removed in 1.6.X releases, and it was remaining only as a placeholder - Updated modding documentation to account for these changes & to remove some TODO's Fixed regression from #777 that could led to either duplicated bonuses or to multiple application of updaters. It introduced double-recursion - node parents were gathered recursively, and then bonuses were also collected recursively within each parent. This created situation where updater could be applied different number of times. For example, hero bonus that is propagated to unit in combat could be selected directly, or via hero->combat unit chain, or via hero->garrison unit->combat unit chains, leading to different calls to updaters if updater handles garrison unit node type
603 lines
16 KiB
C++
603 lines
16 KiB
C++
/*
|
|
* Limiters.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 "Limiters.h"
|
|
#include "Updaters.h"
|
|
|
|
#include "../GameLibrary.h"
|
|
#include "../entities/faction/CFaction.h"
|
|
#include "../entities/faction/CTownHandler.h"
|
|
#include "../spells/CSpellHandler.h"
|
|
#include "../CCreatureHandler.h"
|
|
#include "../CCreatureSet.h"
|
|
#include "../texts/CGeneralTextHandler.h"
|
|
#include "../CSkillHandler.h"
|
|
#include "../CStack.h"
|
|
#include "../CArtHandler.h"
|
|
#include "../TerrainHandler.h"
|
|
#include "../constants/StringConstants.h"
|
|
#include "../battle/BattleInfo.h"
|
|
#include "../json/JsonUtils.h"
|
|
|
|
VCMI_LIB_NAMESPACE_BEGIN
|
|
|
|
const std::map<std::string, TLimiterPtr> bonusLimiterMap =
|
|
{
|
|
{"SHOOTER_ONLY", std::make_shared<HasAnotherBonusLimiter>(BonusType::SHOOTER)},
|
|
{"DRAGON_NATURE", std::make_shared<HasAnotherBonusLimiter>(BonusType::DRAGON_NATURE)},
|
|
{"IS_UNDEAD", std::make_shared<HasAnotherBonusLimiter>(BonusType::UNDEAD)},
|
|
{"CREATURE_NATIVE_TERRAIN", std::make_shared<CreatureTerrainLimiter>()},
|
|
{"CREATURE_FACTION", std::make_shared<AllOfLimiter>(std::initializer_list<TLimiterPtr>{std::make_shared<CreatureLevelLimiter>(), std::make_shared<FactionLimiter>()})},
|
|
{"SAME_FACTION", std::make_shared<FactionLimiter>()},
|
|
{"CREATURES_ONLY", std::make_shared<CreatureLevelLimiter>()},
|
|
{"OPPOSITE_SIDE", std::make_shared<OppositeSideLimiter>()},
|
|
};
|
|
|
|
static const CStack * retrieveStackBattle(const CBonusSystemNode * node)
|
|
{
|
|
switch(node->getNodeType())
|
|
{
|
|
case CBonusSystemNode::STACK_BATTLE:
|
|
return dynamic_cast<const CStack *>(node);
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
static const CStackInstance * retrieveStackInstance(const CBonusSystemNode * node)
|
|
{
|
|
switch(node->getNodeType())
|
|
{
|
|
case CBonusSystemNode::STACK_INSTANCE:
|
|
return (dynamic_cast<const CStackInstance *>(node));
|
|
case CBonusSystemNode::STACK_BATTLE:
|
|
return (dynamic_cast<const CStack *>(node))->base;
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
static const CCreature * retrieveCreature(const CBonusSystemNode *node)
|
|
{
|
|
switch(node->getNodeType())
|
|
{
|
|
case CBonusSystemNode::CREATURE:
|
|
return (dynamic_cast<const CCreature *>(node));
|
|
case CBonusSystemNode::STACK_BATTLE:
|
|
return (dynamic_cast<const CStack *>(node))->unitType();
|
|
default:
|
|
const CStackInstance * csi = retrieveStackInstance(node);
|
|
if(csi)
|
|
return csi->getCreature();
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
ILimiter::EDecision ILimiter::limit(const BonusLimitationContext &context) const /*return true to drop the bonus */
|
|
{
|
|
return ILimiter::EDecision::ACCEPT;
|
|
}
|
|
|
|
std::string ILimiter::toString() const
|
|
{
|
|
return typeid(*this).name();
|
|
}
|
|
|
|
JsonNode ILimiter::toJsonNode() const
|
|
{
|
|
JsonNode root;
|
|
root["type"].String() = toString();
|
|
return root;
|
|
}
|
|
|
|
void ILimiter::acceptUpdater(IUpdater& visitor) {}
|
|
|
|
ILimiter::EDecision CCreatureTypeLimiter::limit(const BonusLimitationContext &context) const
|
|
{
|
|
const CCreature *c = retrieveCreature(&context.node);
|
|
if(!c)
|
|
return ILimiter::EDecision::DISCARD;
|
|
|
|
auto accept = c->getId() == creatureID || (includeUpgrades && creatureID.toCreature()->isMyUpgrade(c));
|
|
return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
|
|
//drop bonus if it's not our creature and (we don`t check upgrades or its not our upgrade)
|
|
}
|
|
|
|
CCreatureTypeLimiter::CCreatureTypeLimiter(const CCreature & creature_, bool IncludeUpgrades)
|
|
: creatureID(creature_.getId()), includeUpgrades(IncludeUpgrades)
|
|
{
|
|
}
|
|
|
|
void CCreatureTypeLimiter::setCreature(const CreatureID & id)
|
|
{
|
|
creatureID = id;
|
|
}
|
|
|
|
std::string CCreatureTypeLimiter::toString() const
|
|
{
|
|
boost::format fmt("CCreatureTypeLimiter(creature=%s, includeUpgrades=%s)");
|
|
fmt % creatureID.toEntity(LIBRARY)->getJsonKey() % (includeUpgrades ? "true" : "false");
|
|
return fmt.str();
|
|
}
|
|
|
|
JsonNode CCreatureTypeLimiter::toJsonNode() const
|
|
{
|
|
JsonNode root;
|
|
|
|
root["type"].String() = "CREATURE_TYPE_LIMITER";
|
|
root["parameters"].Vector().emplace_back(creatureID.toEntity(LIBRARY)->getJsonKey());
|
|
root["parameters"].Vector().emplace_back(includeUpgrades);
|
|
|
|
return root;
|
|
}
|
|
|
|
void CCreatureTypeLimiter::acceptUpdater(IUpdater & visitor)
|
|
{
|
|
visitor.visitLimiter(*this);
|
|
}
|
|
|
|
HasAnotherBonusLimiter::HasAnotherBonusLimiter( BonusType bonus )
|
|
: type(bonus), isSubtypeRelevant(false), isSourceRelevant(false), isSourceIDRelevant(false)
|
|
{
|
|
}
|
|
|
|
HasAnotherBonusLimiter::HasAnotherBonusLimiter( BonusType bonus, BonusSubtypeID _subtype )
|
|
: type(bonus), subtype(_subtype), isSubtypeRelevant(true), isSourceRelevant(false), isSourceIDRelevant(false)
|
|
{
|
|
}
|
|
|
|
HasAnotherBonusLimiter::HasAnotherBonusLimiter(BonusType bonus, BonusSource src)
|
|
: type(bonus), source(src), isSubtypeRelevant(false), isSourceRelevant(true), isSourceIDRelevant(false)
|
|
{
|
|
}
|
|
|
|
HasAnotherBonusLimiter::HasAnotherBonusLimiter(BonusType bonus, BonusSubtypeID _subtype, BonusSource src)
|
|
: type(bonus), subtype(_subtype), isSubtypeRelevant(true), source(src), isSourceRelevant(true), isSourceIDRelevant(false)
|
|
{
|
|
}
|
|
|
|
ILimiter::EDecision HasAnotherBonusLimiter::limit(const BonusLimitationContext &context) const
|
|
{
|
|
boost::container::static_vector<CSelector, 4> selectorSegments;
|
|
|
|
if (type != BonusType::NONE)
|
|
selectorSegments.push_back(Selector::type()(type));
|
|
if(isSubtypeRelevant)
|
|
selectorSegments.push_back(Selector::subtype()(subtype));
|
|
if(isSourceRelevant && isSourceIDRelevant)
|
|
selectorSegments.push_back(Selector::source(source, sid));
|
|
else if (isSourceRelevant)
|
|
selectorSegments.push_back(Selector::sourceTypeSel(source));
|
|
|
|
auto mySelector = selectorSegments.empty() ? Selector::none : selectorSegments[0];
|
|
|
|
for (size_t i = 1; i <selectorSegments.size(); ++i)
|
|
mySelector = mySelector.And(selectorSegments[i]);
|
|
|
|
//if we have a bonus of required type accepted, limiter should accept also this bonus
|
|
if(context.alreadyAccepted.getFirst(mySelector))
|
|
return ILimiter::EDecision::ACCEPT;
|
|
|
|
//if there are no matching bonuses pending, we can (and must) reject right away
|
|
if(!context.stillUndecided.getFirst(mySelector))
|
|
return ILimiter::EDecision::DISCARD;
|
|
|
|
//do not accept for now but it may change if more bonuses gets included
|
|
return ILimiter::EDecision::NOT_SURE;
|
|
}
|
|
|
|
std::string HasAnotherBonusLimiter::toString() const
|
|
{
|
|
std::string typeName = vstd::findKey(bonusNameMap, type);
|
|
if(isSubtypeRelevant)
|
|
{
|
|
boost::format fmt("HasAnotherBonusLimiter(type=%s, subtype=%s)");
|
|
fmt % typeName % subtype.toString();
|
|
return fmt.str();
|
|
}
|
|
else
|
|
{
|
|
boost::format fmt("HasAnotherBonusLimiter(type=%s)");
|
|
fmt % typeName;
|
|
return fmt.str();
|
|
}
|
|
}
|
|
|
|
JsonNode HasAnotherBonusLimiter::toJsonNode() const
|
|
{
|
|
JsonNode root;
|
|
std::string typeName = vstd::findKey(bonusNameMap, type);
|
|
auto sourceTypeName = vstd::findKey(bonusSourceMap, source);
|
|
|
|
root["type"].String() = "HAS_ANOTHER_BONUS_LIMITER";
|
|
root["parameters"].Vector().emplace_back(typeName);
|
|
if(isSubtypeRelevant)
|
|
root["parameters"].Vector().emplace_back(subtype.toString());
|
|
if(isSourceRelevant)
|
|
root["parameters"].Vector().emplace_back(sourceTypeName);
|
|
|
|
return root;
|
|
}
|
|
|
|
void HasAnotherBonusLimiter::acceptUpdater(IUpdater & visitor)
|
|
{
|
|
visitor.visitLimiter(*this);
|
|
}
|
|
|
|
ILimiter::EDecision UnitOnHexLimiter::limit(const BonusLimitationContext &context) const
|
|
{
|
|
const auto * stack = retrieveStackBattle(&context.node);
|
|
if(!stack)
|
|
return ILimiter::EDecision::DISCARD;
|
|
|
|
auto accept = false;
|
|
|
|
for (const auto & hex : stack->getHexes())
|
|
accept |= applicableHexes.contains(hex);
|
|
|
|
return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
|
|
}
|
|
|
|
UnitOnHexLimiter::UnitOnHexLimiter(const BattleHexArray & applicableHexes):
|
|
applicableHexes(applicableHexes)
|
|
{
|
|
}
|
|
|
|
JsonNode UnitOnHexLimiter::toJsonNode() const
|
|
{
|
|
JsonNode root;
|
|
|
|
root["type"].String() = "UNIT_ON_HEXES";
|
|
for(const auto & hex : applicableHexes)
|
|
root["parameters"].Vector().emplace_back(hex.toInt());
|
|
|
|
return root;
|
|
}
|
|
|
|
void UnitOnHexLimiter::acceptUpdater(IUpdater& visitor)
|
|
{
|
|
visitor.visitLimiter(*this);
|
|
}
|
|
|
|
|
|
CreatureTerrainLimiter::CreatureTerrainLimiter()
|
|
: terrainType(ETerrainId::NATIVE_TERRAIN)
|
|
{
|
|
}
|
|
|
|
CreatureTerrainLimiter::CreatureTerrainLimiter(TerrainId terrain):
|
|
terrainType(terrain)
|
|
{
|
|
}
|
|
|
|
ILimiter::EDecision CreatureTerrainLimiter::limit(const BonusLimitationContext &context) const
|
|
{
|
|
const CStack *stack = retrieveStackBattle(&context.node);
|
|
if(stack)
|
|
{
|
|
if (terrainType == ETerrainId::NATIVE_TERRAIN && stack->isOnNativeTerrain())//terrainType not specified = native
|
|
return ILimiter::EDecision::ACCEPT;
|
|
|
|
if(terrainType != ETerrainId::NATIVE_TERRAIN && stack->isOnTerrain(terrainType))
|
|
return ILimiter::EDecision::ACCEPT;
|
|
|
|
}
|
|
return ILimiter::EDecision::DISCARD;
|
|
//TODO neutral creatues
|
|
}
|
|
|
|
std::string CreatureTerrainLimiter::toString() const
|
|
{
|
|
boost::format fmt("CreatureTerrainLimiter(terrainType=%s)");
|
|
auto terrainName = LIBRARY->terrainTypeHandler->getById(terrainType)->getJsonKey();
|
|
fmt % (terrainType == ETerrainId::NATIVE_TERRAIN ? "native" : terrainName);
|
|
return fmt.str();
|
|
}
|
|
|
|
JsonNode CreatureTerrainLimiter::toJsonNode() const
|
|
{
|
|
JsonNode root;
|
|
|
|
root["type"].String() = "CREATURE_TERRAIN_LIMITER";
|
|
auto terrainName = LIBRARY->terrainTypeHandler->getById(terrainType)->getJsonKey();
|
|
root["parameters"].Vector().emplace_back(terrainName);
|
|
|
|
return root;
|
|
}
|
|
|
|
void CreatureTerrainLimiter::acceptUpdater(IUpdater & visitor)
|
|
{
|
|
visitor.visitLimiter(*this);
|
|
}
|
|
|
|
FactionLimiter::FactionLimiter(FactionID creatureFaction)
|
|
: faction(creatureFaction)
|
|
{
|
|
}
|
|
|
|
ILimiter::EDecision FactionLimiter::limit(const BonusLimitationContext &context) const
|
|
{
|
|
const auto * bearer = dynamic_cast<const INativeTerrainProvider*>(&context.node);
|
|
|
|
if(bearer)
|
|
{
|
|
if(faction != FactionID::DEFAULT)
|
|
return bearer->getFactionID() == faction ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
|
|
|
|
switch(context.b.source)
|
|
{
|
|
case BonusSource::CREATURE_ABILITY:
|
|
return bearer->getFactionID() == context.b.sid.as<CreatureID>().toCreature()->getFactionID() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
|
|
|
|
case BonusSource::TOWN_STRUCTURE:
|
|
return bearer->getFactionID() == context.b.sid.as<BuildingTypeUniqueID>().getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
|
|
|
|
//TODO: other sources of bonuses
|
|
}
|
|
}
|
|
return ILimiter::EDecision::DISCARD; //Discard by default
|
|
}
|
|
|
|
std::string FactionLimiter::toString() const
|
|
{
|
|
boost::format fmt("FactionLimiter(faction=%s)");
|
|
fmt % LIBRARY->factions()->getById(faction)->getJsonKey();
|
|
return fmt.str();
|
|
}
|
|
|
|
JsonNode FactionLimiter::toJsonNode() const
|
|
{
|
|
JsonNode root;
|
|
|
|
root["type"].String() = "FACTION_LIMITER";
|
|
root["parameters"].Vector().emplace_back(LIBRARY->factions()->getById(faction)->getJsonKey());
|
|
|
|
return root;
|
|
}
|
|
|
|
void FactionLimiter::acceptUpdater(IUpdater & visitor)
|
|
{
|
|
visitor.visitLimiter(*this);
|
|
}
|
|
|
|
CreatureLevelLimiter::CreatureLevelLimiter(uint32_t minLevel, uint32_t maxLevel) :
|
|
minLevel(minLevel),
|
|
maxLevel(maxLevel)
|
|
{
|
|
}
|
|
|
|
ILimiter::EDecision CreatureLevelLimiter::limit(const BonusLimitationContext &context) const
|
|
{
|
|
const auto *c = retrieveCreature(&context.node);
|
|
auto accept = c && (c->getLevel() < maxLevel && c->getLevel() >= minLevel);
|
|
return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; //drop bonus for non-creatures or non-native residents
|
|
}
|
|
|
|
std::string CreatureLevelLimiter::toString() const
|
|
{
|
|
boost::format fmt("CreatureLevelLimiter(minLevel=%d,maxLevel=%d)");
|
|
fmt % minLevel % maxLevel;
|
|
return fmt.str();
|
|
}
|
|
|
|
JsonNode CreatureLevelLimiter::toJsonNode() const
|
|
{
|
|
JsonNode root;
|
|
|
|
root["type"].String() = "CREATURE_LEVEL_LIMITER";
|
|
root["parameters"].Vector().emplace_back(minLevel);
|
|
root["parameters"].Vector().emplace_back(maxLevel);
|
|
|
|
return root;
|
|
}
|
|
|
|
void CreatureLevelLimiter::acceptUpdater(IUpdater& visitor)
|
|
{
|
|
visitor.visitLimiter(*this);
|
|
}
|
|
|
|
CreatureAlignmentLimiter::CreatureAlignmentLimiter(EAlignment Alignment)
|
|
: alignment(Alignment)
|
|
{
|
|
}
|
|
|
|
ILimiter::EDecision CreatureAlignmentLimiter::limit(const BonusLimitationContext &context) const
|
|
{
|
|
const auto * c = retrieveCreature(&context.node);
|
|
if(c) {
|
|
if(alignment == EAlignment::GOOD && c->isGood())
|
|
return ILimiter::EDecision::ACCEPT;
|
|
if(alignment == EAlignment::EVIL && c->isEvil())
|
|
return ILimiter::EDecision::ACCEPT;
|
|
if(alignment == EAlignment::NEUTRAL && !c->isEvil() && !c->isGood())
|
|
return ILimiter::EDecision::ACCEPT;
|
|
}
|
|
|
|
return ILimiter::EDecision::DISCARD;
|
|
}
|
|
|
|
std::string CreatureAlignmentLimiter::toString() const
|
|
{
|
|
boost::format fmt("CreatureAlignmentLimiter(alignment=%s)");
|
|
fmt % GameConstants::ALIGNMENT_NAMES[vstd::to_underlying(alignment)];
|
|
return fmt.str();
|
|
}
|
|
|
|
JsonNode CreatureAlignmentLimiter::toJsonNode() const
|
|
{
|
|
JsonNode root;
|
|
|
|
root["type"].String() = "CREATURE_ALIGNMENT_LIMITER";
|
|
root["parameters"].Vector().emplace_back(GameConstants::ALIGNMENT_NAMES[vstd::to_underlying(alignment)]);
|
|
|
|
return root;
|
|
}
|
|
|
|
void CreatureAlignmentLimiter::acceptUpdater(IUpdater & visitor)
|
|
{
|
|
visitor.visitLimiter(*this);
|
|
}
|
|
|
|
RankRangeLimiter::RankRangeLimiter(ui8 Min, ui8 Max)
|
|
:minRank(Min), maxRank(Max)
|
|
{
|
|
}
|
|
|
|
RankRangeLimiter::RankRangeLimiter()
|
|
{
|
|
minRank = maxRank = -1;
|
|
}
|
|
|
|
ILimiter::EDecision RankRangeLimiter::limit(const BonusLimitationContext &context) const
|
|
{
|
|
const CStackInstance * csi = retrieveStackInstance(&context.node);
|
|
if(csi)
|
|
{
|
|
if (csi->getNodeType() == CBonusSystemNode::COMMANDER) //no stack exp bonuses for commander creatures
|
|
return ILimiter::EDecision::DISCARD;
|
|
if (csi->getExpRank() > minRank && csi->getExpRank() < maxRank)
|
|
return ILimiter::EDecision::ACCEPT;
|
|
}
|
|
return ILimiter::EDecision::DISCARD;
|
|
}
|
|
|
|
void RankRangeLimiter::acceptUpdater(IUpdater & visitor)
|
|
{
|
|
visitor.visitLimiter(*this);
|
|
}
|
|
|
|
OppositeSideLimiter::OppositeSideLimiter(PlayerColor Owner):
|
|
owner(std::move(Owner))
|
|
{
|
|
}
|
|
|
|
ILimiter::EDecision OppositeSideLimiter::limit(const BonusLimitationContext & context) const
|
|
{
|
|
auto contextOwner = context.node.getOwner();
|
|
auto decision = (owner == contextOwner || owner == PlayerColor::CANNOT_DETERMINE) ? ILimiter::EDecision::DISCARD : ILimiter::EDecision::ACCEPT;
|
|
return decision;
|
|
}
|
|
|
|
void OppositeSideLimiter::acceptUpdater(IUpdater & visitor)
|
|
{
|
|
visitor.visitLimiter(*this);
|
|
}
|
|
|
|
// Aggregate/Boolean Limiters
|
|
|
|
AggregateLimiter::AggregateLimiter(std::vector<TLimiterPtr> limiters):
|
|
limiters(std::move(limiters))
|
|
{
|
|
}
|
|
|
|
void AggregateLimiter::add(const TLimiterPtr & limiter)
|
|
{
|
|
if(limiter)
|
|
limiters.push_back(limiter);
|
|
}
|
|
|
|
JsonNode AggregateLimiter::toJsonNode() const
|
|
{
|
|
JsonNode result;
|
|
result.Vector().emplace_back(getAggregator());
|
|
for(const auto & l : limiters)
|
|
result.Vector().push_back(l->toJsonNode());
|
|
return result;
|
|
}
|
|
|
|
void AggregateLimiter::acceptUpdater(IUpdater & visitor)
|
|
{
|
|
visitor.visitLimiter(*this);
|
|
}
|
|
|
|
const std::string AllOfLimiter::aggregator = "allOf";
|
|
const std::string & AllOfLimiter::getAggregator() const
|
|
{
|
|
return aggregator;
|
|
}
|
|
|
|
AllOfLimiter::AllOfLimiter(std::vector<TLimiterPtr> limiters):
|
|
AggregateLimiter(limiters)
|
|
{
|
|
}
|
|
|
|
ILimiter::EDecision AllOfLimiter::limit(const BonusLimitationContext & context) const
|
|
{
|
|
bool wasntSure = false;
|
|
|
|
for(const auto & limiter : limiters)
|
|
{
|
|
auto result = limiter->limit(context);
|
|
if(result == ILimiter::EDecision::DISCARD)
|
|
return result;
|
|
if(result == ILimiter::EDecision::NOT_SURE)
|
|
wasntSure = true;
|
|
}
|
|
|
|
return wasntSure ? ILimiter::EDecision::NOT_SURE : ILimiter::EDecision::ACCEPT;
|
|
}
|
|
|
|
const std::string AnyOfLimiter::aggregator = "anyOf";
|
|
const std::string & AnyOfLimiter::getAggregator() const
|
|
{
|
|
return aggregator;
|
|
}
|
|
|
|
AnyOfLimiter::AnyOfLimiter(std::vector<TLimiterPtr> limiters):
|
|
AggregateLimiter(limiters)
|
|
{
|
|
}
|
|
|
|
ILimiter::EDecision AnyOfLimiter::limit(const BonusLimitationContext & context) const
|
|
{
|
|
bool wasntSure = false;
|
|
|
|
for(const auto & limiter : limiters)
|
|
{
|
|
auto result = limiter->limit(context);
|
|
if(result == ILimiter::EDecision::ACCEPT)
|
|
return result;
|
|
if(result == ILimiter::EDecision::NOT_SURE)
|
|
wasntSure = true;
|
|
}
|
|
|
|
return wasntSure ? ILimiter::EDecision::NOT_SURE : ILimiter::EDecision::DISCARD;
|
|
}
|
|
|
|
const std::string NoneOfLimiter::aggregator = "noneOf";
|
|
const std::string & NoneOfLimiter::getAggregator() const
|
|
{
|
|
return aggregator;
|
|
}
|
|
|
|
NoneOfLimiter::NoneOfLimiter(std::vector<TLimiterPtr> limiters):
|
|
AggregateLimiter(limiters)
|
|
{
|
|
}
|
|
|
|
ILimiter::EDecision NoneOfLimiter::limit(const BonusLimitationContext & context) const
|
|
{
|
|
bool wasntSure = false;
|
|
|
|
for(const auto & limiter : limiters)
|
|
{
|
|
auto result = limiter->limit(context);
|
|
if(result == ILimiter::EDecision::ACCEPT)
|
|
return ILimiter::EDecision::DISCARD;
|
|
if(result == ILimiter::EDecision::NOT_SURE)
|
|
wasntSure = true;
|
|
}
|
|
|
|
return wasntSure ? ILimiter::EDecision::NOT_SURE : ILimiter::EDecision::ACCEPT;
|
|
}
|
|
|
|
VCMI_LIB_NAMESPACE_END
|