2
0
Эх сурвалжийг харах

vcmi: move limiters outside of HeroBonus.cpp

This will help for recompilation.
Konstantin 2 жил өмнө
parent
commit
416faf521e

+ 1 - 0
AI/VCAI/VCAI.cpp

@@ -22,6 +22,7 @@
 #include "../../lib/CGameState.h"
 #include "../../lib/NetPacksBase.h"
 #include "../../lib/NetPacks.h"
+#include "../../lib/bonuses/ILimiter.h"
 #include "../../lib/serializer/CTypeList.h"
 #include "../../lib/serializer/BinarySerializer.h"
 #include "../../lib/serializer/BinaryDeserializer.h"

+ 1 - 0
client/CPlayerInterface.cpp

@@ -43,6 +43,7 @@
 #include "../lib/CArtHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CHeroHandler.h"
+#include "../lib/bonuses/ILimiter.h"
 #include "../lib/serializer/CTypeList.h"
 #include "../lib/serializer/BinaryDeserializer.h"
 #include "../lib/serializer/BinarySerializer.h"

+ 2 - 0
cmake_modules/VCMI_lib.cmake

@@ -28,6 +28,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/battle/Unit.cpp
 
 		${MAIN_LIB_DIR}/bonuses/HeroBonus.cpp
+		${MAIN_LIB_DIR}/bonuses/ILimiter.cpp
 
 		${MAIN_LIB_DIR}/events/ApplyDamage.cpp
 		${MAIN_LIB_DIR}/events/GameResumed.cpp
@@ -302,6 +303,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/battle/Unit.h
 
 		${MAIN_LIB_DIR}/bonuses/HeroBonus.h
+		${MAIN_LIB_DIR}/bonuses/ILimiter.h
 
 		${MAIN_LIB_DIR}/events/ApplyDamage.h
 		${MAIN_LIB_DIR}/events/GameResumed.h

+ 1 - 0
lib/CCreatureHandler.cpp

@@ -19,6 +19,7 @@
 #include "CModHandler.h"
 #include "GameSettings.h"
 #include "StringConstants.h"
+#include "bonuses/ILimiter.h"
 #include "serializer/JsonDeserializer.h"
 #include "serializer/JsonUpdater.h"
 #include "mapObjects/CObjectClassesHandler.h"

+ 1 - 0
lib/CHeroHandler.cpp

@@ -24,6 +24,7 @@
 #include "CSkillHandler.h"
 #include "mapObjects/CObjectClassesHandler.h"
 #include "BattleFieldHandler.h"
+#include "bonuses/ILimiter.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 0
lib/IGameCallback.cpp

@@ -18,6 +18,7 @@
 #include "CModHandler.h"
 #include "BattleFieldHandler.h"
 #include "ObstacleHandler.h"
+#include "bonuses/ILimiter.h"
 
 #include "serializer/CSerializer.h" // for SAVEGAME_MAGIC
 #include "serializer/BinaryDeserializer.h"

+ 1 - 0
lib/JsonNode.cpp

@@ -14,6 +14,7 @@
 #include "ScopeGuard.h"
 
 #include "bonuses/HeroBonus.h"
+#include "bonuses/ILimiter.h"
 #include "filesystem/Filesystem.h"
 #include "VCMI_Lib.h" //for identifier resolution
 #include "CModHandler.h"

+ 1 - 0
lib/battle/BattleInfo.cpp

@@ -9,6 +9,7 @@
  */
 #include "StdInc.h"
 #include "BattleInfo.h"
+#include "../bonuses/ILimiter.h"
 #include "../CStack.h"
 #include "../CHeroHandler.h"
 #include "../NetPacks.h"

+ 17 - 526
lib/bonuses/HeroBonus.cpp

@@ -10,21 +10,22 @@
 
 #include "StdInc.h"
 #include "HeroBonus.h"
-
-#include "VCMI_Lib.h"
-#include "spells/CSpellHandler.h"
-#include "CCreatureHandler.h"
-#include "CCreatureSet.h"
-#include "CHeroHandler.h"
-#include "CTownHandler.h"
-#include "CGeneralTextHandler.h"
-#include "CSkillHandler.h"
-#include "CStack.h"
-#include "CArtHandler.h"
-#include "CModHandler.h"
-#include "TerrainHandler.h"
-#include "StringConstants.h"
-#include "battle/BattleInfo.h"
+#include "ILimiter.h"
+
+#include "../VCMI_Lib.h"
+#include "../spells/CSpellHandler.h"
+#include "../CCreatureHandler.h"
+#include "../CCreatureSet.h"
+#include "../CHeroHandler.h"
+#include "../CTownHandler.h"
+#include "../CGeneralTextHandler.h"
+#include "../CSkillHandler.h"
+#include "../CStack.h"
+#include "../CArtHandler.h"
+#include "../CModHandler.h"
+#include "../TerrainHandler.h"
+#include "../StringConstants.h"
+#include "../battle/BattleInfo.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -69,18 +70,6 @@ const std::map<std::string, Bonus::LimitEffect> bonusLimitEffect =
 	BONUS_ITEM(ONLY_MELEE_FIGHT)
 };
 
-const std::map<std::string, TLimiterPtr> bonusLimiterMap =
-{
-	{"SHOOTER_ONLY", std::make_shared<HasAnotherBonusLimiter>(Bonus::SHOOTER)},
-	{"DRAGON_NATURE", std::make_shared<HasAnotherBonusLimiter>(Bonus::DRAGON_NATURE)},
-	{"IS_UNDEAD", std::make_shared<HasAnotherBonusLimiter>(Bonus::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>()},
-};
-
 const std::map<std::string, TPropagatorPtr> bonusPropagatorMap =
 {
 	{"BATTLE_WIDE", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::BATTLE)},
@@ -750,30 +739,6 @@ std::shared_ptr<const Bonus> IBonusBearer::getBonus(const CSelector &selector) c
 	return bonuses->getFirst(Selector::all);
 }
 
-const CStack * retrieveStackBattle(const CBonusSystemNode * node)
-{
-	switch(node->getNodeType())
-	{
-	case CBonusSystemNode::STACK_BATTLE:
-		return dynamic_cast<const CStack *>(node);
-	default:
-		return nullptr;
-	}
-}
-
-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;
-	}
-}
-
 PlayerColor CBonusSystemNode::retrieveNodeOwner(const CBonusSystemNode * node)
 {
 	return node ? node->getOwner() : PlayerColor::CANNOT_DETERMINE;
@@ -1398,7 +1363,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, undecided};
+			BonusLimitationContext context = {*b, *this, out, undecided};
 			auto decision = b->limiter ? b->limiter->limit(context) : ILimiter::EDecision::ACCEPT; //bonuses without limiters will be accepted by default
 			if(decision == ILimiter::EDecision::DISCARD)
 			{
@@ -1957,22 +1922,6 @@ namespace Selector
 	DLL_LINKAGE CSelector none([](const Bonus * b){return false;});
 }
 
-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->type;
-		return nullptr;
-	}
-}
-
 DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const BonusList &bonusList)
 {
 	for (ui32 i = 0; i < bonusList.size(); i++)
@@ -2035,168 +1984,6 @@ std::shared_ptr<Bonus> Bonus::addLimiter(const TLimiterPtr & Limiter)
 	return this->shared_from_this();
 }
 
-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(JsonNode::JsonType::DATA_STRUCT);
-	root["type"].String() = toString();
-	return root;
-}
-
-ILimiter::EDecision CCreatureTypeLimiter::limit(const BonusLimitationContext &context) const
-{
-	const CCreature *c = retrieveCreature(&context.node);
-	if(!c)
-		return ILimiter::EDecision::DISCARD;
-	
-	auto accept =  c->getId() == creature->getId() || (includeUpgrades && creature->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)
-	: creature(&creature_), includeUpgrades(IncludeUpgrades)
-{
-}
-
-void CCreatureTypeLimiter::setCreature(const CreatureID & id)
-{
-	creature = VLC->creh->objects[id];
-}
-
-std::string CCreatureTypeLimiter::toString() const
-{
-	boost::format fmt("CCreatureTypeLimiter(creature=%s, includeUpgrades=%s)");
-	fmt % creature->getJsonKey() % (includeUpgrades ? "true" : "false");
-	return fmt.str();
-}
-
-JsonNode CCreatureTypeLimiter::toJsonNode() const
-{
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
-
-	root["type"].String() = "CREATURE_TYPE_LIMITER";
-	root["parameters"].Vector().push_back(JsonUtils::stringNode(creature->getJsonKey()));
-	root["parameters"].Vector().push_back(JsonUtils::boolNode(includeUpgrades));
-
-	return root;
-}
-
-HasAnotherBonusLimiter::HasAnotherBonusLimiter( Bonus::BonusType bonus )
-	: type(bonus), subtype(0), isSubtypeRelevant(false), isSourceRelevant(false), isSourceIDRelevant(false)
-{
-}
-
-HasAnotherBonusLimiter::HasAnotherBonusLimiter( Bonus::BonusType bonus, TBonusSubtype _subtype )
-	: type(bonus), subtype(_subtype), isSubtypeRelevant(true), isSourceRelevant(false), isSourceIDRelevant(false)
-{
-}
-
-HasAnotherBonusLimiter::HasAnotherBonusLimiter(Bonus::BonusType bonus, Bonus::BonusSource src)
-	: type(bonus), source(src), isSubtypeRelevant(false), isSourceRelevant(true), isSourceIDRelevant(false)
-{
-}
-
-HasAnotherBonusLimiter::HasAnotherBonusLimiter(Bonus::BonusType bonus, TBonusSubtype _subtype, Bonus::BonusSource src)
-	: type(bonus), subtype(_subtype), isSubtypeRelevant(true), source(src), isSourceRelevant(true), isSourceIDRelevant(false)
-{
-}
-
-ILimiter::EDecision HasAnotherBonusLimiter::limit(const BonusLimitationContext &context) const
-{
-	//TODO: proper selector config with parsing of JSON
-	auto mySelector = Selector::type()(type);
-
-	if(isSubtypeRelevant)
-		mySelector = mySelector.And(Selector::subtype()(subtype));
-	if(isSourceRelevant && isSourceIDRelevant)
-		mySelector = mySelector.And(Selector::source(source, sid));
-	else if (isSourceRelevant)
-		mySelector = mySelector.And(Selector::sourceTypeSel(source));
-
-	//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=%d)");
-		fmt % typeName % subtype;
-		return fmt.str();
-	}
-	else
-	{
-		boost::format fmt("HasAnotherBonusLimiter(type=%s)");
-		fmt % typeName;
-		return fmt.str();
-	}
-}
-
-JsonNode HasAnotherBonusLimiter::toJsonNode() const
-{
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
-	std::string typeName = vstd::findKey(bonusNameMap, type);
-	auto sourceTypeName = vstd::findKey(bonusSourceMap, source);
-
-	root["type"].String() = "HAS_ANOTHER_BONUS_LIMITER";
-	root["parameters"].Vector().push_back(JsonUtils::stringNode(typeName));
-	if(isSubtypeRelevant)
-		root["parameters"].Vector().push_back(JsonUtils::intNode(subtype));
-	if(isSourceRelevant)
-		root["parameters"].Vector().push_back(JsonUtils::stringNode(sourceTypeName));
-
-	return root;
-}
-
-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.count(hex);
-
-	return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
-}
-
-UnitOnHexLimiter::UnitOnHexLimiter(const std::set<BattleHex> & applicableHexes):
-	applicableHexes(applicableHexes)
-{
-}
-
-JsonNode UnitOnHexLimiter::toJsonNode() const
-{
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
-
-	root["type"].String() = "UNIT_ON_HEXES";
-	for(const auto & hex : applicableHexes)
-		root["parameters"].Vector().push_back(JsonUtils::intNode(hex));
-
-	return root;
-}
-
 bool IPropagator::shouldBeAttached(CBonusSystemNode *dest)
 {
 	return false;
@@ -2222,302 +2009,6 @@ bool CPropagatorNodeType::shouldBeAttached(CBonusSystemNode *dest)
 	return nodeType == dest->getNodeType();
 }
 
-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 = VLC->terrainTypeHandler->getById(terrainType)->getJsonKey();
-	fmt % (terrainType == ETerrainId::NATIVE_TERRAIN ? "native" : terrainName);
-	return fmt.str();
-}
-
-JsonNode CreatureTerrainLimiter::toJsonNode() const
-{
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
-
-	root["type"].String() = "CREATURE_TERRAIN_LIMITER";
-	auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->getJsonKey();
-	root["parameters"].Vector().push_back(JsonUtils::stringNode(terrainName));
-
-	return root;
-}
-
-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->getFaction() == faction ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
-
-		switch(context.b->source)
-		{
-			case Bonus::CREATURE_ABILITY:
-				return faction == CreatureID(context.b->sid).toCreature()->getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
-			
-			case Bonus::TOWN_STRUCTURE:
-				return faction == FactionID(Bonus::getHighFromSid32(context.b->sid)) ? 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 % VLC->factions()->getByIndex(faction)->getJsonKey();
-	return fmt.str();
-}
-
-JsonNode FactionLimiter::toJsonNode() const
-{
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
-
-	root["type"].String() = "FACTION_LIMITER";
-	root["parameters"].Vector().push_back(JsonUtils::stringNode(VLC->factions()->getByIndex(faction)->getJsonKey()));
-
-	return root;
-}
-
-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(JsonNode::JsonType::DATA_STRUCT);
-
-	root["type"].String() = "CREATURE_LEVEL_LIMITER";
-	root["parameters"].Vector().push_back(JsonUtils::intNode(minLevel));
-	root["parameters"].Vector().push_back(JsonUtils::intNode(maxLevel));
-
-	return root;
-}
-
-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(JsonNode::JsonType::DATA_STRUCT);
-
-	root["type"].String() = "CREATURE_ALIGNMENT_LIMITER";
-	root["parameters"].Vector().push_back(JsonUtils::stringNode(GameConstants::ALIGNMENT_NAMES[vstd::to_underlying(alignment)]));
-
-	return root;
-}
-
-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;
-}
-
-OppositeSideLimiter::OppositeSideLimiter(PlayerColor Owner):
-	owner(std::move(Owner))
-{
-}
-
-ILimiter::EDecision OppositeSideLimiter::limit(const BonusLimitationContext & context) const
-{
-	auto contextOwner = CBonusSystemNode::retrieveNodeOwner(& context.node);
-	auto decision = (owner == contextOwner || owner == PlayerColor::CANNOT_DETERMINE) ? ILimiter::EDecision::DISCARD : ILimiter::EDecision::ACCEPT;
-	return decision;
-}
-
-// 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(JsonNode::JsonType::DATA_VECTOR);
-	result.Vector().push_back(JsonUtils::stringNode(getAggregator()));
-	for(const 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;
-}
-
-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;
-}
-
 // Updaters
 
 std::shared_ptr<Bonus> Bonus::addUpdater(const TUpdaterPtr & Updater)

+ 2 - 249
lib/bonuses/HeroBonus.h

@@ -9,10 +9,8 @@
  */
 #pragma once
 
-#include "GameConstants.h"
-#include "JsonNode.h"
-#include "battle/BattleHex.h"
-#include <limits>
+#include "../GameConstants.h"
+#include "../JsonNode.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -662,29 +660,6 @@ public:
 
 DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const BonusList &bonusList);
 
-struct BonusLimitationContext
-{
-	std::shared_ptr<const Bonus> b;
-	const CBonusSystemNode & node;
-	const BonusList & alreadyAccepted;
-	const BonusList & stillUndecided;
-};
-
-class DLL_LINKAGE ILimiter
-{
-public:
-	enum class EDecision : uint8_t {ACCEPT, DISCARD, NOT_SURE};
-
-	virtual ~ILimiter() = default;
-
-	virtual EDecision limit(const BonusLimitationContext &context) const; //0 - accept bonus; 1 - drop bonus; 2 - delay (drops eventually)
-	virtual std::string toString() const;
-	virtual JsonNode toJsonNode() const;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-	}
-};
 
 class DLL_LINKAGE IBonusBearer
 {
@@ -964,227 +939,6 @@ public:
 	}
 };
 
-class DLL_LINKAGE AggregateLimiter : public ILimiter
-{
-protected:
-	std::vector<TLimiterPtr> limiters;
-	virtual const std::string & getAggregator() const = 0;
-	AggregateLimiter(std::vector<TLimiterPtr> limiters = {});
-public:
-	void add(const TLimiterPtr & limiter);
-	JsonNode toJsonNode() const override;
-
-	template <typename Handler> void serialize(Handler & h, const int version)
-	{
-		h & static_cast<ILimiter&>(*this);
-		h & limiters;
-	}
-};
-
-class DLL_LINKAGE AllOfLimiter : public AggregateLimiter
-{
-protected:
-	const std::string & getAggregator() const override;
-public:
-	AllOfLimiter(std::vector<TLimiterPtr> limiters = {});
-	static const std::string aggregator;
-	EDecision limit(const BonusLimitationContext & context) const override;
-};
-
-class DLL_LINKAGE AnyOfLimiter : public AggregateLimiter
-{
-protected:
-	const std::string & getAggregator() const override;
-public:
-	AnyOfLimiter(std::vector<TLimiterPtr> limiters = {});
-	static const std::string aggregator;
-	EDecision limit(const BonusLimitationContext & context) const override;
-};
-
-class DLL_LINKAGE NoneOfLimiter : public AggregateLimiter
-{
-protected:
-	const std::string & getAggregator() const override;
-public:
-	NoneOfLimiter(std::vector<TLimiterPtr> limiters = {});
-	static const std::string aggregator;
-	EDecision limit(const BonusLimitationContext & context) const override;
-};
-
-class DLL_LINKAGE CCreatureTypeLimiter : public ILimiter //affect only stacks of given creature (and optionally it's upgrades)
-{
-public:
-	const CCreature * creature = nullptr;
-	bool includeUpgrades = false;
-
-	CCreatureTypeLimiter() = default;
-	CCreatureTypeLimiter(const CCreature & creature_, bool IncludeUpgrades);
-	void setCreature(const CreatureID & id);
-
-	EDecision limit(const BonusLimitationContext &context) const override;
-	std::string toString() const override;
-	JsonNode toJsonNode() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<ILimiter&>(*this);
-		h & creature;
-		h & includeUpgrades;
-	}
-};
-
-class DLL_LINKAGE HasAnotherBonusLimiter : public ILimiter //applies only to nodes that have another bonus working
-{
-public:
-	Bonus::BonusType type;
-	TBonusSubtype subtype;
-	Bonus::BonusSource source;
-	si32 sid;
-	bool isSubtypeRelevant; //check for subtype only if this is true
-	bool isSourceRelevant; //check for bonus source only if this is true
-	bool isSourceIDRelevant; //check for bonus source only if this is true
-
-	HasAnotherBonusLimiter(Bonus::BonusType bonus = Bonus::NONE);
-	HasAnotherBonusLimiter(Bonus::BonusType bonus, TBonusSubtype _subtype);
-	HasAnotherBonusLimiter(Bonus::BonusType bonus, Bonus::BonusSource src);
-	HasAnotherBonusLimiter(Bonus::BonusType bonus, TBonusSubtype _subtype, Bonus::BonusSource src);
-
-	EDecision limit(const BonusLimitationContext &context) const override;
-	std::string toString() const override;
-	JsonNode toJsonNode() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<ILimiter&>(*this);
-		h & type;
-		h & subtype;
-		h & isSubtypeRelevant;
-		h & source;
-		h & isSourceRelevant;
-		h & sid;
-		h & isSourceIDRelevant;
-	}
-};
-
-class DLL_LINKAGE CreatureTerrainLimiter : public ILimiter //applies only to creatures that are on specified terrain, default native terrain
-{
-public:
-	TerrainId terrainType;
-	CreatureTerrainLimiter();
-	CreatureTerrainLimiter(TerrainId terrain);
-
-	EDecision limit(const BonusLimitationContext &context) const override;
-	std::string toString() const override;
-	JsonNode toJsonNode() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<ILimiter&>(*this);
-		h & terrainType;
-	}
-};
-
-class DLL_LINKAGE CreatureLevelLimiter : public ILimiter //applies only to creatures of given faction
-{
-public:
-	uint32_t minLevel;
-	uint32_t maxLevel;
-	//accept all levels by default, accept creatures of minLevel <= creature->getLevel() < maxLevel
-	CreatureLevelLimiter(uint32_t minLevel = std::numeric_limits<uint32_t>::min(), uint32_t maxLevel = std::numeric_limits<uint32_t>::max());
-
-	EDecision limit(const BonusLimitationContext &context) const override;
-	std::string toString() const override;
-	JsonNode toJsonNode() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<ILimiter&>(*this);
-		h & minLevel;
-		h & maxLevel;
-	}
-};
-
-class DLL_LINKAGE FactionLimiter : public ILimiter //applies only to creatures of given faction
-{
-public:
-	FactionID faction;
-	FactionLimiter(FactionID faction = FactionID::DEFAULT);
-
-	EDecision limit(const BonusLimitationContext &context) const override;
-	std::string toString() const override;
-	JsonNode toJsonNode() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<ILimiter&>(*this);
-		h & faction;
-	}
-};
-
-class DLL_LINKAGE CreatureAlignmentLimiter : public ILimiter //applies only to creatures of given alignment
-{
-public:
-	EAlignment alignment;
-	CreatureAlignmentLimiter(EAlignment Alignment = EAlignment::NEUTRAL);
-
-	EDecision limit(const BonusLimitationContext &context) const override;
-	std::string toString() const override;
-	JsonNode toJsonNode() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<ILimiter&>(*this);
-		h & alignment;
-	}
-};
-
-class DLL_LINKAGE OppositeSideLimiter : public ILimiter //applies only to creatures of enemy army during combat
-{
-public:
-	PlayerColor owner;
-	OppositeSideLimiter(PlayerColor Owner = PlayerColor::CANNOT_DETERMINE);
-
-	EDecision limit(const BonusLimitationContext &context) const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<ILimiter&>(*this);
-		h & owner;
-	}
-};
-
-class DLL_LINKAGE RankRangeLimiter : public ILimiter //applies to creatures with min <= Rank <= max
-{
-public:
-	ui8 minRank, maxRank;
-
-	RankRangeLimiter();
-	RankRangeLimiter(ui8 Min, ui8 Max = 255);
-	EDecision limit(const BonusLimitationContext &context) const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<ILimiter&>(*this);
-		h & minRank;
-		h & maxRank;
-	}
-};
-
-class DLL_LINKAGE UnitOnHexLimiter : public ILimiter //works only on selected hexes
-{
-public:
-	std::set<BattleHex> applicableHexes;
-
-	UnitOnHexLimiter(const std::set<BattleHex> & applicableHexes = {});
-	EDecision limit(const BonusLimitationContext &context) const override;
-	JsonNode toJsonNode() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<ILimiter&>(*this);
-		h & applicableHexes;
-	}
-};
 
 namespace Selector
 {
@@ -1221,7 +975,6 @@ extern DLL_LINKAGE const std::map<std::string, Bonus::ValueType> bonusValueMap;
 extern DLL_LINKAGE const std::map<std::string, Bonus::BonusSource> bonusSourceMap;
 extern DLL_LINKAGE const std::map<std::string, ui16> bonusDurationMap;
 extern DLL_LINKAGE const std::map<std::string, Bonus::LimitEffect> bonusLimitEffect;
-extern DLL_LINKAGE const std::map<std::string, TLimiterPtr> bonusLimiterMap;
 extern DLL_LINKAGE const std::map<std::string, TPropagatorPtr> bonusPropagatorMap;
 extern DLL_LINKAGE const std::map<std::string, TUpdaterPtr> bonusUpdaterMap;
 extern DLL_LINKAGE const std::set<std::string> deprecatedBonusSet;

+ 541 - 0
lib/bonuses/ILimiter.cpp

@@ -0,0 +1,541 @@
+/*
+ * ILimiter.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 "ILimiter.h"
+
+#include "../VCMI_Lib.h"
+#include "../spells/CSpellHandler.h"
+#include "../CCreatureHandler.h"
+#include "../CCreatureSet.h"
+#include "../CHeroHandler.h"
+#include "../CTownHandler.h"
+#include "../CGeneralTextHandler.h"
+#include "../CSkillHandler.h"
+#include "../CStack.h"
+#include "../CArtHandler.h"
+#include "../CModHandler.h"
+#include "../TerrainHandler.h"
+#include "../StringConstants.h"
+#include "../battle/BattleInfo.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+const std::map<std::string, TLimiterPtr> bonusLimiterMap =
+{
+	{"SHOOTER_ONLY", std::make_shared<HasAnotherBonusLimiter>(Bonus::SHOOTER)},
+	{"DRAGON_NATURE", std::make_shared<HasAnotherBonusLimiter>(Bonus::DRAGON_NATURE)},
+	{"IS_UNDEAD", std::make_shared<HasAnotherBonusLimiter>(Bonus::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->type;
+		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(JsonNode::JsonType::DATA_STRUCT);
+	root["type"].String() = toString();
+	return root;
+}
+
+ILimiter::EDecision CCreatureTypeLimiter::limit(const BonusLimitationContext &context) const
+{
+	const CCreature *c = retrieveCreature(&context.node);
+	if(!c)
+		return ILimiter::EDecision::DISCARD;
+	
+	auto accept =  c->getId() == creature->getId() || (includeUpgrades && creature->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)
+	: creature(&creature_), includeUpgrades(IncludeUpgrades)
+{
+}
+
+void CCreatureTypeLimiter::setCreature(const CreatureID & id)
+{
+	creature = VLC->creh->objects[id];
+}
+
+std::string CCreatureTypeLimiter::toString() const
+{
+	boost::format fmt("CCreatureTypeLimiter(creature=%s, includeUpgrades=%s)");
+	fmt % creature->getJsonKey() % (includeUpgrades ? "true" : "false");
+	return fmt.str();
+}
+
+JsonNode CCreatureTypeLimiter::toJsonNode() const
+{
+	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+
+	root["type"].String() = "CREATURE_TYPE_LIMITER";
+	root["parameters"].Vector().push_back(JsonUtils::stringNode(creature->getJsonKey()));
+	root["parameters"].Vector().push_back(JsonUtils::boolNode(includeUpgrades));
+
+	return root;
+}
+
+HasAnotherBonusLimiter::HasAnotherBonusLimiter( Bonus::BonusType bonus )
+	: type(bonus), subtype(0), isSubtypeRelevant(false), isSourceRelevant(false), isSourceIDRelevant(false)
+{
+}
+
+HasAnotherBonusLimiter::HasAnotherBonusLimiter( Bonus::BonusType bonus, TBonusSubtype _subtype )
+	: type(bonus), subtype(_subtype), isSubtypeRelevant(true), isSourceRelevant(false), isSourceIDRelevant(false)
+{
+}
+
+HasAnotherBonusLimiter::HasAnotherBonusLimiter(Bonus::BonusType bonus, Bonus::BonusSource src)
+	: type(bonus), source(src), isSubtypeRelevant(false), isSourceRelevant(true), isSourceIDRelevant(false)
+{
+}
+
+HasAnotherBonusLimiter::HasAnotherBonusLimiter(Bonus::BonusType bonus, TBonusSubtype _subtype, Bonus::BonusSource src)
+	: type(bonus), subtype(_subtype), isSubtypeRelevant(true), source(src), isSourceRelevant(true), isSourceIDRelevant(false)
+{
+}
+
+ILimiter::EDecision HasAnotherBonusLimiter::limit(const BonusLimitationContext &context) const
+{
+	//TODO: proper selector config with parsing of JSON
+	auto mySelector = Selector::type()(type);
+
+	if(isSubtypeRelevant)
+		mySelector = mySelector.And(Selector::subtype()(subtype));
+	if(isSourceRelevant && isSourceIDRelevant)
+		mySelector = mySelector.And(Selector::source(source, sid));
+	else if (isSourceRelevant)
+		mySelector = mySelector.And(Selector::sourceTypeSel(source));
+
+	//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=%d)");
+		fmt % typeName % subtype;
+		return fmt.str();
+	}
+	else
+	{
+		boost::format fmt("HasAnotherBonusLimiter(type=%s)");
+		fmt % typeName;
+		return fmt.str();
+	}
+}
+
+JsonNode HasAnotherBonusLimiter::toJsonNode() const
+{
+	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	std::string typeName = vstd::findKey(bonusNameMap, type);
+	auto sourceTypeName = vstd::findKey(bonusSourceMap, source);
+
+	root["type"].String() = "HAS_ANOTHER_BONUS_LIMITER";
+	root["parameters"].Vector().push_back(JsonUtils::stringNode(typeName));
+	if(isSubtypeRelevant)
+		root["parameters"].Vector().push_back(JsonUtils::intNode(subtype));
+	if(isSourceRelevant)
+		root["parameters"].Vector().push_back(JsonUtils::stringNode(sourceTypeName));
+
+	return root;
+}
+
+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.count(hex);
+
+	return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
+}
+
+UnitOnHexLimiter::UnitOnHexLimiter(const std::set<BattleHex> & applicableHexes):
+	applicableHexes(applicableHexes)
+{
+}
+
+JsonNode UnitOnHexLimiter::toJsonNode() const
+{
+	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+
+	root["type"].String() = "UNIT_ON_HEXES";
+	for(const auto & hex : applicableHexes)
+		root["parameters"].Vector().push_back(JsonUtils::intNode(hex));
+
+	return root;
+}
+
+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 = VLC->terrainTypeHandler->getById(terrainType)->getJsonKey();
+	fmt % (terrainType == ETerrainId::NATIVE_TERRAIN ? "native" : terrainName);
+	return fmt.str();
+}
+
+JsonNode CreatureTerrainLimiter::toJsonNode() const
+{
+	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+
+	root["type"].String() = "CREATURE_TERRAIN_LIMITER";
+	auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->getJsonKey();
+	root["parameters"].Vector().push_back(JsonUtils::stringNode(terrainName));
+
+	return root;
+}
+
+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->getFaction() == faction ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
+
+		switch(context.b.source)
+		{
+			case Bonus::CREATURE_ABILITY:
+				return bearer->getFaction() == CreatureID(context.b.sid).toCreature()->getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
+			
+			case Bonus::TOWN_STRUCTURE:
+				return bearer->getFaction() == FactionID(Bonus::getHighFromSid32(context.b.sid)) ? 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 % VLC->factions()->getByIndex(faction)->getJsonKey();
+	return fmt.str();
+}
+
+JsonNode FactionLimiter::toJsonNode() const
+{
+	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+
+	root["type"].String() = "FACTION_LIMITER";
+	root["parameters"].Vector().push_back(JsonUtils::stringNode(VLC->factions()->getByIndex(faction)->getJsonKey()));
+
+	return root;
+}
+
+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(JsonNode::JsonType::DATA_STRUCT);
+
+	root["type"].String() = "CREATURE_LEVEL_LIMITER";
+	root["parameters"].Vector().push_back(JsonUtils::intNode(minLevel));
+	root["parameters"].Vector().push_back(JsonUtils::intNode(maxLevel));
+
+	return root;
+}
+
+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(JsonNode::JsonType::DATA_STRUCT);
+
+	root["type"].String() = "CREATURE_ALIGNMENT_LIMITER";
+	root["parameters"].Vector().push_back(JsonUtils::stringNode(GameConstants::ALIGNMENT_NAMES[vstd::to_underlying(alignment)]));
+
+	return root;
+}
+
+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;
+}
+
+OppositeSideLimiter::OppositeSideLimiter(PlayerColor Owner):
+	owner(std::move(Owner))
+{
+}
+
+ILimiter::EDecision OppositeSideLimiter::limit(const BonusLimitationContext & context) const
+{
+	auto contextOwner = CBonusSystemNode::retrieveNodeOwner(& context.node);
+	auto decision = (owner == contextOwner || owner == PlayerColor::CANNOT_DETERMINE) ? ILimiter::EDecision::DISCARD : ILimiter::EDecision::ACCEPT;
+	return decision;
+}
+
+// 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(JsonNode::JsonType::DATA_VECTOR);
+	result.Vector().push_back(JsonUtils::stringNode(getAggregator()));
+	for(const 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;
+}
+
+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

+ 264 - 0
lib/bonuses/ILimiter.h

@@ -0,0 +1,264 @@
+/*
+ * ILimiter.h, 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 "HeroBonus.h"
+#include "battle/BattleHex.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+extern DLL_LINKAGE const std::map<std::string, TLimiterPtr> bonusLimiterMap;
+
+struct BonusLimitationContext
+{
+	const Bonus & b;
+	const CBonusSystemNode & node;
+	const BonusList & alreadyAccepted;
+	const BonusList & stillUndecided;
+};
+
+class DLL_LINKAGE ILimiter
+{
+public:
+	enum class EDecision : uint8_t {ACCEPT, DISCARD, NOT_SURE};
+
+	virtual ~ILimiter() = default;
+
+	virtual EDecision limit(const BonusLimitationContext &context) const; //0 - accept bonus; 1 - drop bonus; 2 - delay (drops eventually)
+	virtual std::string toString() const;
+	virtual JsonNode toJsonNode() const;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+	}
+};
+
+class DLL_LINKAGE AggregateLimiter : public ILimiter
+{
+protected:
+	std::vector<TLimiterPtr> limiters;
+	virtual const std::string & getAggregator() const = 0;
+	AggregateLimiter(std::vector<TLimiterPtr> limiters = {});
+public:
+	void add(const TLimiterPtr & limiter);
+	JsonNode toJsonNode() const override;
+
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+		h & static_cast<ILimiter&>(*this);
+		h & limiters;
+	}
+};
+
+class DLL_LINKAGE AllOfLimiter : public AggregateLimiter
+{
+protected:
+	const std::string & getAggregator() const override;
+public:
+	AllOfLimiter(std::vector<TLimiterPtr> limiters = {});
+	static const std::string aggregator;
+	EDecision limit(const BonusLimitationContext & context) const override;
+};
+
+class DLL_LINKAGE AnyOfLimiter : public AggregateLimiter
+{
+protected:
+	const std::string & getAggregator() const override;
+public:
+	AnyOfLimiter(std::vector<TLimiterPtr> limiters = {});
+	static const std::string aggregator;
+	EDecision limit(const BonusLimitationContext & context) const override;
+};
+
+class DLL_LINKAGE NoneOfLimiter : public AggregateLimiter
+{
+protected:
+	const std::string & getAggregator() const override;
+public:
+	NoneOfLimiter(std::vector<TLimiterPtr> limiters = {});
+	static const std::string aggregator;
+	EDecision limit(const BonusLimitationContext & context) const override;
+};
+
+class DLL_LINKAGE CCreatureTypeLimiter : public ILimiter //affect only stacks of given creature (and optionally it's upgrades)
+{
+public:
+	const CCreature * creature = nullptr;
+	bool includeUpgrades = false;
+
+	CCreatureTypeLimiter() = default;
+	CCreatureTypeLimiter(const CCreature & creature_, bool IncludeUpgrades);
+	void setCreature(const CreatureID & id);
+
+	EDecision limit(const BonusLimitationContext &context) const override;
+	std::string toString() const override;
+	JsonNode toJsonNode() const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<ILimiter&>(*this);
+		h & creature;
+		h & includeUpgrades;
+	}
+};
+
+class DLL_LINKAGE HasAnotherBonusLimiter : public ILimiter //applies only to nodes that have another bonus working
+{
+public:
+	Bonus::BonusType type;
+	TBonusSubtype subtype;
+	Bonus::BonusSource source;
+	si32 sid;
+	bool isSubtypeRelevant; //check for subtype only if this is true
+	bool isSourceRelevant; //check for bonus source only if this is true
+	bool isSourceIDRelevant; //check for bonus source only if this is true
+
+	HasAnotherBonusLimiter(Bonus::BonusType bonus = Bonus::NONE);
+	HasAnotherBonusLimiter(Bonus::BonusType bonus, TBonusSubtype _subtype);
+	HasAnotherBonusLimiter(Bonus::BonusType bonus, Bonus::BonusSource src);
+	HasAnotherBonusLimiter(Bonus::BonusType bonus, TBonusSubtype _subtype, Bonus::BonusSource src);
+
+	EDecision limit(const BonusLimitationContext &context) const override;
+	std::string toString() const override;
+	JsonNode toJsonNode() const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<ILimiter&>(*this);
+		h & type;
+		h & subtype;
+		h & isSubtypeRelevant;
+		h & source;
+		h & isSourceRelevant;
+		h & sid;
+		h & isSourceIDRelevant;
+	}
+};
+
+class DLL_LINKAGE CreatureTerrainLimiter : public ILimiter //applies only to creatures that are on specified terrain, default native terrain
+{
+public:
+	TerrainId terrainType;
+	CreatureTerrainLimiter();
+	CreatureTerrainLimiter(TerrainId terrain);
+
+	EDecision limit(const BonusLimitationContext &context) const override;
+	std::string toString() const override;
+	JsonNode toJsonNode() const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<ILimiter&>(*this);
+		h & terrainType;
+	}
+};
+
+class DLL_LINKAGE CreatureLevelLimiter : public ILimiter //applies only to creatures of given faction
+{
+public:
+	uint32_t minLevel;
+	uint32_t maxLevel;
+	//accept all levels by default, accept creatures of minLevel <= creature->getLevel() < maxLevel
+	CreatureLevelLimiter(uint32_t minLevel = std::numeric_limits<uint32_t>::min(), uint32_t maxLevel = std::numeric_limits<uint32_t>::max());
+
+	EDecision limit(const BonusLimitationContext &context) const override;
+	std::string toString() const override;
+	JsonNode toJsonNode() const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<ILimiter&>(*this);
+		h & minLevel;
+		h & maxLevel;
+	}
+};
+
+class DLL_LINKAGE FactionLimiter : public ILimiter //applies only to creatures of given faction
+{
+public:
+	FactionID faction;
+	FactionLimiter(FactionID faction = FactionID::DEFAULT);
+
+	EDecision limit(const BonusLimitationContext &context) const override;
+	std::string toString() const override;
+	JsonNode toJsonNode() const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<ILimiter&>(*this);
+		h & faction;
+	}
+};
+
+class DLL_LINKAGE CreatureAlignmentLimiter : public ILimiter //applies only to creatures of given alignment
+{
+public:
+	EAlignment alignment;
+	CreatureAlignmentLimiter(EAlignment Alignment = EAlignment::NEUTRAL);
+
+	EDecision limit(const BonusLimitationContext &context) const override;
+	std::string toString() const override;
+	JsonNode toJsonNode() const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<ILimiter&>(*this);
+		h & alignment;
+	}
+};
+
+class DLL_LINKAGE OppositeSideLimiter : public ILimiter //applies only to creatures of enemy army during combat
+{
+public:
+	PlayerColor owner;
+	OppositeSideLimiter(PlayerColor Owner = PlayerColor::CANNOT_DETERMINE);
+
+	EDecision limit(const BonusLimitationContext &context) const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<ILimiter&>(*this);
+		h & owner;
+	}
+};
+
+class DLL_LINKAGE RankRangeLimiter : public ILimiter //applies to creatures with min <= Rank <= max
+{
+public:
+	ui8 minRank, maxRank;
+
+	RankRangeLimiter();
+	RankRangeLimiter(ui8 Min, ui8 Max = 255);
+	EDecision limit(const BonusLimitationContext &context) const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<ILimiter&>(*this);
+		h & minRank;
+		h & maxRank;
+	}
+};
+
+class DLL_LINKAGE UnitOnHexLimiter : public ILimiter //works only on selected hexes
+{
+public:
+	std::set<BattleHex> applicableHexes;
+
+	UnitOnHexLimiter(const std::set<BattleHex> & applicableHexes = {});
+	EDecision limit(const BonusLimitationContext &context) const override;
+	JsonNode toJsonNode() const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<ILimiter&>(*this);
+		h & applicableHexes;
+	}
+};
+
+VCMI_LIB_NAMESPACE_END

+ 1 - 0
lib/registerTypes/RegisterTypes.h

@@ -22,6 +22,7 @@
 #include "../mapObjects/CommonConstructors.h"
 #include "../mapObjects/MapObjects.h"
 #include "../battle/CObstacleInstance.h"
+#include "../bonuses/ILimiter.h"
 #include "../CStack.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 1 - 0
lib/spells/effects/Moat.cpp

@@ -16,6 +16,7 @@
 
 #include "../../NetPacks.h"
 #include "../../mapObjects/CGTownInstance.h"
+#include "../../bonuses/ILimiter.h"
 #include "../../battle/IBattleState.h"
 #include "../../battle/CBattleInfoCallback.h"
 #include "../../serializer/JsonSerializeFormat.h"