Przeglądaj źródła

Merge pull request #5844 from IvanSavenko/bonus_fixes

Fixes to bonus system functionality
Ivan Savenko 3 miesięcy temu
rodzic
commit
bd88ae18aa
92 zmienionych plików z 565 dodań i 295 usunięć
  1. 4 5
      AI/BattleAI/StackWithBonuses.cpp
  2. 1 2
      AI/BattleAI/StackWithBonuses.h
  3. 3 3
      AI/Nullkiller/AIUtility.cpp
  4. 1 1
      AI/Nullkiller/Analyzers/ArmyManager.cpp
  5. 1 1
      AI/Nullkiller/Pathfinding/Actors.h
  6. 10 4
      Global.h
  7. 1 0
      Mods/vcmi/Content/config/english.json
  8. 10 10
      client/ArtifactsUIController.cpp
  9. 2 2
      client/ClientCommandManager.cpp
  10. 1 0
      client/battle/BattleActionsController.cpp
  11. 1 1
      client/battle/BattleStacksController.cpp
  12. 6 0
      client/gui/CIntObject.h
  13. 7 0
      client/windows/CCastleInterface.cpp
  14. 2 1
      client/windows/CCastleInterface.h
  15. 6 3
      client/windows/CCreatureWindow.cpp
  16. 4 4
      client/windows/CExchangeWindow.cpp
  17. 1 1
      client/windows/CExchangeWindow.h
  18. 5 5
      client/windows/CHeroWindow.cpp
  19. 1 1
      client/windows/CHeroWindow.h
  20. 1 1
      client/windows/CMarketWindow.cpp
  21. 2 1
      client/windows/CMarketWindow.h
  22. 1 1
      client/windows/CWindowWithArtifacts.cpp
  23. 2 2
      client/windows/CWindowWithArtifacts.h
  24. 6 6
      config/artifacts.json
  25. 4 4
      config/buildingsLibrary.json
  26. 2 2
      config/creatures/castle.json
  27. 3 3
      config/factions/castle.json
  28. 2 2
      config/factions/necropolis.json
  29. 1 1
      config/factions/rampart.json
  30. 1 1
      config/objects/lighthouse.json
  31. 39 2
      config/schemas/bonusInstance.json
  32. 18 6
      docs/modders/Bonus/Bonus_Propagators.md
  33. 61 2
      docs/modders/Bonus/Bonus_Updaters.md
  34. 2 2
      lib/CCreatureHandler.cpp
  35. 10 8
      lib/CCreatureSet.cpp
  36. 2 1
      lib/CCreatureSet.h
  37. 1 1
      lib/CPlayerState.cpp
  38. 5 5
      lib/CStack.cpp
  39. 2 2
      lib/battle/BattleInfo.cpp
  40. 1 1
      lib/battle/CBattleInfoCallback.cpp
  41. 3 3
      lib/battle/CUnitState.cpp
  42. 1 1
      lib/battle/CUnitState.h
  43. 1 1
      lib/battle/DamageCalculator.cpp
  44. 23 0
      lib/bonuses/BonusEnum.h
  45. 7 5
      lib/bonuses/BonusList.cpp
  46. 1 1
      lib/bonuses/BonusList.h
  47. 57 49
      lib/bonuses/CBonusSystemNode.cpp
  48. 8 15
      lib/bonuses/CBonusSystemNode.h
  49. 3 13
      lib/bonuses/IBonusBearer.cpp
  50. 1 3
      lib/bonuses/IBonusBearer.h
  51. 9 9
      lib/bonuses/Limiters.cpp
  52. 26 12
      lib/bonuses/Propagators.cpp
  53. 4 4
      lib/bonuses/Propagators.h
  54. 47 10
      lib/bonuses/Updaters.cpp
  55. 30 3
      lib/bonuses/Updaters.h
  56. 2 2
      lib/entities/artifact/CArtifact.cpp
  57. 1 1
      lib/entities/artifact/CArtifactInstance.cpp
  58. 2 2
      lib/entities/faction/CTownHandler.cpp
  59. 3 4
      lib/gameState/CGameState.cpp
  60. 30 0
      lib/json/JsonBonus.cpp
  61. 3 3
      lib/mapObjects/CArmedInstance.cpp
  62. 1 1
      lib/mapObjects/CArmedInstance.h
  63. 5 1
      lib/mapObjects/CGDwelling.cpp
  64. 1 0
      lib/mapObjects/CGDwelling.h
  65. 1 2
      lib/mapObjects/CGHeroInstance.cpp
  66. 7 8
      lib/mapObjects/CGTownInstance.cpp
  67. 1 7
      lib/mapObjects/CGTownInstance.h
  68. 5 0
      lib/mapObjects/FlaggableMapObject.cpp
  69. 1 1
      lib/mapObjects/FlaggableMapObject.h
  70. 11 2
      lib/mapObjects/MiscObjects.cpp
  71. 3 1
      lib/serializer/RegisterTypes.h
  72. 1 1
      lib/spells/AdventureSpellMechanics.cpp
  73. 8 7
      scripting/lua/api/BonusSystem.cpp
  74. 2 2
      test/battle/CBattleInfoCallbackTest.cpp
  75. 2 2
      test/battle/CHealthTest.cpp
  76. 2 2
      test/mock/BattleFake.cpp
  77. 2 2
      test/mock/mock_BonusBearer.cpp
  78. 1 1
      test/mock/mock_BonusBearer.h
  79. 1 1
      test/mock/mock_battle_Unit.h
  80. 3 3
      test/spells/AbilityCasterTest.cpp
  81. 1 1
      test/spells/targetConditions/AbsoluteLevelConditionTest.cpp
  82. 1 1
      test/spells/targetConditions/AbsoluteSpellConditionTest.cpp
  83. 1 1
      test/spells/targetConditions/BonusConditionTest.cpp
  84. 1 1
      test/spells/targetConditions/CreatureConditionTest.cpp
  85. 1 1
      test/spells/targetConditions/ElementalConditionTest.cpp
  86. 1 1
      test/spells/targetConditions/HealthValueConditionTest.cpp
  87. 1 1
      test/spells/targetConditions/ImmunityNegationConditionTest.cpp
  88. 1 1
      test/spells/targetConditions/NormalLevelConditionTest.cpp
  89. 1 1
      test/spells/targetConditions/NormalSpellConditionTest.cpp
  90. 1 1
      test/spells/targetConditions/ReceptiveFeatureConditionTest.cpp
  91. 1 1
      test/spells/targetConditions/SpellEffectConditionTest.cpp
  92. 1 1
      test/spells/targetConditions/TargetConditionItemFixture.h

+ 4 - 5
AI/BattleAI/StackWithBonuses.cpp

@@ -133,11 +133,10 @@ SlotID StackWithBonuses::unitSlot() const
 	return slot;
 	return slot;
 }
 }
 
 
-TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, const CSelector & limit,
-	const std::string & cachingStr) const
+TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, const std::string & cachingStr) const
 {
 {
 	auto ret = std::make_shared<BonusList>();
 	auto ret = std::make_shared<BonusList>();
-	TConstBonusListPtr originalList = origBearer->getAllBonuses(selector, limit, cachingStr);
+	TConstBonusListPtr originalList = origBearer->getAllBonuses(selector, cachingStr);
 
 
 	vstd::copy_if(*originalList, std::back_inserter(*ret), [this](const std::shared_ptr<Bonus> & b)
 	vstd::copy_if(*originalList, std::back_inserter(*ret), [this](const std::shared_ptr<Bonus> & b)
 	{
 	{
@@ -147,7 +146,7 @@ TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, c
 
 
 	for(const Bonus & bonus : bonusesToUpdate)
 	for(const Bonus & bonus : bonusesToUpdate)
 	{
 	{
-		if(selector(&bonus) && (!limit || limit(&bonus)))
+		if(selector(&bonus))
 		{
 		{
 			if(ret->getFirst(Selector::source(BonusSource::SPELL_EFFECT, bonus.sid).And(Selector::typeSubtype(bonus.type, bonus.subtype))))
 			if(ret->getFirst(Selector::source(BonusSource::SPELL_EFFECT, bonus.sid).And(Selector::typeSubtype(bonus.type, bonus.subtype))))
 			{
 			{
@@ -164,7 +163,7 @@ TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, c
 	for(auto & bonus : bonusesToAdd)
 	for(auto & bonus : bonusesToAdd)
 	{
 	{
 		auto b = std::make_shared<Bonus>(bonus);
 		auto b = std::make_shared<Bonus>(bonus);
-		if(selector(b.get()) && (!limit || !limit(b.get())))
+		if(selector(b.get()))
 			ret->push_back(b);
 			ret->push_back(b);
 	}
 	}
 	//TODO limiters?
 	//TODO limiters?

+ 1 - 2
AI/BattleAI/StackWithBonuses.h

@@ -90,8 +90,7 @@ public:
 	SlotID unitSlot() const override;
 	SlotID unitSlot() const override;
 
 
 	///IBonusBearer
 	///IBonusBearer
-	TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit,
-		const std::string & cachingStr = "") const override;
+	TConstBonusListPtr getAllBonuses(const CSelector & selector, const std::string & cachingStr = "") const override;
 
 
 	int32_t getTreeVersion() const override;
 	int32_t getTreeVersion() const override;
 
 

+ 3 - 3
AI/Nullkiller/AIUtility.cpp

@@ -271,7 +271,7 @@ bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2)
 
 
 double getArtifactBonusRelevance(const CGHeroInstance * hero, const std::shared_ptr<Bonus> & bonus)
 double getArtifactBonusRelevance(const CGHeroInstance * hero, const std::shared_ptr<Bonus> & bonus)
 {
 {
-	if (bonus->propagator && bonus->limiter && bonus->propagator->getPropagatorType() == CBonusSystemNode::BATTLE)
+	if (bonus->propagator && bonus->limiter && bonus->propagator->getPropagatorType() == BonusNodeType::BATTLE_WIDE)
 	{
 	{
 		// assume that this is battle wide / other side propagator+limiter
 		// assume that this is battle wide / other side propagator+limiter
 		// consider it as fully relevant since we don't know about future combat when equipping artifacts
 		// consider it as fully relevant since we don't know about future combat when equipping artifacts
@@ -290,7 +290,7 @@ double getArtifactBonusRelevance(const CGHeroInstance * hero, const std::shared_
 
 
 		for (const auto & slot : hero->Slots())
 		for (const auto & slot : hero->Slots())
 		{
 		{
-			const auto allBonuses = slot.second->getAllBonuses(Selector::all, Selector::all);
+			const auto allBonuses = slot.second->getAllBonuses(Selector::all);
 			BonusLimitationContext context = {*bonus, *slot.second, *allBonuses, stillUndecided};
 			BonusLimitationContext context = {*bonus, *slot.second, *allBonuses, stillUndecided};
 
 
 			uint64_t unitStrength = slot.second->getPower();
 			uint64_t unitStrength = slot.second->getPower();
@@ -526,7 +526,7 @@ int32_t getArtifactBonusScoreImpl(const std::shared_ptr<Bonus> & bonus)
 
 
 int32_t getArtifactBonusScore(const std::shared_ptr<Bonus> & bonus)
 int32_t getArtifactBonusScore(const std::shared_ptr<Bonus> & bonus)
 {
 {
-	if (bonus->propagator && bonus->propagator->getPropagatorType() == CBonusSystemNode::BATTLE)
+	if (bonus->propagator && bonus->propagator->getPropagatorType() == BonusNodeType::BATTLE_WIDE)
 	{
 	{
 		if (bonus->limiter)
 		if (bonus->limiter)
 		{
 		{

+ 1 - 1
AI/Nullkiller/Analyzers/ArmyManager.cpp

@@ -172,7 +172,7 @@ class TemporaryArmy : public CArmedInstance
 public:
 public:
 	void armyChanged() override {}
 	void armyChanged() override {}
 	TemporaryArmy()
 	TemporaryArmy()
-		:CArmedInstance(nullptr, true)
+		:CArmedInstance(nullptr, BonusNodeType::UNKNOWN, true)
 	{
 	{
 	}
 	}
 };
 };

+ 1 - 1
AI/Nullkiller/Pathfinding/Actors.h

@@ -31,7 +31,7 @@ public:
 	bool needsLastStack() const override;
 	bool needsLastStack() const override;
 	std::shared_ptr<SpecialAction> getActorAction() const;
 	std::shared_ptr<SpecialAction> getActorAction() const;
 
 
-	HeroExchangeArmy(): CArmedInstance(nullptr, true), requireBuyArmy(false) {}
+	HeroExchangeArmy(): CArmedInstance(nullptr, BonusNodeType::UNKNOWN, true), requireBuyArmy(false) {}
 };
 };
 
 
 struct ExchangeResult
 struct ExchangeResult

+ 10 - 4
Global.h

@@ -712,13 +712,16 @@ namespace vstd
 		return a + (b - a) * f;
 		return a + (b - a) * f;
 	}
 	}
 
 
-	/// Divides dividend by divisor and rounds result up
+	/// Divides dividend by divisor and rounds result away from zero
 	/// For use with integer-only arithmetic
 	/// For use with integer-only arithmetic
 	template<typename Integer1, typename Integer2>
 	template<typename Integer1, typename Integer2>
 	Integer1 divideAndCeil(const Integer1 & dividend, const Integer2 & divisor)
 	Integer1 divideAndCeil(const Integer1 & dividend, const Integer2 & divisor)
 	{
 	{
 		static_assert(std::is_integral_v<Integer1> && std::is_integral_v<Integer2>, "This function should only be used with integral types");
 		static_assert(std::is_integral_v<Integer1> && std::is_integral_v<Integer2>, "This function should only be used with integral types");
-		return (dividend + divisor - 1) / divisor;
+		if (dividend >= 0)
+			return (dividend + divisor - 1) / divisor;
+		else
+			return (dividend - divisor + 1) / divisor;
 	}
 	}
 
 
 	/// Divides dividend by divisor and rounds result to nearest
 	/// Divides dividend by divisor and rounds result to nearest
@@ -727,10 +730,13 @@ namespace vstd
 	Integer1 divideAndRound(const Integer1 & dividend, const Integer2 & divisor)
 	Integer1 divideAndRound(const Integer1 & dividend, const Integer2 & divisor)
 	{
 	{
 		static_assert(std::is_integral_v<Integer1> && std::is_integral_v<Integer2>, "This function should only be used with integral types");
 		static_assert(std::is_integral_v<Integer1> && std::is_integral_v<Integer2>, "This function should only be used with integral types");
-		return (dividend + divisor / 2 - 1) / divisor;
+		if (dividend >= 0)
+			return (dividend + divisor / 2 - 1) / divisor;
+		else
+			return (dividend - divisor / 2 + 1) / divisor;
 	}
 	}
 
 
-	/// Divides dividend by divisor and rounds result down
+	/// Divides dividend by divisor and rounds result towards zero
 	/// For use with integer-only arithmetic
 	/// For use with integer-only arithmetic
 	template<typename Integer1, typename Integer2>
 	template<typename Integer1, typename Integer2>
 	Integer1 divideAndFloor(const Integer1 & dividend, const Integer2 & divisor)
 	Integer1 divideAndFloor(const Integer1 & dividend, const Integer2 & divisor)

+ 1 - 0
Mods/vcmi/Content/config/english.json

@@ -730,6 +730,7 @@
 	"core.bonus.SUMMON_GUARDIANS.description" : "{Summon guardians}\nAt the start of battle summons ${subtype.creature} (${val}%)",
 	"core.bonus.SUMMON_GUARDIANS.description" : "{Summon guardians}\nAt the start of battle summons ${subtype.creature} (${val}%)",
 	"core.bonus.THREE_HEADED_ATTACK.description" : "{Three-headed attack}\nAttacks three adjacent units",
 	"core.bonus.THREE_HEADED_ATTACK.description" : "{Three-headed attack}\nAttacks three adjacent units",
 	"core.bonus.TRANSMUTATION.description" : "{Transmutation}\n${val}% chance to transform attacked unit to a different type",
 	"core.bonus.TRANSMUTATION.description" : "{Transmutation}\n${val}% chance to transform attacked unit to a different type",
+	"core.bonus.TRANSMUTATION_IMMUNITY.description" : "{Transmutation Immunity}\nThis unit cannot be transformed into another unit by enemy attack",
 	"core.bonus.TWO_HEX_ATTACK_BREATH.description" : "{Breath Attack}\nAttacks by this unit will also hit any unit positioned immediately behind the target",
 	"core.bonus.TWO_HEX_ATTACK_BREATH.description" : "{Breath Attack}\nAttacks by this unit will also hit any unit positioned immediately behind the target",
 	"core.bonus.UNDEAD.description" : "{Undead}\nCreature is Undead and is immune to effects that only affect living",
 	"core.bonus.UNDEAD.description" : "{Undead}\nCreature is Undead and is immune to effects that only affect living",
 	"core.bonus.UNLIMITED_RETALIATIONS.description" : "{Unlimited retaliations}\nThis unit can retaliate against an unlimited number of attacks",
 	"core.bonus.UNLIMITED_RETALIATIONS.description" : "{Unlimited retaliations}\nThis unit can retaliate against an unlimited number of attacks",

+ 10 - 10
client/ArtifactsUIController.cpp

@@ -127,8 +127,8 @@ bool ArtifactsUIController::askToDisassemble(const CGHeroInstance * hero, const
 
 
 void ArtifactsUIController::artifactRemoved()
 void ArtifactsUIController::artifactRemoved()
 {
 {
-	for(const auto & artWin : ENGINE->windows().findWindows<CWindowWithArtifacts>())
-		artWin->update();
+	for(const auto & artWin : ENGINE->windows().findWindows<IArtifactsHolder>())
+		artWin->updateArtifacts();
 	GAME->interface()->waitWhileDialog();
 	GAME->interface()->waitWhileDialog();
 }
 }
 
 
@@ -139,10 +139,10 @@ void ArtifactsUIController::artifactMoved()
 		numOfMovedArts--;
 		numOfMovedArts--;
 
 
 	if(numOfMovedArts == 0)
 	if(numOfMovedArts == 0)
-		for(const auto & artWin : ENGINE->windows().findWindows<CWindowWithArtifacts>())
-		{
-			artWin->update();
-		}
+	{
+		for(const auto & artWin : ENGINE->windows().findWindows<IArtifactsHolder>())
+			artWin->updateArtifacts();
+	}
 	GAME->interface()->waitWhileDialog();
 	GAME->interface()->waitWhileDialog();
 }
 }
 
 
@@ -160,12 +160,12 @@ void ArtifactsUIController::bulkArtMovementStart(size_t totalNumOfArts, size_t p
 
 
 void ArtifactsUIController::artifactAssembled()
 void ArtifactsUIController::artifactAssembled()
 {
 {
-	for(const auto & artWin : ENGINE->windows().findWindows<CWindowWithArtifacts>())
-		artWin->update();
+	for(const auto & artWin : ENGINE->windows().findWindows<IArtifactsHolder>())
+		artWin->updateArtifacts();
 }
 }
 
 
 void ArtifactsUIController::artifactDisassembled()
 void ArtifactsUIController::artifactDisassembled()
 {
 {
-	for(const auto & artWin : ENGINE->windows().findWindows<CWindowWithArtifacts>())
-		artWin->update();
+	for(const auto & artWin : ENGINE->windows().findWindows<IArtifactsHolder>())
+		artWin->updateArtifacts();
 }
 }

+ 2 - 2
client/ClientCommandManager.cpp

@@ -440,14 +440,14 @@ void ClientCommandManager::handleBonusesCommand(std::istringstream & singleWordB
 		return ss.str();
 		return ss.str();
 	};
 	};
 		printCommandMessage("Bonuses of " + GAME->interface()->localState->getCurrentArmy()->getObjectName() + "\n");
 		printCommandMessage("Bonuses of " + GAME->interface()->localState->getCurrentArmy()->getObjectName() + "\n");
-		printCommandMessage(format(*GAME->interface()->localState->getCurrentArmy()->getAllBonuses(Selector::all, Selector::all)) + "\n");
+		printCommandMessage(format(*GAME->interface()->localState->getCurrentArmy()->getAllBonuses(Selector::all)) + "\n");
 
 
 	printCommandMessage("\nInherited bonuses:\n");
 	printCommandMessage("\nInherited bonuses:\n");
 	TCNodes parents;
 	TCNodes parents;
 		GAME->interface()->localState->getCurrentArmy()->getParents(parents);
 		GAME->interface()->localState->getCurrentArmy()->getParents(parents);
 	for(const CBonusSystemNode *parent : parents)
 	for(const CBonusSystemNode *parent : parents)
 	{
 	{
-		printCommandMessage(std::string("\nBonuses from ") + typeid(*parent).name() + "\n" + format(*parent->getAllBonuses(Selector::all, Selector::all)) + "\n");
+		printCommandMessage(std::string("\nBonuses from ") + typeid(*parent).name() + "\n" + format(*parent->getAllBonuses(Selector::all)) + "\n");
 	}
 	}
 }
 }
 
 

+ 1 - 0
client/battle/BattleActionsController.cpp

@@ -530,6 +530,7 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
 				int distance = attacker->position.isValid() ? owner.getBattle()->battleGetDistances(attacker, attacker->getPosition())[attackFromHex.toInt()] : 0;
 				int distance = attacker->position.isValid() ? owner.getBattle()->battleGetDistances(attacker, attacker->getPosition())[attackFromHex.toInt()] : 0;
 				DamageEstimation retaliation;
 				DamageEstimation retaliation;
 				BattleAttackInfo attackInfo(attacker, targetStack, distance, false );
 				BattleAttackInfo attackInfo(attacker, targetStack, distance, false );
+				attackInfo.attackerPos = attackFromHex;
 				DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(attackInfo, &retaliation);
 				DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(attackInfo, &retaliation);
 				estimation.kills.max = std::min<int64_t>(estimation.kills.max, targetStack->getCount());
 				estimation.kills.max = std::min<int64_t>(estimation.kills.max, targetStack->getCount());
 				estimation.kills.min = std::min<int64_t>(estimation.kills.min, targetStack->getCount());
 				estimation.kills.min = std::min<int64_t>(estimation.kills.min, targetStack->getCount());

+ 1 - 1
client/battle/BattleStacksController.cpp

@@ -805,7 +805,7 @@ void BattleStacksController::removeExpiredColorFilters()
 	{
 	{
 		if (!filter.persistent)
 		if (!filter.persistent)
 		{
 		{
-			if (filter.source && !filter.target->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(filter.source->id)), Selector::all))
+			if (filter.source && !filter.target->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(filter.source->id))))
 				return true;
 				return true;
 			if (filter.effectColor == Colors::TRANSPARENCY && filter.transparency == 255)
 			if (filter.effectColor == Colors::TRANSPARENCY && filter.transparency == 255)
 				return true;
 				return true;

+ 6 - 0
client/gui/CIntObject.h

@@ -162,6 +162,12 @@ public:
 	virtual void updateGarrisons() = 0;
 	virtual void updateGarrisons() = 0;
 };
 };
 
 
+class IArtifactsHolder
+{
+public:
+	virtual void updateArtifacts() = 0;
+};
+
 class IMarketHolder
 class IMarketHolder
 {
 {
 public:
 public:

+ 7 - 0
client/windows/CCastleInterface.cpp

@@ -1476,6 +1476,13 @@ CCastleInterface::~CCastleInterface()
 		GAME->interface()->castleInt = nullptr;
 		GAME->interface()->castleInt = nullptr;
 }
 }
 
 
+void CCastleInterface::updateArtifacts()
+{
+	// handle equipping / unequipping Legion pieces
+	for(auto creatureInfoBox : creainfo)
+		creatureInfoBox->update();
+}
+
 void CCastleInterface::updateGarrisons()
 void CCastleInterface::updateGarrisons()
 {
 {
 	garr->setArmy(town->getUpperArmy(), EGarrisonType::UPPER);
 	garr->setArmy(town->getUpperArmy(), EGarrisonType::UPPER);

+ 2 - 1
client/windows/CCastleInterface.h

@@ -223,7 +223,7 @@ public:
 };
 };
 
 
 /// Class which manages the castle window
 /// Class which manages the castle window
-class CCastleInterface : public CStatusbarWindow, public IGarrisonHolder
+class CCastleInterface final : public CStatusbarWindow, public IGarrisonHolder, public IArtifactsHolder
 {
 {
 	std::shared_ptr<CLabel> title;
 	std::shared_ptr<CLabel> title;
 	std::shared_ptr<CLabel> income;
 	std::shared_ptr<CLabel> income;
@@ -257,6 +257,7 @@ public:
 	CCastleInterface(const CGTownInstance * Town, const CGTownInstance * from = nullptr);
 	CCastleInterface(const CGTownInstance * Town, const CGTownInstance * from = nullptr);
 	~CCastleInterface();
 	~CCastleInterface();
 
 
+	void updateArtifacts() override;
 	void updateGarrisons() override;
 	void updateGarrisons() override;
 	bool holdsGarrison(const CArmedInstance * army) override;
 	bool holdsGarrison(const CArmedInstance * army) override;
 
 

+ 6 - 3
client/windows/CCreatureWindow.cpp

@@ -848,7 +848,7 @@ void CStackWindow::init()
 
 
 void CStackWindow::initBonusesList()
 void CStackWindow::initBonusesList()
 {
 {
-	BonusList receivedBonuses = *info->stackNode->getBonuses(CSelector(Bonus::Permanent), Selector::all);
+	BonusList receivedBonuses = *info->stackNode->getBonuses(CSelector(Bonus::Permanent));
 	BonusList abilities = info->creature->getExportedBonusList();
 	BonusList abilities = info->creature->getExportedBonusList();
 
 
 	// remove all bonuses that are not propagated away
 	// remove all bonuses that are not propagated away
@@ -900,9 +900,12 @@ void CStackWindow::initBonusesList()
 		BonusList groupIndepMin = group;
 		BonusList groupIndepMin = group;
 		BonusList groupIndepMax = group;
 		BonusList groupIndepMax = group;
 		BonusList groupNoMinMax = group;
 		BonusList groupNoMinMax = group;
+		BonusList groupBaseOnly = group;
+
 		groupIndepMin.remove_if([](const Bonus * b) { return b->valType != BonusValueType::INDEPENDENT_MIN; });
 		groupIndepMin.remove_if([](const Bonus * b) { return b->valType != BonusValueType::INDEPENDENT_MIN; });
 		groupIndepMax.remove_if([](const Bonus * b) { return b->valType != BonusValueType::INDEPENDENT_MAX; });
 		groupIndepMax.remove_if([](const Bonus * b) { return b->valType != BonusValueType::INDEPENDENT_MAX; });
 		groupNoMinMax.remove_if([](const Bonus * b) { return b->valType == BonusValueType::INDEPENDENT_MAX || b->valType == BonusValueType::INDEPENDENT_MIN; });
 		groupNoMinMax.remove_if([](const Bonus * b) { return b->valType == BonusValueType::INDEPENDENT_MAX || b->valType == BonusValueType::INDEPENDENT_MIN; });
+		groupBaseOnly.remove_if([](const Bonus * b) { return b->valType != BonusValueType::ADDITIVE_VALUE || b->valType == BonusValueType::BASE_NUMBER; });
 
 
 		int valIndepMin = groupIndepMin.totalValue();
 		int valIndepMin = groupIndepMin.totalValue();
 		int valIndepMax = groupIndepMax.totalValue();
 		int valIndepMax = groupIndepMax.totalValue();
@@ -914,8 +917,8 @@ void CStackWindow::initBonusesList()
 			usedGroup = groupIndepMin; // bonus value was limited due to INDEPENDENT_MIN bonus -> show this bonus
 			usedGroup = groupIndepMin; // bonus value was limited due to INDEPENDENT_MIN bonus -> show this bonus
 		else if (!groupIndepMax.empty() && valNoMinMax != valIndepMax)
 		else if (!groupIndepMax.empty() && valNoMinMax != valIndepMax)
 			usedGroup = groupIndepMax; // bonus value was limited due to INDEPENDENT_MAX bonus -> show this bonus
 			usedGroup = groupIndepMax; // bonus value was limited due to INDEPENDENT_MAX bonus -> show this bonus
-		else
-			usedGroup = groupNoMinMax; // bonus value is not limited - show first non-independent bonus
+		else if (!groupBaseOnly.empty())
+			usedGroup = groupNoMinMax; // bonus value is not limited and has bonuses other than percent to base / percent to all - show first non-independent bonus
 
 
 		// It is possible that empty group was selected. For example, there is only INDEPENDENT effect with value of 0, which does not actually has any effect on this unit
 		// It is possible that empty group was selected. For example, there is only INDEPENDENT effect with value of 0, which does not actually has any effect on this unit
 		// For example, orb of vulnerability on unit without any resistances
 		// For example, orb of vulnerability on unit without any resistances

+ 4 - 4
client/windows/CExchangeWindow.cpp

@@ -261,7 +261,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 		}
 		}
 	}
 	}
 
 
-	CExchangeWindow::update();
+	CExchangeWindow::updateArtifacts();
 }
 }
 
 
 void CExchangeWindow::creatureArrowButtonCallback(bool leftToRight, SlotID slotId)
 void CExchangeWindow::creatureArrowButtonCallback(bool leftToRight, SlotID slotId)
@@ -361,7 +361,7 @@ void CExchangeWindow::updateGarrisons()
 {
 {
 	garr->recreateSlots();
 	garr->recreateSlots();
 
 
-	update();
+	updateArtifacts();
 }
 }
 
 
 bool CExchangeWindow::holdsGarrison(const CArmedInstance * army)
 bool CExchangeWindow::holdsGarrison(const CArmedInstance * army)
@@ -375,13 +375,13 @@ void CExchangeWindow::questLogShortcut()
 	GAME->interface()->showQuestLog();
 	GAME->interface()->showQuestLog();
 }
 }
 
 
-void CExchangeWindow::update()
+void CExchangeWindow::updateArtifacts()
 {
 {
 	const bool qeLayout = isQuickExchangeLayoutAvailable();
 	const bool qeLayout = isQuickExchangeLayoutAvailable();
 
 
 	OBJECT_CONSTRUCTION;
 	OBJECT_CONSTRUCTION;
 
 
-	CWindowWithArtifacts::update();
+	CWindowWithArtifacts::updateArtifacts();
 
 
 	for(size_t leftRight : {0, 1})
 	for(size_t leftRight : {0, 1})
 	{
 	{

+ 1 - 1
client/windows/CExchangeWindow.h

@@ -75,7 +75,7 @@ public:
 
 
 	void keyPressed(EShortcut key) override;
 	void keyPressed(EShortcut key) override;
 
 
-	void update() override;
+	void updateArtifacts() override;
 
 
 	// IGarrisonHolder impl
 	// IGarrisonHolder impl
 	void updateGarrisons() override;
 	void updateGarrisons() override;

+ 5 - 5
client/windows/CHeroWindow.cpp

@@ -46,7 +46,7 @@ void CHeroSwitcher::clickPressed(const Point & cursorPosition)
 	//TODO: do not recreate window
 	//TODO: do not recreate window
 	if (false)
 	if (false)
 	{
 	{
-		owner->update();
+		owner->updateArtifacts();
 	}
 	}
 	else
 	else
 	{
 	{
@@ -153,7 +153,7 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero)
 	{
 	{
 		auto divisionRoundUp = [](int x, int y){ return (x + (y - 1)) / y; };
 		auto divisionRoundUp = [](int x, int y){ return (x + (y - 1)) / y; };
 		int lines = divisionRoundUp(hero->secSkills.size(), 2);
 		int lines = divisionRoundUp(hero->secSkills.size(), 2);
-		secSkillSlider = std::make_shared<CSlider>(Point(284, 276), 189, [this](int val){ CHeroWindow::update(); }, 4, lines, 0, Orientation::VERTICAL, CSlider::BROWN);
+		secSkillSlider = std::make_shared<CSlider>(Point(284, 276), 189, [this](int val){ CHeroWindow::updateArtifacts(); }, 4, lines, 0, Orientation::VERTICAL, CSlider::BROWN);
 		secSkillSlider->setPanningStep(48);
 		secSkillSlider->setPanningStep(48);
 		secSkillSlider->setScrollBounds(Rect(-266, 0, secSkillSlider->pos.x - pos.x + secSkillSlider->pos.w, secSkillSlider->pos.h));
 		secSkillSlider->setScrollBounds(Rect(-266, 0, secSkillSlider->pos.x - pos.x + secSkillSlider->pos.w, secSkillSlider->pos.h));
 	}
 	}
@@ -182,14 +182,14 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero)
 	labels.push_back(std::make_shared<CLabel>(69, 232, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, LIBRARY->generaltexth->jktexts[6]));
 	labels.push_back(std::make_shared<CLabel>(69, 232, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, LIBRARY->generaltexth->jktexts[6]));
 	labels.push_back(std::make_shared<CLabel>(213, 232, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, LIBRARY->generaltexth->jktexts[7]));
 	labels.push_back(std::make_shared<CLabel>(213, 232, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, LIBRARY->generaltexth->jktexts[7]));
 
 
-	CHeroWindow::update();
+	CHeroWindow::updateArtifacts();
 }
 }
 
 
-void CHeroWindow::update()
+void CHeroWindow::updateArtifacts()
 {
 {
 	OBJECT_CONSTRUCTION;
 	OBJECT_CONSTRUCTION;
 
 
-	CWindowWithArtifacts::update();
+	CWindowWithArtifacts::updateArtifacts();
 	auto & heroscrn = LIBRARY->generaltexth->heroscrn;
 	auto & heroscrn = LIBRARY->generaltexth->heroscrn;
 	assert(curHero);
 	assert(curHero);
 
 

+ 1 - 1
client/windows/CHeroWindow.h

@@ -101,7 +101,7 @@ public:
 
 
 	CHeroWindow(const CGHeroInstance * hero);
 	CHeroWindow(const CGHeroInstance * hero);
 
 
-	void update() override;
+	void updateArtifacts() override;
 
 
 	void dismissCurrent(); //dismissed currently displayed hero (curHero)
 	void dismissCurrent(); //dismissed currently displayed hero (curHero)
 	void commanderWindow();
 	void commanderWindow();

+ 1 - 1
client/windows/CMarketWindow.cpp

@@ -85,7 +85,7 @@ void CMarketWindow::updateExperience()
 
 
 void CMarketWindow::update()
 void CMarketWindow::update()
 {
 {
-	CWindowWithArtifacts::update();
+	CWindowWithArtifacts::updateArtifacts();
 	assert(marketWidget);
 	assert(marketWidget);
 	marketWidget->update();
 	marketWidget->update();
 }
 }

+ 2 - 1
client/windows/CMarketWindow.h

@@ -20,7 +20,8 @@ public:
 	void updateArtifacts() override;
 	void updateArtifacts() override;
 	void updateGarrisons() override;
 	void updateGarrisons() override;
 	void updateExperience() override;
 	void updateExperience() override;
-	void update() override;
+
+	void update();
 	void close() override;
 	void close() override;
 	bool holdsGarrison(const CArmedInstance * army) override;
 	bool holdsGarrison(const CArmedInstance * army) override;
 
 

+ 1 - 1
client/windows/CWindowWithArtifacts.cpp

@@ -164,7 +164,7 @@ void CWindowWithArtifacts::enableKeyboardShortcuts() const
 		artSet->enableKeyboardShortcuts();
 		artSet->enableKeyboardShortcuts();
 }
 }
 
 
-void CWindowWithArtifacts::update()
+void CWindowWithArtifacts::updateArtifacts()
 {
 {
 	for(const auto & artSet : artSets)
 	for(const auto & artSet : artSets)
 	{
 	{

+ 2 - 2
client/windows/CWindowWithArtifacts.h

@@ -16,7 +16,7 @@
 #include "../widgets/CArtifactsOfHeroBackpack.h"
 #include "../widgets/CArtifactsOfHeroBackpack.h"
 #include "CWindowObject.h"
 #include "CWindowObject.h"
 
 
-class CWindowWithArtifacts : virtual public CWindowObject
+class CWindowWithArtifacts : virtual public CWindowObject, public IArtifactsHolder
 {
 {
 public:
 public:
 	using CArtifactsOfHeroPtr = std::shared_ptr<CArtifactsOfHeroBase>;
 	using CArtifactsOfHeroPtr = std::shared_ptr<CArtifactsOfHeroBase>;
@@ -36,7 +36,7 @@ public:
 	void deactivate() override;
 	void deactivate() override;
 	void enableKeyboardShortcuts() const;
 	void enableKeyboardShortcuts() const;
 
 
-	virtual void update();
+	void updateArtifacts() override;
 
 
 protected:
 protected:
 	void markPossibleSlots() const;
 	void markPossibleSlots() const;

+ 6 - 6
config/artifacts.json

@@ -1733,7 +1733,7 @@
 				"type" : "CREATURE_GROWTH",
 				"type" : "CREATURE_GROWTH",
 				"subtype" : "creatureLevel2",
 				"subtype" : "creatureLevel2",
 				"val" : 5,
 				"val" : 5,
-				"propagator": "VISITED_TOWN_AND_VISITOR"
+				"propagator": "TOWN_AND_VISITOR"
 			}
 			}
 		}
 		}
 	},
 	},
@@ -1746,7 +1746,7 @@
 				"type" : "CREATURE_GROWTH",
 				"type" : "CREATURE_GROWTH",
 				"subtype" : "creatureLevel3",
 				"subtype" : "creatureLevel3",
 				"val" : 4,
 				"val" : 4,
-				"propagator": "VISITED_TOWN_AND_VISITOR"
+				"propagator": "TOWN_AND_VISITOR"
 			}
 			}
 		}
 		}
 	},
 	},
@@ -1759,7 +1759,7 @@
 				"type" : "CREATURE_GROWTH",
 				"type" : "CREATURE_GROWTH",
 				"subtype" : "creatureLevel4",
 				"subtype" : "creatureLevel4",
 				"val" : 3,
 				"val" : 3,
-				"propagator": "VISITED_TOWN_AND_VISITOR"
+				"propagator": "TOWN_AND_VISITOR"
 			}
 			}
 		}
 		}
 	},
 	},
@@ -1772,7 +1772,7 @@
 				"type" : "CREATURE_GROWTH",
 				"type" : "CREATURE_GROWTH",
 				"subtype" : "creatureLevel5",
 				"subtype" : "creatureLevel5",
 				"val" : 2,
 				"val" : 2,
-				"propagator": "VISITED_TOWN_AND_VISITOR"
+				"propagator": "TOWN_AND_VISITOR"
 			}
 			}
 		}
 		}
 	},
 	},
@@ -1785,7 +1785,7 @@
 				"type" : "CREATURE_GROWTH",
 				"type" : "CREATURE_GROWTH",
 				"subtype" : "creatureLevel6",
 				"subtype" : "creatureLevel6",
 				"val" : 1,
 				"val" : 1,
-				"propagator": "VISITED_TOWN_AND_VISITOR"
+				"propagator": "TOWN_AND_VISITOR"
 			}
 			}
 		}
 		}
 	},
 	},
@@ -2094,7 +2094,7 @@
 			"growth" : {
 			"growth" : {
 				"type" : "CREATURE_GROWTH_PERCENT",
 				"type" : "CREATURE_GROWTH_PERCENT",
 				"val" : 50,
 				"val" : 50,
-				"propagator": "PLAYER_PROPAGATOR"
+				"propagator": "PLAYER"
 			}
 			}
 		}
 		}
 	},
 	},

+ 4 - 4
config/buildingsLibrary.json

@@ -12,7 +12,7 @@
 				"val": 1
 				"val": 1
 			},
 			},
 			{
 			{
-				"propagator": "PLAYER_PROPAGATOR",
+				"propagator": "PLAYER",
 				"type": "THIEVES_GUILD_ACCESS",
 				"type": "THIEVES_GUILD_ACCESS",
 				"val": 1
 				"val": 1
 			}
 			}
@@ -130,7 +130,7 @@
 	"lighthouse" : {
 	"lighthouse" : {
 		"bonuses": [
 		"bonuses": [
 			{
 			{
-				"propagator": "PLAYER_PROPAGATOR",
+				"propagator": "PLAYER",
 				"type": "MOVEMENT",
 				"type": "MOVEMENT",
 				"subtype": "heroMovementSea",
 				"subtype": "heroMovementSea",
 				"val": 500
 				"val": 500
@@ -257,10 +257,10 @@
 	"thievesGuild" : {
 	"thievesGuild" : {
 		"bonuses": [
 		"bonuses": [
 			{
 			{
-				"propagator": "PLAYER_PROPAGATOR",
+				"propagator": "PLAYER",
 				"type": "THIEVES_GUILD_ACCESS",
 				"type": "THIEVES_GUILD_ACCESS",
 				"val": 2
 				"val": 2
 			}
 			}
 		]
 		]
 	}
 	}
-}
+}

+ 2 - 2
config/creatures/castle.json

@@ -383,7 +383,7 @@
 			{
 			{
 				"type" : "MORALE",
 				"type" : "MORALE",
 				"val" : 1,
 				"val" : 1,
-				"propagator" : "HERO",
+				"propagator" : "ARMY",
 				"description" : "PLACEHOLDER",
 				"description" : "PLACEHOLDER",
 				"stacking" : "Angels"
 				"stacking" : "Angels"
 			},
 			},
@@ -453,7 +453,7 @@
 			"raisesMorale" : {
 			"raisesMorale" : {
 				"type" : "MORALE",
 				"type" : "MORALE",
 				"val" : 1,
 				"val" : 1,
-				"propagator" : "HERO",
+				"propagator" : "ARMY",
 				"description" : "@creatures.core.angel.bonus.raisesMorale",
 				"description" : "@creatures.core.angel.bonus.raisesMorale",
 				"stacking" : "Angels"
 				"stacking" : "Angels"
 			},
 			},

+ 3 - 3
config/factions/castle.json

@@ -179,7 +179,7 @@
 				"special1":       { 
 				"special1":       { 
 					"bonuses": [
 					"bonuses": [
 						{
 						{
-							"propagator": "PLAYER_PROPAGATOR",
+							"propagator": "PLAYER",
 							"type": "MOVEMENT",
 							"type": "MOVEMENT",
 							"subtype": "heroMovementSea",
 							"subtype": "heroMovementSea",
 							"val": 500
 							"val": 500
@@ -212,14 +212,14 @@
 							"val": 2
 							"val": 2
 						},
 						},
 						{
 						{
-							"propagator": "PLAYER_PROPAGATOR",
+							"propagator": "PLAYER",
 							"type": "THIEVES_GUILD_ACCESS",
 							"type": "THIEVES_GUILD_ACCESS",
 							"val": 1
 							"val": 1
 						}
 						}
 					],
 					],
 					"upgrades" : "tavern"
 					"upgrades" : "tavern"
 				},
 				},
-				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }, "bonuses": [ { "type": "MORALE", "val": 2, "propagator": "PLAYER_PROPAGATOR" } ] },
+				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }, "bonuses": [ { "type": "MORALE", "val": 2, "propagator": "PLAYER" } ] },
 
 
 				"dwellingLvl1":   { "id" : 30, "requires" : [ "fort" ] },
 				"dwellingLvl1":   { "id" : 30, "requires" : [ "fort" ] },
 				"dwellingLvl2":   { "id" : 31, "requires" : [ "dwellingLvl1" ] },
 				"dwellingLvl2":   { "id" : 31, "requires" : [ "dwellingLvl1" ] },

+ 2 - 2
config/factions/necropolis.json

@@ -188,10 +188,10 @@
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
 				"horde1Upgr":     { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
 				"ship":           { "id" : 20, "upgrades" : "shipyard" },
 				"ship":           { "id" : 20, "upgrades" : "shipyard" },
 				"special2":       { "requires" : [ "mageGuild1" ],
 				"special2":       { "requires" : [ "mageGuild1" ],
-					"bonuses": [ { "type": "UNDEAD_RAISE_PERCENTAGE", "val": 10, "propagator": "PLAYER_PROPAGATOR" } ] },
+					"bonuses": [ { "type": "UNDEAD_RAISE_PERCENTAGE", "val": 10, "propagator": "PLAYER" } ] },
 				"special3":       { "requires" : [ "dwellingLvl1" ], "marketModes" : ["creature-undead"] },
 				"special3":       { "requires" : [ "dwellingLvl1" ], "marketModes" : ["creature-undead"] },
 				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 },
 				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 },
-					"bonuses": [ { "type": "UNDEAD_RAISE_PERCENTAGE", "val": 20, "propagator": "PLAYER_PROPAGATOR" } ] },
+					"bonuses": [ { "type": "UNDEAD_RAISE_PERCENTAGE", "val": 20, "propagator": "PLAYER" } ] },
 
 
 				"extraTownHall":  { "id" : 27, "requires" : [ "townHall" ], "mode" : "auto" },
 				"extraTownHall":  { "id" : 27, "requires" : [ "townHall" ], "mode" : "auto" },
 				"extraCityHall":  { "id" : 28, "requires" : [ "cityHall" ], "mode" : "auto" },
 				"extraCityHall":  { "id" : 28, "requires" : [ "cityHall" ], "mode" : "auto" },

+ 1 - 1
config/factions/rampart.json

@@ -196,7 +196,7 @@
 				"special3":       { "type" : "treasury", "requires" : [ "horde1" ] },
 				"special3":       { "type" : "treasury", "requires" : [ "horde1" ] },
 				"horde2":         { "id" : 24, "upgrades" : "dwellingLvl5" },
 				"horde2":         { "id" : 24, "upgrades" : "dwellingLvl5" },
 				"horde2Upgr":     { "id" : 25, "upgrades" : "dwellingUpLvl5", "requires" : [ "horde2" ], "mode" : "auto" },
 				"horde2Upgr":     { "id" : 25, "upgrades" : "dwellingUpLvl5", "requires" : [ "horde2" ], "mode" : "auto" },
-				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }, "bonuses": [ { "type": "LUCK", "val": 2, "propagator": "PLAYER_PROPAGATOR" } ] },
+				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }, "bonuses": [ { "type": "LUCK", "val": 2, "propagator": "PLAYER" } ] },
 
 
 				"extraTownHall":  { "id" : 27, "requires" : [ "townHall" ], "mode" : "auto" },
 				"extraTownHall":  { "id" : 27, "requires" : [ "townHall" ], "mode" : "auto" },
 				"extraCityHall":  { "id" : 28, "requires" : [ "cityHall" ], "mode" : "auto" },
 				"extraCityHall":  { "id" : 28, "requires" : [ "cityHall" ], "mode" : "auto" },

+ 1 - 1
config/objects/lighthouse.json

@@ -21,7 +21,7 @@
 						"type" : "MOVEMENT",
 						"type" : "MOVEMENT",
 						"subtype" : "heroMovementSea",
 						"subtype" : "heroMovementSea",
 						"val" : 500,
 						"val" : 500,
-						"propagator": "PLAYER_PROPAGATOR"
+						"propagator": "PLAYER"
 					}
 					}
 				}
 				}
 			}
 			}

+ 39 - 2
config/schemas/bonusInstance.json

@@ -43,7 +43,7 @@
 			"anyOf" : [
 			"anyOf" : [
 				{
 				{
 					"type" : "string",
 					"type" : "string",
-					"enum" : [ "TIMES_HERO_LEVEL", "TIMES_STACK_LEVEL", "DIVIDE_STACK_LEVEL", "BONUS_OWNER_UPDATER", "TIMES_HERO_LEVEL_DIVIDE_STACK_LEVEL" ]
+					"enum" : [ "TIMES_HERO_LEVEL", "TIMES_STACK_LEVEL", "DIVIDE_STACK_LEVEL", "BONUS_OWNER_UPDATER", "TIMES_STACK_SIZE", "TIMES_HERO_LEVEL_DIVIDE_STACK_LEVEL" ]
 				},
 				},
 				{
 				{
 					"description" : "GROWS_WITH_LEVEL updater",
 					"description" : "GROWS_WITH_LEVEL updater",
@@ -107,6 +107,43 @@
 							"description" : "Maximal bonus value"
 							"description" : "Maximal bonus value"
 						}
 						}
 					}
 					}
+				},
+				{
+					"description" : "TIMES_ARMY_SIZE updater",
+					"type" : "object",
+					"required" : ["type"],
+					"additionalProperties" : false,
+					"properties" : {
+						"type" : {
+							"type" : "string",
+							"const" : "TIMES_ARMY_SIZE",
+						},
+						"stepSize" : {
+							"type" : "integer",
+							"minimum" : 1,
+							"description" : "Size of each step, in levels"
+						},
+						"minimum" : {
+							"type" : "integer",
+							"description" : "Minimal bonus value"
+						},
+						"maximum" : {
+							"type" : "integer",
+							"description" : "Maximal bonus value"
+						},
+						"filteredLevel" : {
+							"type" : "integer",
+							"description" : "Level of units to count"
+						},
+						"filteredFaction" : {
+							"type" : "string",
+							"description" : "Faction of units to count"
+						},
+						"filteredCreature" : {
+							"type" : "string",
+							"description" : "Specific unit to count"
+						}
+					}
 				}
 				}
 			]
 			]
 		}
 		}
@@ -138,7 +175,7 @@
 		"propagator" : {
 		"propagator" : {
 			"description" : "propagator",
 			"description" : "propagator",
 			"type" : "string",
 			"type" : "string",
-			"enum" : [ "BATTLE_WIDE", "VISITED_TOWN_AND_VISITOR", "PLAYER_PROPAGATOR", "HERO", "TEAM_PROPAGATOR", "GLOBAL_EFFECT" ]
+			"enum" : [ "BATTLE_WIDE", "TOWN_AND_VISITOR", "PLAYER", "HERO", "TOWN", "ARMY", "TEAM", "GLOBAL_EFFECT" ]
 		},
 		},
 		"updater" : {
 		"updater" : {
 			"$ref" : "#/definitions/updater"
 			"$ref" : "#/definitions/updater"

+ 18 - 6
docs/modders/Bonus/Bonus_Propagators.md

@@ -1,10 +1,22 @@
 # Bonus Propagators
 # Bonus Propagators
 
 
+Propagators allow to propagate bonus effect "upwards". For example, they can be used to make unit ability battle-wide, or to provide some bonuses to hero. See [Bonus System Guide](../Guides/Bonus_System.md) for more information.
+
 ## Available propagators
 ## Available propagators
 
 
-- BATTLE_WIDE: Affects both sides during battle
-- VISITED_TOWN_AND_VISITOR: Used with Legion artifacts (town visited by hero)
-- PLAYER_PROPAGATOR: Bonus will affect all objects owned by player. Used by Statue of Legion.
-- HERO: Bonus will be transferred to hero (for example from stacks in his army).
-- TEAM_PROPAGATOR: Bonus will affect all objects owned by player and his allies.
-- GLOBAL_EFFECT: This effect will influence all creatures, heroes and towns on the map.
+- `ARMY`: Propagators that allow bonuses to be transferred to an army. It is typically used by creature abilities to affect the army that the creature is part of.
+- `HERO`: Similar to `ARMY`, but works only with armies led by heroes.
+- `TOWN`: Similar to `ARMY`, but only affects units that are part of the town garrison.
+- `TOWN_AND_VISITOR`: Propagator that allows the town and the visiting hero to interact. It can be used to propagate the effects of town buildings to the visiting hero outside of combat or the effects of the hero to the town (e.g. Legion artifacts)
+- `BATTLE_WIDE` - Propagator that allows bonuses to affect all entities in battles. It is typically used for creature abilities or artifacts that need to affect either both sides or only the enemy side in combat.
+- `PLAYER`: The bonus affects all objects owned by the player. Used by the Statue of Legion.
+- `TEAM`: The bonus affects all objects owned by the player and their allies.
+- `GLOBAL_EFFECT`: This effect influences all creatures, towns and recruited heroes on the map.
+
+## Deprecated propagators
+
+These propagators are still supported, but in future they may be removed.
+
+- `VISITED_TOWN_AND_VISITOR`: Replaced by `TOWN_AND_VISITOR`
+- `PLAYER_PROPAGATOR`: Replaced by `PLAYER`
+- `TEAM_PROPAGATOR`: Replaced by `TEAM`

+ 61 - 2
docs/modders/Bonus/Bonus_Updaters.md

@@ -84,19 +84,78 @@ Usage:
 
 
 Effect: Updates val to `val = clamp(val * floor(stackSize / stepSize), minimum, maximum)`, where stackSize is total number of creatures in current unit stack
 Effect: Updates val to `val = clamp(val * floor(stackSize / stepSize), minimum, maximum)`, where stackSize is total number of creatures in current unit stack
 
 
-Parameters `minimum` and `maximum` are optional and can be dropped if not needed
+Example of short form with default parameters:
 
 
-Example:
+```json
+"updater" : "TIMES_STACK_SIZE"
+```
+
+Example of long form with custom parameters:
 
 
 ```json
 ```json
 "updater" : {
 "updater" : {
     "type" : "TIMES_STACK_SIZE",
     "type" : "TIMES_STACK_SIZE",
+    
+    // Optional, by default - unlimited
+    "minimum" : 0,
+    
+    // Optional, by default - unlimited
+    "maximum" : 100,
+    
+    // Optional, by default - 1
+    "stepSize" : 2
+}
+```
+
+## TIMES_ARMY_SIZE
+
+Effect: Updates val to `val = clamp(val * floor(stackSize / stepSize), minimum, maximum)`, where stackSize is total number of creatures in hero army that fulful filter
+
+Parameters:
+
+- `minimum`: minimum possible value of the bonus value. Unlimited by default
+- `maximum`: maximum possible value of the bonus value. Unlimited by default
+- `stepSize`: number of units needed to increase updater multiplier by 1
+- `filteredCreature`: identifier of specific unit to filter
+- `filteredLevel`: level of units that need to be counted. Redundant if `filteredCreature` is used
+- `filteredFaction`: faction of units that need to be counted. Redundant if `filteredCreature` is used
+
+Filtering for specific unit:
+
+```json
+"updater" : {
+    "type" : "TIMES_ARMY_SIZE",
+    "filteredCreature" : "pikeman",
+    
+    // Optional, by default - unlimited
     "minimum" : 0,
     "minimum" : 0,
+    
+    // Optional, by default - unlimited
     "maximum" : 100,
     "maximum" : 100,
+    
+    // Optional, by default - 1
     "stepSize" : 2
     "stepSize" : 2
 }
 }
 ```
 ```
 
 
+Filtering for specific faction:
+
+```json
+"updater" : {
+    "type" : "TIMES_STACK_SIZE",
+    "filteredFaction" : "castle"
+}
+```
+
+Filtering for specific unit level:
+
+```json
+"updater" : {
+    "type" : "TIMES_STACK_SIZE",
+    "filteredLevel" : 2
+}
+```
+
 ## BONUS_OWNER_UPDATER
 ## BONUS_OWNER_UPDATER
 
 
 Helper updater for proper functionality of `OPPOSITE_SIDE` limiter
 Helper updater for proper functionality of `OPPOSITE_SIDE` limiter

+ 2 - 2
lib/CCreatureHandler.cpp

@@ -313,8 +313,8 @@ si32 CCreature::maxAmount(const TResources &res) const //how many creatures can
 }
 }
 
 
 CCreature::CCreature()
 CCreature::CCreature()
+	:CBonusSystemNode(BonusNodeType::CREATURE)
 {
 {
-	setNodeType(CBonusSystemNode::CREATURE);
 	fightValue = AIValue = growth = hordeGrowth = ammMin = ammMax = 0;
 	fightValue = AIValue = growth = hordeGrowth = ammMin = ammMax = 0;
 }
 }
 
 
@@ -329,7 +329,7 @@ void CCreature::addBonus(int val, BonusType type, BonusSubtypeID subtype)
 	BonusList & exported = getExportedBonusList();
 	BonusList & exported = getExportedBonusList();
 
 
 	BonusList existing;
 	BonusList existing;
-	exported.getBonuses(existing, selector, Selector::all);
+	exported.getBonuses(existing, selector);
 
 
 	if(existing.empty())
 	if(existing.empty())
 	{
 	{

+ 10 - 8
lib/CCreatureSet.cpp

@@ -707,20 +707,22 @@ void CCreatureSet::serializeJson(JsonSerializeFormat & handler, const std::strin
 	}
 	}
 }
 }
 
 
-CStackInstance::CStackInstance(IGameInfoCallback *cb, bool isHypothetic)
-	: CBonusSystemNode(isHypothetic)
+CStackInstance::CStackInstance(IGameInfoCallback *cb)
+	: CStackInstance(cb, BonusNodeType::STACK_INSTANCE, false)
+{}
+
+CStackInstance::CStackInstance(IGameInfoCallback *cb, BonusNodeType nodeType, bool isHypothetic)
+	: CBonusSystemNode(nodeType, isHypothetic)
 	, CStackBasicDescriptor(nullptr, 0)
 	, CStackBasicDescriptor(nullptr, 0)
 	, CArtifactSet(cb)
 	, CArtifactSet(cb)
 	, GameCallbackHolder(cb)
 	, GameCallbackHolder(cb)
 	, nativeTerrain(this, Selector::type()(BonusType::TERRAIN_NATIVE))
 	, nativeTerrain(this, Selector::type()(BonusType::TERRAIN_NATIVE))
 	, initiative(this, Selector::type()(BonusType::STACKS_SPEED))
 	, initiative(this, Selector::type()(BonusType::STACKS_SPEED))
 	, totalExperience(0)
 	, totalExperience(0)
-{
-	setNodeType(STACK_INSTANCE);
-}
+{}
 
 
 CStackInstance::CStackInstance(IGameInfoCallback *cb, const CreatureID & id, TQuantity Count, bool isHypothetic)
 CStackInstance::CStackInstance(IGameInfoCallback *cb, const CreatureID & id, TQuantity Count, bool isHypothetic)
-	: CStackInstance(cb, false)
+	: CStackInstance(cb, BonusNodeType::STACK_INSTANCE, false)
 {
 {
 	setType(id);
 	setType(id);
 	setCount(Count);
 	setCount(Count);
@@ -833,6 +835,7 @@ void CStackInstance::setCount(TQuantity newCount)
 	}
 	}
 
 
 	CStackBasicDescriptor::setCount(newCount);
 	CStackBasicDescriptor::setCount(newCount);
+	nodeHasChanged();
 }
 }
 
 
 std::string CStackInstance::bonusToString(const std::shared_ptr<Bonus>& bonus) const
 std::string CStackInstance::bonusToString(const std::shared_ptr<Bonus>& bonus) const
@@ -1039,14 +1042,13 @@ CCommanderInstance::CCommanderInstance(IGameInfoCallback *cb)
 {}
 {}
 
 
 CCommanderInstance::CCommanderInstance(IGameInfoCallback *cb, const CreatureID & id)
 CCommanderInstance::CCommanderInstance(IGameInfoCallback *cb, const CreatureID & id)
-	: CStackInstance(cb)
+	: CStackInstance(cb, BonusNodeType::COMMANDER, false)
 	, name("Commando")
 	, name("Commando")
 {
 {
 	alive = true;
 	alive = true;
 	level = 1;
 	level = 1;
 	setCount(1);
 	setCount(1);
 	setType(nullptr);
 	setType(nullptr);
-	setNodeType (CBonusSystemNode::COMMANDER);
 	secondarySkills.resize (ECommander::SPELL_POWER + 1);
 	secondarySkills.resize (ECommander::SPELL_POWER + 1);
 	setType(id);
 	setType(id);
 	//TODO - parse them
 	//TODO - parse them

+ 2 - 1
lib/CCreatureSet.h

@@ -146,7 +146,8 @@ public:
 	virtual int getLevel() const; //different for regular stack and commander
 	virtual int getLevel() const; //different for regular stack and commander
 	CreatureID getCreatureID() const; //-1 if not available
 	CreatureID getCreatureID() const; //-1 if not available
 	std::string getName() const; //plural or singular
 	std::string getName() const; //plural or singular
-	CStackInstance(IGameInfoCallback *cb, bool isHypothetic	= false);
+	CStackInstance(IGameInfoCallback *cb);
+	CStackInstance(IGameInfoCallback *cb, BonusNodeType nodeType, bool isHypothetic = false);
 	CStackInstance(IGameInfoCallback *cb, const CreatureID & id, TQuantity count, bool isHypothetic = false);
 	CStackInstance(IGameInfoCallback *cb, const CreatureID & id, TQuantity count, bool isHypothetic = false);
 	virtual ~CStackInstance() = default;
 	virtual ~CStackInstance() = default;
 
 

+ 1 - 1
lib/CPlayerState.cpp

@@ -22,7 +22,7 @@
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
 PlayerState::PlayerState(IGameInfoCallback *cb)
 PlayerState::PlayerState(IGameInfoCallback *cb)
-	: CBonusSystemNode(PLAYER)
+	: CBonusSystemNode(BonusNodeType::PLAYER)
 	, GameCallbackHolder(cb)
 	, GameCallbackHolder(cb)
 	, color(-1)
 	, color(-1)
 	, human(false)
 	, human(false)

+ 5 - 5
lib/CStack.cpp

@@ -26,7 +26,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 
 ///CStack
 ///CStack
 CStack::CStack(const CStackInstance * Base, const PlayerColor & O, int I, BattleSide Side, const SlotID & S):
 CStack::CStack(const CStackInstance * Base, const PlayerColor & O, int I, BattleSide Side, const SlotID & S):
-	CBonusSystemNode(STACK_BATTLE),
+	CBonusSystemNode(BonusNodeType::STACK_BATTLE),
 	base(Base),
 	base(Base),
 	ID(I),
 	ID(I),
 	typeID(Base->getId()),
 	typeID(Base->getId()),
@@ -40,7 +40,7 @@ CStack::CStack(const CStackInstance * Base, const PlayerColor & O, int I, Battle
 }
 }
 
 
 CStack::CStack():
 CStack::CStack():
-	CBonusSystemNode(STACK_BATTLE),
+	CBonusSystemNode(BonusNodeType::STACK_BATTLE),
 	owner(PlayerColor::NEUTRAL),
 	owner(PlayerColor::NEUTRAL),
 	slot(SlotID(255)),
 	slot(SlotID(255)),
 	initialPosition(BattleHex())
 	initialPosition(BattleHex())
@@ -48,7 +48,7 @@ CStack::CStack():
 }
 }
 
 
 CStack::CStack(const CStackBasicDescriptor * stack, const PlayerColor & O, int I, BattleSide Side, const SlotID & S):
 CStack::CStack(const CStackBasicDescriptor * stack, const PlayerColor & O, int I, BattleSide Side, const SlotID & S):
-	CBonusSystemNode(STACK_BATTLE),
+	CBonusSystemNode(BonusNodeType::STACK_BATTLE),
 	ID(I),
 	ID(I),
 	typeID(stack->getId()),
 	typeID(stack->getId()),
 	baseAmount(stack->getCount()),
 	baseAmount(stack->getCount()),
@@ -134,7 +134,7 @@ std::vector<SpellID> CStack::activeSpells() const
 		return b->type != BonusType::NONE && b->sid.as<SpellID>().toSpell() && !b->sid.as<SpellID>().toSpell()->isAdventure();
 		return b->type != BonusType::NONE && b->sid.as<SpellID>().toSpell() && !b->sid.as<SpellID>().toSpell()->isAdventure();
 	}));
 	}));
 
 
-	TConstBonusListPtr spellEffects = getBonuses(selector, Selector::all, cachingStr.str());
+	TConstBonusListPtr spellEffects = getBonuses(selector, cachingStr.str());
 	for(const auto & it : *spellEffects)
 	for(const auto & it : *spellEffects)
 	{
 	{
 		if(!vstd::contains(ret, it->sid.as<SpellID>()))  //do not duplicate spells with multiple effects
 		if(!vstd::contains(ret, it->sid.as<SpellID>()))  //do not duplicate spells with multiple effects
@@ -155,7 +155,7 @@ const CGHeroInstance * CStack::getMyHero() const
 		return dynamic_cast<const CGHeroInstance *>(base->getArmy());
 		return dynamic_cast<const CGHeroInstance *>(base->getArmy());
 	else //we are attached directly?
 	else //we are attached directly?
 		for(const CBonusSystemNode * n : getParentNodes())
 		for(const CBonusSystemNode * n : getParentNodes())
-			if(n->getNodeType() == HERO)
+			if(n->getNodeType() == BonusNodeType::HERO)
 				return dynamic_cast<const CGHeroInstance *>(n);
 				return dynamic_cast<const CGHeroInstance *>(n);
 
 
 	return nullptr;
 	return nullptr;

+ 2 - 2
lib/battle/BattleInfo.cpp

@@ -467,7 +467,8 @@ BattleInfo::BattleInfo(IGameInfoCallback *cb, const BattleLayout & layout):
 }
 }
 
 
 BattleInfo::BattleInfo(IGameInfoCallback *cb)
 BattleInfo::BattleInfo(IGameInfoCallback *cb)
-	:GameCallbackHolder(cb),
+	:CBonusSystemNode(BonusNodeType::BATTLE_WIDE),
+	GameCallbackHolder(cb),
 	sides({SideInBattle(cb), SideInBattle(cb)}),
 	sides({SideInBattle(cb), SideInBattle(cb)}),
 	layout(std::make_unique<BattleLayout>()),
 	layout(std::make_unique<BattleLayout>()),
 	round(-1),
 	round(-1),
@@ -477,7 +478,6 @@ BattleInfo::BattleInfo(IGameInfoCallback *cb)
 	tacticsSide(BattleSide::NONE),
 	tacticsSide(BattleSide::NONE),
 	tacticDistance(0)
 	tacticDistance(0)
 {
 {
-	setNodeType(BATTLE);
 }
 }
 
 
 BattleLayout BattleInfo::getLayout() const
 BattleLayout BattleInfo::getLayout() const

+ 1 - 1
lib/battle/CBattleInfoCallback.cpp

@@ -1797,7 +1797,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(vstd::RNG & rand, const ba
 		std::stringstream cachingStr;
 		std::stringstream cachingStr;
 		cachingStr << "source_" << vstd::to_underlying(BonusSource::SPELL_EFFECT) << "id_" << spellID.num;
 		cachingStr << "source_" << vstd::to_underlying(BonusSource::SPELL_EFFECT) << "id_" << spellID.num;
 
 
-		if(subject->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(spellID)), Selector::all, cachingStr.str()))
+		if(subject->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(spellID)), cachingStr.str()))
 			continue;
 			continue;
 
 
 		auto spellPtr = spellID.toSpell();
 		auto spellPtr = spellID.toSpell();

+ 3 - 3
lib/battle/CUnitState.cpp

@@ -495,7 +495,7 @@ bool CUnitState::isGhost() const
 
 
 bool CUnitState::isFrozen() const
 bool CUnitState::isFrozen() const
 {
 {
-	return hasBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(SpellID(SpellID::STONE_GAZE))), Selector::all);
+	return hasBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(SpellID(SpellID::STONE_GAZE))));
 }
 }
 
 
 bool CUnitState::isValidTarget(bool allowDead) const
 bool CUnitState::isValidTarget(bool allowDead) const
@@ -948,9 +948,9 @@ CUnitStateDetached::CUnitStateDetached(const IUnitInfo * unit_, const IBonusBear
 {
 {
 }
 }
 
 
-TConstBonusListPtr CUnitStateDetached::getAllBonuses(const CSelector & selector, const CSelector & limit, const std::string & cachingStr) const
+TConstBonusListPtr CUnitStateDetached::getAllBonuses(const CSelector & selector, const std::string & cachingStr) const
 {
 {
-	return bonus->getAllBonuses(selector, limit, cachingStr);
+	return bonus->getAllBonuses(selector, cachingStr);
 }
 }
 
 
 int32_t CUnitStateDetached::getTreeVersion() const
 int32_t CUnitStateDetached::getTreeVersion() const

+ 1 - 1
lib/battle/CUnitState.h

@@ -278,7 +278,7 @@ public:
 
 
 	CUnitStateDetached & operator= (const CUnitState & other);
 	CUnitStateDetached & operator= (const CUnitState & other);
 
 
-	TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit, const std::string & cachingStr = "") const override;
+	TConstBonusListPtr getAllBonuses(const CSelector & selector, const std::string & cachingStr = "") const override;
 
 
 	int32_t getTreeVersion() const override;
 	int32_t getTreeVersion() const override;
 
 

+ 1 - 1
lib/battle/DamageCalculator.cpp

@@ -523,7 +523,7 @@ int DamageCalculator::battleBonusValue(const IBonusBearer * bearer, const CSelec
 						: Selector::effectRange()(BonusLimitEffect::ONLY_MELEE_FIGHT);
 						: Selector::effectRange()(BonusLimitEffect::ONLY_MELEE_FIGHT);
 
 
 	//any regular bonuses or just ones for melee/ranged
 	//any regular bonuses or just ones for melee/ranged
-	return bearer->getBonuses(selector, noLimit.Or(limitMatches))->totalValue();
+	return bearer->getBonuses(selector)->valOfBonuses(noLimit.Or(limitMatches));
 };
 };
 
 
 DamageEstimation DamageCalculator::calculateDmgRange() const
 DamageEstimation DamageCalculator::calculateDmgRange() const

+ 23 - 0
lib/bonuses/BonusEnum.h

@@ -276,6 +276,29 @@ enum class BonusValueType : uint8_t
 #undef BONUS_VALUE
 #undef BONUS_VALUE
 };
 };
 
 
+enum class BonusNodeType
+{
+	NONE = -1,
+	UNKNOWN,
+	STACK_INSTANCE,
+	STACK_BATTLE,
+	ARMY,
+	ARTIFACT,
+	CREATURE,
+	ARTIFACT_INSTANCE,
+	HERO,
+	PLAYER,
+	TEAM,
+	TOWN_AND_VISITOR,
+	BATTLE_WIDE,
+	COMMANDER,
+	GLOBAL_EFFECTS,
+	BOAT,
+	TOWN
+};
+
+
+
 extern DLL_LINKAGE const std::map<std::string, BonusValueType> bonusValueMap;
 extern DLL_LINKAGE const std::map<std::string, BonusValueType> bonusValueMap;
 extern DLL_LINKAGE const std::map<std::string, BonusSource> bonusSourceMap;
 extern DLL_LINKAGE const std::map<std::string, BonusSource> bonusSourceMap;
 extern DLL_LINKAGE const std::map<std::string, BonusDuration::Type> bonusDurationMap;
 extern DLL_LINKAGE const std::map<std::string, BonusDuration::Type> bonusDurationMap;

+ 7 - 5
lib/bonuses/BonusList.cpp

@@ -70,7 +70,10 @@ int BonusList::totalValue(int baseValue) const
 	};
 	};
 
 
 	auto applyPercentageRoundUp = [](int base, int percent) -> int {
 	auto applyPercentageRoundUp = [](int base, int percent) -> int {
-		return (static_cast<int64_t>(base) * (100 + percent) + 99) / 100;
+		if (base >= 0)
+			return (static_cast<int64_t>(base) * (100 + percent) + 99) / 100;
+		else
+			return (static_cast<int64_t>(base) * (100 + percent) - 99) / 100;
 	};
 	};
 
 
 	auto applyPercentageRoundDown = [](int base, int percent) -> int {
 	auto applyPercentageRoundDown = [](int base, int percent) -> int {
@@ -172,11 +175,11 @@ std::shared_ptr<const Bonus> BonusList::getFirst(const CSelector &selector) cons
 	return nullptr;
 	return nullptr;
 }
 }
 
 
-void BonusList::getBonuses(BonusList & out, const CSelector &selector, const CSelector &limit) const
+void BonusList::getBonuses(BonusList & out, const CSelector &selector) const
 {
 {
 	for(const auto & b : bonuses)
 	for(const auto & b : bonuses)
 	{
 	{
-		if(selector(b.get()) && (!limit || ((bool)limit && limit(b.get()))))
+		if(selector(b.get()))
 			out.push_back(b);
 			out.push_back(b);
 	}
 	}
 }
 }
@@ -190,8 +193,7 @@ void BonusList::getAllBonuses(BonusList &out) const
 int BonusList::valOfBonuses(const CSelector &select, int baseValue) const
 int BonusList::valOfBonuses(const CSelector &select, int baseValue) const
 {
 {
 	BonusList ret;
 	BonusList ret;
-	CSelector limit = nullptr;
-	getBonuses(ret, select, limit);
+	getBonuses(ret, select);
 	return ret.totalValue(baseValue);
 	return ret.totalValue(baseValue);
 }
 }
 
 

+ 1 - 1
lib/bonuses/BonusList.h

@@ -54,7 +54,7 @@ public:
 	// BonusList functions
 	// BonusList functions
 	void stackBonuses();
 	void stackBonuses();
 	int totalValue(int baseValue = 0) const;
 	int totalValue(int baseValue = 0) const;
-	void getBonuses(BonusList &out, const CSelector &selector, const CSelector &limit = nullptr) const;
+	void getBonuses(BonusList &out, const CSelector &selector) const;
 	void getAllBonuses(BonusList &out) const;
 	void getAllBonuses(BonusList &out) const;
 
 
 	//special find functions
 	//special find functions

+ 57 - 49
lib/bonuses/CBonusSystemNode.cpp

@@ -18,6 +18,7 @@
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
 constexpr bool cachingEnabled = true;
 constexpr bool cachingEnabled = true;
+static std::atomic<int32_t> globalCounter = 1;
 
 
 std::shared_ptr<Bonus> CBonusSystemNode::getLocalBonus(const CSelector & selector)
 std::shared_ptr<Bonus> CBonusSystemNode::getLocalBonus(const CSelector & selector)
 {
 {
@@ -64,27 +65,23 @@ void CBonusSystemNode::getAllParents(TCNodes & out) const //retrieves list of pa
 	}
 	}
 }
 }
 
 
-void CBonusSystemNode::getAllBonusesRec(BonusList &out, const CSelector & selector) const
+void CBonusSystemNode::getAllBonusesRec(BonusList &out) const
 {
 {
 	BonusList beforeUpdate;
 	BonusList beforeUpdate;
 
 
 	for(const auto * parent : parentsToInherit)
 	for(const auto * parent : parentsToInherit)
-		parent->getAllBonusesRec(beforeUpdate, selector);
+		parent->getAllBonusesRec(beforeUpdate);
 
 
 	bonuses.getAllBonuses(beforeUpdate);
 	bonuses.getAllBonuses(beforeUpdate);
 
 
 	for(const auto & b : beforeUpdate)
 	for(const auto & b : beforeUpdate)
 	{
 	{
-		//We should not run updaters on non-selected bonuses
-		auto updated = selector(b.get()) && b->updater
-			? getUpdatedBonus(b, b->updater)
-			: b;
-
+		auto updated = b->updater ? getUpdatedBonus(b, b->updater) : b;
 		out.push_back(updated);
 		out.push_back(updated);
 	}
 	}
 }
 }
 
 
-TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr) const
+TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, const std::string &cachingStr) const
 {
 {
 	if (cachingEnabled)
 	if (cachingEnabled)
 	{
 	{
@@ -107,7 +104,7 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co
 		{
 		{
 			// Cached bonuses are up-to-date - use shared/read access and compute results
 			// Cached bonuses are up-to-date - use shared/read access and compute results
 			std::shared_lock lock(sync);
 			std::shared_lock lock(sync);
-			cachedBonuses.getBonuses(*ret, selector, limit);
+			cachedBonuses.getBonuses(*ret, selector);
 		}
 		}
 		else
 		else
 		{
 		{
@@ -117,7 +114,7 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co
 			if (cachedLast == nodeChanged)
 			if (cachedLast == nodeChanged)
 			{
 			{
 				// While our thread was waiting, another one have updated bonus tree. Use cached bonuses.
 				// While our thread was waiting, another one have updated bonus tree. Use cached bonuses.
-				cachedBonuses.getBonuses(*ret, selector, limit);
+				cachedBonuses.getBonuses(*ret, selector);
 			}
 			}
 			else
 			else
 			{
 			{
@@ -126,11 +123,11 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co
 
 
 				cachedBonuses.clear();
 				cachedBonuses.clear();
 
 
-				getAllBonusesRec(allBonuses, Selector::all);
+				getAllBonusesRec(allBonuses);
 				limitBonuses(allBonuses, cachedBonuses);
 				limitBonuses(allBonuses, cachedBonuses);
 				cachedBonuses.stackBonuses();
 				cachedBonuses.stackBonuses();
 				cachedLast = nodeChanged;
 				cachedLast = nodeChanged;
-				cachedBonuses.getBonuses(*ret, selector, limit);
+				cachedBonuses.getBonuses(*ret, selector);
 			}
 			}
 		}
 		}
 
 
@@ -151,20 +148,20 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co
 	}
 	}
 	else
 	else
 	{
 	{
-		return getAllBonusesWithoutCaching(selector, limit);
+		return getAllBonusesWithoutCaching(selector);
 	}
 	}
 }
 }
 
 
-TConstBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit) const
+TConstBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelector &selector) const
 {
 {
 	auto ret = std::make_shared<BonusList>();
 	auto ret = std::make_shared<BonusList>();
 
 
 	// Get bonus results without caching enabled.
 	// Get bonus results without caching enabled.
 	BonusList beforeLimiting;
 	BonusList beforeLimiting;
 	BonusList afterLimiting;
 	BonusList afterLimiting;
-	getAllBonusesRec(beforeLimiting, selector);
+	getAllBonusesRec(beforeLimiting);
 	limitBonuses(beforeLimiting, afterLimiting);
 	limitBonuses(beforeLimiting, afterLimiting);
-	afterLimiting.getBonuses(*ret, selector, limit);
+	afterLimiting.getBonuses(*ret, selector);
 	ret->stackBonuses();
 	ret->stackBonuses();
 	return ret;
 	return ret;
 }
 }
@@ -175,21 +172,17 @@ std::shared_ptr<Bonus> CBonusSystemNode::getUpdatedBonus(const std::shared_ptr<B
 	return updater->createUpdatedBonus(b, * this);
 	return updater->createUpdatedBonus(b, * this);
 }
 }
 
 
-CBonusSystemNode::CBonusSystemNode(bool isHypotetic):
-	nodeType(UNKNOWN),
+CBonusSystemNode::CBonusSystemNode(BonusNodeType NodeType, bool isHypotetic):
+	nodeType(NodeType),
 	cachedLast(0),
 	cachedLast(0),
 	nodeChanged(0),
 	nodeChanged(0),
 	isHypotheticNode(isHypotetic)
 	isHypotheticNode(isHypotetic)
 {
 {
 }
 }
 
 
-CBonusSystemNode::CBonusSystemNode(ENodeTypes NodeType):
-	nodeType(NodeType),
-	cachedLast(0),
-	nodeChanged(0),
-	isHypotheticNode(false)
-{
-}
+CBonusSystemNode::CBonusSystemNode(BonusNodeType NodeType):
+	CBonusSystemNode(NodeType, false)
+{}
 
 
 CBonusSystemNode::~CBonusSystemNode()
 CBonusSystemNode::~CBonusSystemNode()
 {
 {
@@ -218,7 +211,7 @@ void CBonusSystemNode::attachTo(CBonusSystemNode & parent)
 		parent.children.push_back(this);
 		parent.children.push_back(this);
 	}
 	}
 
 
-	nodeHasChanged();
+	parent.nodeHasChanged();
 }
 }
 
 
 void CBonusSystemNode::attachToSource(const CBonusSystemNode & parent)
 void CBonusSystemNode::attachToSource(const CBonusSystemNode & parent)
@@ -253,8 +246,8 @@ void CBonusSystemNode::detachFrom(CBonusSystemNode & parent)
 	}
 	}
 	else
 	else
 	{
 	{
-		logBonus->error("Error on Detach. Node %s (nodeType=%d) has not parent %s (nodeType=%d)"
-			, nodeShortInfo(), nodeType, parent.nodeShortInfo(), parent.nodeType);
+		logBonus->error("Error on Detach. Node %s (nodeType=%d) has not parent %s (nodeType=%d)",
+			nodeShortInfo(), static_cast<int>(nodeType), parent.nodeShortInfo(), static_cast<int>(parent.nodeType));
 	}
 	}
 
 
 	if(!isHypothetic())
 	if(!isHypothetic())
@@ -263,11 +256,11 @@ void CBonusSystemNode::detachFrom(CBonusSystemNode & parent)
 			parent.children -= this;
 			parent.children -= this;
 		else
 		else
 		{
 		{
-			logBonus->error("Error on Detach. Node %s (nodeType=%d) is not a child of %s (nodeType=%d)"
-							, nodeShortInfo(), nodeType, parent.nodeShortInfo(), parent.nodeType);
+			logBonus->error("Error on Detach. Node %s (nodeType=%d) is not a child of %s (nodeType=%d)",
+				nodeShortInfo(), static_cast<int>(nodeType), parent.nodeShortInfo(), static_cast<int>(parent.nodeType));
 		}
 		}
 	}
 	}
-	nodeHasChanged();
+	parent.nodeHasChanged();
 }
 }
 
 
 
 
@@ -287,8 +280,8 @@ void CBonusSystemNode::detachFromSource(const CBonusSystemNode & parent)
 	}
 	}
 	else
 	else
 	{
 	{
-		logBonus->error("Error on Detach. Node %s (nodeType=%d) has not parent %s (nodeType=%d)"
-			, nodeShortInfo(), nodeType, parent.nodeShortInfo(), parent.nodeType);
+		logBonus->error("Error on Detach. Node %s (nodeType=%d) has not parent %s (nodeType=%d)",
+			nodeShortInfo(), static_cast<int>(nodeType), parent.nodeShortInfo(), static_cast<int>(parent.nodeType));
 	}
 	}
 
 
 	nodeHasChanged();
 	nodeHasChanged();
@@ -304,7 +297,7 @@ void CBonusSystemNode::removeBonusesRecursive(const CSelector & s)
 void CBonusSystemNode::reduceBonusDurations(const CSelector &s)
 void CBonusSystemNode::reduceBonusDurations(const CSelector &s)
 {
 {
 	BonusList bl;
 	BonusList bl;
-	exportedBonuses.getBonuses(bl, s, Selector::all);
+	exportedBonuses.getBonuses(bl, s);
 	for(const auto & b : bl)
 	for(const auto & b : bl)
 	{
 	{
 		b->turnsRemain--;
 		b->turnsRemain--;
@@ -355,7 +348,7 @@ void CBonusSystemNode::removeBonus(const std::shared_ptr<Bonus>& b)
 void CBonusSystemNode::removeBonuses(const CSelector & selector)
 void CBonusSystemNode::removeBonuses(const CSelector & selector)
 {
 {
 	BonusList toRemove;
 	BonusList toRemove;
-	exportedBonuses.getBonuses(toRemove, selector, Selector::all);
+	exportedBonuses.getBonuses(toRemove, selector);
 	for(const auto & bonus : toRemove)
 	for(const auto & bonus : toRemove)
 		removeBonus(bonus);
 		removeBonus(bonus);
 }
 }
@@ -364,9 +357,10 @@ bool CBonusSystemNode::actsAsBonusSourceOnly() const
 {
 {
 	switch(nodeType)
 	switch(nodeType)
 	{
 	{
-	case CREATURE:
-	case ARTIFACT:
-	case ARTIFACT_INSTANCE:
+	case BonusNodeType::CREATURE:
+	case BonusNodeType::ARTIFACT:
+	case BonusNodeType::ARTIFACT_INSTANCE:
+	case BonusNodeType::BOAT:
 		return true;
 		return true;
 	default:
 	default:
 		return false;
 		return false;
@@ -382,7 +376,7 @@ void CBonusSystemNode::propagateBonus(const std::shared_ptr<Bonus> & b, const CB
 			: b;
 			: b;
 		bonuses.push_back(propagated);
 		bonuses.push_back(propagated);
 		logBonus->trace("#$# %s #propagated to# %s",  propagated->Description(nullptr), nodeName());
 		logBonus->trace("#$# %s #propagated to# %s",  propagated->Description(nullptr), nodeName());
-		nodeHasChanged();
+		invalidateChildrenNodes(globalCounter);
 	}
 	}
 
 
 	TNodes lchildren;
 	TNodes lchildren;
@@ -400,15 +394,17 @@ void CBonusSystemNode::unpropagateBonus(const std::shared_ptr<Bonus> & b)
 		else
 		else
 			logBonus->warn("Attempt to remove #$# %s, which is not propagated to %s", b->Description(nullptr), nodeName());
 			logBonus->warn("Attempt to remove #$# %s, which is not propagated to %s", b->Description(nullptr), nodeName());
 
 
-		bonuses.remove_if([this, b](const auto & bonus)
+		bonuses.remove_if([b](const auto & bonus)
 		{
 		{
 			if (bonus->propagationUpdater && bonus->propagationUpdater == b->propagationUpdater)
 			if (bonus->propagationUpdater && bonus->propagationUpdater == b->propagationUpdater)
 			{
 			{
-				nodeHasChanged();
+
 				return true;
 				return true;
 			}
 			}
 			return false;
 			return false;
 		});
 		});
+
+		invalidateChildrenNodes(globalCounter);
 	}
 	}
 
 
 	TNodes lchildren;
 	TNodes lchildren;
@@ -550,7 +546,7 @@ void CBonusSystemNode::exportBonuses()
 		exportBonus(b);
 		exportBonus(b);
 }
 }
 
 
-CBonusSystemNode::ENodeTypes CBonusSystemNode::getNodeType() const
+BonusNodeType CBonusSystemNode::getNodeType() const
 {
 {
 	return nodeType;
 	return nodeType;
 }
 }
@@ -560,11 +556,6 @@ const TCNodesVector& CBonusSystemNode::getParentNodes() const
 	return parentsToInherit;
 	return parentsToInherit;
 }
 }
 
 
-void CBonusSystemNode::setNodeType(CBonusSystemNode::ENodeTypes type)
-{
-	nodeType = type;
-}
-
 BonusList & CBonusSystemNode::getExportedBonusList()
 BonusList & CBonusSystemNode::getExportedBonusList()
 {
 {
 	return exportedBonuses;
 	return exportedBonuses;
@@ -612,11 +603,26 @@ void CBonusSystemNode::limitBonuses(const BonusList &allBonuses, BonusList &out)
 
 
 void CBonusSystemNode::nodeHasChanged()
 void CBonusSystemNode::nodeHasChanged()
 {
 {
-	static std::atomic<int32_t> globalCounter = 1;
-
 	invalidateChildrenNodes(++globalCounter);
 	invalidateChildrenNodes(++globalCounter);
 }
 }
 
 
+void CBonusSystemNode::recomputePropagationUpdaters(const CBonusSystemNode & source)
+{
+	for(const auto & b : exportedBonuses)
+	{
+		if (b->propagator && b->propagationUpdater)
+		{
+			unpropagateBonus(b);
+			propagateBonus(b,  source);
+		}
+	}
+
+	for(const CBonusSystemNode * parent : source.parentsToInherit)
+		if (parent->actsAsBonusSourceOnly())
+			recomputePropagationUpdaters(*parent);
+
+}
+
 void CBonusSystemNode::invalidateChildrenNodes(int32_t changeCounter)
 void CBonusSystemNode::invalidateChildrenNodes(int32_t changeCounter)
 {
 {
 	if (nodeChanged == changeCounter)
 	if (nodeChanged == changeCounter)
@@ -624,6 +630,8 @@ void CBonusSystemNode::invalidateChildrenNodes(int32_t changeCounter)
 
 
 	nodeChanged = changeCounter;
 	nodeChanged = changeCounter;
 
 
+	recomputePropagationUpdaters(*this);
+
 	for(CBonusSystemNode * child : children)
 	for(CBonusSystemNode * child : children)
 		child->invalidateChildrenNodes(changeCounter);
 		child->invalidateChildrenNodes(changeCounter);
 }
 }

+ 8 - 15
lib/bonuses/CBonusSystemNode.h

@@ -26,13 +26,6 @@ using TCNodesVector = std::vector<const CBonusSystemNode *>;
 class DLL_LINKAGE CBonusSystemNode : public virtual IBonusBearer, public virtual Serializeable, public boost::noncopyable
 class DLL_LINKAGE CBonusSystemNode : public virtual IBonusBearer, public virtual Serializeable, public boost::noncopyable
 {
 {
 public:
 public:
-	enum ENodeTypes
-	{
-		NONE = -1, 
-		UNKNOWN, STACK_INSTANCE, STACK_BATTLE, SPECIALTY, ARTIFACT, CREATURE, ARTIFACT_INSTANCE, HERO, PLAYER, TEAM,
-		TOWN_AND_VISITOR, BATTLE, COMMANDER, GLOBAL_EFFECTS, ALL_CREATURES, TOWN
-	};
-
 	struct HashStringCompare {
 	struct HashStringCompare {
 		static size_t hash(const std::string& data)
 		static size_t hash(const std::string& data)
 		{
 		{
@@ -53,7 +46,7 @@ private:
 	TNodesVector parentsToPropagate; // we may attach our bonuses to them
 	TNodesVector parentsToPropagate; // we may attach our bonuses to them
 	TNodesVector children;
 	TNodesVector children;
 
 
-	ENodeTypes nodeType;
+	BonusNodeType nodeType;
 	bool isHypotheticNode;
 	bool isHypotheticNode;
 
 
 	mutable BonusList cachedBonuses;
 	mutable BonusList cachedBonuses;
@@ -69,8 +62,8 @@ private:
 	mutable RequestsMap cachedRequests;
 	mutable RequestsMap cachedRequests;
 	mutable std::shared_mutex sync;
 	mutable std::shared_mutex sync;
 
 
-	void getAllBonusesRec(BonusList &out, const CSelector & selector) const;
-	TConstBonusListPtr getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit) const;
+	void getAllBonusesRec(BonusList &out) const;
+	TConstBonusListPtr getAllBonusesWithoutCaching(const CSelector &selector) const;
 	std::shared_ptr<Bonus> getUpdatedBonus(const std::shared_ptr<Bonus> & b, const TUpdaterPtr & updater) const;
 	std::shared_ptr<Bonus> getUpdatedBonus(const std::shared_ptr<Bonus> & b, const TUpdaterPtr & updater) const;
 	void limitBonuses(const BonusList &allBonuses, BonusList &out) const; //out will bo populed with bonuses that are not limited here
 	void limitBonuses(const BonusList &allBonuses, BonusList &out) const; //out will bo populed with bonuses that are not limited here
 
 
@@ -82,6 +75,7 @@ private:
 
 
 	void propagateBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & source);
 	void propagateBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & source);
 	void unpropagateBonus(const std::shared_ptr<Bonus> & b);
 	void unpropagateBonus(const std::shared_ptr<Bonus> & b);
+	void recomputePropagationUpdaters(const CBonusSystemNode & source);
 	bool actsAsBonusSourceOnly() const;
 	bool actsAsBonusSourceOnly() const;
 
 
 	void newRedDescendant(CBonusSystemNode & descendant) const; //propagation needed
 	void newRedDescendant(CBonusSystemNode & descendant) const; //propagation needed
@@ -96,11 +90,11 @@ protected:
 	void exportBonuses();
 	void exportBonuses();
 
 
 public:
 public:
-	explicit CBonusSystemNode(bool isHypotetic = false);
-	explicit CBonusSystemNode(ENodeTypes NodeType);
+	explicit CBonusSystemNode(BonusNodeType nodeType, bool isHypotetic);
+	explicit CBonusSystemNode(BonusNodeType nodeType);
 	virtual ~CBonusSystemNode();
 	virtual ~CBonusSystemNode();
 
 
-	TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const override;
+	TConstBonusListPtr getAllBonuses(const CSelector &selector, const std::string &cachingStr = "") const override;
 	void getParents(TCNodes &out) const;  //retrieves list of parent nodes (nodes to inherit bonuses from),
 	void getParents(TCNodes &out) const;  //retrieves list of parent nodes (nodes to inherit bonuses from),
 
 
 	/// Returns first bonus matching selector
 	/// Returns first bonus matching selector
@@ -129,8 +123,7 @@ public:
 
 
 	BonusList & getExportedBonusList();
 	BonusList & getExportedBonusList();
 	const BonusList & getExportedBonusList() const;
 	const BonusList & getExportedBonusList() const;
-	CBonusSystemNode::ENodeTypes getNodeType() const;
-	void setNodeType(CBonusSystemNode::ENodeTypes type);
+	BonusNodeType getNodeType() const;
 	const TCNodesVector & getParentNodes() const;
 	const TCNodesVector & getParentNodes() const;
 
 
 	void nodeHasChanged();
 	void nodeHasChanged();

+ 3 - 13
lib/bonuses/IBonusBearer.cpp

@@ -17,7 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 
 int IBonusBearer::valOfBonuses(const CSelector &selector, const std::string &cachingStr, int baseValue) const
 int IBonusBearer::valOfBonuses(const CSelector &selector, const std::string &cachingStr, int baseValue) const
 {
 {
-	TConstBonusListPtr hlp = getAllBonuses(selector, nullptr, cachingStr);
+	TConstBonusListPtr hlp = getAllBonuses(selector, cachingStr);
 	return hlp->totalValue(baseValue);
 	return hlp->totalValue(baseValue);
 }
 }
 
 
@@ -27,19 +27,9 @@ bool IBonusBearer::hasBonus(const CSelector &selector, const std::string &cachin
 	return !getBonuses(selector, cachingStr)->empty();
 	return !getBonuses(selector, cachingStr)->empty();
 }
 }
 
 
-bool IBonusBearer::hasBonus(const CSelector &selector, const CSelector &limit, const std::string &cachingStr) const
-{
-	return !getBonuses(selector, limit, cachingStr)->empty();
-}
-
 TConstBonusListPtr IBonusBearer::getBonuses(const CSelector &selector, const std::string &cachingStr) const
 TConstBonusListPtr IBonusBearer::getBonuses(const CSelector &selector, const std::string &cachingStr) const
 {
 {
-	return getAllBonuses(selector, nullptr, cachingStr);
-}
-
-TConstBonusListPtr IBonusBearer::getBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr) const
-{
-	return getAllBonuses(selector, limit, cachingStr);
+	return getAllBonuses(selector, cachingStr);
 }
 }
 
 
 TConstBonusListPtr IBonusBearer::getBonusesFrom(BonusSource source) const
 TConstBonusListPtr IBonusBearer::getBonusesFrom(BonusSource source) const
@@ -120,7 +110,7 @@ bool IBonusBearer::hasBonusFrom(BonusSource source) const
 
 
 std::shared_ptr<const Bonus> IBonusBearer::getBonus(const CSelector &selector) const
 std::shared_ptr<const Bonus> IBonusBearer::getBonus(const CSelector &selector) const
 {
 {
-	auto bonuses = getAllBonuses(selector, Selector::all);
+	auto bonuses = getAllBonuses(selector);
 	return bonuses->getFirst(Selector::all);
 	return bonuses->getFirst(Selector::all);
 }
 }
 
 

+ 1 - 3
lib/bonuses/IBonusBearer.h

@@ -20,11 +20,9 @@ public:
 	// * selector is predicate that tests if Bonus matches our criteria
 	// * selector is predicate that tests if Bonus matches our criteria
 	IBonusBearer() = default;
 	IBonusBearer() = default;
 	virtual ~IBonusBearer() = default;
 	virtual ~IBonusBearer() = default;
-	virtual TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = {}) const = 0;
+	virtual TConstBonusListPtr getAllBonuses(const CSelector &selector, const std::string &cachingStr = {}) const = 0;
 	int valOfBonuses(const CSelector &selector, const std::string &cachingStr = {}, int baseValue = 0) const;
 	int valOfBonuses(const CSelector &selector, const std::string &cachingStr = {}, int baseValue = 0) const;
 	bool hasBonus(const CSelector &selector, const std::string &cachingStr = {}) const;
 	bool hasBonus(const CSelector &selector, const std::string &cachingStr = {}) const;
-	bool hasBonus(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = {}) const;
-	TConstBonusListPtr getBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = {}) const;
 	TConstBonusListPtr getBonuses(const CSelector &selector, const std::string &cachingStr = {}) const;
 	TConstBonusListPtr getBonuses(const CSelector &selector, const std::string &cachingStr = {}) const;
 
 
 	std::shared_ptr<const Bonus> getBonus(const CSelector &selector) const; //returns any bonus visible on node that matches (or nullptr if none matches)
 	std::shared_ptr<const Bonus> getBonus(const CSelector &selector) const; //returns any bonus visible on node that matches (or nullptr if none matches)

+ 9 - 9
lib/bonuses/Limiters.cpp

@@ -45,7 +45,7 @@ static const CStack * retrieveStackBattle(const CBonusSystemNode * node)
 {
 {
 	switch(node->getNodeType())
 	switch(node->getNodeType())
 	{
 	{
-	case CBonusSystemNode::STACK_BATTLE:
+	case BonusNodeType::STACK_BATTLE:
 		return dynamic_cast<const CStack *>(node);
 		return dynamic_cast<const CStack *>(node);
 	default:
 	default:
 		return nullptr;
 		return nullptr;
@@ -56,9 +56,9 @@ static const CStackInstance * retrieveStackInstance(const CBonusSystemNode * nod
 {
 {
 	switch(node->getNodeType())
 	switch(node->getNodeType())
 	{
 	{
-	case CBonusSystemNode::STACK_INSTANCE:
+	case BonusNodeType::STACK_INSTANCE:
 		return (dynamic_cast<const CStackInstance *>(node));
 		return (dynamic_cast<const CStackInstance *>(node));
-	case CBonusSystemNode::STACK_BATTLE:
+	case BonusNodeType::STACK_BATTLE:
 		return (dynamic_cast<const CStack *>(node))->base;
 		return (dynamic_cast<const CStack *>(node))->base;
 	default:
 	default:
 		return nullptr;
 		return nullptr;
@@ -69,9 +69,9 @@ static const CCreature * retrieveCreature(const CBonusSystemNode *node)
 {
 {
 	switch(node->getNodeType())
 	switch(node->getNodeType())
 	{
 	{
-	case CBonusSystemNode::CREATURE:
+	case BonusNodeType::CREATURE:
 		return (dynamic_cast<const CCreature *>(node));
 		return (dynamic_cast<const CCreature *>(node));
-	case CBonusSystemNode::STACK_BATTLE:
+	case BonusNodeType::STACK_BATTLE:
 		return (dynamic_cast<const CStack *>(node))->unitType();
 		return (dynamic_cast<const CStack *>(node))->unitType();
 	default:
 	default:
 		const CStackInstance * csi = retrieveStackInstance(node);
 		const CStackInstance * csi = retrieveStackInstance(node);
@@ -262,7 +262,7 @@ CreatureTerrainLimiter::CreatureTerrainLimiter(TerrainId terrain):
 
 
 ILimiter::EDecision CreatureTerrainLimiter::limit(const BonusLimitationContext &context) const
 ILimiter::EDecision CreatureTerrainLimiter::limit(const BonusLimitationContext &context) const
 {
 {
-	if (context.node.getNodeType() != CBonusSystemNode::STACK_BATTLE && context.node.getNodeType() != CBonusSystemNode::STACK_INSTANCE)
+	if (context.node.getNodeType() != BonusNodeType::STACK_BATTLE && context.node.getNodeType() != BonusNodeType::STACK_INSTANCE)
 		return ILimiter::EDecision::NOT_APPLICABLE;
 		return ILimiter::EDecision::NOT_APPLICABLE;
 
 
 	if (terrainType == ETerrainId::NATIVE_TERRAIN)
 	if (terrainType == ETerrainId::NATIVE_TERRAIN)
@@ -277,7 +277,7 @@ ILimiter::EDecision CreatureTerrainLimiter::limit(const BonusLimitationContext &
 
 
 		// TODO: CStack and CStackInstance need some common base type that represents any stack
 		// TODO: CStack and CStackInstance need some common base type that represents any stack
 		// Closest existing class is ACreature, however it is also used as base for CCreature, which is not a stack
 		// Closest existing class is ACreature, however it is also used as base for CCreature, which is not a stack
-		if (context.node.getNodeType() == CBonusSystemNode::STACK_BATTLE)
+		if (context.node.getNodeType() == BonusNodeType::STACK_BATTLE)
 		{
 		{
 			const auto * unit = dynamic_cast<const CStack *>(&context.node);
 			const auto * unit = dynamic_cast<const CStack *>(&context.node);
 			auto unitNativeTerrain = unit->getFactionID().toEntity(LIBRARY)->getNativeTerrain();
 			auto unitNativeTerrain = unit->getFactionID().toEntity(LIBRARY)->getNativeTerrain();
@@ -294,7 +294,7 @@ ILimiter::EDecision CreatureTerrainLimiter::limit(const BonusLimitationContext &
 	}
 	}
 	else
 	else
 	{
 	{
-		if (context.node.getNodeType() == CBonusSystemNode::STACK_BATTLE)
+		if (context.node.getNodeType() == BonusNodeType::STACK_BATTLE)
 		{
 		{
 			const auto * unit = dynamic_cast<const CStack *>(&context.node);
 			const auto * unit = dynamic_cast<const CStack *>(&context.node);
 			if (unit->getCurrentTerrain() == terrainType)
 			if (unit->getCurrentTerrain() == terrainType)
@@ -462,7 +462,7 @@ ILimiter::EDecision RankRangeLimiter::limit(const BonusLimitationContext &contex
 	const CStackInstance * csi = retrieveStackInstance(&context.node);
 	const CStackInstance * csi = retrieveStackInstance(&context.node);
 	if(csi)
 	if(csi)
 	{
 	{
-		if (csi->getNodeType() == CBonusSystemNode::COMMANDER) //no stack exp bonuses for commander creatures
+		if (csi->getNodeType() == BonusNodeType::COMMANDER) //no stack exp bonuses for commander creatures
 			return ILimiter::EDecision::DISCARD;
 			return ILimiter::EDecision::DISCARD;
 		if (csi->getExpRank() > minRank && csi->getExpRank() < maxRank)
 		if (csi->getExpRank() > minRank && csi->getExpRank() < maxRank)
 			return ILimiter::EDecision::ACCEPT;
 			return ILimiter::EDecision::ACCEPT;

+ 26 - 12
lib/bonuses/Propagators.cpp

@@ -16,37 +16,51 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 
 const std::map<std::string, TPropagatorPtr> bonusPropagatorMap =
 const std::map<std::string, TPropagatorPtr> bonusPropagatorMap =
 {
 {
-	{"BATTLE_WIDE", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::BATTLE)},
-	{"VISITED_TOWN_AND_VISITOR", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::TOWN_AND_VISITOR)},
-	{"PLAYER_PROPAGATOR", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::PLAYER)},
-	{"HERO", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::HERO)},
-	{"TEAM_PROPAGATOR", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::TEAM)}, //untested
-	{"GLOBAL_EFFECT", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::GLOBAL_EFFECTS)}
-}; //untested
+	{"BATTLE_WIDE", std::make_shared<CPropagatorNodeType>(BonusNodeType::BATTLE_WIDE)},
+	{"TOWN_AND_VISITOR", std::make_shared<CPropagatorNodeType>(BonusNodeType::TOWN_AND_VISITOR)},
+	{"PLAYER", std::make_shared<CPropagatorNodeType>(BonusNodeType::PLAYER)},
+	{"HERO", std::make_shared<CPropagatorNodeType>(BonusNodeType::HERO)},
+	{"TOWN", std::make_shared<CPropagatorNodeType>(BonusNodeType::TOWN)},
+	{"ARMY", std::make_shared<CPropagatorNodeType>(BonusNodeType::ARMY)},
+	{"TEAM", std::make_shared<CPropagatorNodeType>(BonusNodeType::TEAM)},
+	{"GLOBAL_EFFECT", std::make_shared<CPropagatorNodeType>(BonusNodeType::GLOBAL_EFFECTS)},
+
+	// deprecated, for compatibility
+	{"VISITED_TOWN_AND_VISITOR", std::make_shared<CPropagatorNodeType>(BonusNodeType::TOWN_AND_VISITOR)},
+	{"PLAYER_PROPAGATOR", std::make_shared<CPropagatorNodeType>(BonusNodeType::PLAYER)},
+	{"TEAM_PROPAGATOR", std::make_shared<CPropagatorNodeType>(BonusNodeType::TEAM)},
+
+};
 
 
 bool IPropagator::shouldBeAttached(CBonusSystemNode *dest) const
 bool IPropagator::shouldBeAttached(CBonusSystemNode *dest) const
 {
 {
 	return false;
 	return false;
 }
 }
 
 
-CBonusSystemNode::ENodeTypes IPropagator::getPropagatorType() const
+BonusNodeType IPropagator::getPropagatorType() const
 {
 {
-	return CBonusSystemNode::ENodeTypes::NONE;
+	return BonusNodeType::NONE;
 }
 }
 
 
-CPropagatorNodeType::CPropagatorNodeType(CBonusSystemNode::ENodeTypes NodeType)
+CPropagatorNodeType::CPropagatorNodeType(BonusNodeType NodeType)
 	: nodeType(NodeType)
 	: nodeType(NodeType)
 {
 {
 }
 }
 
 
-CBonusSystemNode::ENodeTypes CPropagatorNodeType::getPropagatorType() const
+BonusNodeType CPropagatorNodeType::getPropagatorType() const
 {
 {
 	return nodeType;
 	return nodeType;
 }
 }
 
 
 bool CPropagatorNodeType::shouldBeAttached(CBonusSystemNode *dest) const
 bool CPropagatorNodeType::shouldBeAttached(CBonusSystemNode *dest) const
 {
 {
-	return nodeType == dest->getNodeType();
+	if (nodeType == dest->getNodeType())
+		return true;
+
+	if (nodeType == BonusNodeType::ARMY)
+		return dest->getNodeType() == BonusNodeType::HERO || dest->getNodeType() == BonusNodeType::TOWN;
+
+	return false;
 }
 }
 
 
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END

+ 4 - 4
lib/bonuses/Propagators.h

@@ -23,7 +23,7 @@ class DLL_LINKAGE IPropagator : public Serializeable
 public:
 public:
 	virtual ~IPropagator() = default;
 	virtual ~IPropagator() = default;
 	virtual bool shouldBeAttached(CBonusSystemNode *dest) const;
 	virtual bool shouldBeAttached(CBonusSystemNode *dest) const;
-	virtual CBonusSystemNode::ENodeTypes getPropagatorType() const;
+	virtual BonusNodeType getPropagatorType() const;
 
 
 	template <typename Handler> void serialize(Handler &h)
 	template <typename Handler> void serialize(Handler &h)
 	{}
 	{}
@@ -31,12 +31,12 @@ public:
 
 
 class DLL_LINKAGE CPropagatorNodeType : public IPropagator
 class DLL_LINKAGE CPropagatorNodeType : public IPropagator
 {
 {
-	CBonusSystemNode::ENodeTypes nodeType;
+	BonusNodeType nodeType;
 
 
 public:
 public:
-	CPropagatorNodeType(CBonusSystemNode::ENodeTypes NodeType = CBonusSystemNode::ENodeTypes::UNKNOWN);
+	CPropagatorNodeType(BonusNodeType NodeType = BonusNodeType::UNKNOWN);
 	bool shouldBeAttached(CBonusSystemNode *dest) const override;
 	bool shouldBeAttached(CBonusSystemNode *dest) const override;
-	CBonusSystemNode::ENodeTypes getPropagatorType() const override;
+	BonusNodeType getPropagatorType() const override;
 
 
 	template <typename Handler> void serialize(Handler &h)
 	template <typename Handler> void serialize(Handler &h)
 	{
 	{

+ 47 - 10
lib/bonuses/Updaters.cpp

@@ -41,7 +41,7 @@ GrowsWithLevelUpdater::GrowsWithLevelUpdater(int valPer20, int stepSize)
 
 
 std::shared_ptr<Bonus> GrowsWithLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
 std::shared_ptr<Bonus> GrowsWithLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
 {
 {
-	if(context.getNodeType() == CBonusSystemNode::HERO)
+	if(context.getNodeType() == BonusNodeType::HERO)
 	{
 	{
 		int level = dynamic_cast<const CGHeroInstance &>(context).level;
 		int level = dynamic_cast<const CGHeroInstance &>(context).level;
 		int steps = stepSize ? level / stepSize : level;
 		int steps = stepSize ? level / stepSize : level;
@@ -74,7 +74,7 @@ JsonNode GrowsWithLevelUpdater::toJsonNode() const
 
 
 std::shared_ptr<Bonus> TimesHeroLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
 std::shared_ptr<Bonus> TimesHeroLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
 {
 {
-	if(context.getNodeType() == CBonusSystemNode::HERO)
+	if(context.getNodeType() == BonusNodeType::HERO)
 	{
 	{
 		int level = dynamic_cast<const CGHeroInstance &>(context).level;
 		int level = dynamic_cast<const CGHeroInstance &>(context).level;
 		auto newBonus = std::make_shared<Bonus>(*b);
 		auto newBonus = std::make_shared<Bonus>(*b);
@@ -96,7 +96,7 @@ JsonNode TimesHeroLevelUpdater::toJsonNode() const
 
 
 std::shared_ptr<Bonus> TimesHeroLevelDivideStackLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
 std::shared_ptr<Bonus> TimesHeroLevelDivideStackLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
 {
 {
-	if(context.getNodeType() == CBonusSystemNode::HERO)
+	if(context.getNodeType() == BonusNodeType::HERO)
 	{
 	{
 		auto newBonus = TimesHeroLevelUpdater::createUpdatedBonus(b, context);
 		auto newBonus = TimesHeroLevelUpdater::createUpdatedBonus(b, context);
 		newBonus->updater = divideStackLevel;
 		newBonus->updater = divideStackLevel;
@@ -119,19 +119,18 @@ std::shared_ptr<Bonus> TimesStackSizeUpdater::apply(const std::shared_ptr<Bonus>
 {
 {
 	auto newBonus = std::make_shared<Bonus>(*b);
 	auto newBonus = std::make_shared<Bonus>(*b);
 	newBonus->val *= std::clamp(count / stepSize, minimum, maximum);
 	newBonus->val *= std::clamp(count / stepSize, minimum, maximum);
-	newBonus->updater = nullptr; // prevent double-apply
 	return newBonus;
 	return newBonus;
 }
 }
 
 
 std::shared_ptr<Bonus> TimesStackSizeUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
 std::shared_ptr<Bonus> TimesStackSizeUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
 {
 {
-	if(context.getNodeType() == CBonusSystemNode::STACK_INSTANCE || context.getNodeType() == CBonusSystemNode::COMMANDER)
+	if(context.getNodeType() == BonusNodeType::STACK_INSTANCE || context.getNodeType() == BonusNodeType::COMMANDER)
 	{
 	{
 		int count = dynamic_cast<const CStackInstance &>(context).getCount();
 		int count = dynamic_cast<const CStackInstance &>(context).getCount();
 		return apply(b, count);
 		return apply(b, count);
 	}
 	}
 
 
-	if(context.getNodeType() == CBonusSystemNode::STACK_BATTLE)
+	if(context.getNodeType() == BonusNodeType::STACK_BATTLE)
 	{
 	{
 		const auto & stack = dynamic_cast<const CStack &>(context);
 		const auto & stack = dynamic_cast<const CStack &>(context);
 		return apply(b, stack.getCount());
 		return apply(b, stack.getCount());
@@ -149,6 +148,44 @@ JsonNode TimesStackSizeUpdater::toJsonNode() const
 	return JsonNode("TIMES_STACK_SIZE");
 	return JsonNode("TIMES_STACK_SIZE");
 }
 }
 
 
+std::shared_ptr<Bonus> TimesArmySizeUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
+{
+	if(context.getNodeType() == BonusNodeType::ARMY || context.getNodeType() == BonusNodeType::HERO || context.getNodeType() == BonusNodeType::TOWN)
+	{
+		const auto & army = dynamic_cast<const CArmedInstance &>(context);
+		int totalSize = 0;
+		for (const auto & unit : army.Slots())
+		{
+			if (filteredCreature.hasValue() && filteredCreature != unit.second->getCreatureID())
+				continue;
+
+			if (filteredFaction.hasValue() && filteredFaction != unit.second->getFactionID())
+				continue;
+
+			if (filteredLevel != -1 && filteredLevel != unit.second->getLevel())
+				continue;
+
+			totalSize += unit.second->getCount();
+		}
+
+		auto newBonus = std::make_shared<Bonus>(*b);
+		newBonus->val *= std::clamp(totalSize / stepSize, minimum, maximum);
+		return newBonus;
+	}
+	return b;
+}
+
+std::string TimesArmySizeUpdater::toString() const
+{
+	return "TimesArmySizeUpdater";
+}
+
+JsonNode TimesArmySizeUpdater::toJsonNode() const
+{
+	return JsonNode("TIMES_ARMY_SIZE");
+}
+
+
 std::shared_ptr<Bonus> TimesStackLevelUpdater::apply(const std::shared_ptr<Bonus> & b, int level) const
 std::shared_ptr<Bonus> TimesStackLevelUpdater::apply(const std::shared_ptr<Bonus> & b, int level) const
 {
 {
 	auto newBonus = std::make_shared<Bonus>(*b);
 	auto newBonus = std::make_shared<Bonus>(*b);
@@ -159,13 +196,13 @@ std::shared_ptr<Bonus> TimesStackLevelUpdater::apply(const std::shared_ptr<Bonus
 
 
 std::shared_ptr<Bonus> TimesStackLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
 std::shared_ptr<Bonus> TimesStackLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
 {
 {
-	if(context.getNodeType() == CBonusSystemNode::STACK_INSTANCE || context.getNodeType() == CBonusSystemNode::COMMANDER)
+	if(context.getNodeType() == BonusNodeType::STACK_INSTANCE || context.getNodeType() == BonusNodeType::COMMANDER)
 	{
 	{
 		int level = dynamic_cast<const CStackInstance &>(context).getLevel();
 		int level = dynamic_cast<const CStackInstance &>(context).getLevel();
 		return apply(b, level);
 		return apply(b, level);
 	}
 	}
 
 
-	if(context.getNodeType() == CBonusSystemNode::STACK_BATTLE)
+	if(context.getNodeType() == BonusNodeType::STACK_BATTLE)
 	{
 	{
 		const auto & stack = dynamic_cast<const CStack &>(context);
 		const auto & stack = dynamic_cast<const CStack &>(context);
 		//update if stack doesn't have an instance (summons, war machines)
 		//update if stack doesn't have an instance (summons, war machines)
@@ -203,13 +240,13 @@ std::shared_ptr<Bonus> DivideStackLevelUpdater::apply(const std::shared_ptr<Bonu
 
 
 std::shared_ptr<Bonus> DivideStackLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
 std::shared_ptr<Bonus> DivideStackLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
 {
 {
-	if(context.getNodeType() == CBonusSystemNode::STACK_INSTANCE || context.getNodeType() == CBonusSystemNode::COMMANDER)
+	if(context.getNodeType() == BonusNodeType::STACK_INSTANCE || context.getNodeType() == BonusNodeType::COMMANDER)
 	{
 	{
 		int level = dynamic_cast<const CStackInstance &>(context).getLevel();
 		int level = dynamic_cast<const CStackInstance &>(context).getLevel();
 		return apply(b, level);
 		return apply(b, level);
 	}
 	}
 
 
-	if(context.getNodeType() == CBonusSystemNode::STACK_BATTLE)
+	if(context.getNodeType() == BonusNodeType::STACK_BATTLE)
 	{
 	{
 		const auto & stack = dynamic_cast<const CStack &>(context);
 		const auto & stack = dynamic_cast<const CStack &>(context);
 		//update if stack doesn't have an instance (summons, war machines)
 		//update if stack doesn't have an instance (summons, war machines)

+ 30 - 3
lib/bonuses/Updaters.h

@@ -89,9 +89,9 @@ class DLL_LINKAGE TimesStackSizeUpdater : public IUpdater
 {
 {
 	std::shared_ptr<Bonus> apply(const std::shared_ptr<Bonus> & b, int count) const;
 	std::shared_ptr<Bonus> apply(const std::shared_ptr<Bonus> & b, int count) const;
 
 
-	int minimum;
-	int maximum;
-	int stepSize;
+	int minimum = std::numeric_limits<int>::min();
+	int maximum = std::numeric_limits<int>::max();
+	int stepSize = 1;
 public:
 public:
 	TimesStackSizeUpdater() = default;
 	TimesStackSizeUpdater() = default;
 	TimesStackSizeUpdater(int minimum, int maximum, int stepSize)
 	TimesStackSizeUpdater(int minimum, int maximum, int stepSize)
@@ -113,6 +113,33 @@ public:
 	}
 	}
 };
 };
 
 
+class DLL_LINKAGE TimesArmySizeUpdater : public IUpdater
+{
+public:
+	int minimum = std::numeric_limits<int>::min();
+	int maximum = std::numeric_limits<int>::max();
+	int stepSize = 1;
+	int filteredLevel = -1;
+	CreatureID filteredCreature;
+	FactionID filteredFaction;
+	TimesArmySizeUpdater() = default;
+
+	std::shared_ptr<Bonus> createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const override;
+	std::string toString() const override;
+	JsonNode toJsonNode() const override;
+
+	template <typename Handler> void serialize(Handler & h)
+	{
+		h & static_cast<IUpdater &>(*this);
+		h & minimum;
+		h & maximum;
+		h & stepSize;
+		h & filteredLevel;
+		h & filteredCreature;
+		h & filteredFaction;
+	}
+};
+
 class DLL_LINKAGE TimesStackLevelUpdater : public IUpdater
 class DLL_LINKAGE TimesStackLevelUpdater : public IUpdater
 {
 {
 	std::shared_ptr<Bonus> apply(const std::shared_ptr<Bonus> & b, int level) const;
 	std::shared_ptr<Bonus> apply(const std::shared_ptr<Bonus> & b, int level) const;

+ 2 - 2
lib/entities/artifact/CArtifact.cpp

@@ -293,10 +293,10 @@ bool CChargedArtifact::getRemoveOnDepletion() const
 }
 }
 
 
 CArtifact::CArtifact()
 CArtifact::CArtifact()
-	: iconIndex(ArtifactID::NONE),
+	: CBonusSystemNode(BonusNodeType::ARTIFACT),
+	iconIndex(ArtifactID::NONE),
 	price(0)
 	price(0)
 {
 {
-	setNodeType(ARTIFACT);
 	possibleSlots[ArtBearer::HERO]; //we want to generate map entry even if it will be empty
 	possibleSlots[ArtBearer::HERO]; //we want to generate map entry even if it will be empty
 	possibleSlots[ArtBearer::CREATURE]; //we want to generate map entry even if it will be empty
 	possibleSlots[ArtBearer::CREATURE]; //we want to generate map entry even if it will be empty
 	possibleSlots[ArtBearer::COMMANDER];
 	possibleSlots[ArtBearer::COMMANDER];

+ 1 - 1
lib/entities/artifact/CArtifactInstance.cpp

@@ -224,7 +224,7 @@ CArtifactInstance::CArtifactInstance(IGameInfoCallback *cb, const CArtifact * ar
 }
 }
 
 
 CArtifactInstance::CArtifactInstance(IGameInfoCallback *cb)
 CArtifactInstance::CArtifactInstance(IGameInfoCallback *cb)
-	: CBonusSystemNode(ARTIFACT_INSTANCE)
+	: CBonusSystemNode(BonusNodeType::ARTIFACT_INSTANCE)
 	, CCombinedArtifactInstance(cb)
 	, CCombinedArtifactInstance(cb)
 {
 {
 }
 }

+ 2 - 2
lib/entities/faction/CTownHandler.cpp

@@ -251,10 +251,10 @@ void CTownHandler::loadBuildingBonuses(const JsonNode & source, BonusList & bonu
 			bonus->description.appendTextID(building->getNameTextID());
 			bonus->description.appendTextID(building->getNameTextID());
 
 
 		//JsonUtils::parseBuildingBonus produces UNKNOWN type propagator instead of empty.
 		//JsonUtils::parseBuildingBonus produces UNKNOWN type propagator instead of empty.
-		assert(bonus->propagator == nullptr || bonus->propagator->getPropagatorType() != CBonusSystemNode::ENodeTypes::UNKNOWN);
+		assert(bonus->propagator == nullptr || bonus->propagator->getPropagatorType() != BonusNodeType::UNKNOWN);
 
 
 		if(bonus->propagator != nullptr
 		if(bonus->propagator != nullptr
-			&& bonus->propagator->getPropagatorType() == CBonusSystemNode::ENodeTypes::UNKNOWN)
+			&& bonus->propagator->getPropagatorType() == BonusNodeType::UNKNOWN)
 				bonus->addPropagator(emptyPropagator());
 				bonus->addPropagator(emptyPropagator());
 		building->addNewBonus(bonus, bonusList);
 		building->addNewBonus(bonus, bonusList);
 	}
 	}

+ 3 - 4
lib/gameState/CGameState.cpp

@@ -152,9 +152,9 @@ int CGameState::getDate(Date mode) const
 }
 }
 
 
 CGameState::CGameState()
 CGameState::CGameState()
+	:globalEffects(BonusNodeType::GLOBAL_EFFECTS)
 {
 {
 	heroesPool = std::make_unique<TavernHeroesPool>(this);
 	heroesPool = std::make_unique<TavernHeroesPool>(this);
-	globalEffects.setNodeType(CBonusSystemNode::GLOBAL_EFFECTS);
 }
 }
 
 
 CGameState::~CGameState()
 CGameState::~CGameState()
@@ -1596,9 +1596,8 @@ CGHeroInstance * CGameState::getUsedHero(const HeroTypeID & hid) const
 }
 }
 
 
 TeamState::TeamState()
 TeamState::TeamState()
-{
-	setNodeType(TEAM);
-}
+	:CBonusSystemNode(BonusNodeType::TEAM)
+{}
 
 
 CArtifactInstance * CGameState::createScroll(const SpellID & spellId)
 CArtifactInstance * CGameState::createScroll(const SpellID & spellId)
 {
 {

+ 30 - 0
lib/json/JsonBonus.cpp

@@ -386,6 +386,7 @@ static TUpdaterPtr parseUpdater(const JsonNode & updaterJson)
 			{"TIMES_HERO_LEVEL_DIVIDE_STACK_LEVEL", std::make_shared<TimesHeroLevelDivideStackLevelUpdater>()},
 			{"TIMES_HERO_LEVEL_DIVIDE_STACK_LEVEL", std::make_shared<TimesHeroLevelDivideStackLevelUpdater>()},
 			{"DIVIDE_STACK_LEVEL", std::make_shared<DivideStackLevelUpdater>()},
 			{"DIVIDE_STACK_LEVEL", std::make_shared<DivideStackLevelUpdater>()},
 			{"TIMES_STACK_LEVEL", std::make_shared<TimesStackLevelUpdater>()},
 			{"TIMES_STACK_LEVEL", std::make_shared<TimesStackLevelUpdater>()},
+			{"TIMES_STACK_SIZE", std::make_shared<TimesStackSizeUpdater>()},
 			{"BONUS_OWNER_UPDATER", std::make_shared<OwnerUpdater>()}
 			{"BONUS_OWNER_UPDATER", std::make_shared<OwnerUpdater>()}
 	};
 	};
 
 
@@ -427,6 +428,35 @@ static TUpdaterPtr parseUpdater(const JsonNode & updaterJson)
 			}
 			}
 			return std::make_shared<TimesStackSizeUpdater>(minimum, maximum, std::max(1, stepSize));
 			return std::make_shared<TimesStackSizeUpdater>(minimum, maximum, std::max(1, stepSize));
 		}
 		}
+		if(updaterJson["type"].String() == "TIMES_ARMY_SIZE")
+		{
+			auto result = std::make_shared<TimesArmySizeUpdater>();
+
+			result->minimum = updaterJson["minimum"].isNull() ? std::numeric_limits<int>::min() : updaterJson["minimum"].Integer();
+			result->maximum = updaterJson["maximum"].isNull() ? std::numeric_limits<int>::max() : updaterJson["maximum"].Integer();
+			result->stepSize = updaterJson["stepSize"].isNull() ? 1 : updaterJson["stepSize"].Integer();
+			result->filteredLevel = updaterJson["filteredLevel"].isNull() ? -1 : updaterJson["filteredLevel"].Integer();
+			if (result->minimum > result->maximum)
+			{
+				logMod->warn("TIMES_ARMY_SIZE updater: minimum value (%d) is above maximum value(%d)!", result->minimum, result->maximum);
+				std::swap(result->minimum, result->maximum);
+			}
+			if (!updaterJson["filteredFaction"].isNull())
+			{
+				LIBRARY->identifiers()->requestIdentifier( "faction", updaterJson["filteredFaction"], [result](int32_t identifier)
+				{
+					result->filteredFaction = FactionID(identifier);
+				});
+			}
+			if (!updaterJson["filteredCreature"].isNull())
+			{
+				LIBRARY->identifiers()->requestIdentifier( "creature", updaterJson["filteredCreature"], [result](int32_t identifier)
+				{
+					result->filteredCreature = CreatureID(identifier);
+				});
+			}
+			return result;
+		}
 		else
 		else
 			logMod->warn("Unknown updater type \"%s\"", updaterJson["type"].String());
 			logMod->warn("Unknown updater type \"%s\"", updaterJson["type"].String());
 		break;
 		break;

+ 3 - 3
lib/mapObjects/CArmedInstance.cpp

@@ -42,13 +42,13 @@ void CArmedInstance::randomizeArmy(FactionID type)
 }
 }
 
 
 CArmedInstance::CArmedInstance(IGameInfoCallback *cb)
 CArmedInstance::CArmedInstance(IGameInfoCallback *cb)
-	:CArmedInstance(cb, false)
+	:CArmedInstance(cb, BonusNodeType::ARMY, false)
 {
 {
 }
 }
 
 
-CArmedInstance::CArmedInstance(IGameInfoCallback *cb, bool isHypothetic):
+CArmedInstance::CArmedInstance(IGameInfoCallback *cb, BonusNodeType nodeType, bool isHypothetic):
 	CGObjectInstance(cb),
 	CGObjectInstance(cb),
-	CBonusSystemNode(isHypothetic),
+	CBonusSystemNode(nodeType, isHypothetic),
 	nonEvilAlignmentMix(this, Selector::type()(BonusType::NONEVIL_ALIGNMENT_MIX)), // Take Angelic Alliance troop-mixing freedom of non-evil units into account.
 	nonEvilAlignmentMix(this, Selector::type()(BonusType::NONEVIL_ALIGNMENT_MIX)), // Take Angelic Alliance troop-mixing freedom of non-evil units into account.
 	battle(nullptr)
 	battle(nullptr)
 {
 {

+ 1 - 1
lib/mapObjects/CArmedInstance.h

@@ -51,7 +51,7 @@ public:
 	//////////////////////////////////////////////////////////////////////////
 	//////////////////////////////////////////////////////////////////////////
 
 
 	CArmedInstance(IGameInfoCallback *cb);
 	CArmedInstance(IGameInfoCallback *cb);
-	CArmedInstance(IGameInfoCallback *cb, bool isHypothetic);
+	CArmedInstance(IGameInfoCallback *cb, BonusNodeType nodeType, bool isHypothetic);
 
 
 	PlayerColor getOwner() const override
 	PlayerColor getOwner() const override
 	{
 	{

+ 5 - 1
lib/mapObjects/CGDwelling.cpp

@@ -50,7 +50,11 @@ void CGDwellingRandomizationInfo::serializeJson(JsonSerializeFormat & handler)
 }
 }
 
 
 CGDwelling::CGDwelling(IGameInfoCallback *cb):
 CGDwelling::CGDwelling(IGameInfoCallback *cb):
-	CArmedInstance(cb)
+	CGDwelling(cb, BonusNodeType::ARMY)
+{}
+
+CGDwelling::CGDwelling(IGameInfoCallback *cb, BonusNodeType nodeType):
+	CArmedInstance(cb, nodeType, false)
 {}
 {}
 
 
 CGDwelling::~CGDwelling() = default;
 CGDwelling::~CGDwelling() = default;

+ 1 - 0
lib/mapObjects/CGDwelling.h

@@ -39,6 +39,7 @@ public:
 	std::optional<CGDwellingRandomizationInfo> randomizationInfo; //random dwelling options; not serialized
 	std::optional<CGDwellingRandomizationInfo> randomizationInfo; //random dwelling options; not serialized
 	TCreaturesSet creatures; //creatures[level] -> <vector of alternative ids (base creature and upgrades, creatures amount>
 	TCreaturesSet creatures; //creatures[level] -> <vector of alternative ids (base creature and upgrades, creatures amount>
 
 
+	CGDwelling(IGameInfoCallback *cb, BonusNodeType nodeType);
 	CGDwelling(IGameInfoCallback *cb);
 	CGDwelling(IGameInfoCallback *cb);
 	~CGDwelling() override;
 	~CGDwelling() override;
 
 

+ 1 - 2
lib/mapObjects/CGHeroInstance.cpp

@@ -244,7 +244,7 @@ int CGHeroInstance::movementPointsLimitCached(bool onLand, const TurnInfo * ti)
 }
 }
 
 
 CGHeroInstance::CGHeroInstance(IGameInfoCallback * cb)
 CGHeroInstance::CGHeroInstance(IGameInfoCallback * cb)
-	: CArmedInstance(cb),
+	: CArmedInstance(cb, BonusNodeType::HERO, false),
 	CArtifactSet(cb),
 	CArtifactSet(cb),
 	tacticFormationEnabled(false),
 	tacticFormationEnabled(false),
 	inTownGarrison(false),
 	inTownGarrison(false),
@@ -259,7 +259,6 @@ CGHeroInstance::CGHeroInstance(IGameInfoCallback * cb)
 	turnInfoCache(std::make_unique<TurnInfoCache>(this)),
 	turnInfoCache(std::make_unique<TurnInfoCache>(this)),
 	manaPerKnowledgeCached(this, Selector::type()(BonusType::MANA_PER_KNOWLEDGE_PERCENTAGE))
 	manaPerKnowledgeCached(this, Selector::type()(BonusType::MANA_PER_KNOWLEDGE_PERCENTAGE))
 {
 {
-	setNodeType(HERO);
 	ID = Obj::HERO;
 	ID = Obj::HERO;
 	secSkills.emplace_back(SecondarySkill::NONE, -1);
 	secSkills.emplace_back(SecondarySkill::NONE, -1);
 }
 }

+ 7 - 8
lib/mapObjects/CGTownInstance.cpp

@@ -206,7 +206,11 @@ int CGTownInstance::getDwellingBonus(const std::vector<CreatureID>& creatureIds,
 
 
 TResources CGTownInstance::dailyIncome() const
 TResources CGTownInstance::dailyIncome() const
 {
 {
-	TResources ret;
+	ResourceSet ret;
+
+	for (GameResID k : GameResID::ALL_RESOURCES())
+		ret[k] += valOfBonuses(BonusType::GENERATE_RESOURCE, BonusSubtypeID(k));
+
 	for(const auto & p : getTown()->buildings)
 	for(const auto & p : getTown()->buildings)
 	{
 	{
 		BuildingID buildingUpgrade;
 		BuildingID buildingUpgrade;
@@ -261,8 +265,9 @@ TownFortifications CGTownInstance::fortificationsLevel() const
 }
 }
 
 
 CGTownInstance::CGTownInstance(IGameInfoCallback *cb):
 CGTownInstance::CGTownInstance(IGameInfoCallback *cb):
-	CGDwelling(cb),
+	CGDwelling(cb, BonusNodeType::TOWN),
 	IMarket(cb),
 	IMarket(cb),
+	townAndVis(BonusNodeType::TOWN_AND_VISITOR),
 	built(0),
 	built(0),
 	destroyed(0),
 	destroyed(0),
 	identifier(0),
 	identifier(0),
@@ -271,7 +276,6 @@ CGTownInstance::CGTownInstance(IGameInfoCallback *cb):
 	spellResearchAcceptedCounter(0),
 	spellResearchAcceptedCounter(0),
 	spellResearchAllowed(true)
 	spellResearchAllowed(true)
 {
 {
-	setNodeType(CBonusSystemNode::TOWN);
 	attachTo(townAndVis);
 	attachTo(townAndVis);
 }
 }
 
 
@@ -1209,11 +1213,6 @@ GrowthInfo::Entry::Entry(int _count, std::string fullDescription):
 {
 {
 }
 }
 
 
-CTownAndVisitingHero::CTownAndVisitingHero()
-{
-	setNodeType(TOWN_AND_VISITOR);
-}
-
 int GrowthInfo::totalGrowth() const
 int GrowthInfo::totalGrowth() const
 {
 {
 	int ret = 0;
 	int ret = 0;

+ 1 - 7
lib/mapObjects/CGTownInstance.h

@@ -26,12 +26,6 @@ struct DamageRange;
 template<typename ContainedClass>
 template<typename ContainedClass>
 class LogicalExpression;
 class LogicalExpression;
 
 
-class DLL_LINKAGE CTownAndVisitingHero : public CBonusSystemNode
-{
-public:
-	CTownAndVisitingHero();
-};
-
 struct DLL_LINKAGE GrowthInfo
 struct DLL_LINKAGE GrowthInfo
 {
 {
 	struct Entry
 	struct Entry
@@ -61,7 +55,7 @@ class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public I
 public:
 public:
 	enum EFortLevel {NONE = 0, FORT = 1, CITADEL = 2, CASTLE = 3};
 	enum EFortLevel {NONE = 0, FORT = 1, CITADEL = 2, CASTLE = 3};
 
 
-	CTownAndVisitingHero townAndVis;
+	CBonusSystemNode townAndVis;
 	si32 built; //how many buildings has been built this turn
 	si32 built; //how many buildings has been built this turn
 	si32 destroyed; //how many buildings has been destroyed this turn
 	si32 destroyed; //how many buildings has been destroyed this turn
 	ui32 identifier; //special identifier from h3m (only > RoE maps)
 	ui32 identifier; //special identifier from h3m (only > RoE maps)

+ 5 - 0
lib/mapObjects/FlaggableMapObject.cpp

@@ -22,6 +22,11 @@
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
+FlaggableMapObject::FlaggableMapObject(IGameInfoCallback *cb)
+	:CGObjectInstance(cb)
+	,CBonusSystemNode(BonusNodeType::UNKNOWN)
+{}
+
 const IOwnableObject * FlaggableMapObject::asOwnable() const
 const IOwnableObject * FlaggableMapObject::asOwnable() const
 {
 {
 	return this;
 	return this;

+ 1 - 1
lib/mapObjects/FlaggableMapObject.h

@@ -25,7 +25,7 @@ class DLL_LINKAGE FlaggableMapObject final : public CGObjectInstance, public IOw
 	void initBonuses();
 	void initBonuses();
 
 
 public:
 public:
-	using CGObjectInstance::CGObjectInstance;
+	FlaggableMapObject(IGameInfoCallback *cb);
 
 
 	void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override;
 	void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override;
 	void initObj(IGameRandomizer & gameRandomizer) override;
 	void initObj(IGameRandomizer & gameRandomizer) override;

+ 11 - 2
lib/mapObjects/MiscObjects.cpp

@@ -142,6 +142,10 @@ std::vector<CreatureID> CGMine::providedCreatures() const
 ResourceSet CGMine::dailyIncome() const
 ResourceSet CGMine::dailyIncome() const
 {
 {
 	ResourceSet result;
 	ResourceSet result;
+
+	for (GameResID k : GameResID::ALL_RESOURCES())
+		result[k] += valOfBonuses(BonusType::GENERATE_RESOURCE, BonusSubtypeID(k));
+
 	result[producedResource] += defaultResProduction();
 	result[producedResource] += defaultResProduction();
 
 
 	const auto & playerSettings = cb->getPlayerSettings(getOwner());
 	const auto & playerSettings = cb->getPlayerSettings(getOwner());
@@ -867,7 +871,11 @@ const IOwnableObject * CGGarrison::asOwnable() const
 
 
 ResourceSet CGGarrison::dailyIncome() const
 ResourceSet CGGarrison::dailyIncome() const
 {
 {
-	return {};
+	ResourceSet result;
+	for (GameResID k : GameResID::ALL_RESOURCES())
+		result[k] += valOfBonuses(BonusType::GENERATE_RESOURCE, BonusSubtypeID(k));
+
+	return result;
 }
 }
 
 
 std::vector<CreatureID> CGGarrison::providedCreatures() const
 std::vector<CreatureID> CGGarrison::providedCreatures() const
@@ -930,7 +938,7 @@ void CGGarrison::addAntimagicGarrisonBonus()
 	bonus->type = BonusType::BLOCK_ALL_MAGIC;
 	bonus->type = BonusType::BLOCK_ALL_MAGIC;
 	bonus->source = BonusSource::OBJECT_TYPE;
 	bonus->source = BonusSource::OBJECT_TYPE;
 	bonus->sid = BonusSourceID(this->ID);
 	bonus->sid = BonusSourceID(this->ID);
-	bonus->propagator = std::make_shared<CPropagatorNodeType>(CBonusSystemNode::BATTLE);
+	bonus->propagator = std::make_shared<CPropagatorNodeType>(BonusNodeType::BATTLE_WIDE);
 	bonus->duration = BonusDuration::PERMANENT;
 	bonus->duration = BonusDuration::PERMANENT;
 	this->addNewBonus(bonus);
 	this->addNewBonus(bonus);
 }
 }
@@ -987,6 +995,7 @@ void CGMagi::onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance *
 
 
 CGBoat::CGBoat(IGameInfoCallback * cb)
 CGBoat::CGBoat(IGameInfoCallback * cb)
 	: CGObjectInstance(cb)
 	: CGObjectInstance(cb)
+	, CBonusSystemNode(BonusNodeType::BOAT)
 {
 {
 	direction = 4;
 	direction = 4;
 	layer = EPathfindingLayer::SAIL;
 	layer = EPathfindingLayer::SAIL;

+ 3 - 1
lib/serializer/RegisterTypes.h

@@ -86,7 +86,7 @@ void registerTypes(Serializer &s)
 	s.template registerType<CGUniversity>(23);
 	s.template registerType<CGUniversity>(23);
 	s.template registerType<CGHeroPlaceholder>(24);
 	s.template registerType<CGHeroPlaceholder>(24);
 	s.template registerType<CArmedInstance>(25);
 	s.template registerType<CArmedInstance>(25);
-	s.template registerType<CBonusSystemNode>(26);
+//	s.template registerType<CBonusSystemNode>(26);
 	s.template registerType<CCreatureSet>(27);
 	s.template registerType<CCreatureSet>(27);
 	s.template registerType<CGHeroInstance>(28);
 	s.template registerType<CGHeroInstance>(28);
 	s.template registerType<CGDwelling>(30);
 	s.template registerType<CGDwelling>(30);
@@ -298,6 +298,8 @@ void registerTypes(Serializer &s)
 	s.template registerType<DivideStackLevelUpdater>(246);
 	s.template registerType<DivideStackLevelUpdater>(246);
 	s.template registerType<SetHeroExperience>(247);
 	s.template registerType<SetHeroExperience>(247);
 	s.template registerType<GiveStackExperience>(248);
 	s.template registerType<GiveStackExperience>(248);
+	s.template registerType<TimesStackSizeUpdater>(249);
+	s.template registerType<TimesArmySizeUpdater>(250);
 }
 }
 
 
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END

+ 1 - 1
lib/spells/AdventureSpellMechanics.cpp

@@ -317,7 +317,7 @@ bool DimensionDoorMechanics::canBeCastImpl(spells::Problem & problem, const IGam
 	std::stringstream cachingStr;
 	std::stringstream cachingStr;
 	cachingStr << "source_" << vstd::to_underlying(BonusSource::SPELL_EFFECT) << "id_" << owner->id.num;
 	cachingStr << "source_" << vstd::to_underlying(BonusSource::SPELL_EFFECT) << "id_" << owner->id.num;
 
 
-	int castsAlreadyPerformedThisTurn = caster->getHeroCaster()->getBonuses(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(owner->id)), Selector::all, cachingStr.str())->size();
+	int castsAlreadyPerformedThisTurn = caster->getHeroCaster()->getBonuses(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(owner->id)), cachingStr.str())->size();
 	int castsLimit = owner->getLevelPower(schoolLevel);
 	int castsLimit = owner->getLevelPower(schoolLevel);
 
 
 	bool isTournamentRulesLimitEnabled = cb->getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT);
 	bool isTournamentRulesLimitEnabled = cb->getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT);

+ 8 - 7
scripting/lua/api/BonusSystem.cpp

@@ -246,21 +246,22 @@ int BonusBearerProxy::getBonuses(lua_State * L)
 
 
 		if(hasRangeSelector)
 		if(hasRangeSelector)
 		{
 		{
-			auto rangeSelector = [](const Bonus * b)
-			{
-				return false;//TODO: BonusBearerProxy::getBonuses rangeSelector
-			};
+			//TODO: BonusBearerProxy::getBonuses rangeSelector
+			//auto rangeSelector = [](const Bonus * b)
+			//{
+			//	return false;
+			//};
 
 
-			ret = object->getBonuses(selector, rangeSelector);
+			ret = object->getBonuses(selector);
 		}
 		}
 		else
 		else
 		{
 		{
-			ret = object->getBonuses(selector, Selector::all);
+			ret = object->getBonuses(selector);
 		}
 		}
 	}
 	}
 	else
 	else
 	{
 	{
-		ret = object->getBonuses(Selector::all, Selector::all);
+		ret = object->getBonuses(Selector::all);
 	}
 	}
 
 
 	S.clear();
 	S.clear();

+ 2 - 2
test/battle/CBattleInfoCallbackTest.cpp

@@ -83,13 +83,13 @@ public:
 
 
 	void redirectBonusesToFake()
 	void redirectBonusesToFake()
 	{
 	{
-		ON_CALL(*this, getAllBonuses(_, _, _)).WillByDefault(Invoke(&bonusFake, &BonusBearerMock::getAllBonuses));
+		ON_CALL(*this, getAllBonuses(_, _)).WillByDefault(Invoke(&bonusFake, &BonusBearerMock::getAllBonuses));
 		ON_CALL(*this, getTreeVersion()).WillByDefault(Invoke(&bonusFake, &BonusBearerMock::getTreeVersion));
 		ON_CALL(*this, getTreeVersion()).WillByDefault(Invoke(&bonusFake, &BonusBearerMock::getTreeVersion));
 	}
 	}
 
 
 	void expectAnyBonusSystemCall()
 	void expectAnyBonusSystemCall()
 	{
 	{
-		EXPECT_CALL(*this, getAllBonuses(_, _, _)).Times(AtLeast(0));
+		EXPECT_CALL(*this, getAllBonuses(_, _)).Times(AtLeast(0));
 		EXPECT_CALL(*this, getTreeVersion()).Times(AtLeast(0));
 		EXPECT_CALL(*this, getTreeVersion()).Times(AtLeast(0));
 	}
 	}
 
 

+ 2 - 2
test/battle/CHealthTest.cpp

@@ -29,7 +29,7 @@ public:
 
 
 	void setDefaultExpectations()
 	void setDefaultExpectations()
 	{
 	{
-		EXPECT_CALL(mock, getAllBonuses(_, _, _)).WillRepeatedly(Invoke(&bonusMock, &BonusBearerMock::getAllBonuses));
+		EXPECT_CALL(mock, getAllBonuses(_, _)).WillRepeatedly(Invoke(&bonusMock, &BonusBearerMock::getAllBonuses));
 		EXPECT_CALL(mock, getTreeVersion()).WillRepeatedly(Return(1));
 		EXPECT_CALL(mock, getTreeVersion()).WillRepeatedly(Return(1));
 
 
 		bonusMock.addNewBonus(std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::STACK_HEALTH, BonusSource::CREATURE_ABILITY, UNIT_HEALTH, BonusSourceID()));
 		bonusMock.addNewBonus(std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::STACK_HEALTH, BonusSource::CREATURE_ABILITY, UNIT_HEALTH, BonusSourceID()));
@@ -235,7 +235,7 @@ TEST_F(HealthTest, singleUnitStack)
 
 
 	//one Titan
 	//one Titan
 
 
-	EXPECT_CALL(mock, getAllBonuses(_, _, _)).WillRepeatedly(Invoke(&bonusMock, &BonusBearerMock::getAllBonuses));
+	EXPECT_CALL(mock, getAllBonuses(_, _)).WillRepeatedly(Invoke(&bonusMock, &BonusBearerMock::getAllBonuses));
 	EXPECT_CALL(mock, getTreeVersion()).WillRepeatedly(Return(1));
 	EXPECT_CALL(mock, getTreeVersion()).WillRepeatedly(Return(1));
 
 
 	bonusMock.addNewBonus(std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::STACK_HEALTH, BonusSource::CREATURE_ABILITY, 300, BonusSourceID()));
 	bonusMock.addNewBonus(std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::STACK_HEALTH, BonusSource::CREATURE_ABILITY, 300, BonusSourceID()));

+ 2 - 2
test/mock/BattleFake.cpp

@@ -34,13 +34,13 @@ void UnitFake::makeDead()
 
 
 void UnitFake::redirectBonusesToFake()
 void UnitFake::redirectBonusesToFake()
 {
 {
-	ON_CALL(*this, getAllBonuses(_, _, _)).WillByDefault(Invoke(&bonusFake, &BonusBearerMock::getAllBonuses));
+	ON_CALL(*this, getAllBonuses(_, _)).WillByDefault(Invoke(&bonusFake, &BonusBearerMock::getAllBonuses));
 	ON_CALL(*this, getTreeVersion()).WillByDefault(Invoke(&bonusFake, &BonusBearerMock::getTreeVersion));
 	ON_CALL(*this, getTreeVersion()).WillByDefault(Invoke(&bonusFake, &BonusBearerMock::getTreeVersion));
 }
 }
 
 
 void UnitFake::expectAnyBonusSystemCall()
 void UnitFake::expectAnyBonusSystemCall()
 {
 {
-	EXPECT_CALL(*this, getAllBonuses(_, _, _)).Times(AtLeast(0));
+	EXPECT_CALL(*this, getAllBonuses(_, _)).Times(AtLeast(0));
 	EXPECT_CALL(*this, getTreeVersion()).Times(AtLeast(0));
 	EXPECT_CALL(*this, getTreeVersion()).Times(AtLeast(0));
 }
 }
 
 

+ 2 - 2
test/mock/mock_BonusBearer.cpp

@@ -25,7 +25,7 @@ void BonusBearerMock::addNewBonus(const std::shared_ptr<Bonus> & b)
 	treeVersion++;
 	treeVersion++;
 }
 }
 
 
-TConstBonusListPtr BonusBearerMock::getAllBonuses(const CSelector & selector, const CSelector & limit, const std::string & cachingStr) const
+TConstBonusListPtr BonusBearerMock::getAllBonuses(const CSelector & selector, const std::string & cachingStr) const
 {
 {
 	if(cachedLast != treeVersion)
 	if(cachedLast != treeVersion)
 	{
 	{
@@ -34,7 +34,7 @@ TConstBonusListPtr BonusBearerMock::getAllBonuses(const CSelector & selector, co
 	}
 	}
 
 
 	auto ret = std::make_shared<BonusList>();
 	auto ret = std::make_shared<BonusList>();
-	bonuses.getBonuses(*ret, selector, limit);
+	bonuses.getBonuses(*ret, selector);
 	return ret;
 	return ret;
 }
 }
 
 

+ 1 - 1
test/mock/mock_BonusBearer.h

@@ -23,7 +23,7 @@ public:
 
 
 	void addNewBonus(const std::shared_ptr<Bonus> & b);
 	void addNewBonus(const std::shared_ptr<Bonus> & b);
 
 
-	TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit, const std::string & cachingStr = "") const override;
+	TConstBonusListPtr getAllBonuses(const CSelector & selector, const std::string & cachingStr = "") const override;
 
 
 	int32_t getTreeVersion() const override;
 	int32_t getTreeVersion() const override;
 private:
 private:

+ 1 - 1
test/mock/mock_battle_Unit.h

@@ -15,7 +15,7 @@
 class UnitMock : public battle::Unit
 class UnitMock : public battle::Unit
 {
 {
 public:
 public:
-	MOCK_CONST_METHOD3(getAllBonuses, TConstBonusListPtr(const CSelector &, const CSelector &, const std::string &));
+	MOCK_CONST_METHOD2(getAllBonuses, TConstBonusListPtr(const CSelector &, const std::string &));
 	MOCK_CONST_METHOD0(getTreeVersion, int32_t());
 	MOCK_CONST_METHOD0(getTreeVersion, int32_t());
 
 
 	MOCK_CONST_METHOD0(getCasterUnitId, int32_t());
 	MOCK_CONST_METHOD0(getCasterUnitId, int32_t());

+ 3 - 3
test/spells/AbilityCasterTest.cpp

@@ -33,7 +33,7 @@ public:
 protected:
 protected:
 	void SetUp() override
 	void SetUp() override
 	{
 	{
-		ON_CALL(actualCaster, getAllBonuses(_, _, _)).WillByDefault(Invoke(&casterBonuses, &BonusBearerMock::getAllBonuses));
+		ON_CALL(actualCaster, getAllBonuses(_, _)).WillByDefault(Invoke(&casterBonuses, &BonusBearerMock::getAllBonuses));
 		ON_CALL(actualCaster, getTreeVersion()).WillByDefault(Invoke(&casterBonuses, &BonusBearerMock::getTreeVersion));
 		ON_CALL(actualCaster, getTreeVersion()).WillByDefault(Invoke(&casterBonuses, &BonusBearerMock::getTreeVersion));
 	}
 	}
 
 
@@ -57,7 +57,7 @@ TEST_F(AbilityCasterTest, MagicAbilityAffectedByGenericBonus)
 
 
 	casterBonuses.addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::MAGIC_SCHOOL_SKILL, BonusSource::OTHER, 2, BonusSourceID(), BonusSubtypeID(SpellSchool::ANY)));
 	casterBonuses.addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::MAGIC_SCHOOL_SKILL, BonusSource::OTHER, 2, BonusSourceID(), BonusSubtypeID(SpellSchool::ANY)));
 
 
-	EXPECT_CALL(actualCaster, getAllBonuses(_, _, _)).Times(AtLeast(1));
+	EXPECT_CALL(actualCaster, getAllBonuses(_, _)).Times(AtLeast(1));
 	EXPECT_CALL(actualCaster, getTreeVersion()).Times(AtLeast(0));
 	EXPECT_CALL(actualCaster, getTreeVersion()).Times(AtLeast(0));
 
 
 	setupSubject(1);
 	setupSubject(1);
@@ -71,7 +71,7 @@ TEST_F(AbilityCasterTest, MagicAbilityIgnoresSchoolBonus)
 
 
 	casterBonuses.addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::MAGIC_SCHOOL_SKILL, BonusSource::OTHER, 2, BonusSourceID(), BonusSubtypeID(SpellSchool::AIR)));
 	casterBonuses.addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::MAGIC_SCHOOL_SKILL, BonusSource::OTHER, 2, BonusSourceID(), BonusSubtypeID(SpellSchool::AIR)));
 
 
-	EXPECT_CALL(actualCaster, getAllBonuses(_, _, _)).Times(AtLeast(1));
+	EXPECT_CALL(actualCaster, getAllBonuses(_, _)).Times(AtLeast(1));
 	EXPECT_CALL(actualCaster, getTreeVersion()).Times(AtLeast(0));
 	EXPECT_CALL(actualCaster, getTreeVersion()).Times(AtLeast(0));
 
 
 	setupSubject(1);
 	setupSubject(1);

+ 1 - 1
test/spells/targetConditions/AbsoluteLevelConditionTest.cpp

@@ -24,7 +24,7 @@ public:
 	void setDefaultExpectations()
 	void setDefaultExpectations()
 	{
 	{
 		EXPECT_CALL(mechanicsMock, isMagicalEffect()).WillRepeatedly(Return(true));
 		EXPECT_CALL(mechanicsMock, isMagicalEffect()).WillRepeatedly(Return(true));
-		EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(AtLeast(1));
+		EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(AtLeast(1));
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
 	}
 	}
 
 

+ 1 - 1
test/spells/targetConditions/AbsoluteSpellConditionTest.cpp

@@ -24,7 +24,7 @@ public:
 
 
 	void setDefaultExpectations()
 	void setDefaultExpectations()
 	{
 	{
-		EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(AtLeast(1));
+		EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(AtLeast(1));
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
 		EXPECT_CALL(mechanicsMock, getSpellIndex()).WillRepeatedly(Return(castSpell));
 		EXPECT_CALL(mechanicsMock, getSpellIndex()).WillRepeatedly(Return(castSpell));
 	}
 	}

+ 1 - 1
test/spells/targetConditions/BonusConditionTest.cpp

@@ -21,7 +21,7 @@ class BonusConditionTest : public TargetConditionItemTest
 public:
 public:
 	void setDefaultExpectations()
 	void setDefaultExpectations()
 	{
 	{
-		EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(AtLeast(1));
+		EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(AtLeast(1));
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
 	}
 	}
 
 

+ 1 - 1
test/spells/targetConditions/CreatureConditionTest.cpp

@@ -21,7 +21,7 @@ class CreatureConditionTest : public TargetConditionItemTest
 public:
 public:
 	void setDefaultExpectations()
 	void setDefaultExpectations()
 	{
 	{
-		EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(0);
+		EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(0);
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(0);
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(0);
 	}
 	}
 
 

+ 1 - 1
test/spells/targetConditions/ElementalConditionTest.cpp

@@ -23,7 +23,7 @@ public:
 
 
 	void setDefaultExpectations()
 	void setDefaultExpectations()
 	{
 	{
-		EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(AtLeast(1));
+		EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(AtLeast(1));
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
 
 
 		EXPECT_CALL(mechanicsMock, getSpell()).Times(AtLeast(1)).WillRepeatedly(Return(&spellMock));
 		EXPECT_CALL(mechanicsMock, getSpell()).Times(AtLeast(1)).WillRepeatedly(Return(&spellMock));

+ 1 - 1
test/spells/targetConditions/HealthValueConditionTest.cpp

@@ -23,7 +23,7 @@ public:
 	const int64_t EFFECT_VALUE = 101;
 	const int64_t EFFECT_VALUE = 101;
 	void setDefaultExpectations()
 	void setDefaultExpectations()
 	{
 	{
-		EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(0);
+		EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(0);
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(0);
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(0);
 		EXPECT_CALL(unitMock, getAvailableHealth()).WillOnce(Return(UNIT_HP));
 		EXPECT_CALL(unitMock, getAvailableHealth()).WillOnce(Return(UNIT_HP));
 		EXPECT_CALL(mechanicsMock, getEffectValue()).WillOnce(Return(EFFECT_VALUE));
 		EXPECT_CALL(mechanicsMock, getEffectValue()).WillOnce(Return(EFFECT_VALUE));

+ 1 - 1
test/spells/targetConditions/ImmunityNegationConditionTest.cpp

@@ -30,7 +30,7 @@ public:
 	{
 	{
 		ownerMatches = ::testing::get<0>(GetParam());
 		ownerMatches = ::testing::get<0>(GetParam());
 		isMagicalEffect = ::testing::get<1>(GetParam());
 		isMagicalEffect = ::testing::get<1>(GetParam());
-		EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(AtLeast(0));
+		EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(AtLeast(0));
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
 		EXPECT_CALL(mechanicsMock, isMagicalEffect()).Times(AtLeast(0)).WillRepeatedly(Return(isMagicalEffect));
 		EXPECT_CALL(mechanicsMock, isMagicalEffect()).Times(AtLeast(0)).WillRepeatedly(Return(isMagicalEffect));
 		EXPECT_CALL(mechanicsMock, ownerMatches(Eq(&unitMock), Field(&boost::logic::tribool::value, boost::logic::tribool::false_value))).WillRepeatedly(Return(ownerMatches));
 		EXPECT_CALL(mechanicsMock, ownerMatches(Eq(&unitMock), Field(&boost::logic::tribool::value, boost::logic::tribool::false_value))).WillRepeatedly(Return(ownerMatches));

+ 1 - 1
test/spells/targetConditions/NormalLevelConditionTest.cpp

@@ -27,7 +27,7 @@ public:
 		isMagicalEffect = GetParam();
 		isMagicalEffect = GetParam();
 		EXPECT_CALL(mechanicsMock, isMagicalEffect()).WillRepeatedly(Return(isMagicalEffect));
 		EXPECT_CALL(mechanicsMock, isMagicalEffect()).WillRepeatedly(Return(isMagicalEffect));
 		if(isMagicalEffect)
 		if(isMagicalEffect)
-			EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(AtLeast(1));
+			EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(AtLeast(1));
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
 	}
 	}
 
 

+ 1 - 1
test/spells/targetConditions/NormalSpellConditionTest.cpp

@@ -24,7 +24,7 @@ public:
 
 
 	void setDefaultExpectations()
 	void setDefaultExpectations()
 	{
 	{
-		EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(AtLeast(1));
+		EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(AtLeast(1));
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
 		EXPECT_CALL(mechanicsMock, getSpellIndex()).WillRepeatedly(Return(castSpell));
 		EXPECT_CALL(mechanicsMock, getSpellIndex()).WillRepeatedly(Return(castSpell));
 	}
 	}

+ 1 - 1
test/spells/targetConditions/ReceptiveFeatureConditionTest.cpp

@@ -27,7 +27,7 @@ public:
 		isPositive = ::testing::get<0>(GetParam());
 		isPositive = ::testing::get<0>(GetParam());
 		hasBonus = ::testing::get<1>(GetParam());
 		hasBonus = ::testing::get<1>(GetParam());
 
 
-		EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(AtLeast(0));
+		EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(AtLeast(0));
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
 		EXPECT_CALL(mechanicsMock, isPositiveSpell()).WillRepeatedly(Return(isPositive));
 		EXPECT_CALL(mechanicsMock, isPositiveSpell()).WillRepeatedly(Return(isPositive));
 		if(hasBonus)
 		if(hasBonus)

+ 1 - 1
test/spells/targetConditions/SpellEffectConditionTest.cpp

@@ -21,7 +21,7 @@ class SpellEffectConditionTest : public TargetConditionItemTest
 public:
 public:
 	void setDefaultExpectations()
 	void setDefaultExpectations()
 	{
 	{
-		EXPECT_CALL(unitMock, getAllBonuses(_, _, _)).Times(AtLeast(1));
+		EXPECT_CALL(unitMock, getAllBonuses(_, _)).Times(AtLeast(1));
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
 		EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0));
 	}
 	}
 
 

+ 1 - 1
test/spells/targetConditions/TargetConditionItemFixture.h

@@ -37,7 +37,7 @@ protected:
 	void SetUp() override
 	void SetUp() override
 	{
 	{
 		using namespace ::testing;
 		using namespace ::testing;
-		ON_CALL(unitMock, getAllBonuses(_, _, _)).WillByDefault(Invoke(&unitBonuses, &BonusBearerMock::getAllBonuses));
+		ON_CALL(unitMock, getAllBonuses(_, _)).WillByDefault(Invoke(&unitBonuses, &BonusBearerMock::getAllBonuses));
 		ON_CALL(unitMock, getTreeVersion()).WillByDefault(Invoke(&unitBonuses, &BonusBearerMock::getTreeVersion));
 		ON_CALL(unitMock, getTreeVersion()).WillByDefault(Invoke(&unitBonuses, &BonusBearerMock::getTreeVersion));
 	}
 	}
 };
 };