浏览代码

Merge pull request #2105 from rilian-la-te/bonuses-refactor

Split HeroBonus.h to pieces: part1
Nordsoft91 2 年之前
父节点
当前提交
9f5ee885b4
共有 100 个文件被更改,包括 4685 次插入4307 次删除
  1. 2 2
      AI/BattleAI/AttackPossibility.cpp
  2. 2 2
      AI/BattleAI/BattleAI.cpp
  3. 5 5
      AI/BattleAI/BattleExchangeVariant.cpp
  4. 2 2
      AI/BattleAI/StackWithBonuses.cpp
  5. 2 8
      AI/BattleAI/StackWithBonuses.h
  6. 2 2
      AI/Nullkiller/Analyzers/ArmyManager.cpp
  7. 2 2
      AI/Nullkiller/Engine/PriorityEvaluator.cpp
  8. 1 1
      AI/VCAI/ArmyManager.cpp
  9. 4 0
      AI/VCAI/VCAI.cpp
  10. 1 1
      Mods/vcmi/config/vcmi/english.json
  11. 1 1
      Mods/vcmi/config/vcmi/german.json
  12. 1 1
      Mods/vcmi/config/vcmi/polish.json
  13. 1 1
      Mods/vcmi/config/vcmi/russian.json
  14. 1 1
      Mods/vcmi/config/vcmi/ukrainian.json
  15. 4 0
      client/CPlayerInterface.cpp
  16. 1 1
      client/battle/BattleActionsController.cpp
  17. 1 1
      client/battle/BattleInterface.cpp
  18. 8 8
      client/widgets/MiscWidgets.cpp
  19. 2 2
      client/widgets/MiscWidgets.h
  20. 1 1
      client/windows/CCastleInterface.cpp
  21. 4 4
      client/windows/CCreatureWindow.cpp
  22. 1 1
      client/windows/CCreatureWindow.h
  23. 10 0
      client/windows/CHeroWindow.cpp
  24. 8 2
      client/windows/CHeroWindow.h
  25. 1 1
      client/windows/GUIClasses.cpp
  26. 22 2
      cmake_modules/VCMI_lib.cmake
  27. 16 4
      include/vcmi/Creature.h
  28. 0 14
      include/vcmi/Entity.h
  29. 74 0
      include/vcmi/FactionMember.h
  30. 147 3
      lib/BasicTypes.cpp
  31. 1 1
      lib/BattleFieldHandler.h
  32. 2 1
      lib/CArtHandler.h
  33. 0 3
      lib/CBonusTypeHandler.cpp
  34. 1 1
      lib/CBonusTypeHandler.h
  35. 2 5
      lib/CCreatureHandler.cpp
  36. 2 2
      lib/CCreatureHandler.h
  37. 0 7
      lib/CCreatureSet.cpp
  38. 3 3
      lib/CCreatureSet.h
  39. 2 2
      lib/CGameState.cpp
  40. 1 1
      lib/CGameState.h
  41. 2 0
      lib/CHeroHandler.cpp
  42. 2 1
      lib/CHeroHandler.h
  43. 1 1
      lib/CPathfinder.h
  44. 2 1
      lib/CPlayerState.h
  45. 1 1
      lib/CSkillHandler.h
  46. 4 3
      lib/CStack.cpp
  47. 2 1
      lib/CStack.h
  48. 2 1
      lib/CTownHandler.cpp
  49. 2 1
      lib/CTownHandler.h
  50. 1 2
      lib/GameConstants.h
  51. 0 2794
      lib/HeroBonus.cpp
  52. 0 1370
      lib/HeroBonus.h
  53. 4 0
      lib/IGameCallback.cpp
  54. 5 1
      lib/JsonNode.cpp
  55. 1 1
      lib/VCMI_lib.cbp
  56. 1 1
      lib/VCMI_lib.vcxproj
  57. 1 1
      lib/VCMI_lib.vcxproj.filters
  58. 2 0
      lib/battle/BattleInfo.cpp
  59. 2 1
      lib/battle/BattleInfo.h
  60. 2 2
      lib/battle/CBattleInfoCallback.cpp
  61. 6 6
      lib/battle/CUnitState.cpp
  62. 1 0
      lib/battle/CUnitState.h
  63. 2 2
      lib/battle/DamageCalculator.cpp
  64. 4 3
      lib/battle/Unit.h
  65. 418 0
      lib/bonuses/Bonus.cpp
  66. 430 0
      lib/bonuses/Bonus.h
  67. 276 0
      lib/bonuses/BonusList.cpp
  68. 114 0
      lib/bonuses/BonusList.h
  69. 260 0
      lib/bonuses/BonusParams.cpp
  70. 41 0
      lib/bonuses/BonusParams.h
  71. 89 0
      lib/bonuses/BonusSelector.cpp
  72. 156 0
      lib/bonuses/BonusSelector.h
  73. 210 0
      lib/bonuses/CBonusProxy.cpp
  74. 90 0
      lib/bonuses/CBonusProxy.h
  75. 689 0
      lib/bonuses/CBonusSystemNode.cpp
  76. 140 0
      lib/bonuses/CBonusSystemNode.h
  77. 82 0
      lib/bonuses/IBonusBearer.cpp
  78. 43 0
      lib/bonuses/IBonusBearer.h
  79. 541 0
      lib/bonuses/Limiters.cpp
  80. 264 0
      lib/bonuses/Limiters.h
  81. 52 0
      lib/bonuses/Propagators.cpp
  82. 45 0
      lib/bonuses/Propagators.h
  83. 211 0
      lib/bonuses/Updaters.cpp
  84. 117 0
      lib/bonuses/Updaters.h
  85. 2 0
      lib/mapObjects/CArmedInstance.h
  86. 2 2
      lib/mapObjects/CGHeroInstance.cpp
  87. 1 1
      lib/mapObjects/CGHeroInstance.h
  88. 1 1
      lib/mapObjects/CGTownInstance.cpp
  89. 2 1
      lib/mapObjects/CObjectHandler.h
  90. 1 1
      lib/mapObjects/MiscObjects.cpp
  91. 4 0
      lib/registerTypes/RegisterTypes.h
  92. 3 1
      lib/serializer/JsonUpdater.cpp
  93. 1 1
      lib/spells/BonusCaster.cpp
  94. 1 1
      lib/spells/CSpellHandler.h
  95. 1 1
      lib/spells/ISpellMechanics.cpp
  96. 1 1
      lib/spells/ISpellMechanics.h
  97. 2 0
      lib/spells/TargetCondition.cpp
  98. 2 2
      lib/spells/effects/Damage.cpp
  99. 1 1
      lib/spells/effects/Heal.cpp
  100. 1 0
      lib/spells/effects/Moat.cpp

+ 2 - 2
AI/BattleAI/AttackPossibility.cpp

@@ -51,11 +51,11 @@ int64_t AttackPossibility::calculateDamageReduce(
 
 	// FIXME: provide distance info for Jousting bonus
 	auto enemyDamageBeforeAttack = cb.battleEstimateDamage(defender, attacker, 0);
-	auto enemiesKilled = damageDealt / defender->MaxHealth() + (damageDealt % defender->MaxHealth() >= defender->getFirstHPleft() ? 1 : 0);
+	auto enemiesKilled = damageDealt / defender->getMaxHealth() + (damageDealt % defender->getMaxHealth() >= defender->getFirstHPleft() ? 1 : 0);
 	auto enemyDamage = averageDmg(enemyDamageBeforeAttack.damage);
 	auto damagePerEnemy = enemyDamage / (double)defender->getCount();
 
-	return (int64_t)(damagePerEnemy * (enemiesKilled * KILL_BOUNTY + damageDealt * HEALTH_BOUNTY / (double)defender->MaxHealth()));
+	return (int64_t)(damagePerEnemy * (enemiesKilled * KILL_BOUNTY + damageDealt * HEALTH_BOUNTY / (double)defender->getMaxHealth()));
 }
 
 int64_t AttackPossibility::evaluateBlockedShootersDmg(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle & state)

+ 2 - 2
AI/BattleAI/BattleAI.cpp

@@ -109,7 +109,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 			auto healingTargets = cb->battleGetStacks(CBattleInfoEssentials::ONLY_MINE);
 			std::map<int, const CStack*> woundHpToStack;
 			for(auto stack : healingTargets)
-				if(auto woundHp = stack->MaxHealth() - stack->getFirstHPleft())
+				if(auto woundHp = stack->getMaxHealth() - stack->getFirstHPleft())
 					woundHpToStack[woundHp] = stack;
 			if(woundHpToStack.empty())
 				return BattleAction::makeDefend(stack);
@@ -214,7 +214,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 					bestAttack.attackerState->unitType()->getJsonKey(),
 					bestAttack.affectedUnits[0]->unitType()->getJsonKey(),
 					(int)bestAttack.affectedUnits[0]->getCount(), action, (int)bestAttack.from, (int)bestAttack.attack.attacker->getPosition().hex,
-					bestAttack.attack.chargeDistance, bestAttack.attack.attacker->Speed(0, true),
+					bestAttack.attack.chargeDistance, bestAttack.attack.attacker->speed(0, true),
 					bestAttack.defenderDamageReduce, bestAttack.attackerDamageReduce, bestAttack.attackValue()
 				);
 			}

+ 5 - 5
AI/BattleAI/BattleExchangeVariant.cpp

@@ -205,7 +205,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(const battle::Uni
 	if(targets.unreachableEnemies.empty())
 		return result;
 
-	auto speed = activeStack->Speed();
+	auto speed = activeStack->speed();
 
 	if(speed == 0)
 		return result;
@@ -607,7 +607,7 @@ void BattleExchangeEvaluator::updateReachabilityMap(HypotheticBattle & hb)
 
 			for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
 			{
-				bool reachable = unitReachability.distances[hex] <= unit->Speed(turn);
+				bool reachable = unitReachability.distances[hex] <= unit->speed(turn);
 
 				if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
 				{
@@ -617,7 +617,7 @@ void BattleExchangeEvaluator::updateReachabilityMap(HypotheticBattle & hb)
 					{
 						for(BattleHex neighbor : hex.neighbouringTiles())
 						{
-							reachable = unitReachability.distances[neighbor] <= unit->Speed(turn);
+							reachable = unitReachability.distances[neighbor] <= unit->speed(turn);
 
 							if(reachable) break;
 						}
@@ -665,7 +665,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
 			for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
 			{
 				bool enemyUnit = false;
-				bool reachable = unitReachability.distances[hex] <= unit->Speed(turn);
+				bool reachable = unitReachability.distances[hex] <= unit->speed(turn);
 
 				if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
 				{
@@ -677,7 +677,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
 
 						for(BattleHex neighbor : hex.neighbouringTiles())
 						{
-							reachable = unitReachability.distances[neighbor] <= unit->Speed(turn);
+							reachable = unitReachability.distances[neighbor] <= unit->speed(turn);
 
 							if(reachable) break;
 						}

+ 2 - 2
AI/BattleAI/StackWithBonuses.cpp

@@ -36,9 +36,9 @@ void actualizeEffect(TBonusListPtr target, const Bonus & ef)
 	}
 }
 
-StackWithBonuses::StackWithBonuses(const HypotheticBattle * Owner, const CStack * Stack)
+StackWithBonuses::StackWithBonuses(const HypotheticBattle * Owner, const battle::CUnitState * Stack)
 	: battle::CUnitState(),
-	origBearer(Stack),
+	origBearer(Stack->getBonusBearer()),
 	owner(Owner),
 	type(Stack->unitType()),
 	baseAmount(Stack->unitBaseAmount()),

+ 2 - 8
AI/BattleAI/StackWithBonuses.h

@@ -14,16 +14,10 @@
 #include <vcmi/Environment.h>
 #include <vcmi/ServerCallback.h>
 
-#include "../../lib/HeroBonus.h"
+#include "../../lib/bonuses/Bonus.h"
 #include "../../lib/battle/BattleProxy.h"
 #include "../../lib/battle/CUnitState.h"
 
-VCMI_LIB_NAMESPACE_BEGIN
-
-class CStack;
-
-VCMI_LIB_NAMESPACE_END
-
 class HypotheticBattle;
 
 ///Fake random generator, used by AI to evaluate random server behavior
@@ -54,7 +48,7 @@ public:
 	std::vector<Bonus> bonusesToUpdate;
 	std::set<std::shared_ptr<Bonus>> bonusesToRemove;
 
-	StackWithBonuses(const HypotheticBattle * Owner, const CStack * Stack);
+	StackWithBonuses(const HypotheticBattle * Owner, const battle::CUnitState * Stack);
 
 	StackWithBonuses(const HypotheticBattle * Owner, const battle::UnitInfo & info);
 

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

@@ -77,7 +77,7 @@ std::vector<SlotInfo>::iterator ArmyManager::getWeakestCreature(std::vector<Slot
 		if(left.creature->getLevel() != right.creature->getLevel())
 			return left.creature->getLevel() < right.creature->getLevel();
 		
-		return left.creature->Speed() > right.creature->Speed();
+		return left.creature->speed() > right.creature->speed();
 	});
 
 	return weakest;
@@ -150,7 +150,7 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
 
 		for(auto & slot : newArmyInstance.Slots())
 		{
-			auto morale = slot.second->MoraleVal();
+			auto morale = slot.second->moraleVal();
 			auto multiplier = 1.0f;
 
 			const float BadMoraleChance = 0.083f;

+ 2 - 2
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -213,8 +213,8 @@ uint64_t evaluateArtifactArmyValue(CArtifactInstance * art)
 		10 * art->valOfBonuses(Bonus::MOVEMENT, 1)
 		+ 1200 * art->valOfBonuses(Bonus::STACKS_SPEED)
 		+ 700 * art->valOfBonuses(Bonus::MORALE)
-		+ 700 * art->getAttack(false)
-		+ 700 * art->getDefense(false)
+		+ 700 * art->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK)
+		+ 700 * art->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE)
 		+ 700 * art->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::KNOWLEDGE)
 		+ 700 * art->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::SPELL_POWER)
 		+ 500 * art->valOfBonuses(Bonus::LUCK);

+ 1 - 1
AI/VCAI/ArmyManager.cpp

@@ -63,7 +63,7 @@ std::vector<SlotInfo>::iterator ArmyManager::getWeakestCreature(std::vector<Slot
 		if(left.creature->getLevel() != right.creature->getLevel())
 			return left.creature->getLevel() < right.creature->getLevel();
 		
-		return left.creature->Speed() > right.creature->Speed();
+		return left.creature->speed() > right.creature->speed();
 	});
 
 	return weakest;

+ 4 - 0
AI/VCAI/VCAI.cpp

@@ -22,6 +22,10 @@
 #include "../../lib/CGameState.h"
 #include "../../lib/NetPacksBase.h"
 #include "../../lib/NetPacks.h"
+#include "../../lib/bonuses/CBonusSystemNode.h"
+#include "../../lib/bonuses/Limiters.h"
+#include "../../lib/bonuses/Updaters.h"
+#include "../../lib/bonuses/Propagators.h"
 #include "../../lib/serializer/CTypeList.h"
 #include "../../lib/serializer/BinarySerializer.h"
 #include "../../lib/serializer/BinaryDeserializer.h"

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

@@ -234,7 +234,7 @@
 	"core.bonus.HEALER.name": "Healer",
 	"core.bonus.HEALER.description": "Heals allied units",
 	"core.bonus.HP_REGENERATION.name": "Regeneration",
-	"core.bonus.HP_REGENERATION.description": "Heals ${SHval} hit points every round",
+	"core.bonus.HP_REGENERATION.description": "Heals ${val} hit points every round",
 	"core.bonus.JOUSTING.name": "Champion charge",
 	"core.bonus.JOUSTING.description": "+${val}% damage for each hex travelled",
 	"core.bonus.KING.name": "King",

+ 1 - 1
Mods/vcmi/config/vcmi/german.json

@@ -229,7 +229,7 @@
 	"core.bonus.HEALER.name": "Heiler",
 	"core.bonus.HEALER.description": "Heilt verbündete Einheiten",
 	"core.bonus.HP_REGENERATION.name": "Regeneration",
-	"core.bonus.HP_REGENERATION.description": "Heilt ${SHval} Trefferpunkte jede Runde",
+	"core.bonus.HP_REGENERATION.description": "Heilt ${val} Trefferpunkte jede Runde",
 	"core.bonus.JOUSTING.name": "Champion Charge",
 	"core.bonus.JOUSTING.description": "+${val}% Schaden pro zurückgelegtem Feld",
 	"core.bonus.KING.name": "König",

+ 1 - 1
Mods/vcmi/config/vcmi/polish.json

@@ -208,7 +208,7 @@
 	"core.bonus.HEALER.name": "Uzdrowiciel",
 	"core.bonus.HEALER.description": "Leczy sprzymierzone jednostki",
 	"core.bonus.HP_REGENERATION.name": "Regeneracja",
-	"core.bonus.HP_REGENERATION.description": "Leczy ${SHval} punktów zdrowia każdej rundy",
+	"core.bonus.HP_REGENERATION.description": "Leczy ${val} punktów zdrowia każdej rundy",
 	"core.bonus.JOUSTING.name": "Szarża Czempiona",
 	"core.bonus.JOUSTING.description": "+${val}% obrażeń na przebytego heksa",
 	"core.bonus.KING.name": "Król",

+ 1 - 1
Mods/vcmi/config/vcmi/russian.json

@@ -232,7 +232,7 @@
 	"core.bonus.HEALER.name": "Целитель",
 	"core.bonus.HEALER.description": "Исцеляет дружественные юниты",
 	"core.bonus.HP_REGENERATION.name": "Регенерация",
-	"core.bonus.HP_REGENERATION.description": "Исцеляет ${SHval} очков здоровья каждый ход",
+	"core.bonus.HP_REGENERATION.description": "Исцеляет ${val} очков здоровья каждый ход",
 	"core.bonus.JOUSTING.name": "Разгон",
 	"core.bonus.JOUSTING.description": "+${val}% урона за каждую пройденную клетку",
 	"core.bonus.KING.name": "Король",

+ 1 - 1
Mods/vcmi/config/vcmi/ukrainian.json

@@ -208,7 +208,7 @@
 	"core.bonus.HEALER.name" : "Цілитель",
 	"core.bonus.HEALER.description" : "Лікує союзників",
 	"core.bonus.HP_REGENERATION.name" : "Регенерація",
-	"core.bonus.HP_REGENERATION.description" : "Відновлює ${SHval} очок здоров'я кожного раунду",
+	"core.bonus.HP_REGENERATION.description" : "Відновлює ${val} очок здоров'я кожного раунду",
 	"core.bonus.JOUSTING.name" : "Турнірна перевага",
 	"core.bonus.JOUSTING.description" : "+${val}% шкоди за кожен пройдений гекс",
 	"core.bonus.KING.name" : "Король",

+ 4 - 0
client/CPlayerInterface.cpp

@@ -43,6 +43,10 @@
 #include "../lib/CArtHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CHeroHandler.h"
+#include "../lib/bonuses/CBonusSystemNode.h"
+#include "../lib/bonuses/Limiters.h"
+#include "../lib/bonuses/Updaters.h"
+#include "../lib/bonuses/Propagators.h"
 #include "../lib/serializer/CTypeList.h"
 #include "../lib/serializer/BinaryDeserializer.h"
 #include "../lib/serializer/BinarySerializer.h"

+ 1 - 1
client/battle/BattleActionsController.cpp

@@ -529,7 +529,7 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, B
 	switch (action.get())
 	{
 		case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
-			return (targetStack && targetStackOwned && targetStack->Speed() > 0);
+			return (targetStack && targetStackOwned && targetStack->speed() > 0);
 
 		case PossiblePlayerBattleAction::CREATURE_INFO:
 			return (targetStack && targetStackOwned && targetStack->alive());

+ 1 - 1
client/battle/BattleInterface.cpp

@@ -636,7 +636,7 @@ void BattleInterface::tacticPhaseEnd()
 
 static bool immobile(const CStack *s)
 {
-	return !s->Speed(0, true); //should bound stacks be immobile?
+	return !s->speed(0, true); //should bound stacks be immobile?
 }
 
 void BattleInterface::tacticNextStack(const CStack * current)

+ 8 - 8
client/widgets/MiscWidgets.cpp

@@ -372,7 +372,7 @@ CTownTooltip::CTownTooltip(Point pos, const CGTownInstance * town)
 	init(InfoAboutTown(town, true));
 }
 
-void MoraleLuckBox::set(const IBonusBearer * node)
+void MoraleLuckBox::set(const AFactionMember * node)
 {
 	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
 
@@ -385,7 +385,7 @@ void MoraleLuckBox::set(const IBonusBearer * node)
 	bonusValue = 0;
 
 	if(node)
-		bonusValue = morale ? node->MoraleValAndBonusList(modifierList) : node->LuckValAndBonusList(modifierList);
+		bonusValue = morale ? node->moraleValAndBonusList(modifierList) : node->luckValAndBonusList(modifierList);
 
 	int mrlt = (bonusValue>0)-(bonusValue<0); //signum: -1 - bad luck / morale, 0 - neutral, 1 - good
 	hoverText = CGI->generaltexth->heroscrn[hoverTextBase[morale] - mrlt];
@@ -393,21 +393,21 @@ void MoraleLuckBox::set(const IBonusBearer * node)
 	text = CGI->generaltexth->arraytxt[textId[morale]];
 	boost::algorithm::replace_first(text,"%s",CGI->generaltexth->arraytxt[neutralDescr[morale]-mrlt]);
 
-	if (morale && node && (node->hasBonusOfType(Bonus::UNDEAD)
-			|| node->hasBonusOfType(Bonus::NON_LIVING)))
+	if (morale && node && (node->getBonusBearer()->hasBonusOfType(Bonus::UNDEAD)
+			|| node->getBonusBearer()->hasBonusOfType(Bonus::NON_LIVING)))
 	{
 		text += CGI->generaltexth->arraytxt[113]; //unaffected by morale
 		bonusValue = 0;
 	}
-	else if(morale && node && node->hasBonusOfType(Bonus::NO_MORALE))
+	else if(morale && node && node->getBonusBearer()->hasBonusOfType(Bonus::NO_MORALE))
 	{
-		auto noMorale = node->getBonus(Selector::type()(Bonus::NO_MORALE));
+		auto noMorale = node->getBonusBearer()->getBonus(Selector::type()(Bonus::NO_MORALE));
 		text += "\n" + noMorale->Description();
 		bonusValue = 0;
 	}
-	else if (!morale && node && node->hasBonusOfType(Bonus::NO_LUCK))
+	else if (!morale && node && node->getBonusBearer()->hasBonusOfType(Bonus::NO_LUCK))
 	{
-		auto noLuck = node->getBonus(Selector::type()(Bonus::NO_LUCK));
+		auto noLuck = node->getBonusBearer()->getBonus(Selector::type()(Bonus::NO_LUCK));
 		text += "\n" + noLuck->Description();
 		bonusValue = 0;
 	}

+ 2 - 2
client/widgets/MiscWidgets.h

@@ -16,7 +16,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 class CGGarrison;
 struct InfoAboutArmy;
 class CArmedInstance;
-class IBonusBearer;
+class AFactionMember;
 
 VCMI_LIB_NAMESPACE_END
 
@@ -170,7 +170,7 @@ public:
 	bool morale; //true if morale, false if luck
 	bool small;
 
-	void set(const IBonusBearer *node);
+	void set(const AFactionMember *node);
 
 	MoraleLuckBox(bool Morale, const Rect &r, bool Small=false);
 };

+ 1 - 1
client/windows/CCastleInterface.cpp

@@ -1703,7 +1703,7 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance *
 		sizes.y+=21;
 		values.push_back(std::make_shared<LabeledValue>(sizes, CGI->generaltexth->allTexts[199], CGI->generaltexth->fcommands[2], getMyCreature()->getMinDamage(false), getMyCreature()->getMaxDamage(false)));
 		sizes.y+=20;
-		values.push_back(std::make_shared<LabeledValue>(sizes, CGI->generaltexth->allTexts[388], CGI->generaltexth->fcommands[3], getMyCreature()->MaxHealth()));
+		values.push_back(std::make_shared<LabeledValue>(sizes, CGI->generaltexth->allTexts[388], CGI->generaltexth->fcommands[3], getMyCreature()->getMaxHealth()));
 		sizes.y+=21;
 		values.push_back(std::make_shared<LabeledValue>(sizes, CGI->generaltexth->allTexts[193], CGI->generaltexth->fcommands[4], getMyCreature()->valOfBonuses(Bonus::STACKS_SPEED)));
 		sizes.y+=20;

+ 4 - 4
client/windows/CCreatureWindow.cpp

@@ -525,8 +525,8 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
 		addStatLabel(EStat::ATTACK, parent->info->creature->getAttack(battleStack->isShooter()), battleStack->getAttack(battleStack->isShooter()));
 		addStatLabel(EStat::DEFENCE, parent->info->creature->getDefense(battleStack->isShooter()), battleStack->getDefense(battleStack->isShooter()));
 		addStatLabel(EStat::DAMAGE, parent->info->stackNode->getMinDamage(battleStack->isShooter()) * dmgMultiply, battleStack->getMaxDamage(battleStack->isShooter()) * dmgMultiply);
-		addStatLabel(EStat::HEALTH, parent->info->creature->MaxHealth(), battleStack->MaxHealth());
-		addStatLabel(EStat::SPEED, parent->info->creature->Speed(), battleStack->Speed());
+		addStatLabel(EStat::HEALTH, parent->info->creature->getMaxHealth(), battleStack->getMaxHealth());
+		addStatLabel(EStat::SPEED, parent->info->creature->speed(), battleStack->speed());
 
 		if(battleStack->isShooter())
 			addStatLabel(EStat::SHOTS, battleStack->shots.total(), battleStack->shots.available());
@@ -545,8 +545,8 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
 		addStatLabel(EStat::ATTACK, parent->info->creature->getAttack(shooter), parent->info->stackNode->getAttack(shooter));
 		addStatLabel(EStat::DEFENCE, parent->info->creature->getDefense(shooter), parent->info->stackNode->getDefense(shooter));
 		addStatLabel(EStat::DAMAGE, parent->info->stackNode->getMinDamage(shooter) * dmgMultiply, parent->info->stackNode->getMaxDamage(shooter) * dmgMultiply);
-		addStatLabel(EStat::HEALTH, parent->info->creature->MaxHealth(), parent->info->stackNode->MaxHealth());
-		addStatLabel(EStat::SPEED, parent->info->creature->Speed(), parent->info->stackNode->Speed());
+		addStatLabel(EStat::HEALTH, parent->info->creature->getMaxHealth(), parent->info->stackNode->getMaxHealth());
+		addStatLabel(EStat::SPEED, parent->info->creature->speed(), parent->info->stackNode->speed());
 
 		if(shooter)
 			addStatLabel(EStat::SHOTS, parent->info->stackNode->valOfBonuses(Bonus::SHOTS));

+ 1 - 1
client/windows/CCreatureWindow.h

@@ -9,7 +9,7 @@
  */
 #pragma once
 
-#include "../../lib/HeroBonus.h"
+#include "../../lib/bonuses/Bonus.h"
 #include "../widgets/MiscWidgets.h"
 #include "CWindowObject.h"
 

+ 10 - 0
client/windows/CHeroWindow.cpp

@@ -69,6 +69,16 @@ si32 CHeroWithMaybePickedArtifact::manaLimit() const
 	return si32(getPrimSkillLevel(PrimarySkill::KNOWLEDGE) * (valOfBonuses(Bonus::MANA_PER_KNOWLEDGE)));
 }
 
+const IBonusBearer * CHeroWithMaybePickedArtifact::getBonusBearer() const 
+{
+	return this;
+}
+
+FactionID CHeroWithMaybePickedArtifact::getFaction() const
+{
+	return hero->getFaction();
+} 
+
 CHeroWithMaybePickedArtifact::CHeroWithMaybePickedArtifact(CWindowWithArtifacts * Cww, const CGHeroInstance * Hero)
 	: hero(Hero), cww(Cww)
 {

+ 8 - 2
client/windows/CHeroWindow.h

@@ -9,7 +9,10 @@
  */
 #pragma once
 
-#include "../../lib/HeroBonus.h"
+#include <vcmi/FactionMember.h>
+
+#include "../../lib/bonuses/Bonus.h"
+#include "../../lib/bonuses/IBonusBearer.h"
 #include "../widgets/CWindowWithArtifacts.h"
 #include "../widgets/CGarrisonInt.h"
 
@@ -45,7 +48,7 @@ public:
 };
 
 //helper class for calculating values of hero bonuses without bonuses from picked up artifact
-class CHeroWithMaybePickedArtifact : public virtual IBonusBearer
+class CHeroWithMaybePickedArtifact : public IBonusBearer, public AFactionMember
 {
 public:
 	const CGHeroInstance * hero;
@@ -54,6 +57,9 @@ public:
 	CHeroWithMaybePickedArtifact(CWindowWithArtifacts * Cww, const CGHeroInstance * Hero);
 	TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit, const CBonusSystemNode * root = nullptr, const std::string & cachingStr = "") const override;
 
+	const IBonusBearer * getBonusBearer() const override;
+	FactionID getFaction() const override; 
+
 	int64_t getTreeVersion() const override;
 
 	si32 manaLimit() const;

+ 1 - 1
client/windows/GUIClasses.cpp

@@ -61,7 +61,7 @@
 #include "../lib/CStopWatch.h"
 #include "../lib/CTownHandler.h"
 #include "../lib/GameConstants.h"
-#include "../lib/HeroBonus.h"
+#include "../lib/bonuses/Bonus.h"
 #include "../lib/mapping/CMap.h"
 #include "../lib/NetPacksBase.h"
 #include "../lib/StartInfo.h"

+ 22 - 2
cmake_modules/VCMI_lib.cmake

@@ -27,6 +27,17 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/battle/SiegeInfo.cpp
 		${MAIN_LIB_DIR}/battle/Unit.cpp
 
+		${MAIN_LIB_DIR}/bonuses/Bonus.cpp
+		${MAIN_LIB_DIR}/bonuses/BonusList.cpp
+		${MAIN_LIB_DIR}/bonuses/BonusParams.cpp
+		${MAIN_LIB_DIR}/bonuses/BonusSelector.cpp
+		${MAIN_LIB_DIR}/bonuses/CBonusProxy.cpp
+		${MAIN_LIB_DIR}/bonuses/CBonusSystemNode.cpp
+		${MAIN_LIB_DIR}/bonuses/IBonusBearer.cpp
+		${MAIN_LIB_DIR}/bonuses/Limiters.cpp
+		${MAIN_LIB_DIR}/bonuses/Propagators.cpp
+		${MAIN_LIB_DIR}/bonuses/Updaters.cpp
+
 		${MAIN_LIB_DIR}/events/ApplyDamage.cpp
 		${MAIN_LIB_DIR}/events/GameResumed.cpp
 		${MAIN_LIB_DIR}/events/ObjectVisitEnded.cpp
@@ -198,7 +209,6 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/CTownHandler.cpp
 		${MAIN_LIB_DIR}/GameConstants.cpp
 		${MAIN_LIB_DIR}/GameSettings.cpp
-		${MAIN_LIB_DIR}/HeroBonus.cpp
 		${MAIN_LIB_DIR}/IGameCallback.cpp
 		${MAIN_LIB_DIR}/IHandlerBase.cpp
 		${MAIN_LIB_DIR}/JsonDetail.cpp
@@ -300,6 +310,17 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/battle/SiegeInfo.h
 		${MAIN_LIB_DIR}/battle/Unit.h
 
+		${MAIN_LIB_DIR}/bonuses/Bonus.h
+		${MAIN_LIB_DIR}/bonuses/BonusList.h
+		${MAIN_LIB_DIR}/bonuses/BonusParams.h
+		${MAIN_LIB_DIR}/bonuses/BonusSelector.h
+		${MAIN_LIB_DIR}/bonuses/CBonusProxy.h
+		${MAIN_LIB_DIR}/bonuses/CBonusSystemNode.h
+		${MAIN_LIB_DIR}/bonuses/IBonusBearer.h
+		${MAIN_LIB_DIR}/bonuses/Limiters.h
+		${MAIN_LIB_DIR}/bonuses/Propagators.h
+		${MAIN_LIB_DIR}/bonuses/Updaters.h
+
 		${MAIN_LIB_DIR}/events/ApplyDamage.h
 		${MAIN_LIB_DIR}/events/GameResumed.h
 		${MAIN_LIB_DIR}/events/ObjectVisitEnded.h
@@ -478,7 +499,6 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/FunctionList.h
 		${MAIN_LIB_DIR}/GameConstants.h
 		${MAIN_LIB_DIR}/GameSettings.h
-		${MAIN_LIB_DIR}/HeroBonus.h
 		${MAIN_LIB_DIR}/IBonusTypeHandler.h
 		${MAIN_LIB_DIR}/IGameCallback.h
 		${MAIN_LIB_DIR}/IGameEventsReceiver.h

+ 16 - 4
include/vcmi/Creature.h

@@ -10,7 +10,7 @@
 
 #pragma once
 
-#include "Entity.h"
+#include "FactionMember.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -18,7 +18,21 @@ class CreatureID;
 class ResourceSet;
 enum class EGameResID : int8_t;
 
-class DLL_LINKAGE Creature : public EntityWithNativeTerrain<CreatureID>
+/// Base class for creatures and battle stacks
+class DLL_LINKAGE ACreature: public AFactionMember
+{
+public:
+	bool isLiving() const; //non-undead, non-non living or alive
+	ui32 speed(int turn = 0, bool useBind = false) const; //get speed (in moving tiles) of creature with all modificators
+	virtual ui32 getMaxHealth() const; //get max HP of stack with all modifiers
+};
+
+template <typename IdType>
+class DLL_LINKAGE CreatureEntity : public EntityT<IdType>, public ACreature
+{
+};
+
+class DLL_LINKAGE Creature : public CreatureEntity<CreatureID>
 {
 protected:
 	// use getNamePlural/Singular instead
@@ -32,8 +46,6 @@ public:
 	virtual std::string getNamePluralTextID() const = 0;
 	virtual std::string getNameSingularTextID() const = 0;
 
-	virtual uint32_t getMaxHealth() const = 0;
-
 	virtual int32_t getAdvMapAmountMin() const = 0;
 	virtual int32_t getAdvMapAmountMax() const = 0;
 	virtual int32_t getAIValue() const = 0;

+ 0 - 14
include/vcmi/Entity.h

@@ -31,15 +31,6 @@ public:
 	virtual bool isNativeTerrain(Identifier<ETerrainId> terrain) const;
 };
 
-class DLL_LINKAGE IConstBonusNativeTerrainProvider: public IConstBonusProvider, public INativeTerrainProvider
-{
-public:
-	/**
-	Returns native terrain considering some terrain bonuses.
-	*/
-	virtual Identifier<ETerrainId> getNativeTerrain() const;
-};
-
 class DLL_LINKAGE Entity
 {
 public:
@@ -68,9 +59,4 @@ class DLL_LINKAGE EntityWithBonuses : public EntityT<IdType>, public IConstBonus
 {
 };
 
-template <typename IdType>
-class DLL_LINKAGE EntityWithNativeTerrain : public EntityT<IdType>, public IConstBonusNativeTerrainProvider
-{
-};
-
 VCMI_LIB_NAMESPACE_END

+ 74 - 0
include/vcmi/FactionMember.h

@@ -0,0 +1,74 @@
+/*
+ * FactionMember.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include "Entity.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class BonusList;
+
+namespace PrimarySkill
+{
+    enum PrimarySkill : int8_t;
+}
+
+class DLL_LINKAGE AFactionMember: public IConstBonusProvider, public INativeTerrainProvider
+{
+public:
+	/**
+	 Returns native terrain considering some terrain bonuses.
+	*/
+	virtual Identifier<ETerrainId> getNativeTerrain() const;
+	/**
+	 Returns magic resistance considering some bonuses.
+	*/
+	virtual int32_t magicResistance() const;
+	/**
+	 Returns minimal damage of creature or (when implemented) hero.
+	*/
+	virtual int getMinDamage(bool ranged) const;
+	/**
+	 Returns maximal damage of creature or (when implemented) hero.
+	*/
+	virtual int getMaxDamage(bool ranged) const;
+	/**
+	 Returns attack of creature or hero.
+	*/
+	virtual int getAttack(bool ranged) const;
+	/**
+	 Returns defence of creature or hero.
+	*/
+	virtual int getDefense(bool ranged) const;
+	/**
+	 Returns primskill of creature or hero.
+	*/
+	int getPrimSkillLevel(PrimarySkill::PrimarySkill id) const;
+	/**
+	 Returns morale of creature or hero. Taking absolute bonuses into account.
+	 For now, uses range from EGameSettings
+	*/
+	int moraleVal() const;
+	/**
+	 Returns luck of creature or hero. Taking absolute bonuses into account.
+	 For now, uses range from EGameSettings
+	*/
+	int luckVal() const;
+	/**
+	 Returns total value of all morale bonuses and sets bonusList as a pointer to the list of selected bonuses.
+	 @param bonusList is the out param it's list of all selected bonuses
+	 @return total value of all morale in the range from EGameSettings and 0 otherwise
+	*/
+	int moraleValAndBonusList(std::shared_ptr<const BonusList> & bonusList) const;
+	int luckValAndBonusList(std::shared_ptr<const BonusList> & bonusList) const;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 147 - 3
lib/BasicTypes.cpp

@@ -12,10 +12,14 @@
 
 #include "VCMI_Lib.h"
 #include "GameConstants.h"
-#include "HeroBonus.h"
+#include "GameSettings.h"
+#include "bonuses/BonusList.h"
+#include "bonuses/Bonus.h"
+#include "bonuses/IBonusBearer.h"
 
-#include <vcmi/Entity.h>
+#include <vcmi/Creature.h>
 #include <vcmi/Faction.h>
+#include <vcmi/FactionMember.h>
 #include <vcmi/FactionService.h>
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -26,7 +30,7 @@ bool INativeTerrainProvider::isNativeTerrain(TerrainId terrain) const
 	return native == terrain || native == ETerrainId::ANY_TERRAIN;
 }
 
-TerrainId IConstBonusNativeTerrainProvider::getNativeTerrain() const
+TerrainId AFactionMember::getNativeTerrain() const
 {
 	constexpr auto any = TerrainId(ETerrainId::ANY_TERRAIN);
 	const std::string cachingStringNoTerrainPenalty = "type_NO_TERRAIN_PENALTY_sANY";
@@ -38,4 +42,144 @@ TerrainId IConstBonusNativeTerrainProvider::getNativeTerrain() const
 		? any : VLC->factions()->getById(getFaction())->getNativeTerrain();
 }
 
+int32_t AFactionMember::magicResistance() const
+{
+	si32 val = getBonusBearer()->valOfBonuses(Selector::type()(Bonus::MAGIC_RESISTANCE));
+	vstd::amin (val, 100);
+	return val;
+}
+
+int AFactionMember::getAttack(bool ranged) const
+{
+	const std::string cachingStr = "type_PRIMARY_SKILLs_ATTACK";
+
+	static const auto selector = Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK);
+
+	return getBonusBearer()->valOfBonuses(selector, cachingStr);
+}
+
+int AFactionMember::getDefense(bool ranged) const
+{
+	const std::string cachingStr = "type_PRIMARY_SKILLs_DEFENSE";
+
+	static const auto selector = Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE);
+
+	return getBonusBearer()->valOfBonuses(selector, cachingStr);
+}
+
+int AFactionMember::getMinDamage(bool ranged) const
+{
+	const std::string cachingStr = "type_CREATURE_DAMAGEs_0Otype_CREATURE_DAMAGEs_1";
+	static const auto selector = Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 1));
+	return getBonusBearer()->valOfBonuses(selector, cachingStr);
+}
+
+int AFactionMember::getMaxDamage(bool ranged) const
+{
+	const std::string cachingStr = "type_CREATURE_DAMAGEs_0Otype_CREATURE_DAMAGEs_2";
+	static const auto selector = Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 2));
+	return getBonusBearer()->valOfBonuses(selector, cachingStr);
+}
+
+int AFactionMember::getPrimSkillLevel(PrimarySkill::PrimarySkill id) const
+{
+	static const CSelector selectorAllSkills = Selector::type()(Bonus::PRIMARY_SKILL);
+	static const std::string keyAllSkills = "type_PRIMARY_SKILL";
+	auto allSkills = getBonusBearer()->getBonuses(selectorAllSkills, keyAllSkills);
+	auto ret = allSkills->valOfBonuses(Selector::subtype()(id));
+	auto minSkillValue = (id == PrimarySkill::SPELL_POWER || id == PrimarySkill::KNOWLEDGE) ? 1 : 0;
+	return std::max(ret, minSkillValue); //otherwise, some artifacts may cause negative skill value effect, sp=0 works in old saves
+}
+
+int AFactionMember::moraleValAndBonusList(TConstBonusListPtr & bonusList) const
+{
+	static const auto unaffectedByMoraleSelector = Selector::type()(Bonus::NON_LIVING).Or(Selector::type()(Bonus::UNDEAD))
+													.Or(Selector::type()(Bonus::SIEGE_WEAPON)).Or(Selector::type()(Bonus::NO_MORALE));
+
+	static const std::string cachingStrUn = "AFactionMember::unaffectedByMoraleSelector";
+	auto unaffected = getBonusBearer()->hasBonus(unaffectedByMoraleSelector, cachingStrUn);
+	if(unaffected)
+	{
+		if(bonusList && !bonusList->empty())
+			bonusList = std::make_shared<const BonusList>();
+		return 0;
+	}
+
+	static const auto moraleSelector = Selector::type()(Bonus::MORALE);
+	static const std::string cachingStrMor = "type_MORALE";
+	bonusList = getBonusBearer()->getBonuses(moraleSelector, cachingStrMor);
+
+	int32_t maxGoodMorale = VLC->settings()->getVector(EGameSettings::COMBAT_GOOD_MORALE_DICE).size();
+	int32_t maxBadMorale = -VLC->settings()->getVector(EGameSettings::COMBAT_BAD_MORALE_DICE).size();
+
+	return std::clamp(bonusList->totalValue(), maxBadMorale, maxGoodMorale);
+}
+
+int AFactionMember::luckValAndBonusList(TConstBonusListPtr & bonusList) const
+{
+	if(getBonusBearer()->hasBonusOfType(Bonus::NO_LUCK))
+	{
+		if(bonusList && !bonusList->empty())
+			bonusList = std::make_shared<const BonusList>();
+		return 0;
+	}
+
+	static const auto luckSelector = Selector::type()(Bonus::LUCK);
+	static const std::string cachingStrLuck = "type_LUCK";
+	bonusList = getBonusBearer()->getBonuses(luckSelector, cachingStrLuck);
+
+	int32_t maxGoodLuck = VLC->settings()->getVector(EGameSettings::COMBAT_GOOD_LUCK_DICE).size();
+	int32_t maxBadLuck = -VLC->settings()->getVector(EGameSettings::COMBAT_BAD_LUCK_DICE).size();
+
+	return std::clamp(bonusList->totalValue(), maxBadLuck, maxGoodLuck);
+}
+
+int AFactionMember::moraleVal() const
+{
+	TConstBonusListPtr tmp = nullptr;
+	return moraleValAndBonusList(tmp);
+}
+
+int AFactionMember::luckVal() const
+{
+	TConstBonusListPtr tmp = nullptr;
+	return luckValAndBonusList(tmp);
+}
+
+ui32 ACreature::getMaxHealth() const
+{
+	const std::string cachingStr = "type_STACK_HEALTH";
+	static const auto selector = Selector::type()(Bonus::STACK_HEALTH);
+	auto value = getBonusBearer()->valOfBonuses(selector, cachingStr);
+	return std::max(1, value); //never 0
+}
+
+ui32 ACreature::speed(int turn, bool useBind) const
+{
+	//war machines cannot move
+	if(getBonusBearer()->hasBonus(Selector::type()(Bonus::SIEGE_WEAPON).And(Selector::turns(turn))))
+	{
+		return 0;
+	}
+	//bind effect check - doesn't influence stack initiative
+	if(useBind && getBonusBearer()->hasBonus(Selector::type()(Bonus::BIND_EFFECT).And(Selector::turns(turn))))
+	{
+		return 0;
+	}
+
+	return getBonusBearer()->valOfBonuses(Selector::type()(Bonus::STACKS_SPEED).And(Selector::turns(turn)));
+}
+
+bool ACreature::isLiving() const //TODO: theoreticaly there exists "LIVING" bonus in stack experience documentation
+{
+	static const std::string cachingStr = "ACreature::isLiving";
+	static const CSelector selector = Selector::type()(Bonus::UNDEAD)
+		.Or(Selector::type()(Bonus::NON_LIVING))
+		.Or(Selector::type()(Bonus::GARGOYLE))
+		.Or(Selector::type()(Bonus::SIEGE_WEAPON));
+
+	return !getBonusBearer()->hasBonus(selector, cachingStr);
+}
+
+
 VCMI_LIB_NAMESPACE_END

+ 1 - 1
lib/BattleFieldHandler.h

@@ -11,7 +11,7 @@
 
 #include <vcmi/EntityService.h>
 #include <vcmi/Entity.h>
-#include "HeroBonus.h"
+#include "bonuses/Bonus.h"
 #include "GameConstants.h"
 #include "IHandlerBase.h"
 #include "battle/BattleHex.h"

+ 2 - 1
lib/CArtHandler.h

@@ -12,7 +12,8 @@
 #include <vcmi/Artifact.h>
 #include <vcmi/ArtifactService.h>
 
-#include "HeroBonus.h"
+#include "bonuses/Bonus.h"
+#include "bonuses/CBonusSystemNode.h"
 #include "GameConstants.h"
 #include "IHandlerBase.h"
 

+ 0 - 3
lib/CBonusTypeHandler.cpp

@@ -82,9 +82,6 @@ std::string CBonusTypeHandler::bonusToString(const std::shared_ptr<Bonus> & bonu
 	if (text.find("${subtype.spell}") != std::string::npos)
 		boost::algorithm::replace_all(text, "${subtype.spell}", SpellID(bonus->subtype).toSpell()->getNameTranslated());
 
-	if (text.find("${SHval}") != std::string::npos) //regeneration case
-		boost::algorithm::replace_all(text, "${SHval}", std::to_string(std::min(static_cast<si32>(bearer->MaxHealth()),bearer->valOfBonuses(Selector::typeSubtype(bonus->type, bonus->subtype)))));
-
 	return text;
 }
 

+ 1 - 1
lib/CBonusTypeHandler.h

@@ -12,7 +12,7 @@
 
 #include "IBonusTypeHandler.h"
 #include "IHandlerBase.h"
-#include "HeroBonus.h"
+#include "bonuses/Bonus.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 2 - 5
lib/CCreatureHandler.cpp

@@ -19,6 +19,8 @@
 #include "CModHandler.h"
 #include "GameSettings.h"
 #include "StringConstants.h"
+#include "bonuses/Limiters.h"
+#include "bonuses/Updaters.h"
 #include "serializer/JsonDeserializer.h"
 #include "serializer/JsonUpdater.h"
 #include "mapObjects/CObjectClassesHandler.h"
@@ -69,11 +71,6 @@ const IBonusBearer * CCreature::getBonusBearer() const
 	return this;
 }
 
-uint32_t CCreature::getMaxHealth() const
-{
-	return CBonusSystemNode::MaxHealth();
-}
-
 int32_t CCreature::getAdvMapAmountMin() const
 {
 	return ammMin;

+ 2 - 2
lib/CCreatureHandler.h

@@ -9,7 +9,8 @@
  */
 #pragma once
 
-#include "HeroBonus.h"
+#include "bonuses/Bonus.h"
+#include "bonuses/CBonusSystemNode.h"
 #include "ConstTransitivePtr.h"
 #include "ResourceSet.h"
 #include "GameConstants.h"
@@ -168,7 +169,6 @@ public:
 	void registerIcons(const IconRegistar & cb) const override;
 	CreatureID getId() const override;
 	virtual const IBonusBearer * getBonusBearer() const override;
-	uint32_t getMaxHealth() const override;
 
 	int32_t getAdvMapAmountMin() const override;
 	int32_t getAdvMapAmountMax() const override;

+ 0 - 7
lib/CCreatureSet.cpp

@@ -736,13 +736,6 @@ int CStackInstance::getLevel() const
 	return std::max(1, static_cast<int>(type->getLevel()));
 }
 
-si32 CStackInstance::magicResistance() const
-{
-	si32 val = valOfBonuses(Selector::type()(Bonus::MAGIC_RESISTANCE));
-	vstd::amin (val, 100);
-	return val;
-}
-
 void CStackInstance::giveStackExp(TExpType exp)
 {
 	int level = type->getLevel();

+ 3 - 3
lib/CCreatureSet.h

@@ -9,7 +9,8 @@
  */
 #pragma once
 
-#include "HeroBonus.h"
+#include "bonuses/Bonus.h"
+#include "bonuses/CBonusSystemNode.h"
 #include "GameConstants.h"
 #include "CArtHandler.h"
 #include "CCreatureHandler.h"
@@ -63,7 +64,7 @@ public:
 	void serializeJson(JsonSerializeFormat & handler);
 };
 
-class DLL_LINKAGE CStackInstance : public CBonusSystemNode, public CStackBasicDescriptor, public CArtifactSet, public IConstBonusNativeTerrainProvider
+class DLL_LINKAGE CStackInstance : public CBonusSystemNode, public CStackBasicDescriptor, public CArtifactSet, public ACreature
 {
 protected:
 	const CArmedInstance *_armyObj; //stack must be part of some army, army must be part of some object
@@ -106,7 +107,6 @@ public:
 	std::string getQuantityTXT(bool capitalized = true) const;
 	virtual int getExpRank() const;
 	virtual int getLevel() const; //different for regular stack and commander
-	si32 magicResistance() const override;
 	CreatureID getCreatureID() const; //-1 if not available
 	std::string getName() const; //plural or singular
 	virtual void init();

+ 2 - 2
lib/CGameState.cpp

@@ -3089,8 +3089,8 @@ void InfoAboutHero::initFromHero(const CGHeroInstance *h, InfoAboutHero::EInfoLe
 	{
 		//include details about hero
 		details = new Details();
-		details->luck = h->LuckVal();
-		details->morale = h->MoraleVal();
+		details->luck = h->luckVal();
+		details->morale = h->moraleVal();
 		details->mana = h->mana;
 		details->primskills.resize(GameConstants::PRIMARY_SKILLS);
 

+ 1 - 1
lib/CGameState.h

@@ -12,7 +12,7 @@
 #include "CCreatureHandler.h"
 #include "VCMI_Lib.h"
 
-#include "HeroBonus.h"
+#include "bonuses/Bonus.h"
 #include "CCreatureSet.h"
 #include "ConstTransitivePtr.h"
 #include "IGameCallback.h"

+ 2 - 0
lib/CHeroHandler.cpp

@@ -24,6 +24,8 @@
 #include "CSkillHandler.h"
 #include "mapObjects/CObjectClassesHandler.h"
 #include "BattleFieldHandler.h"
+#include "bonuses/Limiters.h"
+#include "bonuses/Updaters.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 2 - 1
lib/CHeroHandler.h

@@ -16,7 +16,8 @@
 
 #include "../lib/ConstTransitivePtr.h"
 #include "GameConstants.h"
-#include "HeroBonus.h"
+#include "bonuses/Bonus.h"
+#include "bonuses/BonusList.h"
 #include "IHandlerBase.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 1 - 1
lib/CPathfinder.h

@@ -11,7 +11,7 @@
 
 #include "VCMI_Lib.h"
 #include "IGameCallback.h"
-#include "HeroBonus.h"
+#include "bonuses/Bonus.h"
 #include "int3.h"
 
 #include <boost/heap/fibonacci_heap.hpp>

+ 2 - 1
lib/CPlayerState.h

@@ -12,7 +12,8 @@
 #include <vcmi/Player.h>
 #include <vcmi/Team.h>
 
-#include "HeroBonus.h"
+#include "bonuses/Bonus.h"
+#include "bonuses/CBonusSystemNode.h"
 #include "ResourceSet.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 1 - 1
lib/CSkillHandler.h

@@ -12,7 +12,7 @@
 #include <vcmi/Skill.h>
 #include <vcmi/SkillService.h>
 
-#include "../lib/HeroBonus.h"
+#include "../lib/bonuses/Bonus.h"
 #include "GameConstants.h"
 #include "IHandlerBase.h"
 

+ 4 - 3
lib/CStack.cpp

@@ -12,6 +12,7 @@
 
 #include <vstd/RNG.h>
 
+#include <vcmi/Entity.h>
 #include <vcmi/ServerCallback.h>
 
 #include "CGeneralTextHandler.h"
@@ -88,7 +89,7 @@ ui32 CStack::level() const
 
 si32 CStack::magicResistance() const
 {
-	auto magicResistance = IBonusBearer::magicResistance();
+	auto magicResistance = AFactionMember::magicResistance();
 
 	si32 auraBonus = 0;
 
@@ -229,7 +230,7 @@ void CStack::prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand, const
 			{
 				customState->casts.use();
 				bsa.flags |= BattleStackAttacked::REBIRTH;
-				int64_t toHeal = customState->MaxHealth() * resurrectedCount;
+				int64_t toHeal = customState->getMaxHealth() * resurrectedCount;
 				//TODO: add one-battle rebirth?
 				customState->heal(toHeal, EHealLevel::RESURRECT, EHealPower::PERMANENT);
 				customState->counterAttacks.use(customState->counterAttacks.available());
@@ -307,7 +308,7 @@ std::string CStack::getName() const
 
 bool CStack::canBeHealed() const
 {
-	return getFirstHPleft() < static_cast<int32_t>(MaxHealth()) && isValidTarget() && !hasBonusOfType(Bonus::SIEGE_WEAPON);
+	return getFirstHPleft() < static_cast<int32_t>(getMaxHealth()) && isValidTarget() && !hasBonusOfType(Bonus::SIEGE_WEAPON);
 }
 
 bool CStack::isOnNativeTerrain() const

+ 2 - 1
lib/CStack.h

@@ -10,7 +10,8 @@
 
 #pragma once
 #include "JsonNode.h"
-#include "HeroBonus.h"
+#include "bonuses/Bonus.h"
+#include "bonuses/CBonusSystemNode.h"
 #include "CCreatureHandler.h" //todo: remove
 #include "battle/BattleHex.h"
 #include "mapObjects/CGHeroInstance.h" // for commander serialization

+ 2 - 1
lib/CTownHandler.cpp

@@ -23,7 +23,8 @@
 #include "filesystem/Filesystem.h"
 #include "mapObjects/CObjectClassesHandler.h"
 #include "mapObjects/CObjectHandler.h"
-#include "HeroBonus.h"
+#include "bonuses/Bonus.h"
+#include "bonuses/Propagators.h"
 #include "ResourceSet.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 2 - 1
lib/CTownHandler.h

@@ -19,7 +19,8 @@
 #include "IHandlerBase.h"
 #include "LogicalExpression.h"
 #include "battle/BattleHex.h"
-#include "HeroBonus.h"
+#include "bonuses/Bonus.h"
+#include "bonuses/BonusList.h"
 #include "Point.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 1 - 2
lib/GameConstants.h

@@ -398,7 +398,7 @@ class TeleportChannelID : public BaseForID<TeleportChannelID, si32>
 // Enum declarations
 namespace PrimarySkill
 {
-	enum PrimarySkill { NONE = -1, ATTACK, DEFENSE, SPELL_POWER, KNOWLEDGE,
+	enum PrimarySkill : int8_t { NONE = -1, ATTACK, DEFENSE, SPELL_POWER, KNOWLEDGE,
 				EXPERIENCE = 4}; //for some reason changePrimSkill uses it
 }
 
@@ -1339,7 +1339,6 @@ enum class EHealPower : ui8
 
 // Typedef declarations
 using TExpType = si64;
-using TBonusSubtype = si32;
 using TQuantity = si32;
 
 using TRmgTemplateZoneId = int;

+ 0 - 2794
lib/HeroBonus.cpp

@@ -1,2794 +0,0 @@
-/*
- * HeroBonus.cpp, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-
-#include "StdInc.h"
-#include "HeroBonus.h"
-
-#include "VCMI_Lib.h"
-#include "spells/CSpellHandler.h"
-#include "CCreatureHandler.h"
-#include "CCreatureSet.h"
-#include "CHeroHandler.h"
-#include "CTownHandler.h"
-#include "CGeneralTextHandler.h"
-#include "CSkillHandler.h"
-#include "CStack.h"
-#include "CArtHandler.h"
-#include "CModHandler.h"
-#include "TerrainHandler.h"
-#include "StringConstants.h"
-#include "battle/BattleInfo.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-#define FOREACH_PARENT(pname) 	TNodes lparents; getParents(lparents); for(CBonusSystemNode *pname : lparents)
-#define FOREACH_RED_CHILD(pname) 	TNodes lchildren; getRedChildren(lchildren); for(CBonusSystemNode *pname : lchildren)
-
-#define BONUS_NAME(x) { #x, Bonus::x },
-	const std::map<std::string, Bonus::BonusType> bonusNameMap = {
-		BONUS_LIST
-	};
-#undef BONUS_NAME
-
-#define BONUS_VALUE(x) { #x, Bonus::x },
-	const std::map<std::string, Bonus::ValueType> bonusValueMap = { BONUS_VALUE_LIST };
-#undef BONUS_VALUE
-
-#define BONUS_SOURCE(x) { #x, Bonus::x },
-	const std::map<std::string, Bonus::BonusSource> bonusSourceMap = { BONUS_SOURCE_LIST };
-#undef BONUS_SOURCE
-
-#define BONUS_ITEM(x) { #x, Bonus::x },
-
-const std::map<std::string, ui16> bonusDurationMap =
-{
-	BONUS_ITEM(PERMANENT)
-	BONUS_ITEM(ONE_BATTLE)
-	BONUS_ITEM(ONE_DAY)
-	BONUS_ITEM(ONE_WEEK)
-	BONUS_ITEM(N_TURNS)
-	BONUS_ITEM(N_DAYS)
-	BONUS_ITEM(UNTIL_BEING_ATTACKED)
-	BONUS_ITEM(UNTIL_ATTACK)
-	BONUS_ITEM(STACK_GETS_TURN)
-	BONUS_ITEM(COMMANDER_KILLED)
-	{ "UNITL_BEING_ATTACKED", Bonus::UNTIL_BEING_ATTACKED }//typo, but used in some mods
-};
-
-const std::map<std::string, Bonus::LimitEffect> bonusLimitEffect =
-{
-	BONUS_ITEM(NO_LIMIT)
-	BONUS_ITEM(ONLY_DISTANCE_FIGHT)
-	BONUS_ITEM(ONLY_MELEE_FIGHT)
-};
-
-const std::map<std::string, TLimiterPtr> bonusLimiterMap =
-{
-	{"SHOOTER_ONLY", std::make_shared<HasAnotherBonusLimiter>(Bonus::SHOOTER)},
-	{"DRAGON_NATURE", std::make_shared<HasAnotherBonusLimiter>(Bonus::DRAGON_NATURE)},
-	{"IS_UNDEAD", std::make_shared<HasAnotherBonusLimiter>(Bonus::UNDEAD)},
-	{"CREATURE_NATIVE_TERRAIN", std::make_shared<CreatureTerrainLimiter>()},
-	{"CREATURE_FACTION", std::make_shared<AllOfLimiter>(std::initializer_list<TLimiterPtr>{std::make_shared<CreatureLevelLimiter>(), std::make_shared<FactionLimiter>()})},
-	{"SAME_FACTION", std::make_shared<FactionLimiter>()},
-	{"CREATURES_ONLY", std::make_shared<CreatureLevelLimiter>()},
-	{"OPPOSITE_SIDE", std::make_shared<OppositeSideLimiter>()},
-};
-
-const std::map<std::string, TPropagatorPtr> bonusPropagatorMap =
-{
-	{"BATTLE_WIDE", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::BATTLE)},
-	{"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
-
-const std::map<std::string, TUpdaterPtr> bonusUpdaterMap =
-{
-	{"TIMES_HERO_LEVEL", std::make_shared<TimesHeroLevelUpdater>()},
-	{"TIMES_STACK_LEVEL", std::make_shared<TimesStackLevelUpdater>()},
-	{"ARMY_MOVEMENT", std::make_shared<ArmyMovementUpdater>()},
-	{"BONUS_OWNER_UPDATER", std::make_shared<OwnerUpdater>()}
-};
-
-const std::set<std::string> deprecatedBonusSet = {
-	"SECONDARY_SKILL_PREMY",
-	"SECONDARY_SKILL_VAL2",
-	"MAXED_SPELL",
-	"LAND_MOVEMENT",
-	"SEA_MOVEMENT",
-	"SIGHT_RADIOUS",
-	"NO_TYPE",
-	"SPECIAL_SECONDARY_SKILL",
-	"FULL_HP_REGENERATION",
-	"KING1",
-	"KING2",
-	"KING3",
-	"BLOCK_MORALE",
-	"BLOCK_LUCK",
-	"SELF_MORALE",
-	"SELF_LUCK"
-};
-
-///CBonusProxy
-CBonusProxy::CBonusProxy(const IBonusBearer * Target, CSelector Selector):
-	bonusListCachedLast(0),
-	target(Target),
-	selector(std::move(Selector)),
-	currentBonusListIndex(0)
-{
-
-}
-
-CBonusProxy::CBonusProxy(const CBonusProxy & other):
-	bonusListCachedLast(other.bonusListCachedLast),
-	target(other.target),
-	selector(other.selector),
-	currentBonusListIndex(other.currentBonusListIndex)
-{
-	bonusList[currentBonusListIndex] = other.bonusList[currentBonusListIndex];
-}
-
-CBonusProxy::CBonusProxy(CBonusProxy && other) noexcept:
-	bonusListCachedLast(0),
-	target(other.target),
-	currentBonusListIndex(0)
-{
-	std::swap(bonusListCachedLast, other.bonusListCachedLast);
-	std::swap(selector, other.selector);
-	std::swap(bonusList, other.bonusList);
-	std::swap(currentBonusListIndex, other.currentBonusListIndex);
-}
-
-CBonusProxy & CBonusProxy::operator=(const CBonusProxy & other)
-{
-	boost::lock_guard<boost::mutex> lock(swapGuard);
-
-	selector = other.selector;
-	swapBonusList(other.bonusList[other.currentBonusListIndex]);
-	bonusListCachedLast = other.bonusListCachedLast;
-
-	return *this;
-}
-
-CBonusProxy & CBonusProxy::operator=(CBonusProxy && other) noexcept
-{
-	std::swap(bonusListCachedLast, other.bonusListCachedLast);
-	std::swap(selector, other.selector);
-	std::swap(bonusList, other.bonusList);
-	std::swap(currentBonusListIndex, other.currentBonusListIndex);
-
-	return *this;
-}
-
-void CBonusProxy::swapBonusList(TConstBonusListPtr other) const
-{
-	// The idea here is to avoid changing active bonusList while it can be read by a different thread.
-	// Because such use of shared ptr is not thread safe
-	// So to avoid this we change the second offline instance and swap active index
-	auto newCurrent = 1 - currentBonusListIndex;
-	bonusList[newCurrent] = std::move(other);
-	currentBonusListIndex = newCurrent;
-}
-
-TConstBonusListPtr CBonusProxy::getBonusList() const
-{
-	auto needUpdateBonusList = [&]() -> bool
-	{
-		return target->getTreeVersion() != bonusListCachedLast || !bonusList[currentBonusListIndex];
-	};
-
-	// avoid locking if everything is up-to-date
-	if(needUpdateBonusList())
-	{
-		boost::lock_guard<boost::mutex>lock(swapGuard);
-
-		if(needUpdateBonusList())
-		{
-			//TODO: support limiters
-			swapBonusList(target->getAllBonuses(selector, Selector::all));
-			bonusListCachedLast = target->getTreeVersion();
-		}
-	}
-
-	return bonusList[currentBonusListIndex];
-}
-
-const BonusList * CBonusProxy::operator->() const
-{
-	return getBonusList().get();
-}
-
-CTotalsProxy::CTotalsProxy(const IBonusBearer * Target, CSelector Selector, int InitialValue):
-	CBonusProxy(Target, std::move(Selector)),
-	initialValue(InitialValue),
-	meleeCachedLast(0),
-	meleeValue(0),
-	rangedCachedLast(0),
-	rangedValue(0)
-{
-}
-
-CTotalsProxy::CTotalsProxy(const CTotalsProxy & other)
-	: CBonusProxy(other),
-	initialValue(other.initialValue),
-	meleeCachedLast(other.meleeCachedLast),
-	meleeValue(other.meleeValue),
-	rangedCachedLast(other.rangedCachedLast),
-	rangedValue(other.rangedValue)
-{
-}
-
-int CTotalsProxy::getValue() const
-{
-	const auto treeVersion = target->getTreeVersion();
-
-	if(treeVersion != valueCachedLast)
-	{
-		auto bonuses = getBonusList();
-
-		value = initialValue + bonuses->totalValue();
-		valueCachedLast = treeVersion;
-	}
-	return value;
-}
-
-int CTotalsProxy::getValueAndList(TConstBonusListPtr & outBonusList) const
-{
-	const auto treeVersion = target->getTreeVersion();
-	outBonusList = getBonusList();
-
-	if(treeVersion != valueCachedLast)
-	{
-		value = initialValue + outBonusList->totalValue();
-		valueCachedLast = treeVersion;
-	}
-	return value;
-}
-
-int CTotalsProxy::getMeleeValue() const
-{
-	static const auto limit = Selector::effectRange()(Bonus::NO_LIMIT).Or(Selector::effectRange()(Bonus::ONLY_MELEE_FIGHT));
-
-	const auto treeVersion = target->getTreeVersion();
-
-	if(treeVersion != meleeCachedLast)
-	{
-		auto bonuses = target->getBonuses(selector, limit);
-		meleeValue = initialValue + bonuses->totalValue();
-		meleeCachedLast = treeVersion;
-	}
-
-	return meleeValue;
-}
-
-int CTotalsProxy::getRangedValue() const
-{
-	static const auto limit = Selector::effectRange()(Bonus::NO_LIMIT).Or(Selector::effectRange()(Bonus::ONLY_DISTANCE_FIGHT));
-
-	const auto treeVersion = target->getTreeVersion();
-
-	if(treeVersion != rangedCachedLast)
-	{
-		auto bonuses = target->getBonuses(selector, limit);
-		rangedValue = initialValue + bonuses->totalValue();
-		rangedCachedLast = treeVersion;
-	}
-
-	return rangedValue;
-}
-
-///CCheckProxy
-CCheckProxy::CCheckProxy(const IBonusBearer * Target, CSelector Selector):
-	target(Target),
-	selector(std::move(Selector)),
-	cachedLast(0),
-	hasBonus(false)
-{
-}
-
-//This constructor should be placed here to avoid side effects
-CCheckProxy::CCheckProxy(const CCheckProxy & other) = default;
-
-bool CCheckProxy::getHasBonus() const
-{
-	const auto treeVersion = target->getTreeVersion();
-
-	if(treeVersion != cachedLast)
-	{
-		hasBonus = target->hasBonus(selector);
-		cachedLast = treeVersion;
-	}
-
-	return hasBonus;
-}
-
-//This constructor should be placed here to avoid side effects
-CAddInfo::CAddInfo() = default;
-
-CAddInfo::CAddInfo(si32 value)
-{
-	if(value != CAddInfo::NONE)
-		push_back(value);
-}
-
-bool CAddInfo::operator==(si32 value) const
-{
-	switch(size())
-	{
-	case 0:
-		return value == CAddInfo::NONE;
-	case 1:
-		return operator[](0) == value;
-	default:
-		return false;
-	}
-}
-
-bool CAddInfo::operator!=(si32 value) const
-{
-	return !operator==(value);
-}
-
-si32 & CAddInfo::operator[](size_type pos)
-{
-	if(pos >= size())
-		resize(pos + 1, CAddInfo::NONE);
-	return vector::operator[](pos);
-}
-
-si32 CAddInfo::operator[](size_type pos) const
-{
-	return pos < size() ? vector::operator[](pos) : CAddInfo::NONE;
-}
-
-std::string CAddInfo::toString() const
-{
-	return toJsonNode().toJson(true);
-}
-
-JsonNode CAddInfo::toJsonNode() const
-{
-	if(size() < 2)
-	{
-		return JsonUtils::intNode(operator[](0));
-	}
-	else
-	{
-		JsonNode node(JsonNode::JsonType::DATA_VECTOR);
-		for(si32 value : *this)
-			node.Vector().push_back(JsonUtils::intNode(value));
-		return node;
-	}
-}
-
-std::atomic<int64_t> CBonusSystemNode::treeChanged(1);
-constexpr bool CBonusSystemNode::cachingEnabled = true;
-
-BonusList::BonusList(bool BelongsToTree) : belongsToTree(BelongsToTree)
-{
-
-}
-
-BonusList::BonusList(const BonusList & bonusList): belongsToTree(false)
-{
-	bonuses.resize(bonusList.size());
-	std::copy(bonusList.begin(), bonusList.end(), bonuses.begin());
-}
-
-BonusList::BonusList(BonusList && other) noexcept: belongsToTree(false)
-{
-	std::swap(belongsToTree, other.belongsToTree);
-	std::swap(bonuses, other.bonuses);
-}
-
-BonusList& BonusList::operator=(const BonusList &bonusList)
-{
-	bonuses.resize(bonusList.size());
-	std::copy(bonusList.begin(), bonusList.end(), bonuses.begin());
-	belongsToTree = false;
-	return *this;
-}
-
-void BonusList::changed() const
-{
-    if(belongsToTree)
-		CBonusSystemNode::treeHasChanged();
-}
-
-void BonusList::stackBonuses()
-{
-	boost::sort(bonuses, [](const std::shared_ptr<Bonus> & b1, const std::shared_ptr<Bonus> & b2) -> bool
-	{
-		if(b1 == b2)
-			return false;
-#define COMPARE_ATT(ATT) if(b1->ATT != b2->ATT) return b1->ATT < b2->ATT
-		COMPARE_ATT(stacking);
-		COMPARE_ATT(type);
-		COMPARE_ATT(subtype);
-		COMPARE_ATT(valType);
-#undef COMPARE_ATT
-		return b1->val > b2->val;
-	});
-	// remove non-stacking
-	size_t next = 1;
-	while(next < bonuses.size())
-	{
-		bool remove = false;
-		std::shared_ptr<Bonus> last = bonuses[next-1];
-		std::shared_ptr<Bonus> current = bonuses[next];
-
-		if(current->stacking.empty())
-			remove = current == last;
-		else if(current->stacking == "ALWAYS")
-			remove = false;
-		else
-			remove = current->stacking == last->stacking
-				&& current->type == last->type
-				&& current->subtype == last->subtype
-				&& current->valType == last->valType;
-
-		if(remove)
-			bonuses.erase(bonuses.begin() + next);
-		else
-			next++;
-	}
-}
-
-int BonusList::totalValue() const
-{
-	struct BonusCollection
-	{
-		int base = 0;
-		int percentToBase = 0;
-		int percentToAll = 0;
-		int additive = 0;
-		int percentToSource = 0;
-		int indepMin = std::numeric_limits<int>::max();
-		int indepMax = std::numeric_limits<int>::min();
-	};
-
-	auto percent = [](int64_t base, int64_t percent) -> int {
-		return static_cast<int>(std::clamp<int64_t>((base * (100 + percent)) / 100, std::numeric_limits<int>::min(), std::numeric_limits<int>::max()));
-	};
-	std::array <BonusCollection, Bonus::BonusSource::NUM_BONUS_SOURCE> sources = {};
-	BonusCollection any;
-	bool hasIndepMax = false;
-	bool hasIndepMin = false;
-
-	for(const auto & b : bonuses)
-	{
-		switch(b->valType)
-		{
-		case Bonus::BASE_NUMBER:
-			sources[b->source].base += b->val;
-			break;
-		case Bonus::PERCENT_TO_ALL:
-			sources[b->source].percentToAll += b->val;
-			break;
-		case Bonus::PERCENT_TO_BASE:
-			sources[b->source].percentToBase += b->val;
-			break;
-		case Bonus::PERCENT_TO_SOURCE:
-			sources[b->source].percentToSource += b->val;
-			break;
-		case Bonus::PERCENT_TO_TARGET_TYPE:
-			sources[b->targetSourceType].percentToSource += b->val;
-			break;
-		case Bonus::ADDITIVE_VALUE:
-			sources[b->source].additive += b->val;
-			break;
-		case Bonus::INDEPENDENT_MAX:
-			hasIndepMax = true;
-			vstd::amax(sources[b->source].indepMax, b->val);
-			break;
-		case Bonus::INDEPENDENT_MIN:
-			hasIndepMin = true;
-			vstd::amin(sources[b->source].indepMin, b->val);
-			break;
-		}
-	}
-	for(const auto & src : sources)
-	{
-		any.base += percent(src.base, src.percentToSource);
-		any.percentToBase += percent(src.percentToBase, src.percentToSource);
-		any.percentToAll += percent(src.percentToAll, src.percentToSource);
-		any.additive += percent(src.additive, src.percentToSource);
-		if(hasIndepMin)
-			vstd::amin(any.indepMin, percent(src.indepMin, src.percentToSource));
-		if(hasIndepMax)
-			vstd::amax(any.indepMax, percent(src.indepMax, src.percentToSource));
-	}
-	any.base = percent(any.base, any.percentToBase);
-	any.base += any.additive;
-	auto valFirst = percent(any.base ,any.percentToAll);
-
-	if(hasIndepMin && hasIndepMax && any.indepMin < any.indepMax)
-		any.indepMax = any.indepMin;
-
-	const int notIndepBonuses = static_cast<int>(std::count_if(bonuses.cbegin(), bonuses.cend(), [](const std::shared_ptr<Bonus>& b)
-	{
-		return b->valType != Bonus::INDEPENDENT_MAX && b->valType != Bonus::INDEPENDENT_MIN;
-	}));
-
-	if(notIndepBonuses)
-		return std::clamp(valFirst, any.indepMax, any.indepMin);
-	
-	return hasIndepMin ? any.indepMin : hasIndepMax ? any.indepMax : 0;
-}
-
-std::shared_ptr<Bonus> BonusList::getFirst(const CSelector &select)
-{
-	for (auto & b : bonuses)
-	{
-		if(select(b.get()))
-			return b;
-	}
-	return nullptr;
-}
-
-std::shared_ptr<const Bonus> BonusList::getFirst(const CSelector &selector) const
-{
-	for(const auto & b : bonuses)
-	{
-		if(selector(b.get()))
-			return b;
-	}
-	return nullptr;
-}
-
-void BonusList::getBonuses(BonusList & out, const CSelector &selector, const CSelector &limit) const
-{
-	out.reserve(bonuses.size());
-	for(const auto & b : bonuses)
-	{
-		//add matching bonuses that matches limit predicate or have NO_LIMIT if no given predicate
-		auto noFightLimit = b->effectRange == Bonus::NO_LIMIT;
-		if(selector(b.get()) && ((!limit && noFightLimit) || ((bool)limit && limit(b.get()))))
-			out.push_back(b);
-	}
-}
-
-void BonusList::getAllBonuses(BonusList &out) const
-{
-	for(const auto & b : bonuses)
-		out.push_back(b);
-}
-
-int BonusList::valOfBonuses(const CSelector &select) const
-{
-	BonusList ret;
-	CSelector limit = nullptr;
-	getBonuses(ret, select, limit);
-	return ret.totalValue();
-}
-
-JsonNode BonusList::toJsonNode() const
-{
-	JsonNode node(JsonNode::JsonType::DATA_VECTOR);
-	for(const std::shared_ptr<Bonus> & b : bonuses)
-		node.Vector().push_back(b->toJsonNode());
-	return node;
-}
-
-void BonusList::push_back(const std::shared_ptr<Bonus> & x)
-{
-	bonuses.push_back(x);
-	changed();
-}
-
-BonusList::TInternalContainer::iterator BonusList::erase(const int position)
-{
-	changed();
-	return bonuses.erase(bonuses.begin() + position);
-}
-
-void BonusList::clear()
-{
-	bonuses.clear();
-	changed();
-}
-
-std::vector<BonusList *>::size_type BonusList::operator-=(const std::shared_ptr<Bonus> & i)
-{
-	auto itr = std::find(bonuses.begin(), bonuses.end(), i);
-	if(itr == bonuses.end())
-		return false;
-	bonuses.erase(itr);
-	changed();
-	return true;
-}
-
-void BonusList::resize(BonusList::TInternalContainer::size_type sz, const std::shared_ptr<Bonus> & c)
-{
-	bonuses.resize(sz, c);
-	changed();
-}
-
-void BonusList::reserve(TInternalContainer::size_type sz)
-{
-	bonuses.reserve(sz);
-}
-
-void BonusList::insert(BonusList::TInternalContainer::iterator position, BonusList::TInternalContainer::size_type n, const std::shared_ptr<Bonus> & x)
-{
-	bonuses.insert(position, n, x);
-	changed();
-}
-
-CSelector IBonusBearer::anaffectedByMoraleSelector =
-Selector::type()(Bonus::NON_LIVING)
-.Or(Selector::type()(Bonus::UNDEAD))
-.Or(Selector::type()(Bonus::SIEGE_WEAPON))
-.Or(Selector::type()(Bonus::NO_MORALE));
-
-CSelector IBonusBearer::moraleSelector = Selector::type()(Bonus::MORALE);
-CSelector IBonusBearer::luckSelector = Selector::type()(Bonus::LUCK);
-
-IBonusBearer::IBonusBearer()
-	:anaffectedByMorale(this, anaffectedByMoraleSelector),
-	moraleValue(this, moraleSelector, 0),
-	luckValue(this, luckSelector, 0)
-{
-}
-
-int IBonusBearer::valOfBonuses(Bonus::BonusType type, const CSelector &selector) const
-{
-	return valOfBonuses(Selector::type()(type).And(selector));
-}
-
-int IBonusBearer::valOfBonuses(Bonus::BonusType type, int subtype) const
-{
-	//This part is performance-critical
-	std::string cachingStr = "type_" + std::to_string(static_cast<int>(type)) + "_" + std::to_string(subtype);
-
-	CSelector s = Selector::type()(type);
-	if(subtype != -1)
-		s = s.And(Selector::subtype()(subtype));
-
-	return valOfBonuses(s, cachingStr);
-}
-
-int IBonusBearer::valOfBonuses(const CSelector &selector, const std::string &cachingStr) const
-{
-	CSelector limit = nullptr;
-	TConstBonusListPtr hlp = getAllBonuses(selector, limit, nullptr, cachingStr);
-	return hlp->totalValue();
-}
-bool IBonusBearer::hasBonus(const CSelector &selector, const std::string &cachingStr) const
-{
-	//TODO: We don't need to count all bonuses and could break on first matching
-	return getBonuses(selector, cachingStr)->size() > 0;
-}
-
-bool IBonusBearer::hasBonus(const CSelector &selector, const CSelector &limit, const std::string &cachingStr) const
-{
-	return getBonuses(selector, limit, cachingStr)->size() > 0;
-}
-
-bool IBonusBearer::hasBonusOfType(Bonus::BonusType type, int subtype) const
-{
-	//This part is performance-ciritcal
-	std::string cachingStr = "type_" + std::to_string(static_cast<int>(type)) + "_" + std::to_string(subtype);
-
-	CSelector s = Selector::type()(type);
-	if(subtype != -1)
-		s = s.And(Selector::subtype()(subtype));
-
-	return hasBonus(s, cachingStr);
-}
-
-TConstBonusListPtr IBonusBearer::getBonuses(const CSelector &selector, const std::string &cachingStr) const
-{
-	return getAllBonuses(selector, nullptr, nullptr, cachingStr);
-}
-
-TConstBonusListPtr IBonusBearer::getBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr) const
-{
-	return getAllBonuses(selector, limit, nullptr, cachingStr);
-}
-
-bool IBonusBearer::hasBonusFrom(Bonus::BonusSource source, ui32 sourceID) const
-{
-	boost::format fmt("source_%did_%d");
-	fmt % static_cast<int>(source) % sourceID;
-
-	return hasBonus(Selector::source(source,sourceID), fmt.str());
-}
-
-int IBonusBearer::MoraleVal() const
-{
-	if(anaffectedByMorale.getHasBonus())
-		return 0;
-
-	return std::clamp(moraleValue.getValue(), -3, +3);
-}
-
-int IBonusBearer::LuckVal() const
-{
-	if(hasBonusOfType(Bonus::NO_LUCK))
-		return 0;
-
-	return std::clamp(luckValue.getValue(), -3, +3);
-}
-
-int IBonusBearer::MoraleValAndBonusList(TConstBonusListPtr & bonusList) const
-{
-	if(anaffectedByMorale.getHasBonus())
-	{
-		if(!bonusList->empty())
-			bonusList = std::make_shared<const BonusList>();
-		return 0;
-	}
-
-	return std::clamp(moraleValue.getValueAndList(bonusList), -3, +3);
-}
-
-int IBonusBearer::LuckValAndBonusList(TConstBonusListPtr & bonusList) const
-{
-	if(hasBonusOfType(Bonus::NO_LUCK))
-	{
-		if(!bonusList->empty())
-			bonusList = std::make_shared<const BonusList>();
-		return 0;
-	}
-
-	return std::clamp(luckValue.getValueAndList(bonusList), -3, +3);
-}
-
-ui32 IBonusBearer::MaxHealth() const
-{
-	const std::string cachingStr = "type_STACK_HEALTH";
-	static const auto selector = Selector::type()(Bonus::STACK_HEALTH);
-	auto value = valOfBonuses(selector, cachingStr);
-	return std::max(1, value); //never 0
-}
-
-int IBonusBearer::getAttack(bool ranged) const
-{
-	const std::string cachingStr = "type_PRIMARY_SKILLs_ATTACK";
-
-	static const auto selector = Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK);
-
-	return getBonuses(selector, nullptr, cachingStr)->totalValue();
-}
-
-int IBonusBearer::getDefense(bool ranged) const
-{
-	const std::string cachingStr = "type_PRIMARY_SKILLs_DEFENSE";
-
-	static const auto selector = Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE);
-
-	return getBonuses(selector, nullptr, cachingStr)->totalValue();
-}
-
-int IBonusBearer::getMinDamage(bool ranged) const
-{
-	const std::string cachingStr = "type_CREATURE_DAMAGEs_0Otype_CREATURE_DAMAGEs_1";
-	static const auto selector = Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 1));
-	return valOfBonuses(selector, cachingStr);
-}
-
-int IBonusBearer::getMaxDamage(bool ranged) const
-{
-	const std::string cachingStr = "type_CREATURE_DAMAGEs_0Otype_CREATURE_DAMAGEs_2";
-	static const auto selector = Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 2));
-	return valOfBonuses(selector, cachingStr);
-}
-
-int IBonusBearer::getPrimSkillLevel(PrimarySkill::PrimarySkill id) const
-{
-	static const CSelector selectorAllSkills = Selector::type()(Bonus::PRIMARY_SKILL);
-	static const std::string keyAllSkills = "type_PRIMARY_SKILL";
-	auto allSkills = getBonuses(selectorAllSkills, keyAllSkills);
-	auto ret = allSkills->valOfBonuses(Selector::subtype()(id));
-	auto minSkillValue = (id == PrimarySkill::SPELL_POWER || id == PrimarySkill::KNOWLEDGE) ? 1 : 0;
-	vstd::amax(ret, minSkillValue); //otherwise, some artifacts may cause negative skill value effect
-	return ret; //sp=0 works in old saves
-}
-
-si32 IBonusBearer::magicResistance() const
-{
-	return valOfBonuses(Bonus::MAGIC_RESISTANCE);
-}
-
-ui32 IBonusBearer::Speed(int turn, bool useBind) const
-{
-	//war machines cannot move
-	if(hasBonus(Selector::type()(Bonus::SIEGE_WEAPON).And(Selector::turns(turn))))
-	{
-		return 0;
-	}
-	//bind effect check - doesn't influence stack initiative
-	if(useBind && hasBonus(Selector::type()(Bonus::BIND_EFFECT).And(Selector::turns(turn))))
-	{
-		return 0;
-	}
-
-	return valOfBonuses(Selector::type()(Bonus::STACKS_SPEED).And(Selector::turns(turn)));
-}
-
-bool IBonusBearer::isLiving() const //TODO: theoreticaly there exists "LIVING" bonus in stack experience documentation
-{
-	static const std::string cachingStr = "IBonusBearer::isLiving";
-	static const CSelector selector = Selector::type()(Bonus::UNDEAD)
-		.Or(Selector::type()(Bonus::NON_LIVING))
-		.Or(Selector::type()(Bonus::GARGOYLE))
-		.Or(Selector::type()(Bonus::SIEGE_WEAPON));
-
-	return !hasBonus(selector, cachingStr);
-}
-
-std::shared_ptr<const Bonus> IBonusBearer::getBonus(const CSelector &selector) const
-{
-	auto bonuses = getAllBonuses(selector, Selector::all);
-	return bonuses->getFirst(Selector::all);
-}
-
-const CStack * retrieveStackBattle(const CBonusSystemNode * node)
-{
-	switch(node->getNodeType())
-	{
-	case CBonusSystemNode::STACK_BATTLE:
-		return dynamic_cast<const CStack *>(node);
-	default:
-		return nullptr;
-	}
-}
-
-const CStackInstance * retrieveStackInstance(const CBonusSystemNode * node)
-{
-	switch(node->getNodeType())
-	{
-	case CBonusSystemNode::STACK_INSTANCE:
-		return (dynamic_cast<const CStackInstance *>(node));
-	case CBonusSystemNode::STACK_BATTLE:
-		return (dynamic_cast<const CStack *>(node))->base;
-	default:
-		return nullptr;
-	}
-}
-
-PlayerColor CBonusSystemNode::retrieveNodeOwner(const CBonusSystemNode * node)
-{
-	return node ? node->getOwner() : PlayerColor::CANNOT_DETERMINE;
-}
-
-std::shared_ptr<Bonus> CBonusSystemNode::getBonusLocalFirst(const CSelector & selector)
-{
-	auto ret = bonuses.getFirst(selector);
-	if(ret)
-		return ret;
-
-	FOREACH_PARENT(pname)
-	{
-		ret = pname->getBonusLocalFirst(selector);
-		if (ret)
-			return ret;
-	}
-
-	return nullptr;
-}
-
-std::shared_ptr<const Bonus> CBonusSystemNode::getBonusLocalFirst(const CSelector & selector) const
-{
-	return (const_cast<CBonusSystemNode*>(this))->getBonusLocalFirst(selector);
-}
-
-void CBonusSystemNode::getParents(TCNodes & out) const /*retrieves list of parent nodes (nodes to inherit bonuses from) */
-{
-	for(const auto & elem : parents)
-	{
-		const CBonusSystemNode *parent = elem;
-		out.insert(parent);
-	}
-}
-
-void CBonusSystemNode::getParents(TNodes &out)
-{
-	for (auto & elem : parents)
-	{
-		const CBonusSystemNode *parent = elem;
-		out.insert(const_cast<CBonusSystemNode*>(parent));
-	}
-}
-
-void CBonusSystemNode::getAllParents(TCNodes & out) const //retrieves list of parent nodes (nodes to inherit bonuses from)
-{
-	for(auto * parent : parents)
-	{
-		out.insert(parent);
-		parent->getAllParents(out);
-	}
-}
-
-void CBonusSystemNode::getAllBonusesRec(BonusList &out, const CSelector & selector) const
-{
-	//out has been reserved sufficient capacity at getAllBonuses() call
-
-	BonusList beforeUpdate;
-	TCNodes lparents;
-	getAllParents(lparents);
-
-	if(!lparents.empty())
-	{
-		//estimate on how many bonuses are missing yet - must be positive
-		beforeUpdate.reserve(std::max(out.capacity() - out.size(), bonuses.size()));
-	}
-	else
-	{
-		beforeUpdate.reserve(bonuses.size()); //at most all local bonuses
-	}
-
-	for(const auto * parent : lparents)
-	{
-		parent->getAllBonusesRec(beforeUpdate, selector);
-	}
-	bonuses.getAllBonuses(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;
-
-		//do not add bonus with updater
-		bool bonusExists = false;
-		for(const auto & bonus : out)
-		{
-			if (bonus == updated)
-				bonusExists = true;
-			if (bonus->updater && bonus->updater == updated->updater)
-				bonusExists = true;
-		}
-
-		if (!bonusExists)
-			out.push_back(updated);
-	}
-}
-
-TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root, const std::string &cachingStr) const
-{
-	bool limitOnUs = (!root || root == this); //caching won't work when we want to limit bonuses against an external node
-	if (CBonusSystemNode::cachingEnabled && limitOnUs)
-	{
-		// Exclusive access for one thread
-		boost::lock_guard<boost::mutex> lock(sync);
-
-		// If the bonus system tree changes(state of a single node or the relations to each other) then
-		// cache all bonus objects. Selector objects doesn't matter.
-		if (cachedLast != treeChanged)
-		{
-			BonusList allBonuses;
-			allBonuses.reserve(cachedBonuses.capacity()); //we assume we'll get about the same number of bonuses
-
-			cachedBonuses.clear();
-			cachedRequests.clear();
-
-			getAllBonusesRec(allBonuses, Selector::all);
-			limitBonuses(allBonuses, cachedBonuses);
-			cachedBonuses.stackBonuses();
-
-			cachedLast = treeChanged;
-		}
-
-		// If a bonus system request comes with a caching string then look up in the map if there are any
-		// pre-calculated bonus results. Limiters can't be cached so they have to be calculated.
-		if(!cachingStr.empty())
-		{
-			auto it = cachedRequests.find(cachingStr);
-			if(it != cachedRequests.end())
-			{
-				//Cached list contains bonuses for our query with applied limiters
-				return it->second;
-			}
-		}
-
-		//We still don't have the bonuses (didn't returned them from cache)
-		//Perform bonus selection
-		auto ret = std::make_shared<BonusList>();
-		cachedBonuses.getBonuses(*ret, selector, limit);
-
-		// Save the results in the cache
-		if(!cachingStr.empty())
-			cachedRequests[cachingStr] = ret;
-
-		return ret;
-	}
-	else
-	{
-		return getAllBonusesWithoutCaching(selector, limit, root);
-	}
-}
-
-TConstBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root) const
-{
-	auto ret = std::make_shared<BonusList>();
-
-	// Get bonus results without caching enabled.
-	BonusList beforeLimiting;
-	BonusList afterLimiting;
-	getAllBonusesRec(beforeLimiting, selector);
-
-	if(!root || root == this)
-	{
-		limitBonuses(beforeLimiting, afterLimiting);
-	}
-	else if(root)
-	{
-		//We want to limit our query against an external node. We get all its bonuses,
-		// add the ones we're considering and see if they're cut out by limiters
-		BonusList rootBonuses;
-		BonusList limitedRootBonuses;
-		getAllBonusesRec(rootBonuses, selector);
-
-		for(const auto & b : beforeLimiting)
-			rootBonuses.push_back(b);
-
-		root->limitBonuses(rootBonuses, limitedRootBonuses);
-
-		for(const auto & b : beforeLimiting)
-			if(vstd::contains(limitedRootBonuses, b))
-				afterLimiting.push_back(b);
-
-	}
-	afterLimiting.getBonuses(*ret, selector, limit);
-	ret->stackBonuses();
-	return ret;
-}
-
-std::shared_ptr<Bonus> CBonusSystemNode::getUpdatedBonus(const std::shared_ptr<Bonus> & b, const TUpdaterPtr & updater) const
-{
-	assert(updater);
-	return updater->createUpdatedBonus(b, * this);
-}
-
-CBonusSystemNode::CBonusSystemNode()
-	:CBonusSystemNode(false)
-{
-}
-
-CBonusSystemNode::CBonusSystemNode(bool isHypotetic):
-	bonuses(true),
-	exportedBonuses(true),
-	nodeType(UNKNOWN),
-	cachedLast(0),
-	isHypotheticNode(isHypotetic)
-{
-}
-
-CBonusSystemNode::CBonusSystemNode(ENodeTypes NodeType):
-	bonuses(true),
-	exportedBonuses(true),
-	nodeType(NodeType),
-	cachedLast(0),
-	isHypotheticNode(false)
-{
-}
-
-CBonusSystemNode::CBonusSystemNode(CBonusSystemNode && other) noexcept:
-	bonuses(std::move(other.bonuses)),
-	exportedBonuses(std::move(other.exportedBonuses)),
-	nodeType(other.nodeType),
-	description(other.description),
-	cachedLast(0),
-	isHypotheticNode(other.isHypotheticNode)
-{
-	std::swap(parents, other.parents);
-	std::swap(children, other.children);
-
-	//fixing bonus tree without recalculation
-
-	if(!isHypothetic())
-	{
-		for(CBonusSystemNode * n : parents)
-		{
-			n->children -= &other;
-			n->children.push_back(this);
-		}
-	}
-
-	for(CBonusSystemNode * n : children)
-	{
-		n->parents -= &other;
-		n->parents.push_back(this);
-	}
-
-	//cache ignored
-
-	//cachedBonuses
-	//cachedRequests
-}
-
-CBonusSystemNode::~CBonusSystemNode()
-{
-	detachFromAll();
-
-	if(!children.empty())
-	{
-		while(!children.empty())
-			children.front()->detachFrom(*this);
-	}
-}
-
-void CBonusSystemNode::attachTo(CBonusSystemNode & parent)
-{
-	assert(!vstd::contains(parents, &parent));
-	parents.push_back(&parent);
-
-	if(!isHypothetic())
-	{
-		if(parent.actsAsBonusSourceOnly())
-			parent.newRedDescendant(*this);
-		else
-			newRedDescendant(parent);
-
-		parent.newChildAttached(*this);
-	}
-
-	CBonusSystemNode::treeHasChanged();
-}
-
-void CBonusSystemNode::detachFrom(CBonusSystemNode & parent)
-{
-	assert(vstd::contains(parents, &parent));
-
-	if(!isHypothetic())
-	{
-		if(parent.actsAsBonusSourceOnly())
-			parent.removedRedDescendant(*this);
-		else
-			removedRedDescendant(parent);
-	}
-
-	if (vstd::contains(parents, &parent))
-	{
-		parents -= &parent;
-	}
-	else
-	{
-		logBonus->error("Error on Detach. Node %s (nodeType=%d) has not parent %s (nodeType=%d)"
-			, nodeShortInfo(), nodeType, parent.nodeShortInfo(), parent.nodeType);
-	}
-
-	if(!isHypothetic())
-	{
-		parent.childDetached(*this);
-	}
-	CBonusSystemNode::treeHasChanged();
-}
-
-void CBonusSystemNode::removeBonusesRecursive(const CSelector & s)
-{
-	removeBonuses(s);
-	for(CBonusSystemNode * child : children)
-		child->removeBonusesRecursive(s);
-}
-
-void CBonusSystemNode::reduceBonusDurations(const CSelector &s)
-{
-	BonusList bl;
-	exportedBonuses.getBonuses(bl, s, Selector::all);
-	for(const auto & b : bl)
-	{
-		b->turnsRemain--;
-		if(b->turnsRemain <= 0)
-			removeBonus(b);
-	}
-
-	for(CBonusSystemNode *child : children)
-		child->reduceBonusDurations(s);
-}
-
-void CBonusSystemNode::addNewBonus(const std::shared_ptr<Bonus>& b)
-{
-	//turnsRemain shouldn't be zero for following durations
-	if(Bonus::NTurns(b.get()) || Bonus::NDays(b.get()) || Bonus::OneWeek(b.get()))
-	{
-		assert(b->turnsRemain);
-	}
-
-	assert(!vstd::contains(exportedBonuses, b));
-	exportedBonuses.push_back(b);
-	exportBonus(b);
-	CBonusSystemNode::treeHasChanged();
-}
-
-void CBonusSystemNode::accumulateBonus(const std::shared_ptr<Bonus>& b)
-{
-	auto bonus = exportedBonuses.getFirst(Selector::typeSubtype(b->type, b->subtype)); //only local bonuses are interesting //TODO: what about value type?
-	if(bonus)
-		bonus->val += b->val;
-	else
-		addNewBonus(std::make_shared<Bonus>(*b)); //duplicate needed, original may get destroyed
-}
-
-void CBonusSystemNode::removeBonus(const std::shared_ptr<Bonus>& b)
-{
-	exportedBonuses -= b;
-	if(b->propagator)
-		unpropagateBonus(b);
-	else
-		bonuses -= b;
-	CBonusSystemNode::treeHasChanged();
-}
-
-void CBonusSystemNode::removeBonuses(const CSelector & selector)
-{
-	BonusList toRemove;
-	exportedBonuses.getBonuses(toRemove, selector, Selector::all);
-	for(const auto & bonus : toRemove)
-		removeBonus(bonus);
-}
-
-bool CBonusSystemNode::actsAsBonusSourceOnly() const
-{
-	switch(nodeType)
-	{
-	case CREATURE:
-	case ARTIFACT:
-	case ARTIFACT_INSTANCE:
-		return true;
-	default:
-		return false;
-	}
-}
-
-void CBonusSystemNode::propagateBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & source)
-{
-	if(b->propagator->shouldBeAttached(this))
-	{
-		auto propagated = b->propagationUpdater 
-			? source.getUpdatedBonus(b, b->propagationUpdater)
-			: b;
-		bonuses.push_back(propagated);
-		logBonus->trace("#$# %s #propagated to# %s",  propagated->Description(), nodeName());
-	}
-
-	FOREACH_RED_CHILD(child)
-		child->propagateBonus(b, source);
-}
-
-void CBonusSystemNode::unpropagateBonus(const std::shared_ptr<Bonus> & b)
-{
-	if(b->propagator->shouldBeAttached(this))
-	{
-		bonuses -= b;
-		logBonus->trace("#$# %s #is no longer propagated to# %s",  b->Description(), nodeName());
-	}
-
-	FOREACH_RED_CHILD(child)
-		child->unpropagateBonus(b);
-}
-
-void CBonusSystemNode::newChildAttached(CBonusSystemNode & child)
-{
-	assert(!vstd::contains(children, &child));
-	children.push_back(&child);
-}
-
-void CBonusSystemNode::childDetached(CBonusSystemNode & child)
-{
-	if(vstd::contains(children, &child))
-		children -= &child;
-	else
-	{
-		logBonus->error("Error on Detach. Node %s (nodeType=%d) is not a child of %s (nodeType=%d)"
-			, child.nodeShortInfo(), child.nodeType, nodeShortInfo(), nodeType);
-	}
-}
-
-void CBonusSystemNode::detachFromAll()
-{
-	while(!parents.empty())
-		detachFrom(*parents.front());
-}
-
-bool CBonusSystemNode::isIndependentNode() const
-{
-	return parents.empty() && children.empty();
-}
-
-std::string CBonusSystemNode::nodeName() const
-{
-	return !description.empty()
-		? description
-		: std::string("Bonus system node of type ") + typeid(*this).name();
-}
-
-std::string CBonusSystemNode::nodeShortInfo() const
-{
-	std::ostringstream str;
-	str << "'" << typeid(* this).name() << "'";
-	description.length() > 0 
-		? str << " (" << description << ")"
-		: str << " (no description)";
-	return str.str();
-}
-
-void CBonusSystemNode::deserializationFix()
-{
-	exportBonuses();
-
-}
-
-void CBonusSystemNode::getRedParents(TNodes & out)
-{
-	FOREACH_PARENT(pname)
-	{
-		if(pname->actsAsBonusSourceOnly())
-		{
-			out.insert(pname);
-		}
-	}
-
-	if(!actsAsBonusSourceOnly())
-	{
-		for(CBonusSystemNode *child : children)
-		{
-			out.insert(child);
-		}
-	}
-}
-
-void CBonusSystemNode::getRedChildren(TNodes &out)
-{
-	FOREACH_PARENT(pname)
-	{
-		if(!pname->actsAsBonusSourceOnly())
-		{
-			out.insert(pname);
-		}
-	}
-
-	if(actsAsBonusSourceOnly())
-	{
-		for(CBonusSystemNode *child : children)
-		{
-			out.insert(child);
-		}
-	}
-}
-
-void CBonusSystemNode::newRedDescendant(CBonusSystemNode & descendant)
-{
-	for(const auto & b : exportedBonuses)
-	{
-		if(b->propagator)
-			descendant.propagateBonus(b, *this);
-	}
-	TNodes redParents;
-	getRedAncestors(redParents); //get all red parents recursively
-
-	for(auto * parent : redParents)
-	{
-		for(const auto & b : parent->exportedBonuses)
-		{
-			if(b->propagator)
-				descendant.propagateBonus(b, *this);
-		}
-	}
-}
-
-void CBonusSystemNode::removedRedDescendant(CBonusSystemNode & descendant)
-{
-	for(const auto & b : exportedBonuses)
-		if(b->propagator)
-			descendant.unpropagateBonus(b);
-
-	TNodes redParents;
-	getRedAncestors(redParents); //get all red parents recursively
-
-	for(auto * parent : redParents)
-	{
-		for(const auto & b : parent->exportedBonuses)
-			if(b->propagator)
-				descendant.unpropagateBonus(b);
-	}
-}
-
-void CBonusSystemNode::getRedAncestors(TNodes &out)
-{
-	getRedParents(out);
-
-	TNodes redParents; 
-	getRedParents(redParents);
-
-	for(CBonusSystemNode * parent : redParents)
-		parent->getRedAncestors(out);
-}
-
-void CBonusSystemNode::exportBonus(const std::shared_ptr<Bonus> & b)
-{
-	if(b->propagator)
-		propagateBonus(b, *this);
-	else
-		bonuses.push_back(b);
-
-	CBonusSystemNode::treeHasChanged();
-}
-
-void CBonusSystemNode::exportBonuses()
-{
-	for(const auto & b : exportedBonuses)
-		exportBonus(b);
-}
-
-CBonusSystemNode::ENodeTypes CBonusSystemNode::getNodeType() const
-{
-	return nodeType;
-}
-
-const BonusList& CBonusSystemNode::getBonusList() const
-{
-	return bonuses;
-}
-
-const TNodesVector& CBonusSystemNode::getParentNodes() const
-{
-	return parents;
-}
-
-const TNodesVector& CBonusSystemNode::getChildrenNodes() const
-{
-	return children;
-}
-
-void CBonusSystemNode::setNodeType(CBonusSystemNode::ENodeTypes type)
-{
-	nodeType = type;
-}
-
-BonusList & CBonusSystemNode::getExportedBonusList()
-{
-	return exportedBonuses;
-}
-
-const BonusList & CBonusSystemNode::getExportedBonusList() const
-{
-	return exportedBonuses;
-}
-
-const std::string& CBonusSystemNode::getDescription() const
-{
-	return description;
-}
-
-void CBonusSystemNode::setDescription(const std::string &description)
-{
-	this->description = description;
-}
-
-void CBonusSystemNode::limitBonuses(const BonusList &allBonuses, BonusList &out) const
-{
-	assert(&allBonuses != &out); //todo should it work in-place?
-
-	BonusList undecided = allBonuses;
-	BonusList & accepted = out;
-
-	while(true)
-	{
-		int undecidedCount = static_cast<int>(undecided.size());
-		for(int i = 0; i < undecided.size(); i++)
-		{
-			auto b = undecided[i];
-			BonusLimitationContext context = {b, *this, out, undecided};
-			auto decision = b->limiter ? b->limiter->limit(context) : ILimiter::EDecision::ACCEPT; //bonuses without limiters will be accepted by default
-			if(decision == ILimiter::EDecision::DISCARD)
-			{
-				undecided.erase(i);
-				i--; continue;
-			}
-			else if(decision == ILimiter::EDecision::ACCEPT)
-			{
-				accepted.push_back(b);
-				undecided.erase(i);
-				i--; continue;
-			}
-			else
-				assert(decision == ILimiter::EDecision::NOT_SURE);
-		}
-
-		if(undecided.size() == undecidedCount) //we haven't moved a single bonus -> limiters reached a stable state
-			return;
-	}
-}
-
-TBonusListPtr CBonusSystemNode::limitBonuses(const BonusList &allBonuses) const
-{
-	auto ret = std::make_shared<BonusList>();
-	limitBonuses(allBonuses, *ret);
-	return ret;
-}
-
-void CBonusSystemNode::treeHasChanged()
-{
-	treeChanged++;
-}
-
-int64_t CBonusSystemNode::getTreeVersion() const
-{
-	return treeChanged;
-}
-
-std::string Bonus::Description(std::optional<si32> customValue) const
-{
-	std::ostringstream str;
-
-	if(description.empty())
-	{
-		if(stacking.empty() || stacking == "ALWAYS")
-		{
-			switch(source)
-			{
-			case ARTIFACT:
-				str << ArtifactID(sid).toArtifact(VLC->artifacts())->getNameTranslated();
-				break;
-			case SPELL_EFFECT:
-				str << SpellID(sid).toSpell(VLC->spells())->getNameTranslated();
-				break;
-			case CREATURE_ABILITY:
-				str << VLC->creh->objects[sid]->getNamePluralTranslated();
-				break;
-			case SECONDARY_SKILL:
-				str << VLC->skillh->getByIndex(sid)->getNameTranslated();
-				break;
-			case HERO_SPECIAL:
-				str << VLC->heroh->objects[sid]->getNameTranslated();
-				break;
-			default:
-				//todo: handle all possible sources
-				str << "Unknown";
-				break;
-			}
-		}
-		else
-			str << stacking;
-	}
-	else
-	{
-		str << description;
-	}
-
-	if(auto value = customValue.value_or(val))
-		str << " " << std::showpos << value;
-
-	return str.str();
-}
-
-JsonNode subtypeToJson(Bonus::BonusType type, int subtype)
-{
-	switch(type)
-	{
-	case Bonus::PRIMARY_SKILL:
-		return JsonUtils::stringNode("primSkill." + PrimarySkill::names[subtype]);
-	case Bonus::SPECIAL_SPELL_LEV:
-	case Bonus::SPECIFIC_SPELL_DAMAGE:
-	case Bonus::SPELL:
-	case Bonus::SPECIAL_PECULIAR_ENCHANT:
-	case Bonus::SPECIAL_ADD_VALUE_ENCHANT:
-	case Bonus::SPECIAL_FIXED_VALUE_ENCHANT:
-		return JsonUtils::stringNode(CModHandler::makeFullIdentifier("", "spell", SpellID::encode(subtype)));
-	case Bonus::IMPROVED_NECROMANCY:
-	case Bonus::SPECIAL_UPGRADE:
-		return JsonUtils::stringNode(CModHandler::makeFullIdentifier("", "creature", CreatureID::encode(subtype)));
-	case Bonus::GENERATE_RESOURCE:
-		return JsonUtils::stringNode("resource." + GameConstants::RESOURCE_NAMES[subtype]);
-	default:
-		return JsonUtils::intNode(subtype);
-	}
-}
-
-JsonNode additionalInfoToJson(Bonus::BonusType type, CAddInfo addInfo)
-{
-	switch(type)
-	{
-	case Bonus::SPECIAL_UPGRADE:
-		return JsonUtils::stringNode(CModHandler::makeFullIdentifier("", "creature", CreatureID::encode(addInfo[0])));
-	default:
-		return addInfo.toJsonNode();
-	}
-}
-
-JsonNode durationToJson(ui16 duration)
-{
-	std::vector<std::string> durationNames;
-	for(ui16 durBit = 1; durBit; durBit = durBit << 1)
-	{
-		if(duration & durBit)
-			durationNames.push_back(vstd::findKey(bonusDurationMap, durBit));
-	}
-	if(durationNames.size() == 1)
-	{
-		return JsonUtils::stringNode(durationNames[0]);
-	}
-	else
-	{
-		JsonNode node(JsonNode::JsonType::DATA_VECTOR);
-		for(const std::string & dur : durationNames)
-			node.Vector().push_back(JsonUtils::stringNode(dur));
-		return node;
-	}
-}
-
-JsonNode Bonus::toJsonNode() const
-{
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
-	// only add values that might reasonably be found in config files
-	root["type"].String() = vstd::findKey(bonusNameMap, type);
-	if(subtype != -1)
-		root["subtype"] = subtypeToJson(type, subtype);
-	if(additionalInfo != CAddInfo::NONE)
-		root["addInfo"] = additionalInfoToJson(type, additionalInfo);
-	if(duration != 0)
-	{
-		JsonNode durationVec(JsonNode::JsonType::DATA_VECTOR);
-		for(const auto & kv : bonusDurationMap)
-		{
-			if(duration & kv.second)
-				durationVec.Vector().push_back(JsonUtils::stringNode(kv.first));
-		}
-		root["duration"] = durationVec;
-	}
-	if(turnsRemain != 0)
-		root["turns"].Integer() = turnsRemain;
-	if(source != OTHER)
-		root["sourceType"].String() = vstd::findKey(bonusSourceMap, source);
-	if(targetSourceType != OTHER)
-		root["targetSourceType"].String() = vstd::findKey(bonusSourceMap, targetSourceType);
-	if(sid != 0)
-		root["sourceID"].Integer() = sid;
-	if(val != 0)
-		root["val"].Integer() = val;
-	if(valType != ADDITIVE_VALUE)
-		root["valueType"].String() = vstd::findKey(bonusValueMap, valType);
-	if(!stacking.empty())
-		root["stacking"].String() = stacking;
-	if(!description.empty())
-		root["description"].String() = description;
-	if(effectRange != NO_LIMIT)
-		root["effectRange"].String() = vstd::findKey(bonusLimitEffect, effectRange);
-	if(duration != PERMANENT)
-		root["duration"] = durationToJson(duration);
-	if(turnsRemain)
-		root["turns"].Integer() = turnsRemain;
-	if(limiter)
-		root["limiters"] = limiter->toJsonNode();
-	if(updater)
-		root["updater"] = updater->toJsonNode();
-	if(propagator)
-		root["propagator"].String() = vstd::findKey(bonusPropagatorMap, propagator);
-	return root;
-}
-
-std::string Bonus::nameForBonus() const
-{
-	switch(type)
-	{
-	case Bonus::PRIMARY_SKILL:
-		return PrimarySkill::names[subtype];
-	case Bonus::SPECIAL_SPELL_LEV:
-	case Bonus::SPECIFIC_SPELL_DAMAGE:
-	case Bonus::SPELL:
-	case Bonus::SPECIAL_PECULIAR_ENCHANT:
-	case Bonus::SPECIAL_ADD_VALUE_ENCHANT:
-	case Bonus::SPECIAL_FIXED_VALUE_ENCHANT:
-		return VLC->spells()->getByIndex(subtype)->getJsonKey();
-	case Bonus::SPECIAL_UPGRADE:
-		return CreatureID::encode(subtype) + "2" + CreatureID::encode(additionalInfo[0]);
-	case Bonus::GENERATE_RESOURCE:
-		return GameConstants::RESOURCE_NAMES[subtype];
-	case Bonus::STACKS_SPEED:
-		return "speed";
-	default:
-		return vstd::findKey(bonusNameMap, type);
-	}
-}
-
-BonusParams::BonusParams(std::string deprecatedTypeStr, std::string deprecatedSubtypeStr, int deprecatedSubtype):
-	isConverted(true)
-{
-	if(deprecatedTypeStr == "SECONDARY_SKILL_PREMY" || deprecatedTypeStr == "SPECIAL_SECONDARY_SKILL")
-	{
-		if(deprecatedSubtype == SecondarySkill::PATHFINDING || deprecatedSubtypeStr == "skill.pathfinding")
-			type = Bonus::ROUGH_TERRAIN_DISCOUNT;
-		else if(deprecatedSubtype == SecondarySkill::DIPLOMACY || deprecatedSubtypeStr == "skill.diplomacy")
-			type = Bonus::WANDERING_CREATURES_JOIN_BONUS;
-		else if(deprecatedSubtype == SecondarySkill::WISDOM || deprecatedSubtypeStr == "skill.wisdom")
-			type = Bonus::MAX_LEARNABLE_SPELL_LEVEL;
-		else if(deprecatedSubtype == SecondarySkill::MYSTICISM || deprecatedSubtypeStr == "skill.mysticism")
-			type = Bonus::MANA_REGENERATION;
-		else if(deprecatedSubtype == SecondarySkill::NECROMANCY || deprecatedSubtypeStr == "skill.necromancy")
-			type = Bonus::UNDEAD_RAISE_PERCENTAGE;
-		else if(deprecatedSubtype == SecondarySkill::LEARNING || deprecatedSubtypeStr == "skill.learning")
-			type = Bonus::HERO_EXPERIENCE_GAIN_PERCENT;
-		else if(deprecatedSubtype == SecondarySkill::RESISTANCE || deprecatedSubtypeStr == "skill.resistance")
-			type = Bonus::MAGIC_RESISTANCE;
-		else if(deprecatedSubtype == SecondarySkill::EAGLE_EYE || deprecatedSubtypeStr == "skill.eagleEye")
-			type = Bonus::LEARN_BATTLE_SPELL_CHANCE;
-		else if(deprecatedSubtype == SecondarySkill::SCOUTING || deprecatedSubtypeStr == "skill.scouting")
-			type = Bonus::SIGHT_RADIUS;
-		else if(deprecatedSubtype == SecondarySkill::INTELLIGENCE || deprecatedSubtypeStr == "skill.intelligence")
-		{
-			type = Bonus::MANA_PER_KNOWLEDGE;
-			valueType = Bonus::PERCENT_TO_BASE;
-			valueTypeRelevant = true;
-		}
-		else if(deprecatedSubtype == SecondarySkill::SORCERY || deprecatedSubtypeStr == "skill.sorcery")
-			type = Bonus::SPELL_DAMAGE;
-		else if(deprecatedSubtype == SecondarySkill::SCHOLAR || deprecatedSubtypeStr == "skill.scholar")
-			type = Bonus::LEARN_MEETING_SPELL_LIMIT;
-		else if(deprecatedSubtype == SecondarySkill::ARCHERY|| deprecatedSubtypeStr == "skill.archery")
-		{
-			subtype = 1;
-			subtypeRelevant = true;
-			type = Bonus::PERCENTAGE_DAMAGE_BOOST;
-		}
-		else if(deprecatedSubtype == SecondarySkill::OFFENCE || deprecatedSubtypeStr == "skill.offence")
-		{
-			subtype = 0;
-			subtypeRelevant = true;
-			type = Bonus::PERCENTAGE_DAMAGE_BOOST;
-		}
-		else if(deprecatedSubtype == SecondarySkill::ARMORER || deprecatedSubtypeStr == "skill.armorer")
-		{
-			subtype = -1;
-			subtypeRelevant = true;
-			type = Bonus::GENERAL_DAMAGE_REDUCTION;
-		}
-		else if(deprecatedSubtype == SecondarySkill::NAVIGATION || deprecatedSubtypeStr == "skill.navigation")
-		{
-			subtype = 0;
-			subtypeRelevant = true;
-			valueType = Bonus::PERCENT_TO_BASE;
-			valueTypeRelevant = true;
-			type = Bonus::MOVEMENT;
-		}
-		else if(deprecatedSubtype == SecondarySkill::LOGISTICS || deprecatedSubtypeStr == "skill.logistics")
-		{
-			subtype = 1;
-			subtypeRelevant = true;
-			valueType = Bonus::PERCENT_TO_BASE;
-			valueTypeRelevant = true;
-			type = Bonus::MOVEMENT;
-		}
-		else if(deprecatedSubtype == SecondarySkill::ESTATES || deprecatedSubtypeStr == "skill.estates")
-		{
-			type = Bonus::GENERATE_RESOURCE;
-			subtype = GameResID(EGameResID::GOLD);
-			subtypeRelevant = true;
-		}
-		else if(deprecatedSubtype == SecondarySkill::AIR_MAGIC || deprecatedSubtypeStr == "skill.airMagic")
-		{
-			type = Bonus::MAGIC_SCHOOL_SKILL;
-			subtypeRelevant = true;
-			subtype = 4;
-		}
-		else if(deprecatedSubtype == SecondarySkill::WATER_MAGIC || deprecatedSubtypeStr == "skill.waterMagic")
-		{
-			type = Bonus::MAGIC_SCHOOL_SKILL;
-			subtypeRelevant = true;
-			subtype = 1;
-		}
-		else if(deprecatedSubtype == SecondarySkill::FIRE_MAGIC || deprecatedSubtypeStr == "skill.fireMagic")
-		{
-			type = Bonus::MAGIC_SCHOOL_SKILL;
-			subtypeRelevant = true;
-			subtype = 2;
-		}
-		else if(deprecatedSubtype == SecondarySkill::EARTH_MAGIC || deprecatedSubtypeStr == "skill.earthMagic")
-		{
-			type = Bonus::MAGIC_SCHOOL_SKILL;
-			subtypeRelevant = true;
-			subtype = 8;
-		}
-		else if (deprecatedSubtype == SecondarySkill::ARTILLERY || deprecatedSubtypeStr == "skill.artillery")
-		{
-			type = Bonus::BONUS_DAMAGE_CHANCE;
-			subtypeRelevant = true;
-			subtypeStr = "core:creature.ballista";
-		}
-		else if (deprecatedSubtype == SecondarySkill::FIRST_AID || deprecatedSubtypeStr == "skill.firstAid")
-		{
-			type = Bonus::SPECIFIC_SPELL_POWER;
-			subtypeRelevant = true;
-			subtypeStr = "core:spell.firstAid";
-		}
-		else if (deprecatedSubtype == SecondarySkill::BALLISTICS || deprecatedSubtypeStr == "skill.ballistics")
-		{
-			type = Bonus::CATAPULT_EXTRA_SHOTS;
-			subtypeRelevant = true;
-			subtypeStr = "core:spell.catapultShot";
-		}
-		else
-			isConverted = false;
-	}
-	else if (deprecatedTypeStr == "SECONDARY_SKILL_VAL2")
-	{
-		if(deprecatedSubtype == SecondarySkill::EAGLE_EYE || deprecatedSubtypeStr == "skill.eagleEye")
-			type = Bonus::LEARN_BATTLE_SPELL_LEVEL_LIMIT;
-		else if (deprecatedSubtype == SecondarySkill::ARTILLERY || deprecatedSubtypeStr == "skill.artillery")
-		{
-			type = Bonus::HERO_GRANTS_ATTACKS;
-			subtypeRelevant = true;
-			subtypeStr = "core:creature.ballista";
-		}
-		else
-			isConverted = false;
-	}
-	else if (deprecatedTypeStr == "SEA_MOVEMENT")
-	{
-		subtype = 0;
-		subtypeRelevant = true;
-		valueType = Bonus::ADDITIVE_VALUE;
-		valueTypeRelevant = true;
-		type = Bonus::MOVEMENT;
-	}
-	else if (deprecatedTypeStr == "LAND_MOVEMENT")
-	{
-		subtype = 1;
-		subtypeRelevant = true;
-		valueType = Bonus::ADDITIVE_VALUE;
-		valueTypeRelevant = true;
-		type = Bonus::MOVEMENT;
-	}
-	else if (deprecatedTypeStr == "MAXED_SPELL")
-	{
-		type = Bonus::SPELL;
-		subtypeStr = deprecatedSubtypeStr;
-		subtypeRelevant = true;
-		valueType = Bonus::INDEPENDENT_MAX;
-		valueTypeRelevant = true;
-		val = 3;
-		valRelevant = true;
-	}
-	else if (deprecatedTypeStr == "FULL_HP_REGENERATION")
-	{
-		type = Bonus::HP_REGENERATION;
-		val = 100000; //very high value to always chose stack health
-		valRelevant = true;
-	}
-	else if (deprecatedTypeStr == "KING1")
-	{
-		type = Bonus::KING;
-		val = 0;
-		valRelevant = true;
-	}
-	else if (deprecatedTypeStr == "KING2")
-	{
-		type = Bonus::KING;
-		val = 2;
-		valRelevant = true;
-	}
-	else if (deprecatedTypeStr == "KING3")
-	{
-		type = Bonus::KING;
-		val = 3;
-		valRelevant = true;
-	}
-	else if (deprecatedTypeStr == "SIGHT_RADIOUS")
-		type = Bonus::SIGHT_RADIUS;
-	else if (deprecatedTypeStr == "SELF_MORALE")
-	{
-		type = Bonus::MORALE;
-		val = 1;
-		valRelevant = true;
-		valueType = Bonus::INDEPENDENT_MAX;
-		valueTypeRelevant = true;
-	}
-	else if (deprecatedTypeStr == "SELF_LUCK")
-	{
-		type = Bonus::LUCK;
-		val = 1;
-		valRelevant = true;
-		valueType = Bonus::INDEPENDENT_MAX;
-		valueTypeRelevant = true;
-	}
-	else
-		isConverted = false;
-}
-
-const JsonNode & BonusParams::toJson()
-{
-	assert(isConverted);
-	if(ret.isNull())
-	{
-		ret["type"].String() = vstd::findKey(bonusNameMap, type);
-		if(subtypeRelevant && !subtypeStr.empty())
-			ret["subtype"].String() = subtypeStr;
-		else if(subtypeRelevant)
-			ret["subtype"].Integer() = subtype;
-		if(valueTypeRelevant)
-			ret["valueType"].String() = vstd::findKey(bonusValueMap, valueType);
-		if(valRelevant)
-			ret["val"].Float() = val;
-		if(targetTypeRelevant)
-			ret["targetSourceType"].String() = vstd::findKey(bonusSourceMap, targetType);
-		jsonCreated = true;
-	}
-	return ret;
-};
-
-CSelector BonusParams::toSelector()
-{
-	assert(isConverted);
-	if(subtypeRelevant && !subtypeStr.empty())
-		JsonUtils::resolveIdentifier(subtype, toJson(), "subtype");
-
-	auto ret = Selector::type()(type);
-	if(subtypeRelevant)
-		ret = ret.And(Selector::subtype()(subtype));
-	if(valueTypeRelevant)
-		ret = ret.And(Selector::valueType(valueType));
-	if(targetTypeRelevant)
-		ret = ret.And(Selector::targetSourceType()(targetType));
-	return ret;
-}
-
-Bonus::Bonus(Bonus::BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::string Desc, si32 Subtype):
-	duration(static_cast<ui16>(Duration)),
-	type(Type),
-	subtype(Subtype),
-	source(Src),
-	val(Val),
-	sid(ID),
-	description(std::move(Desc))
-{
-	boost::algorithm::trim(description);
-	targetSourceType = OTHER;
-}
-
-Bonus::Bonus(Bonus::BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, si32 Subtype, ValueType ValType):
-	duration(static_cast<ui16>(Duration)),
-	type(Type),
-	subtype(Subtype),
-	source(Src),
-	val(Val),
-	sid(ID),
-	valType(ValType)
-{
-	turnsRemain = 0;
-	effectRange = NO_LIMIT;
-	targetSourceType = OTHER;
-}
-
-std::shared_ptr<Bonus> Bonus::addPropagator(const TPropagatorPtr & Propagator)
-{
-	propagator = Propagator;
-	return this->shared_from_this();
-}
-
-namespace Selector
-{
-	DLL_LINKAGE CSelectFieldEqual<Bonus::BonusType> & type()
-	{
-		static CSelectFieldEqual<Bonus::BonusType> stype(&Bonus::type);
-		return stype;
-	}
-
-	DLL_LINKAGE CSelectFieldEqual<TBonusSubtype> & subtype()
-	{
-		static CSelectFieldEqual<TBonusSubtype> ssubtype(&Bonus::subtype);
-		return ssubtype;
-	}
-
-	DLL_LINKAGE CSelectFieldEqual<CAddInfo> & info()
-	{
-		static CSelectFieldEqual<CAddInfo> sinfo(&Bonus::additionalInfo);
-		return sinfo;
-	}
-
-	DLL_LINKAGE CSelectFieldEqual<Bonus::BonusSource> & sourceType()
-	{
-		static CSelectFieldEqual<Bonus::BonusSource> ssourceType(&Bonus::source);
-		return ssourceType;
-	}
-
-	DLL_LINKAGE CSelectFieldEqual<Bonus::BonusSource> & targetSourceType()
-	{
-		static CSelectFieldEqual<Bonus::BonusSource> ssourceType(&Bonus::targetSourceType);
-		return ssourceType;
-	}
-
-	DLL_LINKAGE CSelectFieldEqual<Bonus::LimitEffect> & effectRange()
-	{
-		static CSelectFieldEqual<Bonus::LimitEffect> seffectRange(&Bonus::effectRange);
-		return seffectRange;
-	}
-
-	DLL_LINKAGE CWillLastTurns turns;
-	DLL_LINKAGE CWillLastDays days;
-
-	CSelector DLL_LINKAGE typeSubtype(Bonus::BonusType Type, TBonusSubtype Subtype)
-	{
-		return type()(Type).And(subtype()(Subtype));
-	}
-
-	CSelector DLL_LINKAGE typeSubtypeInfo(Bonus::BonusType type, TBonusSubtype subtype, const CAddInfo & info)
-	{
-		return CSelectFieldEqual<Bonus::BonusType>(&Bonus::type)(type)
-			.And(CSelectFieldEqual<TBonusSubtype>(&Bonus::subtype)(subtype))
-			.And(CSelectFieldEqual<CAddInfo>(&Bonus::additionalInfo)(info));
-	}
-
-	CSelector DLL_LINKAGE source(Bonus::BonusSource source, ui32 sourceID)
-	{
-		return CSelectFieldEqual<Bonus::BonusSource>(&Bonus::source)(source)
-			.And(CSelectFieldEqual<ui32>(&Bonus::sid)(sourceID));
-	}
-
-	CSelector DLL_LINKAGE sourceTypeSel(Bonus::BonusSource source)
-	{
-		return CSelectFieldEqual<Bonus::BonusSource>(&Bonus::source)(source);
-	}
-
-	CSelector DLL_LINKAGE valueType(Bonus::ValueType valType)
-	{
-		return CSelectFieldEqual<Bonus::ValueType>(&Bonus::valType)(valType);
-	}
-
-	DLL_LINKAGE CSelector all([](const Bonus * b){return true;});
-	DLL_LINKAGE CSelector none([](const Bonus * b){return false;});
-}
-
-const CCreature * retrieveCreature(const CBonusSystemNode *node)
-{
-	switch(node->getNodeType())
-	{
-	case CBonusSystemNode::CREATURE:
-		return (dynamic_cast<const CCreature *>(node));
-	case CBonusSystemNode::STACK_BATTLE:
-		return (dynamic_cast<const CStack *>(node))->unitType();
-	default:
-		const CStackInstance * csi = retrieveStackInstance(node);
-		if(csi)
-			return csi->type;
-		return nullptr;
-	}
-}
-
-DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const BonusList &bonusList)
-{
-	for (ui32 i = 0; i < bonusList.size(); i++)
-	{
-		const auto & b = bonusList[i];
-		out << "Bonus " << i << "\n" << *b << std::endl;
-	}
-	return out;
-}
-
-DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const Bonus &bonus)
-{
-	for(const auto & i : bonusNameMap)
-	if(i.second == bonus.type)
-		out << "\tType: " << i.first << " \t";
-
-#define printField(field) out << "\t" #field ": " << (int)bonus.field << "\n"
-	printField(val);
-	printField(subtype);
-	printField(duration);
-	printField(source);
-	printField(sid);
-	if(bonus.additionalInfo != CAddInfo::NONE)
-		out << "\taddInfo: " << bonus.additionalInfo.toString() << "\n";
-	printField(turnsRemain);
-	printField(valType);
-	if(!bonus.stacking.empty())
-		out << "\tstacking: \"" << bonus.stacking << "\"\n";
-	printField(effectRange);
-#undef printField
-
-	if(bonus.limiter)
-		out << "\tLimiter: " << bonus.limiter->toString() << "\n";
-	if(bonus.updater)
-		out << "\tUpdater: " << bonus.updater->toString() << "\n";
-
-	return out;
-}
-
-std::shared_ptr<Bonus> Bonus::addLimiter(const TLimiterPtr & Limiter)
-{
-	if (limiter)
-	{
-		//If we already have limiter list, retrieve it
-		auto limiterList = std::dynamic_pointer_cast<AllOfLimiter>(limiter);
-		if(!limiterList)
-		{
-			//Create a new limiter list with old limiter and the new one will be pushed later
-			limiterList = std::make_shared<AllOfLimiter>();
-			limiterList->add(limiter);
-			limiter = limiterList;
-		}
-
-		limiterList->add(Limiter);
-	}
-	else
-	{
-		limiter = Limiter;
-	}
-	return this->shared_from_this();
-}
-
-ILimiter::EDecision ILimiter::limit(const BonusLimitationContext &context) const /*return true to drop the bonus */
-{
-	return ILimiter::EDecision::ACCEPT;
-}
-
-std::string ILimiter::toString() const
-{
-	return typeid(*this).name();
-}
-
-JsonNode ILimiter::toJsonNode() const
-{
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
-	root["type"].String() = toString();
-	return root;
-}
-
-ILimiter::EDecision CCreatureTypeLimiter::limit(const BonusLimitationContext &context) const
-{
-	const CCreature *c = retrieveCreature(&context.node);
-	if(!c)
-		return ILimiter::EDecision::DISCARD;
-	
-	auto accept =  c->getId() == creature->getId() || (includeUpgrades && creature->isMyUpgrade(c));
-	return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
-	//drop bonus if it's not our creature and (we don`t check upgrades or its not our upgrade)
-}
-
-CCreatureTypeLimiter::CCreatureTypeLimiter(const CCreature & creature_, bool IncludeUpgrades)
-	: creature(&creature_), includeUpgrades(IncludeUpgrades)
-{
-}
-
-void CCreatureTypeLimiter::setCreature(const CreatureID & id)
-{
-	creature = VLC->creh->objects[id];
-}
-
-std::string CCreatureTypeLimiter::toString() const
-{
-	boost::format fmt("CCreatureTypeLimiter(creature=%s, includeUpgrades=%s)");
-	fmt % creature->getJsonKey() % (includeUpgrades ? "true" : "false");
-	return fmt.str();
-}
-
-JsonNode CCreatureTypeLimiter::toJsonNode() const
-{
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
-
-	root["type"].String() = "CREATURE_TYPE_LIMITER";
-	root["parameters"].Vector().push_back(JsonUtils::stringNode(creature->getJsonKey()));
-	root["parameters"].Vector().push_back(JsonUtils::boolNode(includeUpgrades));
-
-	return root;
-}
-
-HasAnotherBonusLimiter::HasAnotherBonusLimiter( Bonus::BonusType bonus )
-	: type(bonus), subtype(0), isSubtypeRelevant(false), isSourceRelevant(false), isSourceIDRelevant(false)
-{
-}
-
-HasAnotherBonusLimiter::HasAnotherBonusLimiter( Bonus::BonusType bonus, TBonusSubtype _subtype )
-	: type(bonus), subtype(_subtype), isSubtypeRelevant(true), isSourceRelevant(false), isSourceIDRelevant(false)
-{
-}
-
-HasAnotherBonusLimiter::HasAnotherBonusLimiter(Bonus::BonusType bonus, Bonus::BonusSource src)
-	: type(bonus), source(src), isSubtypeRelevant(false), isSourceRelevant(true), isSourceIDRelevant(false)
-{
-}
-
-HasAnotherBonusLimiter::HasAnotherBonusLimiter(Bonus::BonusType bonus, TBonusSubtype _subtype, Bonus::BonusSource src)
-	: type(bonus), subtype(_subtype), isSubtypeRelevant(true), source(src), isSourceRelevant(true), isSourceIDRelevant(false)
-{
-}
-
-ILimiter::EDecision HasAnotherBonusLimiter::limit(const BonusLimitationContext &context) const
-{
-	//TODO: proper selector config with parsing of JSON
-	auto mySelector = Selector::type()(type);
-
-	if(isSubtypeRelevant)
-		mySelector = mySelector.And(Selector::subtype()(subtype));
-	if(isSourceRelevant && isSourceIDRelevant)
-		mySelector = mySelector.And(Selector::source(source, sid));
-	else if (isSourceRelevant)
-		mySelector = mySelector.And(Selector::sourceTypeSel(source));
-
-	//if we have a bonus of required type accepted, limiter should accept also this bonus
-	if(context.alreadyAccepted.getFirst(mySelector))
-		return ILimiter::EDecision::ACCEPT;
-
-	//if there are no matching bonuses pending, we can (and must) reject right away
-	if(!context.stillUndecided.getFirst(mySelector))
-		return ILimiter::EDecision::DISCARD;
-
-	//do not accept for now but it may change if more bonuses gets included
-	return ILimiter::EDecision::NOT_SURE;
-}
-
-std::string HasAnotherBonusLimiter::toString() const
-{
-	std::string typeName = vstd::findKey(bonusNameMap, type);
-	if(isSubtypeRelevant)
-	{
-		boost::format fmt("HasAnotherBonusLimiter(type=%s, subtype=%d)");
-		fmt % typeName % subtype;
-		return fmt.str();
-	}
-	else
-	{
-		boost::format fmt("HasAnotherBonusLimiter(type=%s)");
-		fmt % typeName;
-		return fmt.str();
-	}
-}
-
-JsonNode HasAnotherBonusLimiter::toJsonNode() const
-{
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
-	std::string typeName = vstd::findKey(bonusNameMap, type);
-	auto sourceTypeName = vstd::findKey(bonusSourceMap, source);
-
-	root["type"].String() = "HAS_ANOTHER_BONUS_LIMITER";
-	root["parameters"].Vector().push_back(JsonUtils::stringNode(typeName));
-	if(isSubtypeRelevant)
-		root["parameters"].Vector().push_back(JsonUtils::intNode(subtype));
-	if(isSourceRelevant)
-		root["parameters"].Vector().push_back(JsonUtils::stringNode(sourceTypeName));
-
-	return root;
-}
-
-ILimiter::EDecision UnitOnHexLimiter::limit(const BonusLimitationContext &context) const
-{
-	const auto * stack = retrieveStackBattle(&context.node);
-	if(!stack)
-		return ILimiter::EDecision::DISCARD;
-
-	auto accept = false;
-	for (const auto & hex : stack->getHexes())
-		accept |= !!applicableHexes.count(hex);
-
-	return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
-}
-
-UnitOnHexLimiter::UnitOnHexLimiter(const std::set<BattleHex> & applicableHexes):
-	applicableHexes(applicableHexes)
-{
-}
-
-JsonNode UnitOnHexLimiter::toJsonNode() const
-{
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
-
-	root["type"].String() = "UNIT_ON_HEXES";
-	for(const auto & hex : applicableHexes)
-		root["parameters"].Vector().push_back(JsonUtils::intNode(hex));
-
-	return root;
-}
-
-bool IPropagator::shouldBeAttached(CBonusSystemNode *dest)
-{
-	return false;
-}
-
-CBonusSystemNode::ENodeTypes IPropagator::getPropagatorType() const
-{
-	return CBonusSystemNode::ENodeTypes::NONE;
-}
-
-CPropagatorNodeType::CPropagatorNodeType(CBonusSystemNode::ENodeTypes NodeType)
-	: nodeType(NodeType)
-{
-}
-
-CBonusSystemNode::ENodeTypes CPropagatorNodeType::getPropagatorType() const
-{
-	return nodeType;
-}
-
-bool CPropagatorNodeType::shouldBeAttached(CBonusSystemNode *dest)
-{
-	return nodeType == dest->getNodeType();
-}
-
-CreatureTerrainLimiter::CreatureTerrainLimiter()
-	: terrainType(ETerrainId::NATIVE_TERRAIN)
-{
-}
-
-CreatureTerrainLimiter::CreatureTerrainLimiter(TerrainId terrain):
-	terrainType(terrain)
-{
-}
-
-ILimiter::EDecision CreatureTerrainLimiter::limit(const BonusLimitationContext &context) const
-{
-	const CStack *stack = retrieveStackBattle(&context.node);
-	if(stack)
-	{
-		if (terrainType == ETerrainId::NATIVE_TERRAIN && stack->isOnNativeTerrain())//terrainType not specified = native
-			return ILimiter::EDecision::ACCEPT;
-
-		if(terrainType != ETerrainId::NATIVE_TERRAIN && stack->isOnTerrain(terrainType))
-			return ILimiter::EDecision::ACCEPT;
-
-	}
-	return ILimiter::EDecision::DISCARD;
-	//TODO neutral creatues
-}
-
-std::string CreatureTerrainLimiter::toString() const
-{
-	boost::format fmt("CreatureTerrainLimiter(terrainType=%s)");
-	auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->getJsonKey();
-	fmt % (terrainType == ETerrainId::NATIVE_TERRAIN ? "native" : terrainName);
-	return fmt.str();
-}
-
-JsonNode CreatureTerrainLimiter::toJsonNode() const
-{
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
-
-	root["type"].String() = "CREATURE_TERRAIN_LIMITER";
-	auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->getJsonKey();
-	root["parameters"].Vector().push_back(JsonUtils::stringNode(terrainName));
-
-	return root;
-}
-
-FactionLimiter::FactionLimiter(FactionID creatureFaction)
-	: faction(creatureFaction)
-{
-}
-
-ILimiter::EDecision FactionLimiter::limit(const BonusLimitationContext &context) const
-{
-	const auto * bearer = dynamic_cast<const INativeTerrainProvider*>(&context.node);
-
-	if(bearer)
-	{
-		if(faction != FactionID::DEFAULT)
-			return bearer->getFaction() == faction ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
-
-		switch(context.b->source)
-		{
-			case Bonus::CREATURE_ABILITY:
-				return faction == CreatureID(context.b->sid).toCreature()->getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
-			
-			case Bonus::TOWN_STRUCTURE:
-				return faction == FactionID(Bonus::getHighFromSid32(context.b->sid)) ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
-
-			//TODO: other sources of bonuses
-		}
-	}
-	return ILimiter::EDecision::DISCARD; //Discard by default
-}
-
-std::string FactionLimiter::toString() const
-{
-	boost::format fmt("FactionLimiter(faction=%s)");
-	fmt % VLC->factions()->getByIndex(faction)->getJsonKey();
-	return fmt.str();
-}
-
-JsonNode FactionLimiter::toJsonNode() const
-{
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
-
-	root["type"].String() = "FACTION_LIMITER";
-	root["parameters"].Vector().push_back(JsonUtils::stringNode(VLC->factions()->getByIndex(faction)->getJsonKey()));
-
-	return root;
-}
-
-CreatureLevelLimiter::CreatureLevelLimiter(uint32_t minLevel, uint32_t maxLevel) :
-	minLevel(minLevel),
-	maxLevel(maxLevel)
-{
-}
-
-ILimiter::EDecision CreatureLevelLimiter::limit(const BonusLimitationContext &context) const
-{
-	const auto *c = retrieveCreature(&context.node);
-	auto accept = c && (c->getLevel() < maxLevel && c->getLevel() >= minLevel);
-	return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; //drop bonus for non-creatures or non-native residents
-}
-
-std::string CreatureLevelLimiter::toString() const
-{
-	boost::format fmt("CreatureLevelLimiter(minLevel=%d,maxLevel=%d)");
-	fmt % minLevel % maxLevel;
-	return fmt.str();
-}
-
-JsonNode CreatureLevelLimiter::toJsonNode() const
-{
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
-
-	root["type"].String() = "CREATURE_LEVEL_LIMITER";
-	root["parameters"].Vector().push_back(JsonUtils::intNode(minLevel));
-	root["parameters"].Vector().push_back(JsonUtils::intNode(maxLevel));
-
-	return root;
-}
-
-CreatureAlignmentLimiter::CreatureAlignmentLimiter(EAlignment Alignment)
-	: alignment(Alignment)
-{
-}
-
-ILimiter::EDecision CreatureAlignmentLimiter::limit(const BonusLimitationContext &context) const
-{
-	const auto * c = retrieveCreature(&context.node);
-	if(c) {
-		if(alignment == EAlignment::GOOD && c->isGood())
-			return ILimiter::EDecision::ACCEPT;
-		if(alignment == EAlignment::EVIL && c->isEvil())
-			return ILimiter::EDecision::ACCEPT;
-		if(alignment == EAlignment::NEUTRAL && !c->isEvil() && !c->isGood())
-			return ILimiter::EDecision::ACCEPT;
-	}
-
-	return ILimiter::EDecision::DISCARD;
-}
-
-std::string CreatureAlignmentLimiter::toString() const
-{
-	boost::format fmt("CreatureAlignmentLimiter(alignment=%s)");
-	fmt % GameConstants::ALIGNMENT_NAMES[vstd::to_underlying(alignment)];
-	return fmt.str();
-}
-
-JsonNode CreatureAlignmentLimiter::toJsonNode() const
-{
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
-
-	root["type"].String() = "CREATURE_ALIGNMENT_LIMITER";
-	root["parameters"].Vector().push_back(JsonUtils::stringNode(GameConstants::ALIGNMENT_NAMES[vstd::to_underlying(alignment)]));
-
-	return root;
-}
-
-RankRangeLimiter::RankRangeLimiter(ui8 Min, ui8 Max)
-	:minRank(Min), maxRank(Max)
-{
-}
-
-RankRangeLimiter::RankRangeLimiter()
-{
-	minRank = maxRank = -1;
-}
-
-ILimiter::EDecision RankRangeLimiter::limit(const BonusLimitationContext &context) const
-{
-	const CStackInstance * csi = retrieveStackInstance(&context.node);
-	if(csi)
-	{
-		if (csi->getNodeType() == CBonusSystemNode::COMMANDER) //no stack exp bonuses for commander creatures
-			return ILimiter::EDecision::DISCARD;
-		if (csi->getExpRank() > minRank && csi->getExpRank() < maxRank)
-			return ILimiter::EDecision::ACCEPT;
-	}
-	return ILimiter::EDecision::DISCARD;
-}
-
-OppositeSideLimiter::OppositeSideLimiter(PlayerColor Owner):
-	owner(std::move(Owner))
-{
-}
-
-ILimiter::EDecision OppositeSideLimiter::limit(const BonusLimitationContext & context) const
-{
-	auto contextOwner = CBonusSystemNode::retrieveNodeOwner(& context.node);
-	auto decision = (owner == contextOwner || owner == PlayerColor::CANNOT_DETERMINE) ? ILimiter::EDecision::DISCARD : ILimiter::EDecision::ACCEPT;
-	return decision;
-}
-
-// Aggregate/Boolean Limiters
-
-AggregateLimiter::AggregateLimiter(std::vector<TLimiterPtr> limiters):
-	limiters(std::move(limiters))
-{
-}
-
-void AggregateLimiter::add(const TLimiterPtr & limiter)
-{
-	if(limiter)
-		limiters.push_back(limiter);
-}
-
-JsonNode AggregateLimiter::toJsonNode() const
-{
-	JsonNode result(JsonNode::JsonType::DATA_VECTOR);
-	result.Vector().push_back(JsonUtils::stringNode(getAggregator()));
-	for(const auto & l : limiters)
-		result.Vector().push_back(l->toJsonNode());
-	return result;
-}
-
-const std::string AllOfLimiter::aggregator = "allOf";
-const std::string & AllOfLimiter::getAggregator() const
-{
-	return aggregator;
-}
-
-AllOfLimiter::AllOfLimiter(std::vector<TLimiterPtr> limiters):
-	AggregateLimiter(limiters)
-{
-}
-
-ILimiter::EDecision AllOfLimiter::limit(const BonusLimitationContext & context) const
-{
-	bool wasntSure = false;
-
-	for(const auto & limiter : limiters)
-	{
-		auto result = limiter->limit(context);
-		if(result == ILimiter::EDecision::DISCARD)
-			return result;
-		if(result == ILimiter::EDecision::NOT_SURE)
-			wasntSure = true;
-	}
-
-	return wasntSure ? ILimiter::EDecision::NOT_SURE : ILimiter::EDecision::ACCEPT;
-}
-
-const std::string AnyOfLimiter::aggregator = "anyOf";
-const std::string & AnyOfLimiter::getAggregator() const
-{
-	return aggregator;
-}
-
-AnyOfLimiter::AnyOfLimiter(std::vector<TLimiterPtr> limiters):
-	AggregateLimiter(limiters)
-{
-}
-
-ILimiter::EDecision AnyOfLimiter::limit(const BonusLimitationContext & context) const
-{
-	bool wasntSure = false;
-
-	for(const auto & limiter : limiters)
-	{
-		auto result = limiter->limit(context);
-		if(result == ILimiter::EDecision::ACCEPT)
-			return result;
-		if(result == ILimiter::EDecision::NOT_SURE)
-			wasntSure = true;
-	}
-
-	return wasntSure ? ILimiter::EDecision::NOT_SURE : ILimiter::EDecision::DISCARD;
-}
-
-const std::string NoneOfLimiter::aggregator = "noneOf";
-const std::string & NoneOfLimiter::getAggregator() const
-{
-	return aggregator;
-}
-
-NoneOfLimiter::NoneOfLimiter(std::vector<TLimiterPtr> limiters):
-	AggregateLimiter(limiters)
-{
-}
-
-ILimiter::EDecision NoneOfLimiter::limit(const BonusLimitationContext & context) const
-{
-	bool wasntSure = false;
-
-	for(const auto & limiter : limiters)
-	{
-		auto result = limiter->limit(context);
-		if(result == ILimiter::EDecision::ACCEPT)
-			return ILimiter::EDecision::DISCARD;
-		if(result == ILimiter::EDecision::NOT_SURE)
-			wasntSure = true;
-	}
-
-	return wasntSure ? ILimiter::EDecision::NOT_SURE : ILimiter::EDecision::ACCEPT;
-}
-
-// Updaters
-
-std::shared_ptr<Bonus> Bonus::addUpdater(const TUpdaterPtr & Updater)
-{
-	updater = Updater;
-	return this->shared_from_this();
-}
-
-std::shared_ptr<Bonus> IUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
-{
-	return b;
-}
-
-std::string IUpdater::toString() const
-{
-	return typeid(*this).name();
-}
-
-JsonNode IUpdater::toJsonNode() const
-{
-	return JsonNode(JsonNode::JsonType::DATA_NULL);
-}
-
-GrowsWithLevelUpdater::GrowsWithLevelUpdater(int valPer20, int stepSize) : valPer20(valPer20), stepSize(stepSize)
-{
-}
-
-std::shared_ptr<Bonus> GrowsWithLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
-{
-	if(context.getNodeType() == CBonusSystemNode::HERO)
-	{
-		int level = dynamic_cast<const CGHeroInstance &>(context).level;
-		int steps = stepSize ? level / stepSize : level;
-		//rounding follows format for HMM3 creature specialty bonus
-		int newVal = (valPer20 * steps + 19) / 20;
-		//return copy of bonus with updated val
-		std::shared_ptr<Bonus> newBonus = std::make_shared<Bonus>(*b);
-		newBonus->val = newVal;
-		return newBonus;
-	}
-	return b;
-}
-
-std::string GrowsWithLevelUpdater::toString() const
-{
-	return boost::str(boost::format("GrowsWithLevelUpdater(valPer20=%d, stepSize=%d)") % valPer20 % stepSize);
-}
-
-JsonNode GrowsWithLevelUpdater::toJsonNode() const
-{
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
-
-	root["type"].String() = "GROWS_WITH_LEVEL";
-	root["parameters"].Vector().push_back(JsonUtils::intNode(valPer20));
-	if(stepSize > 1)
-		root["parameters"].Vector().push_back(JsonUtils::intNode(stepSize));
-
-	return root;
-}
-
-std::shared_ptr<Bonus> TimesHeroLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
-{
-	if(context.getNodeType() == CBonusSystemNode::HERO)
-	{
-		int level = dynamic_cast<const CGHeroInstance &>(context).level;
-		std::shared_ptr<Bonus> newBonus = std::make_shared<Bonus>(*b);
-		newBonus->val *= level;
-		return newBonus;
-	}
-	return b;
-}
-
-std::string TimesHeroLevelUpdater::toString() const
-{
-	return "TimesHeroLevelUpdater";
-}
-
-JsonNode TimesHeroLevelUpdater::toJsonNode() const
-{
-	return JsonUtils::stringNode("TIMES_HERO_LEVEL");
-}
-
-ArmyMovementUpdater::ArmyMovementUpdater():
-	base(20),
-	divider(3),
-	multiplier(10),
-	max(700)
-{
-}
-
-ArmyMovementUpdater::ArmyMovementUpdater(int base, int divider, int multiplier, int max):
-	base(base),
-	divider(divider),
-	multiplier(multiplier),
-	max(max)
-{
-}
-
-std::shared_ptr<Bonus> ArmyMovementUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
-{
-	if(b->type == Bonus::MOVEMENT && context.getNodeType() == CBonusSystemNode::HERO)
-	{
-		auto speed = static_cast<const CGHeroInstance &>(context).getLowestCreatureSpeed();
-		si32 armySpeed = speed * base / divider;
-		auto counted = armySpeed * multiplier;
-		auto newBonus = std::make_shared<Bonus>(*b);
-		newBonus->source = Bonus::ARMY;
-		newBonus->val += vstd::amin(counted, max);
-		return newBonus;
-	}
-	if(b->type != Bonus::MOVEMENT)
-		logGlobal->error("ArmyMovementUpdater should only be used for MOVEMENT bonus!");
-	return b;
-}
-
-std::string ArmyMovementUpdater::toString() const
-{
-	return "ArmyMovementUpdater";
-}
-
-JsonNode ArmyMovementUpdater::toJsonNode() const
-{
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
-
-	root["type"].String() = "ARMY_MOVEMENT";
-	root["parameters"].Vector().push_back(JsonUtils::intNode(base));
-	root["parameters"].Vector().push_back(JsonUtils::intNode(divider));
-	root["parameters"].Vector().push_back(JsonUtils::intNode(multiplier));
-	root["parameters"].Vector().push_back(JsonUtils::intNode(max));
-
-	return root;
-}
-std::shared_ptr<Bonus> TimesStackLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
-{
-	if(context.getNodeType() == CBonusSystemNode::STACK_INSTANCE)
-	{
-		int level = dynamic_cast<const CStackInstance &>(context).getLevel();
-		std::shared_ptr<Bonus> newBonus = std::make_shared<Bonus>(*b);
-		newBonus->val *= level;
-		return newBonus;
-	}
-	else if(context.getNodeType() == CBonusSystemNode::STACK_BATTLE)
-	{
-		const auto & stack = dynamic_cast<const CStack &>(context);
-		//only update if stack doesn't have an instance (summons, war machines)
-		//otherwise we'd end up multiplying twice
-		if(stack.base == nullptr)
-		{
-			int level = stack.unitType()->getLevel();
-			std::shared_ptr<Bonus> newBonus = std::make_shared<Bonus>(*b);
-			newBonus->val *= level;
-			return newBonus;
-		}
-	}
-	return b;
-}
-
-std::string TimesStackLevelUpdater::toString() const
-{
-	return "TimesStackLevelUpdater";
-}
-
-JsonNode TimesStackLevelUpdater::toJsonNode() const
-{
-	return JsonUtils::stringNode("TIMES_STACK_LEVEL");
-}
-
-std::string OwnerUpdater::toString() const
-{
-	return "OwnerUpdater";
-}
-
-JsonNode OwnerUpdater::toJsonNode() const
-{
-	return JsonUtils::stringNode("BONUS_OWNER_UPDATER");
-}
-
-std::shared_ptr<Bonus> OwnerUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
-{
-	auto owner = CBonusSystemNode::retrieveNodeOwner(&context);
-
-	if(owner == PlayerColor::UNFLAGGABLE)
-		owner = PlayerColor::NEUTRAL;
-
-	std::shared_ptr<Bonus> updated =
-		std::make_shared<Bonus>(static_cast<Bonus::BonusDuration>(b->duration), b->type, b->source, b->val, b->sid, b->subtype, b->valType);
-	updated->limiter = std::make_shared<OppositeSideLimiter>(owner);
-	return updated;
-}
-
-VCMI_LIB_NAMESPACE_END

+ 0 - 1370
lib/HeroBonus.h

@@ -1,1370 +0,0 @@
-/*
- * HeroBonus.h, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-#pragma once
-
-#include "GameConstants.h"
-#include "JsonNode.h"
-#include "battle/BattleHex.h"
-#include <limits>
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-class CCreature;
-struct Bonus;
-class IBonusBearer;
-class CBonusSystemNode;
-class ILimiter;
-class IPropagator;
-class IUpdater;
-class BonusList;
-
-using TBonusListPtr = std::shared_ptr<BonusList>;
-using TConstBonusListPtr = std::shared_ptr<const BonusList>;
-using TLimiterPtr = std::shared_ptr<ILimiter>;
-using TPropagatorPtr = std::shared_ptr<IPropagator>;
-using TUpdaterPtr = std::shared_ptr<IUpdater>;
-using TNodes = std::set<CBonusSystemNode *>;
-using TCNodes = std::set<const CBonusSystemNode *>;
-using TNodesVector = std::vector<CBonusSystemNode *>;
-
-class CSelector : std::function<bool(const Bonus*)>
-{
-	using TBase = std::function<bool(const Bonus*)>;
-public:
-	CSelector() = default;
-	template<typename T>
-	CSelector(const T &t,	//SFINAE trick -> include this c-tor in overload resolution only if parameter is class
-							//(includes functors, lambdas) or function. Without that VC is going mad about ambiguities.
-		typename std::enable_if < boost::mpl::or_ < std::is_class<T>, std::is_function<T >> ::value>::type *dummy = nullptr)
-		: TBase(t)
-	{}
-
-	CSelector(std::nullptr_t)
-	{}
-
-	CSelector And(CSelector rhs) const
-	{
-		//lambda may likely outlive "this" (it can be even a temporary) => we copy the OBJECT (not pointer)
-		auto thisCopy = *this;
-		return [thisCopy, rhs](const Bonus *b) mutable { return thisCopy(b) && rhs(b); };
-	}
-	CSelector Or(CSelector rhs) const
-	{
-		auto thisCopy = *this;
-		return [thisCopy, rhs](const Bonus *b) mutable { return thisCopy(b) || rhs(b); };
-	}
-
-	CSelector Not() const
-	{
-		auto thisCopy = *this;
-		return [thisCopy](const Bonus *b) mutable { return !thisCopy(b); };
-	}
-
-	bool operator()(const Bonus *b) const
-	{
-		return TBase::operator()(b);
-	}
-
-	operator bool() const
-	{
-		return !!static_cast<const TBase&>(*this);
-	}
-};
-
-class DLL_LINKAGE CBonusProxy
-{
-public:
-	CBonusProxy(const IBonusBearer * Target, CSelector Selector);
-	CBonusProxy(const CBonusProxy & other);
-	CBonusProxy(CBonusProxy && other) noexcept;
-
-	CBonusProxy & operator=(CBonusProxy && other) noexcept;
-	CBonusProxy & operator=(const CBonusProxy & other);
-	const BonusList * operator->() const;
-	TConstBonusListPtr getBonusList() const;
-
-protected:
-	CSelector selector;
-	const IBonusBearer * target;
-	mutable int64_t bonusListCachedLast;
-	mutable TConstBonusListPtr bonusList[2];
-	mutable int currentBonusListIndex;
-	mutable boost::mutex swapGuard;
-	void swapBonusList(TConstBonusListPtr other) const;
-};
-
-class DLL_LINKAGE CTotalsProxy : public CBonusProxy
-{
-public:
-	CTotalsProxy(const IBonusBearer * Target, CSelector Selector, int InitialValue);
-	CTotalsProxy(const CTotalsProxy & other);
-	CTotalsProxy(CTotalsProxy && other) = delete;
-
-	CTotalsProxy & operator=(const CTotalsProxy & other) = default;
-	CTotalsProxy & operator=(CTotalsProxy && other) = delete;
-
-	int getMeleeValue() const;
-	int getRangedValue() const;
-	int getValue() const;
-	/**
-	Returns total value of all selected bonuses and sets bonusList as a pointer to the list of selected bonuses
-	@param bonusList is the out list of all selected bonuses
-	@return total value of all selected bonuses and 0 otherwise
-	*/
-	int getValueAndList(TConstBonusListPtr & bonusList) const;
-
-private:
-	int initialValue;
-
-	mutable int64_t valueCachedLast = 0;
-	mutable int value = 0;
-
-	mutable int64_t meleeCachedLast;
-	mutable int meleeValue;
-
-	mutable int64_t rangedCachedLast;
-	mutable int rangedValue;
-};
-
-class DLL_LINKAGE CCheckProxy
-{
-public:
-	CCheckProxy(const IBonusBearer * Target, CSelector Selector);
-	CCheckProxy(const CCheckProxy & other);
-	CCheckProxy& operator= (const CCheckProxy & other) = default;
-
-	bool getHasBonus() const;
-
-private:
-	const IBonusBearer * target;
-	CSelector selector;
-
-	mutable int64_t cachedLast;
-	mutable bool hasBonus;
-};
-
-class DLL_LINKAGE CAddInfo : public std::vector<si32>
-{
-public:
-	enum { NONE = -1 };
-
-	CAddInfo();
-	CAddInfo(si32 value);
-
-	bool operator==(si32 value) const;
-	bool operator!=(si32 value) const;
-
-	si32 & operator[](size_type pos);
-	si32 operator[](size_type pos) const;
-
-	std::string toString() const;
-	JsonNode toJsonNode() const;
-};
-
-#define BONUS_TREE_DESERIALIZATION_FIX if(!h.saving && h.smartPointerSerialization) deserializationFix();
-
-#define BONUS_LIST										\
-	BONUS_NAME(NONE) 									\
-	BONUS_NAME(LEVEL_COUNTER) /* for commander artifacts*/ \
-	BONUS_NAME(MOVEMENT) /*Subtype is 1 - land, 0 - sea*/ \
-	BONUS_NAME(MORALE) \
-	BONUS_NAME(LUCK) \
-	BONUS_NAME(PRIMARY_SKILL) /*uses subtype to pick skill; additional info if set: 1 - only melee, 2 - only distance*/  \
-	BONUS_NAME(SIGHT_RADIUS) \
-	BONUS_NAME(MANA_REGENERATION) /*points per turn apart from normal (1 + mysticism)*/  \
-	BONUS_NAME(FULL_MANA_REGENERATION) /*all mana points are replenished every day*/  \
-	BONUS_NAME(NONEVIL_ALIGNMENT_MIX) /*good and neutral creatures can be mixed without morale penalty*/  \
-	BONUS_NAME(SURRENDER_DISCOUNT) /*%*/  \
-	BONUS_NAME(STACKS_SPEED)  /*additional info - percent of speed bonus applied after direct bonuses; >0 - added, <0 - subtracted to this part*/ \
-	BONUS_NAME(FLYING_MOVEMENT) /*value - penalty percentage*/ \
-	BONUS_NAME(SPELL_DURATION) \
-	BONUS_NAME(AIR_SPELL_DMG_PREMY) \
-	BONUS_NAME(EARTH_SPELL_DMG_PREMY) \
-	BONUS_NAME(FIRE_SPELL_DMG_PREMY) \
-	BONUS_NAME(WATER_SPELL_DMG_PREMY) \
-	BONUS_NAME(WATER_WALKING) /*value - penalty percentage*/ \
-	BONUS_NAME(NEGATE_ALL_NATURAL_IMMUNITIES) \
-	BONUS_NAME(STACK_HEALTH) \
-	BONUS_NAME(FIRE_SPELLS) \
-	BONUS_NAME(AIR_SPELLS) \
-	BONUS_NAME(WATER_SPELLS) \
-	BONUS_NAME(EARTH_SPELLS) \
-	BONUS_NAME(GENERATE_RESOURCE) /*daily value, uses subtype (resource type)*/  \
-	BONUS_NAME(CREATURE_GROWTH) /*for legion artifacts: value - week growth bonus, subtype - monster level if aplicable*/  \
-	BONUS_NAME(WHIRLPOOL_PROTECTION) /*hero won't lose army when teleporting through whirlpool*/  \
-	BONUS_NAME(SPELL) /*hero knows spell, val - skill level (0 - 3), subtype - spell id*/  \
-	BONUS_NAME(SPELLS_OF_LEVEL) /*hero knows all spells of given level, val - skill level; subtype - level*/  \
-	BONUS_NAME(BATTLE_NO_FLEEING) /*for shackles of war*/ \
-	BONUS_NAME(MAGIC_SCHOOL_SKILL) /* //eg. for magic plains terrain, subtype: school of magic (0 - all, 1 - fire, 2 - air, 4 - water, 8 - earth), value - level*/ \
-	BONUS_NAME(FREE_SHOOTING) /*stacks can shoot even if otherwise blocked (sharpshooter's bow effect)*/ \
-	BONUS_NAME(OPENING_BATTLE_SPELL) /*casts a spell at expert level at beginning of battle, val - spell power, subtype - spell id*/ \
-	BONUS_NAME(IMPROVED_NECROMANCY) /* raise more powerful creatures: subtype - creature type raised, addInfo - [required necromancy level, required stack level], val - necromancy level for this purpose */ \
-	BONUS_NAME(CREATURE_GROWTH_PERCENT) /*increases growth of all units in all towns, val - percentage*/ \
-	BONUS_NAME(FREE_SHIP_BOARDING) /*movement points preserved with ship boarding and landing*/  \
-	BONUS_NAME(FLYING)									\
-	BONUS_NAME(SHOOTER)									\
-	BONUS_NAME(CHARGE_IMMUNITY)							\
-	BONUS_NAME(ADDITIONAL_ATTACK)						\
-	BONUS_NAME(UNLIMITED_RETALIATIONS)					\
-	BONUS_NAME(NO_MELEE_PENALTY)						\
-	BONUS_NAME(JOUSTING) /*for champions*/				\
-	BONUS_NAME(HATE) /*eg. angels hate devils, subtype - ID of hated creature, val - damage bonus percent */ \
-	BONUS_NAME(KING) /* val - required slayer bonus val to affect */\
-	BONUS_NAME(MAGIC_RESISTANCE) /*in % (value)*/		\
-	BONUS_NAME(CHANGES_SPELL_COST_FOR_ALLY) /*in mana points (value) , eg. mage*/ \
-	BONUS_NAME(CHANGES_SPELL_COST_FOR_ENEMY) /*in mana points (value) , eg. pegasus */ \
-	BONUS_NAME(SPELL_AFTER_ATTACK) /* subtype - spell id, value - chance %, addInfo[0] - level, addInfo[1] -> [0 - all attacks, 1 - shot only, 2 - melee only] */ \
-	BONUS_NAME(SPELL_BEFORE_ATTACK) /* subtype - spell id, value - chance %, addInfo[0] - level, addInfo[1] -> [0 - all attacks, 1 - shot only, 2 - melee only] */ \
-	BONUS_NAME(SPELL_RESISTANCE_AURA) /*eg. unicorns, value - resistance bonus in % for adjacent creatures*/ \
-	BONUS_NAME(LEVEL_SPELL_IMMUNITY) /*creature is immune to all spell with level below or equal to value of this bonus */ \
-	BONUS_NAME(BLOCK_MAGIC_ABOVE) /*blocks casting spells of the level > value */ \
-	BONUS_NAME(BLOCK_ALL_MAGIC) /*blocks casting spells*/ \
-	BONUS_NAME(TWO_HEX_ATTACK_BREATH) /*eg. dragons*/	\
-	BONUS_NAME(SPELL_DAMAGE_REDUCTION) /*eg. golems; value - reduction in %, subtype - spell school; -1 - all, 0 - air, 1 - fire, 2 - water, 3 - earth*/ \
-	BONUS_NAME(NO_WALL_PENALTY)							\
-	BONUS_NAME(NON_LIVING) /*eg. golems, cannot be rised or healed, only neutral morale */				\
-	BONUS_NAME(RANDOM_SPELLCASTER) /*eg. master genie, val - level*/ \
-	BONUS_NAME(BLOCKS_RETALIATION) /*eg. naga*/			\
-	BONUS_NAME(SPELL_IMMUNITY) /*subid - spell id*/		\
-	BONUS_NAME(MANA_CHANNELING) /*value in %, eg. familiar*/ \
-	BONUS_NAME(SPELL_LIKE_ATTACK) /*subtype - spell, value - spell level; range is taken from spell, but damage from creature; eg. magog*/ \
-	BONUS_NAME(THREE_HEADED_ATTACK) /*eg. cerberus*/	\
-	BONUS_NAME(GENERAL_DAMAGE_PREMY)						\
-	BONUS_NAME(FIRE_IMMUNITY)	/*subtype 0 - all, 1 - all except positive, 2 - only damage spells*/						\
-	BONUS_NAME(WATER_IMMUNITY)							\
-	BONUS_NAME(EARTH_IMMUNITY)							\
-	BONUS_NAME(AIR_IMMUNITY)							\
-	BONUS_NAME(MIND_IMMUNITY)							\
-	BONUS_NAME(FIRE_SHIELD)								\
-	BONUS_NAME(UNDEAD)									\
-	BONUS_NAME(HP_REGENERATION) /*creature regenerates val HP every new round*/					\
-	BONUS_NAME(MANA_DRAIN) /*value - spell points per turn*/ \
-	BONUS_NAME(LIFE_DRAIN)								\
-	BONUS_NAME(DOUBLE_DAMAGE_CHANCE) /*value in %, eg. dread knight*/ \
-	BONUS_NAME(RETURN_AFTER_STRIKE)						\
-	BONUS_NAME(SPELLCASTER) /*subtype - spell id, value - level of school, additional info - weighted chance. use SPECIFIC_SPELL_POWER, CREATURE_SPELL_POWER or CREATURE_ENCHANT_POWER for calculating the power*/ \
-	BONUS_NAME(CATAPULT)								\
-	BONUS_NAME(ENEMY_DEFENCE_REDUCTION) /*in % (value) eg. behemots*/ \
-	BONUS_NAME(GENERAL_DAMAGE_REDUCTION) /* shield / air shield effect, also armorer skill/petrify effect for subtype -1*/ \
-	BONUS_NAME(GENERAL_ATTACK_REDUCTION) /*eg. while stoned or blinded - in %,// subtype not used, use ONLY_MELEE_FIGHT / DISTANCE_FIGHT*/ \
-	BONUS_NAME(DEFENSIVE_STANCE) /* val - bonus to defense while defending */ \
-	BONUS_NAME(ATTACKS_ALL_ADJACENT) /*eg. hydra*/		\
-	BONUS_NAME(MORE_DAMAGE_FROM_SPELL) /*value - damage increase in %, subtype - spell id*/ \
-	BONUS_NAME(FEAR)									\
-	BONUS_NAME(FEARLESS)								\
-	BONUS_NAME(NO_DISTANCE_PENALTY)						\
-	BONUS_NAME(ENCHANTER)/* for Enchanter spells, val - skill level, subtype - spell id, additionalInfo - cooldown */ \
-	BONUS_NAME(HEALER)									\
-	BONUS_NAME(SIEGE_WEAPON)							\
-	BONUS_NAME(HYPNOTIZED)								\
-	BONUS_NAME(NO_RETALIATION) /*temporary bonus for basilisk, unicorn and scorpicore paralyze*/\
-	BONUS_NAME(ADDITIONAL_RETALIATION) /*value - number of additional retaliations*/ \
-	BONUS_NAME(MAGIC_MIRROR) /* value - chance of redirecting in %*/ \
-	BONUS_NAME(ALWAYS_MINIMUM_DAMAGE) /*unit does its minimum damage from range; subtype: -1 - any attack, 0 - melee, 1 - ranged, value: additional damage penalty (it'll subtracted from dmg), additional info - multiplicative anti-bonus for dmg in % [eg 20 means that creature will inflict 80% of normal minimal dmg]*/ \
-	BONUS_NAME(ALWAYS_MAXIMUM_DAMAGE) /*eg. bless effect, subtype: -1 - any attack, 0 - melee, 1 - ranged, value: additional damage, additional info - multiplicative bonus for dmg in %*/ \
-	BONUS_NAME(ATTACKS_NEAREST_CREATURE) /*while in berserk*/ \
-	BONUS_NAME(IN_FRENZY) /*value - level*/				\
-	BONUS_NAME(SLAYER) /*value - level*/				\
-	BONUS_NAME(FORGETFULL) /*forgetfulness spell effect, value - level*/ \
-	BONUS_NAME(NOT_ACTIVE) /* subtype - spell ID (paralyze, blind, stone gaze) for graphical effect*/ 								\
-	BONUS_NAME(NO_LUCK) /*eg. when fighting on cursed ground*/	\
-	BONUS_NAME(NO_MORALE) /*eg. when fighting on cursed ground*/ \
-	BONUS_NAME(DARKNESS) /*val = radius */ \
-	BONUS_NAME(SPECIAL_SPELL_LEV) /*subtype = id, val = value per level in percent*/\
-	BONUS_NAME(SPELL_DAMAGE) /*val = value, now works for sorcery*/\
-	BONUS_NAME(SPECIFIC_SPELL_DAMAGE) /*subtype = id of spell, val = value*/\
-	BONUS_NAME(SPECIAL_PECULIAR_ENCHANT) /*blesses and curses with id = val dependent on unit's level, subtype = 0 or 1 for Coronius*/\
-	BONUS_NAME(SPECIAL_UPGRADE) /*subtype = base, additionalInfo = target */\
-	BONUS_NAME(DRAGON_NATURE) \
-	BONUS_NAME(CREATURE_DAMAGE)/*subtype 0 = both, 1 = min, 2 = max*/\
-	BONUS_NAME(EXP_MULTIPLIER)/* val - percent of additional exp gained by stack/commander (base value 100)*/\
-	BONUS_NAME(SHOTS)\
-	BONUS_NAME(DEATH_STARE) /*subtype 0 - gorgon, 1 - commander*/\
-	BONUS_NAME(POISON) /*val - max health penalty from poison possible*/\
-	BONUS_NAME(BIND_EFFECT) /*doesn't do anything particular, works as a marker)*/\
-	BONUS_NAME(ACID_BREATH) /*additional val damage per creature after attack, additional info - chance in percent*/\
-	BONUS_NAME(RECEPTIVE) /*accepts friendly spells even with immunity*/\
-	BONUS_NAME(DIRECT_DAMAGE_IMMUNITY) /*direct damage spells, that is*/\
-	BONUS_NAME(CASTS) /*how many times creature can cast activated spell*/ \
-	BONUS_NAME(SPECIFIC_SPELL_POWER) /* value used for Thunderbolt and Resurrection cast by units, subtype - spell id */\
-	BONUS_NAME(CREATURE_SPELL_POWER) /* value per unit, divided by 100 (so faerie Dragons have 800)*/ \
-	BONUS_NAME(CREATURE_ENCHANT_POWER) /* total duration of spells cast by creature */ \
-	BONUS_NAME(ENCHANTED) /* permanently enchanted with spell subID of level = val, if val > 3 then spell is mass and has level of val-3*/ \
-	BONUS_NAME(REBIRTH) /* val - percent of life restored, subtype = 0 - regular, 1 - at least one unit (sacred Phoenix) */\
-	BONUS_NAME(ADDITIONAL_UNITS) /*val of units with id = subtype will be added to hero's army at the beginning of battle */\
-	BONUS_NAME(SPOILS_OF_WAR) /*val * 10^-6 * gained exp resources of subtype will be given to hero after battle*/\
-	BONUS_NAME(BLOCK)\
-	BONUS_NAME(DISGUISED) /* subtype - spell level */\
-	BONUS_NAME(VISIONS) /* subtype - spell level */\
-	BONUS_NAME(NO_TERRAIN_PENALTY) /* subtype - terrain type */\
-	BONUS_NAME(SOUL_STEAL) /*val - number of units gained per enemy killed, subtype = 0 - gained units survive after battle, 1 - they do not*/ \
-	BONUS_NAME(TRANSMUTATION) /*val - chance to trigger in %, subtype = 0 - resurrection based on HP, 1 - based on unit count, additional info - target creature ID (attacker default)*/\
-	BONUS_NAME(SUMMON_GUARDIANS) /*val - amount in % of stack count, subtype = creature ID*/\
-	BONUS_NAME(CATAPULT_EXTRA_SHOTS) /*val - power of catapult effect, requires CATAPULT bonus to work*/\
-	BONUS_NAME(RANGED_RETALIATION) /*allows shooters to perform ranged retaliation*/\
-	BONUS_NAME(BLOCKS_RANGED_RETALIATION) /*disallows ranged retaliation for shooter unit, BLOCKS_RETALIATION bonus is for melee retaliation only*/\
-	BONUS_NAME(MANUAL_CONTROL) /* manually control warmachine with id = subtype, chance = val */  \
-	BONUS_NAME(WIDE_BREATH) /* initial desigh: dragon breath affecting multiple nearby hexes */\
-	BONUS_NAME(FIRST_STRIKE) /* first counterattack, then attack if possible */\
-	BONUS_NAME(SYNERGY_TARGET) /* dummy skill for alternative upgrades mod */\
-	BONUS_NAME(SHOOTS_ALL_ADJACENT) /* H4 Cyclops-like shoot (attacks all hexes neighboring with target) without spell-like mechanics */\
-	BONUS_NAME(BLOCK_MAGIC_BELOW) /*blocks casting spells of the level < value */ \
-	BONUS_NAME(DESTRUCTION) /*kills extra units after hit, subtype = 0 - kill percentage of units, 1 - kill amount, val = chance in percent to trigger, additional info - amount/percentage to kill*/ \
-	BONUS_NAME(SPECIAL_CRYSTAL_GENERATION) /*crystal dragon crystal generation*/ \
-	BONUS_NAME(NO_SPELLCAST_BY_DEFAULT) /*spellcast will not be default attack option for this creature*/ \
-	BONUS_NAME(GARGOYLE) /* gargoyle is special than NON_LIVING, cannot be rised or healed */ \
-	BONUS_NAME(SPECIAL_ADD_VALUE_ENCHANT) /*specialty spell like Aenin has, increased effect of spell, additionalInfo = value to add*/\
-	BONUS_NAME(SPECIAL_FIXED_VALUE_ENCHANT) /*specialty spell like Melody has, constant spell effect (i.e. 3 luck), additionalInfo = value to fix.*/\
-	BONUS_NAME(TOWN_MAGIC_WELL) /*one-time pseudo-bonus to implement Magic Well in the town*/\
-	BONUS_NAME(LIMITED_SHOOTING_RANGE) /*limits range of shooting creatures, doesn't adjust any other mechanics (half vs full damage etc). val - range in hexes, additional info - optional new range for broken arrow mechanic */\
-	BONUS_NAME(LEARN_BATTLE_SPELL_CHANCE) /*skill-agnostic eagle eye chance. subtype = 0 - from enemy, 1 - TODO: from entire battlefield*/\
-	BONUS_NAME(LEARN_BATTLE_SPELL_LEVEL_LIMIT) /*skill-agnostic eagle eye limit, subtype - school (-1 for all), others TODO*/\
-	BONUS_NAME(PERCENTAGE_DAMAGE_BOOST) /*skill-agnostic archery and offence, subtype is 0 for offence and 1 for archery*/\
-	BONUS_NAME(LEARN_MEETING_SPELL_LIMIT) /*skill-agnostic scholar, subtype is -1 for all, TODO for others (> 0)*/\
-	BONUS_NAME(ROUGH_TERRAIN_DISCOUNT) /*skill-agnostic pathfinding*/\
-	BONUS_NAME(WANDERING_CREATURES_JOIN_BONUS) /*skill-agnostic diplomacy*/\
-	BONUS_NAME(BEFORE_BATTLE_REPOSITION) /*skill-agnostic tactics, bonus for allowing tactics*/\
-	BONUS_NAME(BEFORE_BATTLE_REPOSITION_BLOCK) /*skill-agnostic tactics, bonus for blocking opposite tactics. For now donble side tactics is TODO.*/\
-	BONUS_NAME(HERO_EXPERIENCE_GAIN_PERCENT) /*skill-agnostic learning, and we can use it as a global effect also*/\
-	BONUS_NAME(UNDEAD_RAISE_PERCENTAGE) /*Percentage of killed enemy creatures to be raised after battle as undead*/\
-	BONUS_NAME(MANA_PER_KNOWLEDGE) /*Percentage rate of translating 10 hero knowledge to mana, used to intelligence and global bonus*/\
-	BONUS_NAME(HERO_GRANTS_ATTACKS) /*If hero can grant additional attacks to creature, value is number of attacks, subtype is creatureID*/\
-	BONUS_NAME(BONUS_DAMAGE_PERCENTAGE) /*If hero can grant conditional damage to creature, value is percentage, subtype is creatureID*/\
-	BONUS_NAME(BONUS_DAMAGE_CHANCE) /*If hero can grant additional damage to creature, value is chance, subtype is creatureID*/\
-	BONUS_NAME(MAX_LEARNABLE_SPELL_LEVEL) /*This can work as wisdom before. val = max learnable spell level*/\
-	/* end of list */
-
-
-#define BONUS_SOURCE_LIST \
-	BONUS_SOURCE(ARTIFACT)\
-	BONUS_SOURCE(ARTIFACT_INSTANCE)\
-	BONUS_SOURCE(OBJECT)\
-	BONUS_SOURCE(CREATURE_ABILITY)\
-	BONUS_SOURCE(TERRAIN_NATIVE)\
-	BONUS_SOURCE(TERRAIN_OVERLAY)\
-	BONUS_SOURCE(SPELL_EFFECT)\
-	BONUS_SOURCE(TOWN_STRUCTURE)\
-	BONUS_SOURCE(HERO_BASE_SKILL)\
-	BONUS_SOURCE(SECONDARY_SKILL)\
-	BONUS_SOURCE(HERO_SPECIAL)\
-	BONUS_SOURCE(ARMY)\
-	BONUS_SOURCE(CAMPAIGN_BONUS)\
-	BONUS_SOURCE(SPECIAL_WEEK)\
-	BONUS_SOURCE(STACK_EXPERIENCE)\
-	BONUS_SOURCE(COMMANDER) /*TODO: consider using simply STACK_INSTANCE */\
-	BONUS_SOURCE(GLOBAL) /*used for base bonuses which all heroes or all stacks should have*/\
-	BONUS_SOURCE(OTHER) /*used for defensive stance and default value of spell level limit*/
-
-#define BONUS_VALUE_LIST \
-	BONUS_VALUE(ADDITIVE_VALUE)\
-	BONUS_VALUE(BASE_NUMBER)\
-	BONUS_VALUE(PERCENT_TO_ALL)\
-	BONUS_VALUE(PERCENT_TO_BASE)\
-	BONUS_VALUE(PERCENT_TO_SOURCE) /*Adds value only to bonuses with same source*/\
-	BONUS_VALUE(PERCENT_TO_TARGET_TYPE) /*Adds value only to bonuses with SourceType target*/\
-	BONUS_VALUE(INDEPENDENT_MAX) /*used for SPELL bonus */\
-	BONUS_VALUE(INDEPENDENT_MIN) //used for SECONDARY_SKILL_PREMY bonus
-
-/// Struct for handling bonuses of several types. Can be transferred to any hero
-struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
-{
-	enum { EVERY_TYPE = -1 };
-
-	enum BonusType
-	{
-#define BONUS_NAME(x) x,
-		BONUS_LIST
-#undef BONUS_NAME
-	};
-	enum BonusDuration //when bonus is automatically removed
-	{
-		PERMANENT = 1,
-		ONE_BATTLE = 2, //at the end of battle
-		ONE_DAY = 4,   //at the end of day
-		ONE_WEEK = 8, //at the end of week (bonus lasts till the end of week, thats NOT 7 days
-		N_TURNS = 16, //used during battles, after battle bonus is always removed
-		N_DAYS = 32,
-		UNTIL_BEING_ATTACKED = 64, /*removed after attack and counterattacks are performed*/
-		UNTIL_ATTACK = 128, /*removed after attack and counterattacks are performed*/
-		STACK_GETS_TURN = 256, /*removed when stack gets its turn - used for defensive stance*/
-		COMMANDER_KILLED = 512
-	};
-	enum BonusSource
-	{
-#define BONUS_SOURCE(x) x,
-		BONUS_SOURCE_LIST
-#undef BONUS_SOURCE
-		NUM_BONUS_SOURCE /*This is a dummy value, which will be always last*/
-	};
-
-	enum LimitEffect
-	{
-		NO_LIMIT = 0,
-		ONLY_DISTANCE_FIGHT=1, ONLY_MELEE_FIGHT, //used to mark bonuses for attack/defense primary skills from spells like Precision (distance only)
-	};
-
-	enum ValueType
-	{
-#define BONUS_VALUE(x) x,
-		BONUS_VALUE_LIST
-#undef BONUS_VALUE
-	};
-
-	ui16 duration = PERMANENT; //uses BonusDuration values
-	si16 turnsRemain = 0; //used if duration is N_TURNS, N_DAYS or ONE_WEEK
-
-	BonusType type = NONE; //uses BonusType values - says to what is this bonus - 1 byte
-	TBonusSubtype subtype = -1; //-1 if not applicable - 4 bytes
-
-	BonusSource source = OTHER; //source type" uses BonusSource values - what gave that bonus
-	BonusSource targetSourceType;//Bonuses of what origin this amplifies, uses BonusSource values. Needed for PERCENT_TO_TARGET_TYPE.
-	si32 val = 0;
-	ui32 sid = 0; //source id: id of object/artifact/spell
-	ValueType valType = ADDITIVE_VALUE;
-	std::string stacking; // bonuses with the same stacking value don't stack (e.g. Angel/Archangel morale bonus)
-
-	CAddInfo additionalInfo;
-	LimitEffect effectRange = NO_LIMIT; //if not NO_LIMIT, bonus will be omitted by default
-
-	TLimiterPtr limiter;
-	TPropagatorPtr propagator;
-	TUpdaterPtr updater;
-	TUpdaterPtr propagationUpdater;
-
-	std::string description;
-
-	Bonus(BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::string Desc, si32 Subtype=-1);
-	Bonus(BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, si32 Subtype=-1, ValueType ValType = ADDITIVE_VALUE);
-	Bonus() = default;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & duration;
-		h & type;
-		h & subtype;
-		h & source;
-		h & val;
-		h & sid;
-		h & description;
-		h & additionalInfo;
-		h & turnsRemain;
-		h & valType;
-		h & stacking;
-		h & effectRange;
-		h & limiter;
-		h & propagator;
-		h & updater;
-		h & propagationUpdater;
-		h & targetSourceType;
-	}
-
-	template <typename Ptr>
-	static bool compareByAdditionalInfo(const Ptr& a, const Ptr& b)
-	{
-		return a->additionalInfo < b->additionalInfo;
-	}
-	static bool NDays(const Bonus *hb)
-	{
-		return hb->duration & Bonus::N_DAYS;
-	}
-	static bool NTurns(const Bonus *hb)
-	{
-		return hb->duration & Bonus::N_TURNS;
-	}
-	static bool OneDay(const Bonus *hb)
-	{
-		return hb->duration & Bonus::ONE_DAY;
-	}
-	static bool OneWeek(const Bonus *hb)
-	{
-		return hb->duration & Bonus::ONE_WEEK;
-	}
-	static bool OneBattle(const Bonus *hb)
-	{
-		return hb->duration & Bonus::ONE_BATTLE;
-	}
-	static bool Permanent(const Bonus *hb)
-	{
-		return hb->duration & Bonus::PERMANENT;
-	}
-	static bool UntilGetsTurn(const Bonus *hb)
-	{
-		return hb->duration & Bonus::STACK_GETS_TURN;
-	}
-	static bool UntilAttack(const Bonus *hb)
-	{
-		return hb->duration & Bonus::UNTIL_ATTACK;
-	}
-	static bool UntilBeingAttacked(const Bonus *hb)
-	{
-		return hb->duration & Bonus::UNTIL_BEING_ATTACKED;
-	}
-	static bool UntilCommanderKilled(const Bonus *hb)
-	{
-		return hb->duration & Bonus::COMMANDER_KILLED;
-	}
-	inline bool operator == (const BonusType & cf) const
-	{
-		return type == cf;
-	}
-	inline void operator += (const ui32 Val) //no return
-	{
-		val += Val;
-	}
-	STRONG_INLINE static ui32 getSid32(ui32 high, ui32 low)
-	{
-		return (high << 16) + low;
-	}
-
-	STRONG_INLINE static ui32 getHighFromSid32(ui32 sid)
-	{
-		return sid >> 16;
-	}
-
-	STRONG_INLINE static ui32 getLowFromSid32(ui32 sid)
-	{
-		return sid & 0x0000FFFF;
-	}
-
-	std::string Description(std::optional<si32> customValue = {}) const;
-	JsonNode toJsonNode() const;
-	std::string nameForBonus() const; // generate suitable name for bonus - e.g. for storing in json struct
-
-	std::shared_ptr<Bonus> addLimiter(const TLimiterPtr & Limiter); //returns this for convenient chain-calls
-	std::shared_ptr<Bonus> addPropagator(const TPropagatorPtr & Propagator); //returns this for convenient chain-calls
-	std::shared_ptr<Bonus> addUpdater(const TUpdaterPtr & Updater); //returns this for convenient chain-calls
-};
-
-DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const Bonus &bonus);
-
-struct DLL_LINKAGE BonusParams {
-	bool isConverted;
-	Bonus::BonusType type = Bonus::NONE;
-	TBonusSubtype subtype = -1;
-	std::string subtypeStr;
-	bool subtypeRelevant = false;
-	Bonus::ValueType valueType = Bonus::BASE_NUMBER;
-	bool valueTypeRelevant = false;
-	si32 val = 0;
-	bool valRelevant = false;
-	Bonus::BonusSource targetType = Bonus::SECONDARY_SKILL;
-	bool targetTypeRelevant = false;
-
-	BonusParams(bool isConverted = true) : isConverted(isConverted) {};
-	BonusParams(std::string deprecatedTypeStr, std::string deprecatedSubtypeStr = "", int deprecatedSubtype = 0);
-	const JsonNode & toJson();
-	CSelector toSelector();
-private:
-	JsonNode ret;
-	bool jsonCreated = false;
-};
-
-class DLL_LINKAGE BonusList
-{
-public:
-	using TInternalContainer = std::vector<std::shared_ptr<Bonus>>;
-
-private:
-	TInternalContainer bonuses;
-	bool belongsToTree;
-	void changed() const;
-
-public:
-	using const_reference = TInternalContainer::const_reference;
-	using value_type = TInternalContainer::value_type;
-
-	using const_iterator = TInternalContainer::const_iterator;
-	using iterator = TInternalContainer::iterator;
-
-	BonusList(bool BelongsToTree = false);
-	BonusList(const BonusList &bonusList);
-	BonusList(BonusList && other) noexcept;
-	BonusList& operator=(const BonusList &bonusList);
-
-	// wrapper functions of the STL vector container
-	TInternalContainer::size_type size() const { return bonuses.size(); }
-	void push_back(const std::shared_ptr<Bonus> & x);
-	TInternalContainer::iterator erase (const int position);
-	void clear();
-	bool empty() const { return bonuses.empty(); }
-	void resize(TInternalContainer::size_type sz, const std::shared_ptr<Bonus> & c = nullptr);
-	void reserve(TInternalContainer::size_type sz);
-	TInternalContainer::size_type capacity() const { return bonuses.capacity(); }
-	STRONG_INLINE std::shared_ptr<Bonus> &operator[] (TInternalContainer::size_type n) { return bonuses[n]; }
-	STRONG_INLINE const std::shared_ptr<Bonus> &operator[] (TInternalContainer::size_type n) const { return bonuses[n]; }
-	std::shared_ptr<Bonus> &back() { return bonuses.back(); }
-	std::shared_ptr<Bonus> &front() { return bonuses.front(); }
-	const std::shared_ptr<Bonus> &back() const { return bonuses.back(); }
-	const std::shared_ptr<Bonus> &front() const { return bonuses.front(); }
-
-	// There should be no non-const access to provide solid,robust bonus caching
-	TInternalContainer::const_iterator begin() const { return bonuses.begin(); }
-	TInternalContainer::const_iterator end() const { return bonuses.end(); }
-	TInternalContainer::size_type operator-=(const std::shared_ptr<Bonus> & i);
-
-	// BonusList functions
-	void stackBonuses();
-	int totalValue() const;
-	void getBonuses(BonusList &out, const CSelector &selector, const CSelector &limit = nullptr) const;
-	void getAllBonuses(BonusList &out) const;
-
-	//special find functions
-	std::shared_ptr<Bonus> getFirst(const CSelector &select);
-	std::shared_ptr<const Bonus> getFirst(const CSelector &select) const;
-	int valOfBonuses(const CSelector &select) const;
-
-	// conversion / output
-	JsonNode toJsonNode() const;
-
-	// remove_if implementation for STL vector types
-	template <class Predicate>
-	void remove_if(Predicate pred)
-	{
-		BonusList newList;
-		for(const auto & b : bonuses)
-		{
-			if (!pred(b.get()))
-				newList.push_back(b);
-		}
-		bonuses.clear();
-		bonuses.resize(newList.size());
-		std::copy(newList.begin(), newList.end(), bonuses.begin());
-	}
-
-	template <class InputIterator>
-	void insert(const int position, InputIterator first, InputIterator last);
-	void insert(TInternalContainer::iterator position, TInternalContainer::size_type n, const std::shared_ptr<Bonus> & x);
-
-	template <typename Handler>
-	void serialize(Handler &h, const int version)
-	{
-		h & static_cast<TInternalContainer&>(bonuses);
-	}
-
-	// C++ for range support
-	auto begin () -> decltype (bonuses.begin())
-	{
-		return bonuses.begin();
-	}
-
-	auto end () -> decltype (bonuses.end())
-	{
-		return bonuses.end();
-	}
-};
-
-// Extensions for BOOST_FOREACH to enable iterating of BonusList objects
-// Don't touch/call this functions
-inline BonusList::iterator range_begin(BonusList & x)
-{
-	return x.begin();
-}
-
-inline BonusList::iterator range_end(BonusList & x)
-{
-	return x.end();
-}
-
-inline BonusList::const_iterator range_begin(const BonusList & x)
-{
-	return x.begin();
-}
-
-inline BonusList::const_iterator range_end(const BonusList & x)
-{
-	return x.end();
-}
-
-DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const BonusList &bonusList);
-
-struct BonusLimitationContext
-{
-	std::shared_ptr<const Bonus> b;
-	const CBonusSystemNode & node;
-	const BonusList & alreadyAccepted;
-	const BonusList & stillUndecided;
-};
-
-class DLL_LINKAGE ILimiter
-{
-public:
-	enum class EDecision : uint8_t {ACCEPT, DISCARD, NOT_SURE};
-
-	virtual ~ILimiter() = default;
-
-	virtual EDecision limit(const BonusLimitationContext &context) const; //0 - accept bonus; 1 - drop bonus; 2 - delay (drops eventually)
-	virtual std::string toString() const;
-	virtual JsonNode toJsonNode() const;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-	}
-};
-
-class DLL_LINKAGE IBonusBearer
-{
-private:
-	static CSelector anaffectedByMoraleSelector;
-	CCheckProxy anaffectedByMorale;
-	static CSelector moraleSelector;
-	CTotalsProxy moraleValue;
-	static CSelector luckSelector;
-	CTotalsProxy luckValue;
-
-public:
-	//new bonusing node interface
-	// * selector is predicate that tests if HeroBonus matches our criteria
-	// * root is node on which call was made (nullptr will be replaced with this)
-	//interface
-	IBonusBearer();
-	virtual ~IBonusBearer() = default;
-	virtual TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const = 0;
-	int valOfBonuses(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;
-
-	std::shared_ptr<const Bonus> getBonus(const CSelector &selector) const; //returns any bonus visible on node that matches (or nullptr if none matches)
-
-	//legacy interface
-	int valOfBonuses(Bonus::BonusType type, const CSelector &selector) const;
-	int valOfBonuses(Bonus::BonusType type, int subtype = -1) const; //subtype -> subtype of bonus, if -1 then anyt;
-	bool hasBonusOfType(Bonus::BonusType type, int subtype = -1) const;//determines if hero has a bonus of given type (and optionally subtype)
-	bool hasBonusFrom(Bonus::BonusSource source, ui32 sourceID) const;
-
-	//various hlp functions for non-trivial values
-	//used for stacks and creatures only
-
-	virtual int getMinDamage(bool ranged) const;
-	virtual int getMaxDamage(bool ranged) const;
-	virtual int getAttack(bool ranged) const;
-	virtual int getDefense(bool ranged) const;
-
-	int MoraleVal() const; //range [-3, +3]
-	int LuckVal() const; //range [-3, +3]
-	/**
-	 Returns total value of all morale bonuses and sets bonusList as a pointer to the list of selected bonuses.
-	 @param bonusList is the out param it's list of all selected bonuses
-	 @return total value of all morale in the range [-3, +3] and 0 otherwise
-	*/
-	int MoraleValAndBonusList(TConstBonusListPtr & bonusList) const;
-	int LuckValAndBonusList(TConstBonusListPtr & bonusList) const;
-
-	ui32 MaxHealth() const; //get max HP of stack with all modifiers
-	bool isLiving() const; //non-undead, non-non living or alive
-	virtual si32 magicResistance() const;
-	ui32 Speed(int turn = 0, bool useBind = false) const; //get speed of creature with all modificators
-
-	int getPrimSkillLevel(PrimarySkill::PrimarySkill id) const;
-
-	virtual int64_t getTreeVersion() const = 0;
-};
-
-class DLL_LINKAGE CBonusSystemNode : public virtual IBonusBearer, public boost::noncopyable
-{
-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
-	};
-private:
-	BonusList bonuses; //wielded bonuses (local or up-propagated here)
-	BonusList exportedBonuses; //bonuses coming from this node (wielded or propagated away)
-
-	TNodesVector parents; //parents -> we inherit bonuses from them, we may attach our bonuses to them
-	TNodesVector children;
-
-	ENodeTypes nodeType;
-	std::string description;
-	bool isHypotheticNode;
-
-	static const bool cachingEnabled;
-	mutable BonusList cachedBonuses;
-	mutable int64_t cachedLast;
-	static std::atomic<int64_t> treeChanged;
-
-	// Setting a value to cachingStr before getting any bonuses caches the result for later requests.
-	// This string needs to be unique, that's why it has to be setted in the following manner:
-	// [property key]_[value] => only for selector
-	mutable std::map<std::string, TBonusListPtr > cachedRequests;
-	mutable boost::mutex sync;
-
-	void getAllBonusesRec(BonusList &out, const CSelector & selector) const;
-	TConstBonusListPtr getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr) const;
-	std::shared_ptr<Bonus> getUpdatedBonus(const std::shared_ptr<Bonus> & b, const TUpdaterPtr & updater) const;
-
-public:
-	explicit CBonusSystemNode();
-	explicit CBonusSystemNode(bool isHypotetic);
-	explicit CBonusSystemNode(ENodeTypes NodeType);
-	CBonusSystemNode(CBonusSystemNode && other) noexcept;
-	virtual ~CBonusSystemNode();
-
-	void limitBonuses(const BonusList &allBonuses, BonusList &out) const; //out will bo populed with bonuses that are not limited here
-	TBonusListPtr limitBonuses(const BonusList &allBonuses) const; //same as above, returns out by val for convienence
-	TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override;
-	void getParents(TCNodes &out) const;  //retrieves list of parent nodes (nodes to inherit bonuses from),
-	std::shared_ptr<const Bonus> getBonusLocalFirst(const CSelector & selector) const;
-
-	//non-const interface
-	void getParents(TNodes &out);  //retrieves list of parent nodes (nodes to inherit bonuses from)
-
-	void getRedParents(TNodes &out);  //retrieves list of red parent nodes (nodes bonuses propagate from)
-	void getRedAncestors(TNodes &out);
-	void getRedChildren(TNodes &out);
-	void getAllParents(TCNodes & out) const;
-	static PlayerColor retrieveNodeOwner(const CBonusSystemNode * node);
-	std::shared_ptr<Bonus> getBonusLocalFirst(const CSelector & selector);
-
-	void attachTo(CBonusSystemNode & parent);
-	void detachFrom(CBonusSystemNode & parent);
-	void detachFromAll();
-	virtual void addNewBonus(const std::shared_ptr<Bonus>& b);
-	void accumulateBonus(const std::shared_ptr<Bonus>& b); //add value of bonus with same type/subtype or create new
-
-	void newChildAttached(CBonusSystemNode & child);
-	void childDetached(CBonusSystemNode & child);
-	void propagateBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & source);
-	void unpropagateBonus(const std::shared_ptr<Bonus> & b);
-	void removeBonus(const std::shared_ptr<Bonus>& b);
-	void removeBonuses(const CSelector & selector);
-	void removeBonusesRecursive(const CSelector & s);
-	void newRedDescendant(CBonusSystemNode & descendant); //propagation needed
-	void removedRedDescendant(CBonusSystemNode & descendant); //de-propagation needed
-
-	bool isIndependentNode() const; //node is independent when it has no parents nor children
-	bool actsAsBonusSourceOnly() const;
-	///updates count of remaining turns and removes outdated bonuses by selector
-	void reduceBonusDurations(const CSelector &s);
-	virtual std::string bonusToString(const std::shared_ptr<Bonus>& bonus, bool description) const {return "";}; //description or bonus name
-	virtual std::string nodeName() const;
-	virtual std::string nodeShortInfo() const;
-	bool isHypothetic() const { return isHypotheticNode; }
-
-	void deserializationFix();
-	void exportBonus(const std::shared_ptr<Bonus> & b);
-	void exportBonuses();
-
-	const BonusList &getBonusList() const;
-	BonusList & getExportedBonusList();
-	const BonusList & getExportedBonusList() const;
-	CBonusSystemNode::ENodeTypes getNodeType() const;
-	void setNodeType(CBonusSystemNode::ENodeTypes type);
-	const TNodesVector &getParentNodes() const;
-	const TNodesVector &getChildrenNodes() const;
-	const std::string &getDescription() const;
-	void setDescription(const std::string &description);
-
-	static void treeHasChanged();
-
-	int64_t getTreeVersion() const override;
-
-	virtual PlayerColor getOwner() const
-	{
-		return PlayerColor::NEUTRAL;
-	}
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-//		h & bonuses;
-		h & nodeType;
-		h & exportedBonuses;
-		h & description;
-		BONUS_TREE_DESERIALIZATION_FIX
-		//h & parents & children;
-	}
-
-	friend class CBonusProxy;
-};
-
-class DLL_LINKAGE IPropagator
-{
-public:
-	virtual ~IPropagator() = default;
-	virtual bool shouldBeAttached(CBonusSystemNode *dest);
-	virtual CBonusSystemNode::ENodeTypes getPropagatorType() const;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{}
-};
-
-class DLL_LINKAGE CPropagatorNodeType : public IPropagator
-{
-	CBonusSystemNode::ENodeTypes nodeType;
-
-public:
-	CPropagatorNodeType(CBonusSystemNode::ENodeTypes NodeType = CBonusSystemNode::ENodeTypes::UNKNOWN);
-	bool shouldBeAttached(CBonusSystemNode *dest) override;
-	CBonusSystemNode::ENodeTypes getPropagatorType() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & nodeType;
-	}
-};
-
-template<typename T>
-class CSelectFieldEqual
-{
-	T Bonus::*ptr;
-
-public:
-	CSelectFieldEqual(T Bonus::*Ptr)
-		: ptr(Ptr)
-	{
-	}
-
-	CSelector operator()(const T &valueToCompareAgainst) const
-	{
-		auto ptr2 = ptr; //We need a COPY because we don't want to reference this (might be outlived by lambda)
-		return [ptr2, valueToCompareAgainst](const Bonus *bonus)
-		{
-			return bonus->*ptr2 == valueToCompareAgainst;
-		};
-	}
-};
-
-template<typename T> //can be same, needed for subtype field
-class CSelectFieldEqualOrEvery
-{
-	T Bonus::*ptr;
-	T val;
-public:
-	CSelectFieldEqualOrEvery(T Bonus::*Ptr, const T &Val)
-		: ptr(Ptr), val(Val)
-	{
-	}
-	bool operator()(const Bonus *bonus) const
-	{
-		return (bonus->*ptr == val) || (bonus->*ptr == static_cast<T>(Bonus::EVERY_TYPE));
-	}
-	CSelectFieldEqualOrEvery& operator()(const T &setVal)
-	{
-		val = setVal;
-		return *this;
-	}
-};
-
-class DLL_LINKAGE CWillLastTurns
-{
-public:
-	int turnsRequested;
-
-	bool operator()(const Bonus *bonus) const
-	{
-		return turnsRequested <= 0					//every present effect will last zero (or "less") turns
-			|| !Bonus::NTurns(bonus) //so do every not expriing after N-turns effect
-			|| bonus->turnsRemain > turnsRequested;
-	}
-	CWillLastTurns& operator()(const int &setVal)
-	{
-		turnsRequested = setVal;
-		return *this;
-	}
-};
-
-class DLL_LINKAGE CWillLastDays
-{
-public:
-	int daysRequested;
-
-	bool operator()(const Bonus *bonus) const
-	{
-		if(daysRequested <= 0 || Bonus::Permanent(bonus) || Bonus::OneBattle(bonus))
-			return true;
-		else if(Bonus::OneDay(bonus))
-			return false;
-		else if(Bonus::NDays(bonus) || Bonus::OneWeek(bonus))
-		{
-			return bonus->turnsRemain > daysRequested;
-		}
-
-		return false; // TODO: ONE_WEEK need support for turnsRemain, but for now we'll exclude all unhandled durations
-	}
-	CWillLastDays& operator()(const int &setVal)
-	{
-		daysRequested = setVal;
-		return *this;
-	}
-};
-
-class DLL_LINKAGE AggregateLimiter : public ILimiter
-{
-protected:
-	std::vector<TLimiterPtr> limiters;
-	virtual const std::string & getAggregator() const = 0;
-	AggregateLimiter(std::vector<TLimiterPtr> limiters = {});
-public:
-	void add(const TLimiterPtr & limiter);
-	JsonNode toJsonNode() const override;
-
-	template <typename Handler> void serialize(Handler & h, const int version)
-	{
-		h & static_cast<ILimiter&>(*this);
-		h & limiters;
-	}
-};
-
-class DLL_LINKAGE AllOfLimiter : public AggregateLimiter
-{
-protected:
-	const std::string & getAggregator() const override;
-public:
-	AllOfLimiter(std::vector<TLimiterPtr> limiters = {});
-	static const std::string aggregator;
-	EDecision limit(const BonusLimitationContext & context) const override;
-};
-
-class DLL_LINKAGE AnyOfLimiter : public AggregateLimiter
-{
-protected:
-	const std::string & getAggregator() const override;
-public:
-	AnyOfLimiter(std::vector<TLimiterPtr> limiters = {});
-	static const std::string aggregator;
-	EDecision limit(const BonusLimitationContext & context) const override;
-};
-
-class DLL_LINKAGE NoneOfLimiter : public AggregateLimiter
-{
-protected:
-	const std::string & getAggregator() const override;
-public:
-	NoneOfLimiter(std::vector<TLimiterPtr> limiters = {});
-	static const std::string aggregator;
-	EDecision limit(const BonusLimitationContext & context) const override;
-};
-
-class DLL_LINKAGE CCreatureTypeLimiter : public ILimiter //affect only stacks of given creature (and optionally it's upgrades)
-{
-public:
-	const CCreature * creature = nullptr;
-	bool includeUpgrades = false;
-
-	CCreatureTypeLimiter() = default;
-	CCreatureTypeLimiter(const CCreature & creature_, bool IncludeUpgrades);
-	void setCreature(const CreatureID & id);
-
-	EDecision limit(const BonusLimitationContext &context) const override;
-	std::string toString() const override;
-	JsonNode toJsonNode() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<ILimiter&>(*this);
-		h & creature;
-		h & includeUpgrades;
-	}
-};
-
-class DLL_LINKAGE HasAnotherBonusLimiter : public ILimiter //applies only to nodes that have another bonus working
-{
-public:
-	Bonus::BonusType type;
-	TBonusSubtype subtype;
-	Bonus::BonusSource source;
-	si32 sid;
-	bool isSubtypeRelevant; //check for subtype only if this is true
-	bool isSourceRelevant; //check for bonus source only if this is true
-	bool isSourceIDRelevant; //check for bonus source only if this is true
-
-	HasAnotherBonusLimiter(Bonus::BonusType bonus = Bonus::NONE);
-	HasAnotherBonusLimiter(Bonus::BonusType bonus, TBonusSubtype _subtype);
-	HasAnotherBonusLimiter(Bonus::BonusType bonus, Bonus::BonusSource src);
-	HasAnotherBonusLimiter(Bonus::BonusType bonus, TBonusSubtype _subtype, Bonus::BonusSource src);
-
-	EDecision limit(const BonusLimitationContext &context) const override;
-	std::string toString() const override;
-	JsonNode toJsonNode() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<ILimiter&>(*this);
-		h & type;
-		h & subtype;
-		h & isSubtypeRelevant;
-		h & source;
-		h & isSourceRelevant;
-		h & sid;
-		h & isSourceIDRelevant;
-	}
-};
-
-class DLL_LINKAGE CreatureTerrainLimiter : public ILimiter //applies only to creatures that are on specified terrain, default native terrain
-{
-public:
-	TerrainId terrainType;
-	CreatureTerrainLimiter();
-	CreatureTerrainLimiter(TerrainId terrain);
-
-	EDecision limit(const BonusLimitationContext &context) const override;
-	std::string toString() const override;
-	JsonNode toJsonNode() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<ILimiter&>(*this);
-		h & terrainType;
-	}
-};
-
-class DLL_LINKAGE CreatureLevelLimiter : public ILimiter //applies only to creatures of given faction
-{
-public:
-	uint32_t minLevel;
-	uint32_t maxLevel;
-	//accept all levels by default, accept creatures of minLevel <= creature->getLevel() < maxLevel
-	CreatureLevelLimiter(uint32_t minLevel = std::numeric_limits<uint32_t>::min(), uint32_t maxLevel = std::numeric_limits<uint32_t>::max());
-
-	EDecision limit(const BonusLimitationContext &context) const override;
-	std::string toString() const override;
-	JsonNode toJsonNode() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<ILimiter&>(*this);
-		h & minLevel;
-		h & maxLevel;
-	}
-};
-
-class DLL_LINKAGE FactionLimiter : public ILimiter //applies only to creatures of given faction
-{
-public:
-	FactionID faction;
-	FactionLimiter(FactionID faction = FactionID::DEFAULT);
-
-	EDecision limit(const BonusLimitationContext &context) const override;
-	std::string toString() const override;
-	JsonNode toJsonNode() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<ILimiter&>(*this);
-		h & faction;
-	}
-};
-
-class DLL_LINKAGE CreatureAlignmentLimiter : public ILimiter //applies only to creatures of given alignment
-{
-public:
-	EAlignment alignment;
-	CreatureAlignmentLimiter(EAlignment Alignment = EAlignment::NEUTRAL);
-
-	EDecision limit(const BonusLimitationContext &context) const override;
-	std::string toString() const override;
-	JsonNode toJsonNode() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<ILimiter&>(*this);
-		h & alignment;
-	}
-};
-
-class DLL_LINKAGE OppositeSideLimiter : public ILimiter //applies only to creatures of enemy army during combat
-{
-public:
-	PlayerColor owner;
-	OppositeSideLimiter(PlayerColor Owner = PlayerColor::CANNOT_DETERMINE);
-
-	EDecision limit(const BonusLimitationContext &context) const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<ILimiter&>(*this);
-		h & owner;
-	}
-};
-
-class DLL_LINKAGE RankRangeLimiter : public ILimiter //applies to creatures with min <= Rank <= max
-{
-public:
-	ui8 minRank, maxRank;
-
-	RankRangeLimiter();
-	RankRangeLimiter(ui8 Min, ui8 Max = 255);
-	EDecision limit(const BonusLimitationContext &context) const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<ILimiter&>(*this);
-		h & minRank;
-		h & maxRank;
-	}
-};
-
-class DLL_LINKAGE UnitOnHexLimiter : public ILimiter //works only on selected hexes
-{
-public:
-	std::set<BattleHex> applicableHexes;
-
-	UnitOnHexLimiter(const std::set<BattleHex> & applicableHexes = {});
-	EDecision limit(const BonusLimitationContext &context) const override;
-	JsonNode toJsonNode() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<ILimiter&>(*this);
-		h & applicableHexes;
-	}
-};
-
-namespace Selector
-{
-	extern DLL_LINKAGE CSelectFieldEqual<Bonus::BonusType> & type();
-	extern DLL_LINKAGE CSelectFieldEqual<TBonusSubtype> & subtype();
-	extern DLL_LINKAGE CSelectFieldEqual<CAddInfo> & info();
-	extern DLL_LINKAGE CSelectFieldEqual<Bonus::BonusSource> & sourceType();
-	extern DLL_LINKAGE CSelectFieldEqual<Bonus::BonusSource> & targetSourceType();
-	extern DLL_LINKAGE CSelectFieldEqual<Bonus::LimitEffect> & effectRange();
-	extern DLL_LINKAGE CWillLastTurns turns;
-	extern DLL_LINKAGE CWillLastDays days;
-
-	CSelector DLL_LINKAGE typeSubtype(Bonus::BonusType Type, TBonusSubtype Subtype);
-	CSelector DLL_LINKAGE typeSubtypeInfo(Bonus::BonusType type, TBonusSubtype subtype, const CAddInfo & info);
-	CSelector DLL_LINKAGE source(Bonus::BonusSource source, ui32 sourceID);
-	CSelector DLL_LINKAGE sourceTypeSel(Bonus::BonusSource source);
-	CSelector DLL_LINKAGE valueType(Bonus::ValueType valType);
-
-	/**
-	 * Selects all bonuses
-	 * Usage example: Selector::all.And(<functor>).And(<functor>)...)
-	 */
-	extern DLL_LINKAGE CSelector all;
-
-	/**
-	 * Selects nothing
-	 * Usage example: Selector::none.Or(<functor>).Or(<functor>)...)
-	 */
-	extern DLL_LINKAGE CSelector none;
-}
-
-extern DLL_LINKAGE const std::map<std::string, Bonus::BonusType> bonusNameMap;
-extern DLL_LINKAGE const std::map<std::string, Bonus::ValueType> bonusValueMap;
-extern DLL_LINKAGE const std::map<std::string, Bonus::BonusSource> bonusSourceMap;
-extern DLL_LINKAGE const std::map<std::string, ui16> bonusDurationMap;
-extern DLL_LINKAGE const std::map<std::string, Bonus::LimitEffect> bonusLimitEffect;
-extern DLL_LINKAGE const std::map<std::string, TLimiterPtr> bonusLimiterMap;
-extern DLL_LINKAGE const std::map<std::string, TPropagatorPtr> bonusPropagatorMap;
-extern DLL_LINKAGE const std::map<std::string, TUpdaterPtr> bonusUpdaterMap;
-extern DLL_LINKAGE const std::set<std::string> deprecatedBonusSet;
-
-// BonusList template that requires full interface of CBonusSystemNode
-template <class InputIterator>
-void BonusList::insert(const int position, InputIterator first, InputIterator last)
-{
-	bonuses.insert(bonuses.begin() + position, first, last);
-	changed();
-}
-
-// observers for updating bonuses based on certain events (e.g. hero gaining level)
-
-class DLL_LINKAGE IUpdater
-{
-public:
-	virtual ~IUpdater() = default;
-
-	virtual std::shared_ptr<Bonus> createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const;
-	virtual std::string toString() const;
-	virtual JsonNode toJsonNode() const;
-
-	template <typename Handler> void serialize(Handler & h, const int version)
-	{
-	}
-};
-
-class DLL_LINKAGE GrowsWithLevelUpdater : public IUpdater
-{
-public:
-	int valPer20 = 0;
-	int stepSize = 1;
-
-	GrowsWithLevelUpdater() = default;
-	GrowsWithLevelUpdater(int valPer20, int stepSize = 1);
-
-	template <typename Handler> void serialize(Handler & h, const int version)
-	{
-		h & static_cast<IUpdater &>(*this);
-		h & valPer20;
-		h & stepSize;
-	}
-
-	std::shared_ptr<Bonus> createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const override;
-	virtual std::string toString() const override;
-	virtual JsonNode toJsonNode() const override;
-};
-
-class DLL_LINKAGE TimesHeroLevelUpdater : public IUpdater
-{
-public:
-	template <typename Handler> void serialize(Handler & h, const int version)
-	{
-		h & static_cast<IUpdater &>(*this);
-	}
-
-	std::shared_ptr<Bonus> createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const override;
-	virtual std::string toString() const override;
-	virtual JsonNode toJsonNode() const override;
-};
-
-class DLL_LINKAGE TimesStackLevelUpdater : public IUpdater
-{
-public:
-	template <typename Handler> void serialize(Handler & h, const int version)
-	{
-		h & static_cast<IUpdater &>(*this);
-	}
-
-	std::shared_ptr<Bonus> createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const override;
-	virtual std::string toString() const override;
-	virtual JsonNode toJsonNode() const override;
-};
-
-class DLL_LINKAGE ArmyMovementUpdater : public IUpdater
-{
-public:
-	si32 base;
-	si32 divider;
-	si32 multiplier;
-	si32 max;
-	ArmyMovementUpdater();
-	ArmyMovementUpdater(int base, int divider, int multiplier, int max);
-	template <typename Handler> void serialize(Handler & h, const int version)
-	{
-		h & static_cast<IUpdater &>(*this);
-		h & base;
-		h & divider;
-		h & multiplier;
-		h & max;
-	}
-
-	std::shared_ptr<Bonus> createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const override;
-	virtual std::string toString() const override;
-	virtual JsonNode toJsonNode() const override;
-};
-
-class DLL_LINKAGE OwnerUpdater : public IUpdater
-{
-public:
-	template <typename Handler> void serialize(Handler& h, const int version)
-	{
-		h & static_cast<IUpdater &>(*this);
-	}
-
-	std::shared_ptr<Bonus> createUpdatedBonus(const std::shared_ptr<Bonus>& b, const CBonusSystemNode& context) const override;
-	virtual std::string toString() const override;
-	virtual JsonNode toJsonNode() const override;
-};
-
-VCMI_LIB_NAMESPACE_END

+ 4 - 0
lib/IGameCallback.cpp

@@ -18,6 +18,10 @@
 #include "CModHandler.h"
 #include "BattleFieldHandler.h"
 #include "ObstacleHandler.h"
+#include "bonuses/CBonusSystemNode.h"
+#include "bonuses/Limiters.h"
+#include "bonuses/Propagators.h"
+#include "bonuses/Updaters.h"
 
 #include "serializer/CSerializer.h" // for SAVEGAME_MAGIC
 #include "serializer/BinaryDeserializer.h"

+ 5 - 1
lib/JsonNode.cpp

@@ -13,7 +13,11 @@
 
 #include "ScopeGuard.h"
 
-#include "HeroBonus.h"
+#include "bonuses/BonusParams.h"
+#include "bonuses/Bonus.h"
+#include "bonuses/Limiters.h"
+#include "bonuses/Propagators.h"
+#include "bonuses/Updaters.h"
 #include "filesystem/Filesystem.h"
 #include "VCMI_Lib.h" //for identifier resolution
 #include "CModHandler.h"

+ 1 - 1
lib/VCMI_lib.cbp

@@ -245,7 +245,7 @@
 		<Unit filename="GameConstants.cpp" />
 		<Unit filename="GameConstants.h" />
 		<Unit filename="HeroBonus.cpp" />
-		<Unit filename="HeroBonus.h" />
+		<Unit filename="Bonus.h" />
 		<Unit filename="IBonusTypeHandler.h" />
 		<Unit filename="IGameCallback.cpp" />
 		<Unit filename="IGameCallback.h" />

+ 1 - 1
lib/VCMI_lib.vcxproj

@@ -426,7 +426,7 @@
     <ClInclude Include="logging\CLogger.h" />
     <ClInclude Include="logging\CBasicLogConfigurator.h" />
     <ClInclude Include="GameConstants.h" />
-    <ClInclude Include="HeroBonus.h" />
+    <ClInclude Include="Bonus.h" />
     <ClInclude Include="IGameCallback.h" />
     <ClInclude Include="CGameInfoCallback.h" />
     <ClInclude Include="IGameEventsReceiver.h" />

+ 1 - 1
lib/VCMI_lib.vcxproj.filters

@@ -407,7 +407,7 @@
     <ClInclude Include="CondSh.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="HeroBonus.h">
+    <ClInclude Include="Bonus.h">
       <Filter>Header Files</Filter>
     </ClInclude>
     <ClInclude Include="IGameCallback.h">

+ 2 - 0
lib/battle/BattleInfo.cpp

@@ -9,6 +9,8 @@
  */
 #include "StdInc.h"
 #include "BattleInfo.h"
+#include "bonuses/Limiters.h"
+#include "bonuses/Updaters.h"
 #include "../CStack.h"
 #include "../CHeroHandler.h"
 #include "../NetPacks.h"

+ 2 - 1
lib/battle/BattleInfo.h

@@ -9,7 +9,8 @@
  */
 #pragma once
 #include "../int3.h"
-#include "../HeroBonus.h"
+#include "../bonuses/Bonus.h"
+#include "../bonuses/CBonusSystemNode.h"
 #include "CBattleInfoCallback.h"
 #include "IBattleState.h"
 #include "SiegeInfo.h"

+ 2 - 2
lib/battle/CBattleInfoCallback.cpp

@@ -247,7 +247,7 @@ std::vector<PossiblePlayerBattleAction> CBattleInfoCallback::getClientActionsFor
 		allowedActionList.push_back(PossiblePlayerBattleAction::ATTACK); //all active stacks can attack
 		allowedActionList.push_back(PossiblePlayerBattleAction::WALK_AND_ATTACK); //not all stacks can always walk, but we will check this elsewhere
 
-		if(stack->canMove() && stack->Speed(0, true)) //probably no reason to try move war machines or bound stacks
+		if(stack->canMove() && stack->speed(0, true)) //probably no reason to try move war machines or bound stacks
 			allowedActionList.push_back(PossiblePlayerBattleAction::MOVE_STACK);
 
 		const auto * siegedTown = battleGetDefendedTown();
@@ -566,7 +566,7 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const Reacha
 	if(!unit->getPosition().isValid()) //turrets
 		return ret;
 
-	auto unitSpeed = unit->Speed(0, true);
+	auto unitSpeed = unit->speed(0, true);
 
 	const bool tacticsPhase = battleTacticDist() && battleGetTacticsSide() == unit->unitSide();
 

+ 6 - 6
lib/battle/CUnitState.cpp

@@ -179,7 +179,7 @@ void CHealth::init()
 {
 	reset();
 	fullUnits = owner->unitBaseAmount() > 1 ? owner->unitBaseAmount() - 1 : 0;
-	firstHPleft = owner->unitBaseAmount() > 0 ? owner->MaxHealth() : 0;
+	firstHPleft = owner->unitBaseAmount() > 0 ? owner->getMaxHealth() : 0;
 }
 
 void CHealth::addResurrected(int32_t amount)
@@ -190,12 +190,12 @@ void CHealth::addResurrected(int32_t amount)
 
 int64_t CHealth::available() const
 {
-	return static_cast<int64_t>(firstHPleft) + owner->MaxHealth() * fullUnits;
+	return static_cast<int64_t>(firstHPleft) + owner->getMaxHealth() * fullUnits;
 }
 
 int64_t CHealth::total() const
 {
-	return static_cast<int64_t>(owner->MaxHealth()) * owner->unitBaseAmount();
+	return static_cast<int64_t>(owner->getMaxHealth()) * owner->unitBaseAmount();
 }
 
 void CHealth::damage(int64_t & amount)
@@ -230,7 +230,7 @@ void CHealth::damage(int64_t & amount)
 
 void CHealth::heal(int64_t & amount, EHealLevel level, EHealPower power)
 {
-	const int32_t unitHealth = owner->MaxHealth();
+	const int32_t unitHealth = owner->getMaxHealth();
 	const int32_t oldCount = getCount();
 
 	int64_t maxHeal = std::numeric_limits<int64_t>::max();
@@ -267,7 +267,7 @@ void CHealth::heal(int64_t & amount, EHealLevel level, EHealPower power)
 
 void CHealth::setFromTotal(const int64_t totalHealth)
 {
-	const int32_t unitHealth = owner->MaxHealth();
+	const int32_t unitHealth = owner->getMaxHealth();
 	firstHPleft = totalHealth % unitHealth;
 	fullUnits = static_cast<int32_t>(totalHealth / unitHealth);
 
@@ -306,7 +306,7 @@ void CHealth::takeResurrected()
 	{
 		int64_t totalHealth = available();
 
-		totalHealth -= resurrected * owner->MaxHealth();
+		totalHealth -= resurrected * owner->getMaxHealth();
 		vstd::amax(totalHealth, 0);
 		setFromTotal(totalHealth);
 		resurrected = 0;

+ 1 - 0
lib/battle/CUnitState.h

@@ -11,6 +11,7 @@
 #pragma once
 
 #include "Unit.h"
+#include "../bonuses/CBonusProxy.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 2 - 2
lib/battle/DamageCalculator.cpp

@@ -13,7 +13,7 @@
 #include "CBattleInfoCallback.h"
 #include "Unit.h"
 
-#include "../HeroBonus.h"
+#include "../bonuses/Bonus.h"
 #include "../mapObjects/CGTownInstance.h"
 #include "../spells/CSpellHandler.h"
 #include "../GameSettings.h"
@@ -464,7 +464,7 @@ int64_t DamageCalculator::getCasualties(int64_t damageDealt) const
 		return 0;
 
 	int64_t damageLeft = damageDealt - info.defender->getFirstHPleft();
-	int64_t killsLeft = damageLeft / info.defender->MaxHealth();
+	int64_t killsLeft = damageLeft / info.defender->getMaxHealth();
 
 	return 1 + killsLeft;
 }

+ 4 - 3
lib/battle/Unit.h

@@ -10,10 +10,11 @@
 
 #pragma once
 
-#include <vcmi/Entity.h>
+#include <vcmi/Creature.h>
 #include <vcmi/spells/Caster.h>
 
-#include "../HeroBonus.h"
+#include "../bonuses/Bonus.h"
+#include "../bonuses/IBonusBearer.h"
 
 #include "IUnitInfo.h"
 #include "BattleHex.h"
@@ -41,7 +42,7 @@ namespace BattlePhases
 
 class CUnitState;
 
-class DLL_LINKAGE Unit : public IUnitInfo, public spells::Caster, public virtual IBonusBearer, public IConstBonusNativeTerrainProvider
+class DLL_LINKAGE Unit : public IUnitInfo, public spells::Caster, public virtual IBonusBearer, public ACreature
 {
 public:
 	virtual ~Unit();

+ 418 - 0
lib/bonuses/Bonus.cpp

@@ -0,0 +1,418 @@
+/*
+ * Bonus.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "Bonus.h"
+#include "CBonusSystemNode.h"
+#include "Limiters.h"
+#include "Updaters.h"
+#include "Propagators.h"
+
+#include "../VCMI_Lib.h"
+#include "../spells/CSpellHandler.h"
+#include "../CCreatureHandler.h"
+#include "../CCreatureSet.h"
+#include "../CHeroHandler.h"
+#include "../CTownHandler.h"
+#include "../CGeneralTextHandler.h"
+#include "../CSkillHandler.h"
+#include "../CStack.h"
+#include "../CArtHandler.h"
+#include "../CModHandler.h"
+#include "../TerrainHandler.h"
+#include "../StringConstants.h"
+#include "../battle/BattleInfo.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+#define BONUS_NAME(x) { #x, Bonus::x },
+	const std::map<std::string, Bonus::BonusType> bonusNameMap = {
+		BONUS_LIST
+	};
+#undef BONUS_NAME
+
+#define BONUS_VALUE(x) { #x, Bonus::x },
+	const std::map<std::string, Bonus::ValueType> bonusValueMap = { BONUS_VALUE_LIST };
+#undef BONUS_VALUE
+
+#define BONUS_SOURCE(x) { #x, Bonus::x },
+	const std::map<std::string, Bonus::BonusSource> bonusSourceMap = { BONUS_SOURCE_LIST };
+#undef BONUS_SOURCE
+
+#define BONUS_ITEM(x) { #x, Bonus::x },
+
+const std::map<std::string, ui16> bonusDurationMap =
+{
+	BONUS_ITEM(PERMANENT)
+	BONUS_ITEM(ONE_BATTLE)
+	BONUS_ITEM(ONE_DAY)
+	BONUS_ITEM(ONE_WEEK)
+	BONUS_ITEM(N_TURNS)
+	BONUS_ITEM(N_DAYS)
+	BONUS_ITEM(UNTIL_BEING_ATTACKED)
+	BONUS_ITEM(UNTIL_ATTACK)
+	BONUS_ITEM(STACK_GETS_TURN)
+	BONUS_ITEM(COMMANDER_KILLED)
+	{ "UNITL_BEING_ATTACKED", Bonus::UNTIL_BEING_ATTACKED }//typo, but used in some mods
+};
+
+const std::map<std::string, Bonus::LimitEffect> bonusLimitEffect =
+{
+	BONUS_ITEM(NO_LIMIT)
+	BONUS_ITEM(ONLY_DISTANCE_FIGHT)
+	BONUS_ITEM(ONLY_MELEE_FIGHT)
+};
+
+const std::set<std::string> deprecatedBonusSet = {
+	"SECONDARY_SKILL_PREMY",
+	"SECONDARY_SKILL_VAL2",
+	"MAXED_SPELL",
+	"LAND_MOVEMENT",
+	"SEA_MOVEMENT",
+	"SIGHT_RADIOUS",
+	"NO_TYPE",
+	"SPECIAL_SECONDARY_SKILL",
+	"FULL_HP_REGENERATION",
+	"KING1",
+	"KING2",
+	"KING3",
+	"BLOCK_MORALE",
+	"BLOCK_LUCK",
+	"SELF_MORALE",
+	"SELF_LUCK",
+	"DIRECT_DAMAGE_IMMUNITY"
+};
+
+//This constructor should be placed here to avoid side effects
+CAddInfo::CAddInfo() = default;
+
+CAddInfo::CAddInfo(si32 value)
+{
+	if(value != CAddInfo::NONE)
+		push_back(value);
+}
+
+bool CAddInfo::operator==(si32 value) const
+{
+	switch(size())
+	{
+	case 0:
+		return value == CAddInfo::NONE;
+	case 1:
+		return operator[](0) == value;
+	default:
+		return false;
+	}
+}
+
+bool CAddInfo::operator!=(si32 value) const
+{
+	return !operator==(value);
+}
+
+si32 & CAddInfo::operator[](size_type pos)
+{
+	if(pos >= size())
+		resize(pos + 1, CAddInfo::NONE);
+	return vector::operator[](pos);
+}
+
+si32 CAddInfo::operator[](size_type pos) const
+{
+	return pos < size() ? vector::operator[](pos) : CAddInfo::NONE;
+}
+
+std::string CAddInfo::toString() const
+{
+	return toJsonNode().toJson(true);
+}
+
+JsonNode CAddInfo::toJsonNode() const
+{
+	if(size() < 2)
+	{
+		return JsonUtils::intNode(operator[](0));
+	}
+	else
+	{
+		JsonNode node(JsonNode::JsonType::DATA_VECTOR);
+		for(si32 value : *this)
+			node.Vector().push_back(JsonUtils::intNode(value));
+		return node;
+	}
+}
+std::string Bonus::Description(std::optional<si32> customValue) const
+{
+	std::ostringstream str;
+
+	if(description.empty())
+	{
+		if(stacking.empty() || stacking == "ALWAYS")
+		{
+			switch(source)
+			{
+			case ARTIFACT:
+				str << ArtifactID(sid).toArtifact(VLC->artifacts())->getNameTranslated();
+				break;
+			case SPELL_EFFECT:
+				str << SpellID(sid).toSpell(VLC->spells())->getNameTranslated();
+				break;
+			case CREATURE_ABILITY:
+				str << VLC->creh->objects[sid]->getNamePluralTranslated();
+				break;
+			case SECONDARY_SKILL:
+				str << VLC->skillh->getByIndex(sid)->getNameTranslated();
+				break;
+			case HERO_SPECIAL:
+				str << VLC->heroh->objects[sid]->getNameTranslated();
+				break;
+			default:
+				//todo: handle all possible sources
+				str << "Unknown";
+				break;
+			}
+		}
+		else
+			str << stacking;
+	}
+	else
+	{
+		str << description;
+	}
+
+	if(auto value = customValue.value_or(val))
+		str << " " << std::showpos << value;
+
+	return str.str();
+}
+
+JsonNode subtypeToJson(Bonus::BonusType type, int subtype)
+{
+	switch(type)
+	{
+	case Bonus::PRIMARY_SKILL:
+		return JsonUtils::stringNode("primSkill." + PrimarySkill::names[subtype]);
+	case Bonus::SPECIAL_SPELL_LEV:
+	case Bonus::SPECIFIC_SPELL_DAMAGE:
+	case Bonus::SPELL:
+	case Bonus::SPECIAL_PECULIAR_ENCHANT:
+	case Bonus::SPECIAL_ADD_VALUE_ENCHANT:
+	case Bonus::SPECIAL_FIXED_VALUE_ENCHANT:
+		return JsonUtils::stringNode(CModHandler::makeFullIdentifier("", "spell", SpellID::encode(subtype)));
+	case Bonus::IMPROVED_NECROMANCY:
+	case Bonus::SPECIAL_UPGRADE:
+		return JsonUtils::stringNode(CModHandler::makeFullIdentifier("", "creature", CreatureID::encode(subtype)));
+	case Bonus::GENERATE_RESOURCE:
+		return JsonUtils::stringNode("resource." + GameConstants::RESOURCE_NAMES[subtype]);
+	default:
+		return JsonUtils::intNode(subtype);
+	}
+}
+
+JsonNode additionalInfoToJson(Bonus::BonusType type, CAddInfo addInfo)
+{
+	switch(type)
+	{
+	case Bonus::SPECIAL_UPGRADE:
+		return JsonUtils::stringNode(CModHandler::makeFullIdentifier("", "creature", CreatureID::encode(addInfo[0])));
+	default:
+		return addInfo.toJsonNode();
+	}
+}
+
+JsonNode durationToJson(ui16 duration)
+{
+	std::vector<std::string> durationNames;
+	for(ui16 durBit = 1; durBit; durBit = durBit << 1)
+	{
+		if(duration & durBit)
+			durationNames.push_back(vstd::findKey(bonusDurationMap, durBit));
+	}
+	if(durationNames.size() == 1)
+	{
+		return JsonUtils::stringNode(durationNames[0]);
+	}
+	else
+	{
+		JsonNode node(JsonNode::JsonType::DATA_VECTOR);
+		for(const std::string & dur : durationNames)
+			node.Vector().push_back(JsonUtils::stringNode(dur));
+		return node;
+	}
+}
+
+JsonNode Bonus::toJsonNode() const
+{
+	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	// only add values that might reasonably be found in config files
+	root["type"].String() = vstd::findKey(bonusNameMap, type);
+	if(subtype != -1)
+		root["subtype"] = subtypeToJson(type, subtype);
+	if(additionalInfo != CAddInfo::NONE)
+		root["addInfo"] = additionalInfoToJson(type, additionalInfo);
+	if(duration != 0)
+	{
+		JsonNode durationVec(JsonNode::JsonType::DATA_VECTOR);
+		for(const auto & kv : bonusDurationMap)
+		{
+			if(duration & kv.second)
+				durationVec.Vector().push_back(JsonUtils::stringNode(kv.first));
+		}
+		root["duration"] = durationVec;
+	}
+	if(turnsRemain != 0)
+		root["turns"].Integer() = turnsRemain;
+	if(source != OTHER)
+		root["sourceType"].String() = vstd::findKey(bonusSourceMap, source);
+	if(targetSourceType != OTHER)
+		root["targetSourceType"].String() = vstd::findKey(bonusSourceMap, targetSourceType);
+	if(sid != 0)
+		root["sourceID"].Integer() = sid;
+	if(val != 0)
+		root["val"].Integer() = val;
+	if(valType != ADDITIVE_VALUE)
+		root["valueType"].String() = vstd::findKey(bonusValueMap, valType);
+	if(!stacking.empty())
+		root["stacking"].String() = stacking;
+	if(!description.empty())
+		root["description"].String() = description;
+	if(effectRange != NO_LIMIT)
+		root["effectRange"].String() = vstd::findKey(bonusLimitEffect, effectRange);
+	if(duration != PERMANENT)
+		root["duration"] = durationToJson(duration);
+	if(turnsRemain)
+		root["turns"].Integer() = turnsRemain;
+	if(limiter)
+		root["limiters"] = limiter->toJsonNode();
+	if(updater)
+		root["updater"] = updater->toJsonNode();
+	if(propagator)
+		root["propagator"].String() = vstd::findKey(bonusPropagatorMap, propagator);
+	return root;
+}
+
+std::string Bonus::nameForBonus() const
+{
+	switch(type)
+	{
+	case Bonus::PRIMARY_SKILL:
+		return PrimarySkill::names[subtype];
+	case Bonus::SPECIAL_SPELL_LEV:
+	case Bonus::SPECIFIC_SPELL_DAMAGE:
+	case Bonus::SPELL:
+	case Bonus::SPECIAL_PECULIAR_ENCHANT:
+	case Bonus::SPECIAL_ADD_VALUE_ENCHANT:
+	case Bonus::SPECIAL_FIXED_VALUE_ENCHANT:
+		return VLC->spells()->getByIndex(subtype)->getJsonKey();
+	case Bonus::SPECIAL_UPGRADE:
+		return CreatureID::encode(subtype) + "2" + CreatureID::encode(additionalInfo[0]);
+	case Bonus::GENERATE_RESOURCE:
+		return GameConstants::RESOURCE_NAMES[subtype];
+	case Bonus::STACKS_SPEED:
+		return "speed";
+	default:
+		return vstd::findKey(bonusNameMap, type);
+	}
+}
+
+Bonus::Bonus(Bonus::BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::string Desc, si32 Subtype):
+	duration(static_cast<ui16>(Duration)),
+	type(Type),
+	subtype(Subtype),
+	source(Src),
+	val(Val),
+	sid(ID),
+	description(std::move(Desc))
+{
+	boost::algorithm::trim(description);
+	targetSourceType = OTHER;
+}
+
+Bonus::Bonus(Bonus::BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, si32 Subtype, ValueType ValType):
+	duration(static_cast<ui16>(Duration)),
+	type(Type),
+	subtype(Subtype),
+	source(Src),
+	val(Val),
+	sid(ID),
+	valType(ValType)
+{
+	turnsRemain = 0;
+	effectRange = NO_LIMIT;
+	targetSourceType = OTHER;
+}
+
+std::shared_ptr<Bonus> Bonus::addPropagator(const TPropagatorPtr & Propagator)
+{
+	propagator = Propagator;
+	return this->shared_from_this();
+}
+
+DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const Bonus &bonus)
+{
+	for(const auto & i : bonusNameMap)
+	if(i.second == bonus.type)
+		out << "\tType: " << i.first << " \t";
+
+#define printField(field) out << "\t" #field ": " << (int)bonus.field << "\n"
+	printField(val);
+	printField(subtype);
+	printField(duration);
+	printField(source);
+	printField(sid);
+	if(bonus.additionalInfo != CAddInfo::NONE)
+		out << "\taddInfo: " << bonus.additionalInfo.toString() << "\n";
+	printField(turnsRemain);
+	printField(valType);
+	if(!bonus.stacking.empty())
+		out << "\tstacking: \"" << bonus.stacking << "\"\n";
+	printField(effectRange);
+#undef printField
+
+	if(bonus.limiter)
+		out << "\tLimiter: " << bonus.limiter->toString() << "\n";
+	if(bonus.updater)
+		out << "\tUpdater: " << bonus.updater->toString() << "\n";
+
+	return out;
+}
+
+std::shared_ptr<Bonus> Bonus::addLimiter(const TLimiterPtr & Limiter)
+{
+	if (limiter)
+	{
+		//If we already have limiter list, retrieve it
+		auto limiterList = std::dynamic_pointer_cast<AllOfLimiter>(limiter);
+		if(!limiterList)
+		{
+			//Create a new limiter list with old limiter and the new one will be pushed later
+			limiterList = std::make_shared<AllOfLimiter>();
+			limiterList->add(limiter);
+			limiter = limiterList;
+		}
+
+		limiterList->add(Limiter);
+	}
+	else
+	{
+		limiter = Limiter;
+	}
+	return this->shared_from_this();
+}
+
+// Updaters
+
+std::shared_ptr<Bonus> Bonus::addUpdater(const TUpdaterPtr & Updater)
+{
+	updater = Updater;
+	return this->shared_from_this();
+}
+
+VCMI_LIB_NAMESPACE_END

+ 430 - 0
lib/bonuses/Bonus.h

@@ -0,0 +1,430 @@
+/*
+ * Bonus.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../JsonNode.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+struct Bonus;
+class IBonusBearer;
+class CBonusSystemNode;
+class ILimiter;
+class IPropagator;
+class IUpdater;
+class BonusList;
+
+using TBonusSubtype = int32_t;
+using TBonusListPtr = std::shared_ptr<BonusList>;
+using TConstBonusListPtr = std::shared_ptr<const BonusList>;
+using TLimiterPtr = std::shared_ptr<ILimiter>;
+using TPropagatorPtr = std::shared_ptr<IPropagator>;
+using TUpdaterPtr = std::shared_ptr<IUpdater>;
+
+class DLL_LINKAGE CAddInfo : public std::vector<si32>
+{
+public:
+	enum { NONE = -1 };
+
+	CAddInfo();
+	CAddInfo(si32 value);
+
+	bool operator==(si32 value) const;
+	bool operator!=(si32 value) const;
+
+	si32 & operator[](size_type pos);
+	si32 operator[](size_type pos) const;
+
+	std::string toString() const;
+	JsonNode toJsonNode() const;
+};
+
+#define BONUS_TREE_DESERIALIZATION_FIX if(!h.saving && h.smartPointerSerialization) deserializationFix();
+
+#define BONUS_LIST										\
+	BONUS_NAME(NONE) 									\
+	BONUS_NAME(LEVEL_COUNTER) /* for commander artifacts*/ \
+	BONUS_NAME(MOVEMENT) /*Subtype is 1 - land, 0 - sea*/ \
+	BONUS_NAME(MORALE) \
+	BONUS_NAME(LUCK) \
+	BONUS_NAME(PRIMARY_SKILL) /*uses subtype to pick skill; additional info if set: 1 - only melee, 2 - only distance*/  \
+	BONUS_NAME(SIGHT_RADIUS) \
+	BONUS_NAME(MANA_REGENERATION) /*points per turn apart from normal (1 + mysticism)*/  \
+	BONUS_NAME(FULL_MANA_REGENERATION) /*all mana points are replenished every day*/  \
+	BONUS_NAME(NONEVIL_ALIGNMENT_MIX) /*good and neutral creatures can be mixed without morale penalty*/  \
+	BONUS_NAME(SURRENDER_DISCOUNT) /*%*/  \
+	BONUS_NAME(STACKS_SPEED)  /*additional info - percent of speed bonus applied after direct bonuses; >0 - added, <0 - subtracted to this part*/ \
+	BONUS_NAME(FLYING_MOVEMENT) /*value - penalty percentage*/ \
+	BONUS_NAME(SPELL_DURATION) \
+	BONUS_NAME(AIR_SPELL_DMG_PREMY) \
+	BONUS_NAME(EARTH_SPELL_DMG_PREMY) \
+	BONUS_NAME(FIRE_SPELL_DMG_PREMY) \
+	BONUS_NAME(WATER_SPELL_DMG_PREMY) \
+	BONUS_NAME(WATER_WALKING) /*value - penalty percentage*/ \
+	BONUS_NAME(NEGATE_ALL_NATURAL_IMMUNITIES) \
+	BONUS_NAME(STACK_HEALTH) \
+	BONUS_NAME(FIRE_SPELLS) \
+	BONUS_NAME(AIR_SPELLS) \
+	BONUS_NAME(WATER_SPELLS) \
+	BONUS_NAME(EARTH_SPELLS) \
+	BONUS_NAME(GENERATE_RESOURCE) /*daily value, uses subtype (resource type)*/  \
+	BONUS_NAME(CREATURE_GROWTH) /*for legion artifacts: value - week growth bonus, subtype - monster level if aplicable*/  \
+	BONUS_NAME(WHIRLPOOL_PROTECTION) /*hero won't lose army when teleporting through whirlpool*/  \
+	BONUS_NAME(SPELL) /*hero knows spell, val - skill level (0 - 3), subtype - spell id*/  \
+	BONUS_NAME(SPELLS_OF_LEVEL) /*hero knows all spells of given level, val - skill level; subtype - level*/  \
+	BONUS_NAME(BATTLE_NO_FLEEING) /*for shackles of war*/ \
+	BONUS_NAME(MAGIC_SCHOOL_SKILL) /* //eg. for magic plains terrain, subtype: school of magic (0 - all, 1 - fire, 2 - air, 4 - water, 8 - earth), value - level*/ \
+	BONUS_NAME(FREE_SHOOTING) /*stacks can shoot even if otherwise blocked (sharpshooter's bow effect)*/ \
+	BONUS_NAME(OPENING_BATTLE_SPELL) /*casts a spell at expert level at beginning of battle, val - spell power, subtype - spell id*/ \
+	BONUS_NAME(IMPROVED_NECROMANCY) /* raise more powerful creatures: subtype - creature type raised, addInfo - [required necromancy level, required stack level], val - necromancy level for this purpose */ \
+	BONUS_NAME(CREATURE_GROWTH_PERCENT) /*increases growth of all units in all towns, val - percentage*/ \
+	BONUS_NAME(FREE_SHIP_BOARDING) /*movement points preserved with ship boarding and landing*/  \
+	BONUS_NAME(FLYING)									\
+	BONUS_NAME(SHOOTER)									\
+	BONUS_NAME(CHARGE_IMMUNITY)							\
+	BONUS_NAME(ADDITIONAL_ATTACK)						\
+	BONUS_NAME(UNLIMITED_RETALIATIONS)					\
+	BONUS_NAME(NO_MELEE_PENALTY)						\
+	BONUS_NAME(JOUSTING) /*for champions*/				\
+	BONUS_NAME(HATE) /*eg. angels hate devils, subtype - ID of hated creature, val - damage bonus percent */ \
+	BONUS_NAME(KING) /* val - required slayer bonus val to affect */\
+	BONUS_NAME(MAGIC_RESISTANCE) /*in % (value)*/		\
+	BONUS_NAME(CHANGES_SPELL_COST_FOR_ALLY) /*in mana points (value) , eg. mage*/ \
+	BONUS_NAME(CHANGES_SPELL_COST_FOR_ENEMY) /*in mana points (value) , eg. pegasus */ \
+	BONUS_NAME(SPELL_AFTER_ATTACK) /* subtype - spell id, value - chance %, addInfo[0] - level, addInfo[1] -> [0 - all attacks, 1 - shot only, 2 - melee only] */ \
+	BONUS_NAME(SPELL_BEFORE_ATTACK) /* subtype - spell id, value - chance %, addInfo[0] - level, addInfo[1] -> [0 - all attacks, 1 - shot only, 2 - melee only] */ \
+	BONUS_NAME(SPELL_RESISTANCE_AURA) /*eg. unicorns, value - resistance bonus in % for adjacent creatures*/ \
+	BONUS_NAME(LEVEL_SPELL_IMMUNITY) /*creature is immune to all spell with level below or equal to value of this bonus */ \
+	BONUS_NAME(BLOCK_MAGIC_ABOVE) /*blocks casting spells of the level > value */ \
+	BONUS_NAME(BLOCK_ALL_MAGIC) /*blocks casting spells*/ \
+	BONUS_NAME(TWO_HEX_ATTACK_BREATH) /*eg. dragons*/	\
+	BONUS_NAME(SPELL_DAMAGE_REDUCTION) /*eg. golems; value - reduction in %, subtype - spell school; -1 - all, 0 - air, 1 - fire, 2 - water, 3 - earth*/ \
+	BONUS_NAME(NO_WALL_PENALTY)							\
+	BONUS_NAME(NON_LIVING) /*eg. golems, cannot be rised or healed, only neutral morale */				\
+	BONUS_NAME(RANDOM_SPELLCASTER) /*eg. master genie, val - level*/ \
+	BONUS_NAME(BLOCKS_RETALIATION) /*eg. naga*/			\
+	BONUS_NAME(SPELL_IMMUNITY) /*subid - spell id*/		\
+	BONUS_NAME(MANA_CHANNELING) /*value in %, eg. familiar*/ \
+	BONUS_NAME(SPELL_LIKE_ATTACK) /*subtype - spell, value - spell level; range is taken from spell, but damage from creature; eg. magog*/ \
+	BONUS_NAME(THREE_HEADED_ATTACK) /*eg. cerberus*/	\
+	BONUS_NAME(GENERAL_DAMAGE_PREMY)						\
+	BONUS_NAME(FIRE_IMMUNITY)	/*subtype 0 - all, 1 - all except positive, 2 - only damage spells*/						\
+	BONUS_NAME(WATER_IMMUNITY)							\
+	BONUS_NAME(EARTH_IMMUNITY)							\
+	BONUS_NAME(AIR_IMMUNITY)							\
+	BONUS_NAME(MIND_IMMUNITY)							\
+	BONUS_NAME(FIRE_SHIELD)								\
+	BONUS_NAME(UNDEAD)									\
+	BONUS_NAME(HP_REGENERATION) /*creature regenerates val HP every new round*/					\
+	BONUS_NAME(MANA_DRAIN) /*value - spell points per turn*/ \
+	BONUS_NAME(LIFE_DRAIN)								\
+	BONUS_NAME(DOUBLE_DAMAGE_CHANCE) /*value in %, eg. dread knight*/ \
+	BONUS_NAME(RETURN_AFTER_STRIKE)						\
+	BONUS_NAME(SPELLCASTER) /*subtype - spell id, value - level of school, additional info - weighted chance. use SPECIFIC_SPELL_POWER, CREATURE_SPELL_POWER or CREATURE_ENCHANT_POWER for calculating the power*/ \
+	BONUS_NAME(CATAPULT)								\
+	BONUS_NAME(ENEMY_DEFENCE_REDUCTION) /*in % (value) eg. behemots*/ \
+	BONUS_NAME(GENERAL_DAMAGE_REDUCTION) /* shield / air shield effect, also armorer skill/petrify effect for subtype -1*/ \
+	BONUS_NAME(GENERAL_ATTACK_REDUCTION) /*eg. while stoned or blinded - in %,// subtype not used, use ONLY_MELEE_FIGHT / DISTANCE_FIGHT*/ \
+	BONUS_NAME(DEFENSIVE_STANCE) /* val - bonus to defense while defending */ \
+	BONUS_NAME(ATTACKS_ALL_ADJACENT) /*eg. hydra*/		\
+	BONUS_NAME(MORE_DAMAGE_FROM_SPELL) /*value - damage increase in %, subtype - spell id*/ \
+	BONUS_NAME(FEAR)									\
+	BONUS_NAME(FEARLESS)								\
+	BONUS_NAME(NO_DISTANCE_PENALTY)						\
+	BONUS_NAME(ENCHANTER)/* for Enchanter spells, val - skill level, subtype - spell id, additionalInfo - cooldown */ \
+	BONUS_NAME(HEALER)									\
+	BONUS_NAME(SIEGE_WEAPON)							\
+	BONUS_NAME(HYPNOTIZED)								\
+	BONUS_NAME(NO_RETALIATION) /*temporary bonus for basilisk, unicorn and scorpicore paralyze*/\
+	BONUS_NAME(ADDITIONAL_RETALIATION) /*value - number of additional retaliations*/ \
+	BONUS_NAME(MAGIC_MIRROR) /* value - chance of redirecting in %*/ \
+	BONUS_NAME(ALWAYS_MINIMUM_DAMAGE) /*unit does its minimum damage from range; subtype: -1 - any attack, 0 - melee, 1 - ranged, value: additional damage penalty (it'll subtracted from dmg), additional info - multiplicative anti-bonus for dmg in % [eg 20 means that creature will inflict 80% of normal minimal dmg]*/ \
+	BONUS_NAME(ALWAYS_MAXIMUM_DAMAGE) /*eg. bless effect, subtype: -1 - any attack, 0 - melee, 1 - ranged, value: additional damage, additional info - multiplicative bonus for dmg in %*/ \
+	BONUS_NAME(ATTACKS_NEAREST_CREATURE) /*while in berserk*/ \
+	BONUS_NAME(IN_FRENZY) /*value - level*/				\
+	BONUS_NAME(SLAYER) /*value - level*/				\
+	BONUS_NAME(FORGETFULL) /*forgetfulness spell effect, value - level*/ \
+	BONUS_NAME(NOT_ACTIVE) /* subtype - spell ID (paralyze, blind, stone gaze) for graphical effect*/ 								\
+	BONUS_NAME(NO_LUCK) /*eg. when fighting on cursed ground*/	\
+	BONUS_NAME(NO_MORALE) /*eg. when fighting on cursed ground*/ \
+	BONUS_NAME(DARKNESS) /*val = radius */ \
+	BONUS_NAME(SPECIAL_SPELL_LEV) /*subtype = id, val = value per level in percent*/\
+	BONUS_NAME(SPELL_DAMAGE) /*val = value, now works for sorcery*/\
+	BONUS_NAME(SPECIFIC_SPELL_DAMAGE) /*subtype = id of spell, val = value*/\
+	BONUS_NAME(SPECIAL_PECULIAR_ENCHANT) /*blesses and curses with id = val dependent on unit's level, subtype = 0 or 1 for Coronius*/\
+	BONUS_NAME(SPECIAL_UPGRADE) /*subtype = base, additionalInfo = target */\
+	BONUS_NAME(DRAGON_NATURE) \
+	BONUS_NAME(CREATURE_DAMAGE)/*subtype 0 = both, 1 = min, 2 = max*/\
+	BONUS_NAME(EXP_MULTIPLIER)/* val - percent of additional exp gained by stack/commander (base value 100)*/\
+	BONUS_NAME(SHOTS)\
+	BONUS_NAME(DEATH_STARE) /*subtype 0 - gorgon, 1 - commander*/\
+	BONUS_NAME(POISON) /*val - max health penalty from poison possible*/\
+	BONUS_NAME(BIND_EFFECT) /*doesn't do anything particular, works as a marker)*/\
+	BONUS_NAME(ACID_BREATH) /*additional val damage per creature after attack, additional info - chance in percent*/\
+	BONUS_NAME(RECEPTIVE) /*accepts friendly spells even with immunity*/\
+	BONUS_NAME(DIRECT_DAMAGE_IMMUNITY) /*direct damage spells, that is*/\
+	BONUS_NAME(CASTS) /*how many times creature can cast activated spell*/ \
+	BONUS_NAME(SPECIFIC_SPELL_POWER) /* value used for Thunderbolt and Resurrection cast by units, subtype - spell id */\
+	BONUS_NAME(CREATURE_SPELL_POWER) /* value per unit, divided by 100 (so faerie Dragons have 800)*/ \
+	BONUS_NAME(CREATURE_ENCHANT_POWER) /* total duration of spells cast by creature */ \
+	BONUS_NAME(ENCHANTED) /* permanently enchanted with spell subID of level = val, if val > 3 then spell is mass and has level of val-3*/ \
+	BONUS_NAME(REBIRTH) /* val - percent of life restored, subtype = 0 - regular, 1 - at least one unit (sacred Phoenix) */\
+	BONUS_NAME(ADDITIONAL_UNITS) /*val of units with id = subtype will be added to hero's army at the beginning of battle */\
+	BONUS_NAME(SPOILS_OF_WAR) /*val * 10^-6 * gained exp resources of subtype will be given to hero after battle*/\
+	BONUS_NAME(BLOCK)\
+	BONUS_NAME(DISGUISED) /* subtype - spell level */\
+	BONUS_NAME(VISIONS) /* subtype - spell level */\
+	BONUS_NAME(NO_TERRAIN_PENALTY) /* subtype - terrain type */\
+	BONUS_NAME(SOUL_STEAL) /*val - number of units gained per enemy killed, subtype = 0 - gained units survive after battle, 1 - they do not*/ \
+	BONUS_NAME(TRANSMUTATION) /*val - chance to trigger in %, subtype = 0 - resurrection based on HP, 1 - based on unit count, additional info - target creature ID (attacker default)*/\
+	BONUS_NAME(SUMMON_GUARDIANS) /*val - amount in % of stack count, subtype = creature ID*/\
+	BONUS_NAME(CATAPULT_EXTRA_SHOTS) /*val - power of catapult effect, requires CATAPULT bonus to work*/\
+	BONUS_NAME(RANGED_RETALIATION) /*allows shooters to perform ranged retaliation*/\
+	BONUS_NAME(BLOCKS_RANGED_RETALIATION) /*disallows ranged retaliation for shooter unit, BLOCKS_RETALIATION bonus is for melee retaliation only*/\
+	BONUS_NAME(MANUAL_CONTROL) /* manually control warmachine with id = subtype, chance = val */  \
+	BONUS_NAME(WIDE_BREATH) /* initial desigh: dragon breath affecting multiple nearby hexes */\
+	BONUS_NAME(FIRST_STRIKE) /* first counterattack, then attack if possible */\
+	BONUS_NAME(SYNERGY_TARGET) /* dummy skill for alternative upgrades mod */\
+	BONUS_NAME(SHOOTS_ALL_ADJACENT) /* H4 Cyclops-like shoot (attacks all hexes neighboring with target) without spell-like mechanics */\
+	BONUS_NAME(BLOCK_MAGIC_BELOW) /*blocks casting spells of the level < value */ \
+	BONUS_NAME(DESTRUCTION) /*kills extra units after hit, subtype = 0 - kill percentage of units, 1 - kill amount, val = chance in percent to trigger, additional info - amount/percentage to kill*/ \
+	BONUS_NAME(SPECIAL_CRYSTAL_GENERATION) /*crystal dragon crystal generation*/ \
+	BONUS_NAME(NO_SPELLCAST_BY_DEFAULT) /*spellcast will not be default attack option for this creature*/ \
+	BONUS_NAME(GARGOYLE) /* gargoyle is special than NON_LIVING, cannot be rised or healed */ \
+	BONUS_NAME(SPECIAL_ADD_VALUE_ENCHANT) /*specialty spell like Aenin has, increased effect of spell, additionalInfo = value to add*/\
+	BONUS_NAME(SPECIAL_FIXED_VALUE_ENCHANT) /*specialty spell like Melody has, constant spell effect (i.e. 3 luck), additionalInfo = value to fix.*/\
+	BONUS_NAME(TOWN_MAGIC_WELL) /*one-time pseudo-bonus to implement Magic Well in the town*/\
+	BONUS_NAME(LIMITED_SHOOTING_RANGE) /*limits range of shooting creatures, doesn't adjust any other mechanics (half vs full damage etc). val - range in hexes, additional info - optional new range for broken arrow mechanic */\
+	BONUS_NAME(LEARN_BATTLE_SPELL_CHANCE) /*skill-agnostic eagle eye chance. subtype = 0 - from enemy, 1 - TODO: from entire battlefield*/\
+	BONUS_NAME(LEARN_BATTLE_SPELL_LEVEL_LIMIT) /*skill-agnostic eagle eye limit, subtype - school (-1 for all), others TODO*/\
+	BONUS_NAME(PERCENTAGE_DAMAGE_BOOST) /*skill-agnostic archery and offence, subtype is 0 for offence and 1 for archery*/\
+	BONUS_NAME(LEARN_MEETING_SPELL_LIMIT) /*skill-agnostic scholar, subtype is -1 for all, TODO for others (> 0)*/\
+	BONUS_NAME(ROUGH_TERRAIN_DISCOUNT) /*skill-agnostic pathfinding*/\
+	BONUS_NAME(WANDERING_CREATURES_JOIN_BONUS) /*skill-agnostic diplomacy*/\
+	BONUS_NAME(BEFORE_BATTLE_REPOSITION) /*skill-agnostic tactics, bonus for allowing tactics*/\
+	BONUS_NAME(BEFORE_BATTLE_REPOSITION_BLOCK) /*skill-agnostic tactics, bonus for blocking opposite tactics. For now donble side tactics is TODO.*/\
+	BONUS_NAME(HERO_EXPERIENCE_GAIN_PERCENT) /*skill-agnostic learning, and we can use it as a global effect also*/\
+	BONUS_NAME(UNDEAD_RAISE_PERCENTAGE) /*Percentage of killed enemy creatures to be raised after battle as undead*/\
+	BONUS_NAME(MANA_PER_KNOWLEDGE) /*Percentage rate of translating 10 hero knowledge to mana, used to intelligence and global bonus*/\
+	BONUS_NAME(HERO_GRANTS_ATTACKS) /*If hero can grant additional attacks to creature, value is number of attacks, subtype is creatureID*/\
+	BONUS_NAME(BONUS_DAMAGE_PERCENTAGE) /*If hero can grant conditional damage to creature, value is percentage, subtype is creatureID*/\
+	BONUS_NAME(BONUS_DAMAGE_CHANCE) /*If hero can grant additional damage to creature, value is chance, subtype is creatureID*/\
+	BONUS_NAME(MAX_LEARNABLE_SPELL_LEVEL) /*This can work as wisdom before. val = max learnable spell level*/\
+	/* end of list */
+
+
+#define BONUS_SOURCE_LIST \
+	BONUS_SOURCE(ARTIFACT)\
+	BONUS_SOURCE(ARTIFACT_INSTANCE)\
+	BONUS_SOURCE(OBJECT)\
+	BONUS_SOURCE(CREATURE_ABILITY)\
+	BONUS_SOURCE(TERRAIN_NATIVE)\
+	BONUS_SOURCE(TERRAIN_OVERLAY)\
+	BONUS_SOURCE(SPELL_EFFECT)\
+	BONUS_SOURCE(TOWN_STRUCTURE)\
+	BONUS_SOURCE(HERO_BASE_SKILL)\
+	BONUS_SOURCE(SECONDARY_SKILL)\
+	BONUS_SOURCE(HERO_SPECIAL)\
+	BONUS_SOURCE(ARMY)\
+	BONUS_SOURCE(CAMPAIGN_BONUS)\
+	BONUS_SOURCE(SPECIAL_WEEK)\
+	BONUS_SOURCE(STACK_EXPERIENCE)\
+	BONUS_SOURCE(COMMANDER) /*TODO: consider using simply STACK_INSTANCE */\
+	BONUS_SOURCE(GLOBAL) /*used for base bonuses which all heroes or all stacks should have*/\
+	BONUS_SOURCE(OTHER) /*used for defensive stance and default value of spell level limit*/
+
+#define BONUS_VALUE_LIST \
+	BONUS_VALUE(ADDITIVE_VALUE)\
+	BONUS_VALUE(BASE_NUMBER)\
+	BONUS_VALUE(PERCENT_TO_ALL)\
+	BONUS_VALUE(PERCENT_TO_BASE)\
+	BONUS_VALUE(PERCENT_TO_SOURCE) /*Adds value only to bonuses with same source*/\
+	BONUS_VALUE(PERCENT_TO_TARGET_TYPE) /*Adds value only to bonuses with SourceType target*/\
+	BONUS_VALUE(INDEPENDENT_MAX) /*used for SPELL bonus */\
+	BONUS_VALUE(INDEPENDENT_MIN) //used for SECONDARY_SKILL_PREMY bonus
+
+/// Struct for handling bonuses of several types. Can be transferred to any hero
+struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
+{
+	enum BonusType
+	{
+#define BONUS_NAME(x) x,
+		BONUS_LIST
+#undef BONUS_NAME
+	};
+	enum BonusDuration //when bonus is automatically removed
+	{
+		PERMANENT = 1,
+		ONE_BATTLE = 2, //at the end of battle
+		ONE_DAY = 4,   //at the end of day
+		ONE_WEEK = 8, //at the end of week (bonus lasts till the end of week, thats NOT 7 days
+		N_TURNS = 16, //used during battles, after battle bonus is always removed
+		N_DAYS = 32,
+		UNTIL_BEING_ATTACKED = 64, /*removed after attack and counterattacks are performed*/
+		UNTIL_ATTACK = 128, /*removed after attack and counterattacks are performed*/
+		STACK_GETS_TURN = 256, /*removed when stack gets its turn - used for defensive stance*/
+		COMMANDER_KILLED = 512
+	};
+	enum BonusSource
+	{
+#define BONUS_SOURCE(x) x,
+		BONUS_SOURCE_LIST
+#undef BONUS_SOURCE
+		NUM_BONUS_SOURCE /*This is a dummy value, which will be always last*/
+	};
+
+	enum LimitEffect
+	{
+		NO_LIMIT = 0,
+		ONLY_DISTANCE_FIGHT=1, ONLY_MELEE_FIGHT, //used to mark bonuses for attack/defense primary skills from spells like Precision (distance only)
+	};
+
+	enum ValueType
+	{
+#define BONUS_VALUE(x) x,
+		BONUS_VALUE_LIST
+#undef BONUS_VALUE
+	};
+
+	ui16 duration = PERMANENT; //uses BonusDuration values
+	si16 turnsRemain = 0; //used if duration is N_TURNS, N_DAYS or ONE_WEEK
+
+	BonusType type = NONE; //uses BonusType values - says to what is this bonus - 1 byte
+	TBonusSubtype subtype = -1; //-1 if not applicable - 4 bytes
+
+	BonusSource source = OTHER; //source type" uses BonusSource values - what gave that bonus
+	BonusSource targetSourceType;//Bonuses of what origin this amplifies, uses BonusSource values. Needed for PERCENT_TO_TARGET_TYPE.
+	si32 val = 0;
+	ui32 sid = 0; //source id: id of object/artifact/spell
+	ValueType valType = ADDITIVE_VALUE;
+	std::string stacking; // bonuses with the same stacking value don't stack (e.g. Angel/Archangel morale bonus)
+
+	CAddInfo additionalInfo;
+	LimitEffect effectRange = NO_LIMIT; //if not NO_LIMIT, bonus will be omitted by default
+
+	TLimiterPtr limiter;
+	TPropagatorPtr propagator;
+	TUpdaterPtr updater;
+	TUpdaterPtr propagationUpdater;
+
+	std::string description;
+
+	Bonus(BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::string Desc, si32 Subtype=-1);
+	Bonus(BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, si32 Subtype=-1, ValueType ValType = ADDITIVE_VALUE);
+	Bonus() = default;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & duration;
+		h & type;
+		h & subtype;
+		h & source;
+		h & val;
+		h & sid;
+		h & description;
+		h & additionalInfo;
+		h & turnsRemain;
+		h & valType;
+		h & stacking;
+		h & effectRange;
+		h & limiter;
+		h & propagator;
+		h & updater;
+		h & propagationUpdater;
+		h & targetSourceType;
+	}
+
+	template <typename Ptr>
+	static bool compareByAdditionalInfo(const Ptr& a, const Ptr& b)
+	{
+		return a->additionalInfo < b->additionalInfo;
+	}
+	static bool NDays(const Bonus *hb)
+	{
+		return hb->duration & Bonus::N_DAYS;
+	}
+	static bool NTurns(const Bonus *hb)
+	{
+		return hb->duration & Bonus::N_TURNS;
+	}
+	static bool OneDay(const Bonus *hb)
+	{
+		return hb->duration & Bonus::ONE_DAY;
+	}
+	static bool OneWeek(const Bonus *hb)
+	{
+		return hb->duration & Bonus::ONE_WEEK;
+	}
+	static bool OneBattle(const Bonus *hb)
+	{
+		return hb->duration & Bonus::ONE_BATTLE;
+	}
+	static bool Permanent(const Bonus *hb)
+	{
+		return hb->duration & Bonus::PERMANENT;
+	}
+	static bool UntilGetsTurn(const Bonus *hb)
+	{
+		return hb->duration & Bonus::STACK_GETS_TURN;
+	}
+	static bool UntilAttack(const Bonus *hb)
+	{
+		return hb->duration & Bonus::UNTIL_ATTACK;
+	}
+	static bool UntilBeingAttacked(const Bonus *hb)
+	{
+		return hb->duration & Bonus::UNTIL_BEING_ATTACKED;
+	}
+	static bool UntilCommanderKilled(const Bonus *hb)
+	{
+		return hb->duration & Bonus::COMMANDER_KILLED;
+	}
+	inline bool operator == (const BonusType & cf) const
+	{
+		return type == cf;
+	}
+	inline void operator += (const ui32 Val) //no return
+	{
+		val += Val;
+	}
+	STRONG_INLINE static ui32 getSid32(ui32 high, ui32 low)
+	{
+		return (high << 16) + low;
+	}
+
+	STRONG_INLINE static ui32 getHighFromSid32(ui32 sid)
+	{
+		return sid >> 16;
+	}
+
+	STRONG_INLINE static ui32 getLowFromSid32(ui32 sid)
+	{
+		return sid & 0x0000FFFF;
+	}
+
+	std::string Description(std::optional<si32> customValue = {}) const;
+	JsonNode toJsonNode() const;
+	std::string nameForBonus() const; // generate suitable name for bonus - e.g. for storing in json struct
+
+	std::shared_ptr<Bonus> addLimiter(const TLimiterPtr & Limiter); //returns this for convenient chain-calls
+	std::shared_ptr<Bonus> addPropagator(const TPropagatorPtr & Propagator); //returns this for convenient chain-calls
+	std::shared_ptr<Bonus> addUpdater(const TUpdaterPtr & Updater); //returns this for convenient chain-calls
+};
+
+DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const Bonus &bonus);
+
+extern DLL_LINKAGE const std::map<std::string, Bonus::BonusType> bonusNameMap;
+extern DLL_LINKAGE const std::map<std::string, Bonus::ValueType> bonusValueMap;
+extern DLL_LINKAGE const std::map<std::string, Bonus::BonusSource> bonusSourceMap;
+extern DLL_LINKAGE const std::map<std::string, ui16> bonusDurationMap;
+extern DLL_LINKAGE const std::map<std::string, Bonus::LimitEffect> bonusLimitEffect;
+extern DLL_LINKAGE const std::set<std::string> deprecatedBonusSet;
+
+VCMI_LIB_NAMESPACE_END

+ 276 - 0
lib/bonuses/BonusList.cpp

@@ -0,0 +1,276 @@
+/*
+ * BonusList.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "CBonusSystemNode.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+BonusList::BonusList(bool BelongsToTree) : belongsToTree(BelongsToTree)
+{
+}
+
+BonusList::BonusList(const BonusList & bonusList): belongsToTree(false)
+{
+	bonuses.resize(bonusList.size());
+	std::copy(bonusList.begin(), bonusList.end(), bonuses.begin());
+}
+
+BonusList::BonusList(BonusList && other) noexcept: belongsToTree(false)
+{
+	std::swap(belongsToTree, other.belongsToTree);
+	std::swap(bonuses, other.bonuses);
+}
+
+BonusList& BonusList::operator=(const BonusList &bonusList)
+{
+	bonuses.resize(bonusList.size());
+	std::copy(bonusList.begin(), bonusList.end(), bonuses.begin());
+	belongsToTree = false;
+	return *this;
+}
+
+void BonusList::changed() const
+{
+    if(belongsToTree)
+		CBonusSystemNode::treeHasChanged();
+}
+
+void BonusList::stackBonuses()
+{
+	boost::sort(bonuses, [](const std::shared_ptr<Bonus> & b1, const std::shared_ptr<Bonus> & b2) -> bool
+	{
+		if(b1 == b2)
+			return false;
+#define COMPARE_ATT(ATT) if(b1->ATT != b2->ATT) return b1->ATT < b2->ATT
+		COMPARE_ATT(stacking);
+		COMPARE_ATT(type);
+		COMPARE_ATT(subtype);
+		COMPARE_ATT(valType);
+#undef COMPARE_ATT
+		return b1->val > b2->val;
+	});
+	// remove non-stacking
+	size_t next = 1;
+	while(next < bonuses.size())
+	{
+		bool remove = false;
+		std::shared_ptr<Bonus> last = bonuses[next-1];
+		std::shared_ptr<Bonus> current = bonuses[next];
+
+		if(current->stacking.empty())
+			remove = current == last;
+		else if(current->stacking == "ALWAYS")
+			remove = false;
+		else
+			remove = current->stacking == last->stacking
+				&& current->type == last->type
+				&& current->subtype == last->subtype
+				&& current->valType == last->valType;
+
+		if(remove)
+			bonuses.erase(bonuses.begin() + next);
+		else
+			next++;
+	}
+}
+
+int BonusList::totalValue() const
+{
+	struct BonusCollection
+	{
+		int base = 0;
+		int percentToBase = 0;
+		int percentToAll = 0;
+		int additive = 0;
+		int percentToSource = 0;
+		int indepMin = std::numeric_limits<int>::max();
+		int indepMax = std::numeric_limits<int>::min();
+	};
+
+	auto percent = [](int64_t base, int64_t percent) -> int {
+		return static_cast<int>(std::clamp<int64_t>((base * (100 + percent)) / 100, std::numeric_limits<int>::min(), std::numeric_limits<int>::max()));
+	};
+	std::array <BonusCollection, Bonus::BonusSource::NUM_BONUS_SOURCE> sources = {};
+	BonusCollection any;
+	bool hasIndepMax = false;
+	bool hasIndepMin = false;
+
+	for(const auto & b : bonuses)
+	{
+		switch(b->valType)
+		{
+		case Bonus::BASE_NUMBER:
+			sources[b->source].base += b->val;
+			break;
+		case Bonus::PERCENT_TO_ALL:
+			sources[b->source].percentToAll += b->val;
+			break;
+		case Bonus::PERCENT_TO_BASE:
+			sources[b->source].percentToBase += b->val;
+			break;
+		case Bonus::PERCENT_TO_SOURCE:
+			sources[b->source].percentToSource += b->val;
+			break;
+		case Bonus::PERCENT_TO_TARGET_TYPE:
+			sources[b->targetSourceType].percentToSource += b->val;
+			break;
+		case Bonus::ADDITIVE_VALUE:
+			sources[b->source].additive += b->val;
+			break;
+		case Bonus::INDEPENDENT_MAX:
+			hasIndepMax = true;
+			vstd::amax(sources[b->source].indepMax, b->val);
+			break;
+		case Bonus::INDEPENDENT_MIN:
+			hasIndepMin = true;
+			vstd::amin(sources[b->source].indepMin, b->val);
+			break;
+		}
+	}
+	for(const auto & src : sources)
+	{
+		any.base += percent(src.base, src.percentToSource);
+		any.percentToBase += percent(src.percentToBase, src.percentToSource);
+		any.percentToAll += percent(src.percentToAll, src.percentToSource);
+		any.additive += percent(src.additive, src.percentToSource);
+		if(hasIndepMin)
+			vstd::amin(any.indepMin, percent(src.indepMin, src.percentToSource));
+		if(hasIndepMax)
+			vstd::amax(any.indepMax, percent(src.indepMax, src.percentToSource));
+	}
+	any.base = percent(any.base, any.percentToBase);
+	any.base += any.additive;
+	auto valFirst = percent(any.base ,any.percentToAll);
+
+	if(hasIndepMin && hasIndepMax && any.indepMin < any.indepMax)
+		any.indepMax = any.indepMin;
+
+	const int notIndepBonuses = static_cast<int>(std::count_if(bonuses.cbegin(), bonuses.cend(), [](const std::shared_ptr<Bonus>& b)
+	{
+		return b->valType != Bonus::INDEPENDENT_MAX && b->valType != Bonus::INDEPENDENT_MIN;
+	}));
+
+	if(notIndepBonuses)
+		return std::clamp(valFirst, any.indepMax, any.indepMin);
+
+	return hasIndepMin ? any.indepMin : hasIndepMax ? any.indepMax : 0;
+}
+
+std::shared_ptr<Bonus> BonusList::getFirst(const CSelector &select)
+{
+	for (auto & b : bonuses)
+	{
+		if(select(b.get()))
+			return b;
+	}
+	return nullptr;
+}
+
+std::shared_ptr<const Bonus> BonusList::getFirst(const CSelector &selector) const
+{
+	for(const auto & b : bonuses)
+	{
+		if(selector(b.get()))
+			return b;
+	}
+	return nullptr;
+}
+
+void BonusList::getBonuses(BonusList & out, const CSelector &selector, const CSelector &limit) const
+{
+	out.reserve(bonuses.size());
+	for(const auto & b : bonuses)
+	{
+		//add matching bonuses that matches limit predicate or have NO_LIMIT if no given predicate
+		auto noFightLimit = b->effectRange == Bonus::NO_LIMIT;
+		if(selector(b.get()) && ((!limit && noFightLimit) || ((bool)limit && limit(b.get()))))
+			out.push_back(b);
+	}
+}
+
+void BonusList::getAllBonuses(BonusList &out) const
+{
+	for(const auto & b : bonuses)
+		out.push_back(b);
+}
+
+int BonusList::valOfBonuses(const CSelector &select) const
+{
+	BonusList ret;
+	CSelector limit = nullptr;
+	getBonuses(ret, select, limit);
+	return ret.totalValue();
+}
+
+JsonNode BonusList::toJsonNode() const
+{
+	JsonNode node(JsonNode::JsonType::DATA_VECTOR);
+	for(const std::shared_ptr<Bonus> & b : bonuses)
+		node.Vector().push_back(b->toJsonNode());
+	return node;
+}
+
+void BonusList::push_back(const std::shared_ptr<Bonus> & x)
+{
+	bonuses.push_back(x);
+	changed();
+}
+
+BonusList::TInternalContainer::iterator BonusList::erase(const int position)
+{
+	changed();
+	return bonuses.erase(bonuses.begin() + position);
+}
+
+void BonusList::clear()
+{
+	bonuses.clear();
+	changed();
+}
+
+std::vector<BonusList *>::size_type BonusList::operator-=(const std::shared_ptr<Bonus> & i)
+{
+	auto itr = std::find(bonuses.begin(), bonuses.end(), i);
+	if(itr == bonuses.end())
+		return false;
+	bonuses.erase(itr);
+	changed();
+	return true;
+}
+
+void BonusList::resize(BonusList::TInternalContainer::size_type sz, const std::shared_ptr<Bonus> & c)
+{
+	bonuses.resize(sz, c);
+	changed();
+}
+
+void BonusList::reserve(TInternalContainer::size_type sz)
+{
+	bonuses.reserve(sz);
+}
+
+void BonusList::insert(BonusList::TInternalContainer::iterator position, BonusList::TInternalContainer::size_type n, const std::shared_ptr<Bonus> & x)
+{
+	bonuses.insert(position, n, x);
+	changed();
+}
+
+DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const BonusList &bonusList)
+{
+	for (ui32 i = 0; i < bonusList.size(); i++)
+	{
+		const auto & b = bonusList[i];
+		out << "Bonus " << i << "\n" << *b << std::endl;
+	}
+	return out;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 114 - 0
lib/bonuses/BonusList.h

@@ -0,0 +1,114 @@
+/*
+ * BonusList.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "Bonus.h"
+#include "BonusSelector.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class DLL_LINKAGE BonusList
+{
+public:
+	using TInternalContainer = std::vector<std::shared_ptr<Bonus>>;
+
+private:
+	TInternalContainer bonuses;
+	bool belongsToTree;
+	void changed() const;
+
+public:
+	using const_reference = TInternalContainer::const_reference;
+	using value_type = TInternalContainer::value_type;
+
+	using const_iterator = TInternalContainer::const_iterator;
+	using iterator = TInternalContainer::iterator;
+
+	BonusList(bool BelongsToTree = false);
+	BonusList(const BonusList &bonusList);
+	BonusList(BonusList && other) noexcept;
+	BonusList& operator=(const BonusList &bonusList);
+
+	// wrapper functions of the STL vector container
+	TInternalContainer::size_type size() const { return bonuses.size(); }
+	void push_back(const std::shared_ptr<Bonus> & x);
+	TInternalContainer::iterator erase (const int position);
+	void clear();
+	bool empty() const { return bonuses.empty(); }
+	void resize(TInternalContainer::size_type sz, const std::shared_ptr<Bonus> & c = nullptr);
+	void reserve(TInternalContainer::size_type sz);
+	TInternalContainer::size_type capacity() const { return bonuses.capacity(); }
+	STRONG_INLINE std::shared_ptr<Bonus> &operator[] (TInternalContainer::size_type n) { return bonuses[n]; }
+	STRONG_INLINE const std::shared_ptr<Bonus> &operator[] (TInternalContainer::size_type n) const { return bonuses[n]; }
+	std::shared_ptr<Bonus> &back() { return bonuses.back(); }
+	std::shared_ptr<Bonus> &front() { return bonuses.front(); }
+	const std::shared_ptr<Bonus> &back() const { return bonuses.back(); }
+	const std::shared_ptr<Bonus> &front() const { return bonuses.front(); }
+
+	// There should be no non-const access to provide solid,robust bonus caching
+	TInternalContainer::const_iterator begin() const { return bonuses.begin(); }
+	TInternalContainer::const_iterator end() const { return bonuses.end(); }
+	TInternalContainer::size_type operator-=(const std::shared_ptr<Bonus> & i);
+
+	// BonusList functions
+	void stackBonuses();
+	int totalValue() const;
+	void getBonuses(BonusList &out, const CSelector &selector, const CSelector &limit = nullptr) const;
+	void getAllBonuses(BonusList &out) const;
+
+	//special find functions
+	std::shared_ptr<Bonus> getFirst(const CSelector &select);
+	std::shared_ptr<const Bonus> getFirst(const CSelector &select) const;
+	int valOfBonuses(const CSelector &select) const;
+
+	// conversion / output
+	JsonNode toJsonNode() const;
+
+	// remove_if implementation for STL vector types
+	template <class Predicate>
+	void remove_if(Predicate pred)
+	{
+		BonusList newList;
+		for(const auto & b : bonuses)
+		{
+			if (!pred(b.get()))
+				newList.push_back(b);
+		}
+		bonuses.clear();
+		bonuses.resize(newList.size());
+		std::copy(newList.begin(), newList.end(), bonuses.begin());
+	}
+
+	template <class InputIterator>
+	void insert(const int position, InputIterator first, InputIterator last);
+	void insert(TInternalContainer::iterator position, TInternalContainer::size_type n, const std::shared_ptr<Bonus> & x);
+
+	template <typename Handler>
+	void serialize(Handler &h, const int version)
+	{
+		h & static_cast<TInternalContainer&>(bonuses);
+	}
+
+	// C++ for range support
+	auto begin () -> decltype (bonuses.begin())
+	{
+		return bonuses.begin();
+	}
+
+	auto end () -> decltype (bonuses.end())
+	{
+		return bonuses.end();
+	}
+};
+
+DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const BonusList &bonusList);
+
+
+VCMI_LIB_NAMESPACE_END

+ 260 - 0
lib/bonuses/BonusParams.cpp

@@ -0,0 +1,260 @@
+/*
+ * BonusParams.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+
+#include "BonusParams.h"
+#include "BonusSelector.h"
+
+#include "../ResourceSet.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+BonusParams::BonusParams(std::string deprecatedTypeStr, std::string deprecatedSubtypeStr, int deprecatedSubtype):
+	isConverted(true)
+{
+	if(deprecatedTypeStr == "SECONDARY_SKILL_PREMY" || deprecatedTypeStr == "SPECIAL_SECONDARY_SKILL")
+	{
+		if(deprecatedSubtype == SecondarySkill::PATHFINDING || deprecatedSubtypeStr == "skill.pathfinding")
+			type = Bonus::ROUGH_TERRAIN_DISCOUNT;
+		else if(deprecatedSubtype == SecondarySkill::DIPLOMACY || deprecatedSubtypeStr == "skill.diplomacy")
+			type = Bonus::WANDERING_CREATURES_JOIN_BONUS;
+		else if(deprecatedSubtype == SecondarySkill::WISDOM || deprecatedSubtypeStr == "skill.wisdom")
+			type = Bonus::MAX_LEARNABLE_SPELL_LEVEL;
+		else if(deprecatedSubtype == SecondarySkill::MYSTICISM || deprecatedSubtypeStr == "skill.mysticism")
+			type = Bonus::MANA_REGENERATION;
+		else if(deprecatedSubtype == SecondarySkill::NECROMANCY || deprecatedSubtypeStr == "skill.necromancy")
+			type = Bonus::UNDEAD_RAISE_PERCENTAGE;
+		else if(deprecatedSubtype == SecondarySkill::LEARNING || deprecatedSubtypeStr == "skill.learning")
+			type = Bonus::HERO_EXPERIENCE_GAIN_PERCENT;
+		else if(deprecatedSubtype == SecondarySkill::RESISTANCE || deprecatedSubtypeStr == "skill.resistance")
+			type = Bonus::MAGIC_RESISTANCE;
+		else if(deprecatedSubtype == SecondarySkill::EAGLE_EYE || deprecatedSubtypeStr == "skill.eagleEye")
+			type = Bonus::LEARN_BATTLE_SPELL_CHANCE;
+		else if(deprecatedSubtype == SecondarySkill::SCOUTING || deprecatedSubtypeStr == "skill.scouting")
+			type = Bonus::SIGHT_RADIUS;
+		else if(deprecatedSubtype == SecondarySkill::INTELLIGENCE || deprecatedSubtypeStr == "skill.intelligence")
+		{
+			type = Bonus::MANA_PER_KNOWLEDGE;
+			valueType = Bonus::PERCENT_TO_BASE;
+			valueTypeRelevant = true;
+		}
+		else if(deprecatedSubtype == SecondarySkill::SORCERY || deprecatedSubtypeStr == "skill.sorcery")
+			type = Bonus::SPELL_DAMAGE;
+		else if(deprecatedSubtype == SecondarySkill::SCHOLAR || deprecatedSubtypeStr == "skill.scholar")
+			type = Bonus::LEARN_MEETING_SPELL_LIMIT;
+		else if(deprecatedSubtype == SecondarySkill::ARCHERY|| deprecatedSubtypeStr == "skill.archery")
+		{
+			subtype = 1;
+			subtypeRelevant = true;
+			type = Bonus::PERCENTAGE_DAMAGE_BOOST;
+		}
+		else if(deprecatedSubtype == SecondarySkill::OFFENCE || deprecatedSubtypeStr == "skill.offence")
+		{
+			subtype = 0;
+			subtypeRelevant = true;
+			type = Bonus::PERCENTAGE_DAMAGE_BOOST;
+		}
+		else if(deprecatedSubtype == SecondarySkill::ARMORER || deprecatedSubtypeStr == "skill.armorer")
+		{
+			subtype = -1;
+			subtypeRelevant = true;
+			type = Bonus::GENERAL_DAMAGE_REDUCTION;
+		}
+		else if(deprecatedSubtype == SecondarySkill::NAVIGATION || deprecatedSubtypeStr == "skill.navigation")
+		{
+			subtype = 0;
+			subtypeRelevant = true;
+			valueType = Bonus::PERCENT_TO_BASE;
+			valueTypeRelevant = true;
+			type = Bonus::MOVEMENT;
+		}
+		else if(deprecatedSubtype == SecondarySkill::LOGISTICS || deprecatedSubtypeStr == "skill.logistics")
+		{
+			subtype = 1;
+			subtypeRelevant = true;
+			valueType = Bonus::PERCENT_TO_BASE;
+			valueTypeRelevant = true;
+			type = Bonus::MOVEMENT;
+		}
+		else if(deprecatedSubtype == SecondarySkill::ESTATES || deprecatedSubtypeStr == "skill.estates")
+		{
+			type = Bonus::GENERATE_RESOURCE;
+			subtype = GameResID(EGameResID::GOLD);
+			subtypeRelevant = true;
+		}
+		else if(deprecatedSubtype == SecondarySkill::AIR_MAGIC || deprecatedSubtypeStr == "skill.airMagic")
+		{
+			type = Bonus::MAGIC_SCHOOL_SKILL;
+			subtypeRelevant = true;
+			subtype = 4;
+		}
+		else if(deprecatedSubtype == SecondarySkill::WATER_MAGIC || deprecatedSubtypeStr == "skill.waterMagic")
+		{
+			type = Bonus::MAGIC_SCHOOL_SKILL;
+			subtypeRelevant = true;
+			subtype = 1;
+		}
+		else if(deprecatedSubtype == SecondarySkill::FIRE_MAGIC || deprecatedSubtypeStr == "skill.fireMagic")
+		{
+			type = Bonus::MAGIC_SCHOOL_SKILL;
+			subtypeRelevant = true;
+			subtype = 2;
+		}
+		else if(deprecatedSubtype == SecondarySkill::EARTH_MAGIC || deprecatedSubtypeStr == "skill.earthMagic")
+		{
+			type = Bonus::MAGIC_SCHOOL_SKILL;
+			subtypeRelevant = true;
+			subtype = 8;
+		}
+		else if (deprecatedSubtype == SecondarySkill::ARTILLERY || deprecatedSubtypeStr == "skill.artillery")
+		{
+			type = Bonus::BONUS_DAMAGE_CHANCE;
+			subtypeRelevant = true;
+			subtypeStr = "core:creature.ballista";
+		}
+		else if (deprecatedSubtype == SecondarySkill::FIRST_AID || deprecatedSubtypeStr == "skill.firstAid")
+		{
+			type = Bonus::SPECIFIC_SPELL_POWER;
+			subtypeRelevant = true;
+			subtypeStr = "core:spell.firstAid";
+		}
+		else if (deprecatedSubtype == SecondarySkill::BALLISTICS || deprecatedSubtypeStr == "skill.ballistics")
+		{
+			type = Bonus::CATAPULT_EXTRA_SHOTS;
+			subtypeRelevant = true;
+			subtypeStr = "core:spell.catapultShot";
+		}
+		else
+			isConverted = false;
+	}
+	else if (deprecatedTypeStr == "SECONDARY_SKILL_VAL2")
+	{
+		if(deprecatedSubtype == SecondarySkill::EAGLE_EYE || deprecatedSubtypeStr == "skill.eagleEye")
+			type = Bonus::LEARN_BATTLE_SPELL_LEVEL_LIMIT;
+		else if (deprecatedSubtype == SecondarySkill::ARTILLERY || deprecatedSubtypeStr == "skill.artillery")
+		{
+			type = Bonus::HERO_GRANTS_ATTACKS;
+			subtypeRelevant = true;
+			subtypeStr = "core:creature.ballista";
+		}
+		else
+			isConverted = false;
+	}
+	else if (deprecatedTypeStr == "SEA_MOVEMENT")
+	{
+		subtype = 0;
+		subtypeRelevant = true;
+		valueType = Bonus::ADDITIVE_VALUE;
+		valueTypeRelevant = true;
+		type = Bonus::MOVEMENT;
+	}
+	else if (deprecatedTypeStr == "LAND_MOVEMENT")
+	{
+		subtype = 1;
+		subtypeRelevant = true;
+		valueType = Bonus::ADDITIVE_VALUE;
+		valueTypeRelevant = true;
+		type = Bonus::MOVEMENT;
+	}
+	else if (deprecatedTypeStr == "MAXED_SPELL")
+	{
+		type = Bonus::SPELL;
+		subtypeStr = deprecatedSubtypeStr;
+		subtypeRelevant = true;
+		valueType = Bonus::INDEPENDENT_MAX;
+		valueTypeRelevant = true;
+		val = 3;
+		valRelevant = true;
+	}
+	else if (deprecatedTypeStr == "FULL_HP_REGENERATION")
+	{
+		type = Bonus::HP_REGENERATION;
+		val = 100000; //very high value to always chose stack health
+		valRelevant = true;
+	}
+	else if (deprecatedTypeStr == "KING1")
+	{
+		type = Bonus::KING;
+		val = 0;
+		valRelevant = true;
+	}
+	else if (deprecatedTypeStr == "KING2")
+	{
+		type = Bonus::KING;
+		val = 2;
+		valRelevant = true;
+	}
+	else if (deprecatedTypeStr == "KING3")
+	{
+		type = Bonus::KING;
+		val = 3;
+		valRelevant = true;
+	}
+	else if (deprecatedTypeStr == "SIGHT_RADIOUS")
+		type = Bonus::SIGHT_RADIUS;
+	else if (deprecatedTypeStr == "SELF_MORALE")
+	{
+		type = Bonus::MORALE;
+		val = 1;
+		valRelevant = true;
+		valueType = Bonus::INDEPENDENT_MAX;
+		valueTypeRelevant = true;
+	}
+	else if (deprecatedTypeStr == "SELF_LUCK")
+	{
+		type = Bonus::LUCK;
+		val = 1;
+		valRelevant = true;
+		valueType = Bonus::INDEPENDENT_MAX;
+		valueTypeRelevant = true;
+	}
+	else
+		isConverted = false;
+}
+
+const JsonNode & BonusParams::toJson()
+{
+	assert(isConverted);
+	if(ret.isNull())
+	{
+		ret["type"].String() = vstd::findKey(bonusNameMap, type);
+		if(subtypeRelevant && !subtypeStr.empty())
+			ret["subtype"].String() = subtypeStr;
+		else if(subtypeRelevant)
+			ret["subtype"].Integer() = subtype;
+		if(valueTypeRelevant)
+			ret["valueType"].String() = vstd::findKey(bonusValueMap, valueType);
+		if(valRelevant)
+			ret["val"].Float() = val;
+		if(targetTypeRelevant)
+			ret["targetSourceType"].String() = vstd::findKey(bonusSourceMap, targetType);
+		jsonCreated = true;
+	}
+	return ret;
+};
+
+CSelector BonusParams::toSelector()
+{
+	assert(isConverted);
+	if(subtypeRelevant && !subtypeStr.empty())
+		JsonUtils::resolveIdentifier(subtype, toJson(), "subtype");
+
+	auto ret = Selector::type()(type);
+	if(subtypeRelevant)
+		ret = ret.And(Selector::subtype()(subtype));
+	if(valueTypeRelevant)
+		ret = ret.And(Selector::valueType(valueType));
+	if(targetTypeRelevant)
+		ret = ret.And(Selector::targetSourceType()(targetType));
+	return ret;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 41 - 0
lib/bonuses/BonusParams.h

@@ -0,0 +1,41 @@
+/*
+ * BonusParams.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "Bonus.h"
+
+#include "../GameConstants.h"
+#include "../JsonNode.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+struct DLL_LINKAGE BonusParams {
+	bool isConverted;
+	Bonus::BonusType type = Bonus::NONE;
+	TBonusSubtype subtype = -1;
+	std::string subtypeStr;
+	bool subtypeRelevant = false;
+	Bonus::ValueType valueType = Bonus::BASE_NUMBER;
+	bool valueTypeRelevant = false;
+	si32 val = 0;
+	bool valRelevant = false;
+	Bonus::BonusSource targetType = Bonus::SECONDARY_SKILL;
+	bool targetTypeRelevant = false;
+
+	BonusParams(bool isConverted = true) : isConverted(isConverted) {};
+	BonusParams(std::string deprecatedTypeStr, std::string deprecatedSubtypeStr = "", int deprecatedSubtype = 0);
+	const JsonNode & toJson();
+	CSelector toSelector();
+private:
+	JsonNode ret;
+	bool jsonCreated = false;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 89 - 0
lib/bonuses/BonusSelector.cpp

@@ -0,0 +1,89 @@
+/*
+ * BonusSelector.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "BonusSelector.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+namespace Selector
+{
+	DLL_LINKAGE CSelectFieldEqual<Bonus::BonusType> & type()
+	{
+		static CSelectFieldEqual<Bonus::BonusType> stype(&Bonus::type);
+		return stype;
+	}
+
+	DLL_LINKAGE CSelectFieldEqual<TBonusSubtype> & subtype()
+	{
+		static CSelectFieldEqual<TBonusSubtype> ssubtype(&Bonus::subtype);
+		return ssubtype;
+	}
+
+	DLL_LINKAGE CSelectFieldEqual<CAddInfo> & info()
+	{
+		static CSelectFieldEqual<CAddInfo> sinfo(&Bonus::additionalInfo);
+		return sinfo;
+	}
+
+	DLL_LINKAGE CSelectFieldEqual<Bonus::BonusSource> & sourceType()
+	{
+		static CSelectFieldEqual<Bonus::BonusSource> ssourceType(&Bonus::source);
+		return ssourceType;
+	}
+
+	DLL_LINKAGE CSelectFieldEqual<Bonus::BonusSource> & targetSourceType()
+	{
+		static CSelectFieldEqual<Bonus::BonusSource> ssourceType(&Bonus::targetSourceType);
+		return ssourceType;
+	}
+
+	DLL_LINKAGE CSelectFieldEqual<Bonus::LimitEffect> & effectRange()
+	{
+		static CSelectFieldEqual<Bonus::LimitEffect> seffectRange(&Bonus::effectRange);
+		return seffectRange;
+	}
+
+	DLL_LINKAGE CWillLastTurns turns;
+	DLL_LINKAGE CWillLastDays days;
+
+	CSelector DLL_LINKAGE typeSubtype(Bonus::BonusType Type, TBonusSubtype Subtype)
+	{
+		return type()(Type).And(subtype()(Subtype));
+	}
+
+	CSelector DLL_LINKAGE typeSubtypeInfo(Bonus::BonusType type, TBonusSubtype subtype, const CAddInfo & info)
+	{
+		return CSelectFieldEqual<Bonus::BonusType>(&Bonus::type)(type)
+			.And(CSelectFieldEqual<TBonusSubtype>(&Bonus::subtype)(subtype))
+			.And(CSelectFieldEqual<CAddInfo>(&Bonus::additionalInfo)(info));
+	}
+
+	CSelector DLL_LINKAGE source(Bonus::BonusSource source, ui32 sourceID)
+	{
+		return CSelectFieldEqual<Bonus::BonusSource>(&Bonus::source)(source)
+			.And(CSelectFieldEqual<ui32>(&Bonus::sid)(sourceID));
+	}
+
+	CSelector DLL_LINKAGE sourceTypeSel(Bonus::BonusSource source)
+	{
+		return CSelectFieldEqual<Bonus::BonusSource>(&Bonus::source)(source);
+	}
+
+	CSelector DLL_LINKAGE valueType(Bonus::ValueType valType)
+	{
+		return CSelectFieldEqual<Bonus::ValueType>(&Bonus::valType)(valType);
+	}
+
+	DLL_LINKAGE CSelector all([](const Bonus * b){return true;});
+	DLL_LINKAGE CSelector none([](const Bonus * b){return false;});
+}
+
+VCMI_LIB_NAMESPACE_END

+ 156 - 0
lib/bonuses/BonusSelector.h

@@ -0,0 +1,156 @@
+/*
+ * BonusSelector.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include "Bonus.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class CSelector : std::function<bool(const Bonus*)>
+{
+	using TBase = std::function<bool(const Bonus*)>;
+public:
+	CSelector() = default;
+	template<typename T>
+	CSelector(const T &t,	//SFINAE trick -> include this c-tor in overload resolution only if parameter is class
+							//(includes functors, lambdas) or function. Without that VC is going mad about ambiguities.
+		typename std::enable_if < boost::mpl::or_ < std::is_class<T>, std::is_function<T >> ::value>::type *dummy = nullptr)
+		: TBase(t)
+	{}
+
+	CSelector(std::nullptr_t)
+	{}
+
+	CSelector And(CSelector rhs) const
+	{
+		//lambda may likely outlive "this" (it can be even a temporary) => we copy the OBJECT (not pointer)
+		auto thisCopy = *this;
+		return [thisCopy, rhs](const Bonus *b) mutable { return thisCopy(b) && rhs(b); };
+	}
+	CSelector Or(CSelector rhs) const
+	{
+		auto thisCopy = *this;
+		return [thisCopy, rhs](const Bonus *b) mutable { return thisCopy(b) || rhs(b); };
+	}
+
+	CSelector Not() const
+	{
+		auto thisCopy = *this;
+		return [thisCopy](const Bonus *b) mutable { return !thisCopy(b); };
+	}
+
+	bool operator()(const Bonus *b) const
+	{
+		return TBase::operator()(b);
+	}
+
+	operator bool() const
+	{
+		return !!static_cast<const TBase&>(*this);
+	}
+};
+
+template<typename T>
+class CSelectFieldEqual
+{
+	T Bonus::*ptr;
+
+public:
+	CSelectFieldEqual(T Bonus::*Ptr)
+		: ptr(Ptr)
+	{
+	}
+
+	CSelector operator()(const T &valueToCompareAgainst) const
+	{
+		auto ptr2 = ptr; //We need a COPY because we don't want to reference this (might be outlived by lambda)
+		return [ptr2, valueToCompareAgainst](const Bonus *bonus)
+		{
+			return bonus->*ptr2 == valueToCompareAgainst;
+		};
+	}
+};
+
+class DLL_LINKAGE CWillLastTurns
+{
+public:
+	int turnsRequested;
+
+	bool operator()(const Bonus *bonus) const
+	{
+		return turnsRequested <= 0					//every present effect will last zero (or "less") turns
+			|| !Bonus::NTurns(bonus) //so do every not expriing after N-turns effect
+			|| bonus->turnsRemain > turnsRequested;
+	}
+	CWillLastTurns& operator()(const int &setVal)
+	{
+		turnsRequested = setVal;
+		return *this;
+	}
+};
+
+class DLL_LINKAGE CWillLastDays
+{
+public:
+	int daysRequested;
+
+	bool operator()(const Bonus *bonus) const
+	{
+		if(daysRequested <= 0 || Bonus::Permanent(bonus) || Bonus::OneBattle(bonus))
+			return true;
+		else if(Bonus::OneDay(bonus))
+			return false;
+		else if(Bonus::NDays(bonus) || Bonus::OneWeek(bonus))
+		{
+			return bonus->turnsRemain > daysRequested;
+		}
+
+		return false; // TODO: ONE_WEEK need support for turnsRemain, but for now we'll exclude all unhandled durations
+	}
+	CWillLastDays& operator()(const int &setVal)
+	{
+		daysRequested = setVal;
+		return *this;
+	}
+};
+
+
+namespace Selector
+{
+	extern DLL_LINKAGE CSelectFieldEqual<Bonus::BonusType> & type();
+	extern DLL_LINKAGE CSelectFieldEqual<TBonusSubtype> & subtype();
+	extern DLL_LINKAGE CSelectFieldEqual<CAddInfo> & info();
+	extern DLL_LINKAGE CSelectFieldEqual<Bonus::BonusSource> & sourceType();
+	extern DLL_LINKAGE CSelectFieldEqual<Bonus::BonusSource> & targetSourceType();
+	extern DLL_LINKAGE CSelectFieldEqual<Bonus::LimitEffect> & effectRange();
+	extern DLL_LINKAGE CWillLastTurns turns;
+	extern DLL_LINKAGE CWillLastDays days;
+
+	CSelector DLL_LINKAGE typeSubtype(Bonus::BonusType Type, TBonusSubtype Subtype);
+	CSelector DLL_LINKAGE typeSubtypeInfo(Bonus::BonusType type, TBonusSubtype subtype, const CAddInfo & info);
+	CSelector DLL_LINKAGE source(Bonus::BonusSource source, ui32 sourceID);
+	CSelector DLL_LINKAGE sourceTypeSel(Bonus::BonusSource source);
+	CSelector DLL_LINKAGE valueType(Bonus::ValueType valType);
+
+	/**
+	 * Selects all bonuses
+	 * Usage example: Selector::all.And(<functor>).And(<functor>)...)
+	 */
+	extern DLL_LINKAGE CSelector all;
+
+	/**
+	 * Selects nothing
+	 * Usage example: Selector::none.Or(<functor>).Or(<functor>)...)
+	 */
+	extern DLL_LINKAGE CSelector none;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 210 - 0
lib/bonuses/CBonusProxy.cpp

@@ -0,0 +1,210 @@
+/*
+ * CBonusProxy.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "BonusList.h"
+#include "CBonusProxy.h"
+#include "IBonusBearer.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+///CBonusProxy
+CBonusProxy::CBonusProxy(const IBonusBearer * Target, CSelector Selector):
+	bonusListCachedLast(0),
+	target(Target),
+	selector(std::move(Selector)),
+	currentBonusListIndex(0)
+{
+}
+
+CBonusProxy::CBonusProxy(const CBonusProxy & other):
+	bonusListCachedLast(other.bonusListCachedLast),
+	target(other.target),
+	selector(other.selector),
+	currentBonusListIndex(other.currentBonusListIndex)
+{
+	bonusList[currentBonusListIndex] = other.bonusList[currentBonusListIndex];
+}
+
+CBonusProxy::CBonusProxy(CBonusProxy && other) noexcept:
+	bonusListCachedLast(0),
+	target(other.target),
+	currentBonusListIndex(0)
+{
+	std::swap(bonusListCachedLast, other.bonusListCachedLast);
+	std::swap(selector, other.selector);
+	std::swap(bonusList, other.bonusList);
+	std::swap(currentBonusListIndex, other.currentBonusListIndex);
+}
+
+CBonusProxy & CBonusProxy::operator=(const CBonusProxy & other)
+{
+	boost::lock_guard<boost::mutex> lock(swapGuard);
+
+	selector = other.selector;
+	swapBonusList(other.bonusList[other.currentBonusListIndex]);
+	bonusListCachedLast = other.bonusListCachedLast;
+
+	return *this;
+}
+
+CBonusProxy & CBonusProxy::operator=(CBonusProxy && other) noexcept
+{
+	std::swap(bonusListCachedLast, other.bonusListCachedLast);
+	std::swap(selector, other.selector);
+	std::swap(bonusList, other.bonusList);
+	std::swap(currentBonusListIndex, other.currentBonusListIndex);
+
+	return *this;
+}
+
+void CBonusProxy::swapBonusList(TConstBonusListPtr other) const
+{
+	// The idea here is to avoid changing active bonusList while it can be read by a different thread.
+	// Because such use of shared ptr is not thread safe
+	// So to avoid this we change the second offline instance and swap active index
+	auto newCurrent = 1 - currentBonusListIndex;
+	bonusList[newCurrent] = std::move(other);
+	currentBonusListIndex = newCurrent;
+}
+
+TConstBonusListPtr CBonusProxy::getBonusList() const
+{
+	auto needUpdateBonusList = [&]() -> bool
+	{
+		return target->getTreeVersion() != bonusListCachedLast || !bonusList[currentBonusListIndex];
+	};
+
+	// avoid locking if everything is up-to-date
+	if(needUpdateBonusList())
+	{
+		boost::lock_guard<boost::mutex>lock(swapGuard);
+
+		if(needUpdateBonusList())
+		{
+			//TODO: support limiters
+			swapBonusList(target->getAllBonuses(selector, Selector::all));
+			bonusListCachedLast = target->getTreeVersion();
+		}
+	}
+
+	return bonusList[currentBonusListIndex];
+}
+
+const BonusList * CBonusProxy::operator->() const
+{
+	return getBonusList().get();
+}
+
+CTotalsProxy::CTotalsProxy(const IBonusBearer * Target, CSelector Selector, int InitialValue):
+	CBonusProxy(Target, std::move(Selector)),
+	initialValue(InitialValue),
+	meleeCachedLast(0),
+	meleeValue(0),
+	rangedCachedLast(0),
+	rangedValue(0)
+{
+}
+
+CTotalsProxy::CTotalsProxy(const CTotalsProxy & other)
+	: CBonusProxy(other),
+	initialValue(other.initialValue),
+	meleeCachedLast(other.meleeCachedLast),
+	meleeValue(other.meleeValue),
+	rangedCachedLast(other.rangedCachedLast),
+	rangedValue(other.rangedValue)
+{
+}
+
+int CTotalsProxy::getValue() const
+{
+	const auto treeVersion = target->getTreeVersion();
+
+	if(treeVersion != valueCachedLast)
+	{
+		auto bonuses = getBonusList();
+
+		value = initialValue + bonuses->totalValue();
+		valueCachedLast = treeVersion;
+	}
+	return value;
+}
+
+int CTotalsProxy::getValueAndList(TConstBonusListPtr & outBonusList) const
+{
+	const auto treeVersion = target->getTreeVersion();
+	outBonusList = getBonusList();
+
+	if(treeVersion != valueCachedLast)
+	{
+		value = initialValue + outBonusList->totalValue();
+		valueCachedLast = treeVersion;
+	}
+	return value;
+}
+
+int CTotalsProxy::getMeleeValue() const
+{
+	static const auto limit = Selector::effectRange()(Bonus::NO_LIMIT).Or(Selector::effectRange()(Bonus::ONLY_MELEE_FIGHT));
+
+	const auto treeVersion = target->getTreeVersion();
+
+	if(treeVersion != meleeCachedLast)
+	{
+		auto bonuses = target->getBonuses(selector, limit);
+		meleeValue = initialValue + bonuses->totalValue();
+		meleeCachedLast = treeVersion;
+	}
+
+	return meleeValue;
+}
+
+int CTotalsProxy::getRangedValue() const
+{
+	static const auto limit = Selector::effectRange()(Bonus::NO_LIMIT).Or(Selector::effectRange()(Bonus::ONLY_DISTANCE_FIGHT));
+
+	const auto treeVersion = target->getTreeVersion();
+
+	if(treeVersion != rangedCachedLast)
+	{
+		auto bonuses = target->getBonuses(selector, limit);
+		rangedValue = initialValue + bonuses->totalValue();
+		rangedCachedLast = treeVersion;
+	}
+
+	return rangedValue;
+}
+
+///CCheckProxy
+CCheckProxy::CCheckProxy(const IBonusBearer * Target, CSelector Selector):
+	target(Target),
+	selector(std::move(Selector)),
+	cachedLast(0),
+	hasBonus(false)
+{
+}
+
+//This constructor should be placed here to avoid side effects
+CCheckProxy::CCheckProxy(const CCheckProxy & other) = default;
+
+bool CCheckProxy::getHasBonus() const
+{
+	const auto treeVersion = target->getTreeVersion();
+
+	if(treeVersion != cachedLast)
+	{
+		hasBonus = target->hasBonus(selector);
+		cachedLast = treeVersion;
+	}
+
+	return hasBonus;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 90 - 0
lib/bonuses/CBonusProxy.h

@@ -0,0 +1,90 @@
+/*
+ * CBonusProxy.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include "Bonus.h"
+#include "BonusSelector.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class DLL_LINKAGE CBonusProxy
+{
+public:
+	CBonusProxy(const IBonusBearer * Target, CSelector Selector);
+	CBonusProxy(const CBonusProxy & other);
+	CBonusProxy(CBonusProxy && other) noexcept;
+
+	CBonusProxy & operator=(CBonusProxy && other) noexcept;
+	CBonusProxy & operator=(const CBonusProxy & other);
+	const BonusList * operator->() const;
+	TConstBonusListPtr getBonusList() const;
+
+protected:
+	CSelector selector;
+	const IBonusBearer * target;
+	mutable int64_t bonusListCachedLast;
+	mutable TConstBonusListPtr bonusList[2];
+	mutable int currentBonusListIndex;
+	mutable boost::mutex swapGuard;
+	void swapBonusList(TConstBonusListPtr other) const;
+};
+
+class DLL_LINKAGE CTotalsProxy : public CBonusProxy
+{
+public:
+	CTotalsProxy(const IBonusBearer * Target, CSelector Selector, int InitialValue);
+	CTotalsProxy(const CTotalsProxy & other);
+	CTotalsProxy(CTotalsProxy && other) = delete;
+
+	CTotalsProxy & operator=(const CTotalsProxy & other) = default;
+	CTotalsProxy & operator=(CTotalsProxy && other) = delete;
+
+	int getMeleeValue() const;
+	int getRangedValue() const;
+	int getValue() const;
+	/**
+	Returns total value of all selected bonuses and sets bonusList as a pointer to the list of selected bonuses
+	@param bonusList is the out list of all selected bonuses
+	@return total value of all selected bonuses and 0 otherwise
+	*/
+	int getValueAndList(TConstBonusListPtr & bonusList) const;
+
+private:
+	int initialValue;
+
+	mutable int64_t valueCachedLast = 0;
+	mutable int value = 0;
+
+	mutable int64_t meleeCachedLast;
+	mutable int meleeValue;
+
+	mutable int64_t rangedCachedLast;
+	mutable int rangedValue;
+};
+
+class DLL_LINKAGE CCheckProxy
+{
+public:
+	CCheckProxy(const IBonusBearer * Target, CSelector Selector);
+	CCheckProxy(const CCheckProxy & other);
+	CCheckProxy& operator= (const CCheckProxy & other) = default;
+
+	bool getHasBonus() const;
+
+private:
+	const IBonusBearer * target;
+	CSelector selector;
+
+	mutable int64_t cachedLast;
+	mutable bool hasBonus;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 689 - 0
lib/bonuses/CBonusSystemNode.cpp

@@ -0,0 +1,689 @@
+/*
+ * CBonusSystemNode.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+
+#include "CBonusSystemNode.h"
+#include "Limiters.h"
+#include "Updaters.h"
+#include "Propagators.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+std::atomic<int64_t> CBonusSystemNode::treeChanged(1);
+constexpr bool CBonusSystemNode::cachingEnabled = true;
+
+#define FOREACH_PARENT(pname) 	TNodes lparents; getParents(lparents); for(CBonusSystemNode *pname : lparents)
+#define FOREACH_RED_CHILD(pname) 	TNodes lchildren; getRedChildren(lchildren); for(CBonusSystemNode *pname : lchildren)
+
+PlayerColor CBonusSystemNode::retrieveNodeOwner(const CBonusSystemNode * node)
+{
+	return node ? node->getOwner() : PlayerColor::CANNOT_DETERMINE;
+}
+
+std::shared_ptr<Bonus> CBonusSystemNode::getBonusLocalFirst(const CSelector & selector)
+{
+	auto ret = bonuses.getFirst(selector);
+	if(ret)
+		return ret;
+
+	FOREACH_PARENT(pname)
+	{
+		ret = pname->getBonusLocalFirst(selector);
+		if (ret)
+			return ret;
+	}
+
+	return nullptr;
+}
+
+std::shared_ptr<const Bonus> CBonusSystemNode::getBonusLocalFirst(const CSelector & selector) const
+{
+	return (const_cast<CBonusSystemNode*>(this))->getBonusLocalFirst(selector);
+}
+
+void CBonusSystemNode::getParents(TCNodes & out) const /*retrieves list of parent nodes (nodes to inherit bonuses from) */
+{
+	for(const auto & elem : parents)
+	{
+		const CBonusSystemNode *parent = elem;
+		out.insert(parent);
+	}
+}
+
+void CBonusSystemNode::getParents(TNodes &out)
+{
+	for (auto & elem : parents)
+	{
+		const CBonusSystemNode *parent = elem;
+		out.insert(const_cast<CBonusSystemNode*>(parent));
+	}
+}
+
+void CBonusSystemNode::getAllParents(TCNodes & out) const //retrieves list of parent nodes (nodes to inherit bonuses from)
+{
+	for(auto * parent : parents)
+	{
+		out.insert(parent);
+		parent->getAllParents(out);
+	}
+}
+
+void CBonusSystemNode::getAllBonusesRec(BonusList &out, const CSelector & selector) const
+{
+	//out has been reserved sufficient capacity at getAllBonuses() call
+
+	BonusList beforeUpdate;
+	TCNodes lparents;
+	getAllParents(lparents);
+
+	if(!lparents.empty())
+	{
+		//estimate on how many bonuses are missing yet - must be positive
+		beforeUpdate.reserve(std::max(out.capacity() - out.size(), bonuses.size()));
+	}
+	else
+	{
+		beforeUpdate.reserve(bonuses.size()); //at most all local bonuses
+	}
+
+	for(const auto * parent : lparents)
+	{
+		parent->getAllBonusesRec(beforeUpdate, selector);
+	}
+	bonuses.getAllBonuses(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;
+
+		//do not add bonus with updater
+		bool bonusExists = false;
+		for(const auto & bonus : out)
+		{
+			if (bonus == updated)
+				bonusExists = true;
+			if (bonus->updater && bonus->updater == updated->updater)
+				bonusExists = true;
+		}
+
+		if (!bonusExists)
+			out.push_back(updated);
+	}
+}
+
+TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root, const std::string &cachingStr) const
+{
+	bool limitOnUs = (!root || root == this); //caching won't work when we want to limit bonuses against an external node
+	if (CBonusSystemNode::cachingEnabled && limitOnUs)
+	{
+		// Exclusive access for one thread
+		boost::lock_guard<boost::mutex> lock(sync);
+
+		// If the bonus system tree changes(state of a single node or the relations to each other) then
+		// cache all bonus objects. Selector objects doesn't matter.
+		if (cachedLast != treeChanged)
+		{
+			BonusList allBonuses;
+			allBonuses.reserve(cachedBonuses.capacity()); //we assume we'll get about the same number of bonuses
+
+			cachedBonuses.clear();
+			cachedRequests.clear();
+
+			getAllBonusesRec(allBonuses, Selector::all);
+			limitBonuses(allBonuses, cachedBonuses);
+			cachedBonuses.stackBonuses();
+
+			cachedLast = treeChanged;
+		}
+
+		// If a bonus system request comes with a caching string then look up in the map if there are any
+		// pre-calculated bonus results. Limiters can't be cached so they have to be calculated.
+		if(!cachingStr.empty())
+		{
+			auto it = cachedRequests.find(cachingStr);
+			if(it != cachedRequests.end())
+			{
+				//Cached list contains bonuses for our query with applied limiters
+				return it->second;
+			}
+		}
+
+		//We still don't have the bonuses (didn't returned them from cache)
+		//Perform bonus selection
+		auto ret = std::make_shared<BonusList>();
+		cachedBonuses.getBonuses(*ret, selector, limit);
+
+		// Save the results in the cache
+		if(!cachingStr.empty())
+			cachedRequests[cachingStr] = ret;
+
+		return ret;
+	}
+	else
+	{
+		return getAllBonusesWithoutCaching(selector, limit, root);
+	}
+}
+
+TConstBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root) const
+{
+	auto ret = std::make_shared<BonusList>();
+
+	// Get bonus results without caching enabled.
+	BonusList beforeLimiting;
+	BonusList afterLimiting;
+	getAllBonusesRec(beforeLimiting, selector);
+
+	if(!root || root == this)
+	{
+		limitBonuses(beforeLimiting, afterLimiting);
+	}
+	else if(root)
+	{
+		//We want to limit our query against an external node. We get all its bonuses,
+		// add the ones we're considering and see if they're cut out by limiters
+		BonusList rootBonuses;
+		BonusList limitedRootBonuses;
+		getAllBonusesRec(rootBonuses, selector);
+
+		for(const auto & b : beforeLimiting)
+			rootBonuses.push_back(b);
+
+		root->limitBonuses(rootBonuses, limitedRootBonuses);
+
+		for(const auto & b : beforeLimiting)
+			if(vstd::contains(limitedRootBonuses, b))
+				afterLimiting.push_back(b);
+
+	}
+	afterLimiting.getBonuses(*ret, selector, limit);
+	ret->stackBonuses();
+	return ret;
+}
+
+std::shared_ptr<Bonus> CBonusSystemNode::getUpdatedBonus(const std::shared_ptr<Bonus> & b, const TUpdaterPtr & updater) const
+{
+	assert(updater);
+	return updater->createUpdatedBonus(b, * this);
+}
+
+CBonusSystemNode::CBonusSystemNode()
+	:CBonusSystemNode(false)
+{
+}
+
+CBonusSystemNode::CBonusSystemNode(bool isHypotetic):
+	bonuses(true),
+	exportedBonuses(true),
+	nodeType(UNKNOWN),
+	cachedLast(0),
+	isHypotheticNode(isHypotetic)
+{
+}
+
+CBonusSystemNode::CBonusSystemNode(ENodeTypes NodeType):
+	bonuses(true),
+	exportedBonuses(true),
+	nodeType(NodeType),
+	cachedLast(0),
+	isHypotheticNode(false)
+{
+}
+
+CBonusSystemNode::CBonusSystemNode(CBonusSystemNode && other) noexcept:
+	bonuses(std::move(other.bonuses)),
+	exportedBonuses(std::move(other.exportedBonuses)),
+	nodeType(other.nodeType),
+	description(other.description),
+	cachedLast(0),
+	isHypotheticNode(other.isHypotheticNode)
+{
+	std::swap(parents, other.parents);
+	std::swap(children, other.children);
+
+	//fixing bonus tree without recalculation
+
+	if(!isHypothetic())
+	{
+		for(CBonusSystemNode * n : parents)
+		{
+			n->children -= &other;
+			n->children.push_back(this);
+		}
+	}
+
+	for(CBonusSystemNode * n : children)
+	{
+		n->parents -= &other;
+		n->parents.push_back(this);
+	}
+
+	//cache ignored
+
+	//cachedBonuses
+	//cachedRequests
+}
+
+CBonusSystemNode::~CBonusSystemNode()
+{
+	detachFromAll();
+
+	if(!children.empty())
+	{
+		while(!children.empty())
+			children.front()->detachFrom(*this);
+	}
+}
+
+void CBonusSystemNode::attachTo(CBonusSystemNode & parent)
+{
+	assert(!vstd::contains(parents, &parent));
+	parents.push_back(&parent);
+
+	if(!isHypothetic())
+	{
+		if(parent.actsAsBonusSourceOnly())
+			parent.newRedDescendant(*this);
+		else
+			newRedDescendant(parent);
+
+		parent.newChildAttached(*this);
+	}
+
+	CBonusSystemNode::treeHasChanged();
+}
+
+void CBonusSystemNode::detachFrom(CBonusSystemNode & parent)
+{
+	assert(vstd::contains(parents, &parent));
+
+	if(!isHypothetic())
+	{
+		if(parent.actsAsBonusSourceOnly())
+			parent.removedRedDescendant(*this);
+		else
+			removedRedDescendant(parent);
+	}
+
+	if (vstd::contains(parents, &parent))
+	{
+		parents -= &parent;
+	}
+	else
+	{
+		logBonus->error("Error on Detach. Node %s (nodeType=%d) has not parent %s (nodeType=%d)"
+			, nodeShortInfo(), nodeType, parent.nodeShortInfo(), parent.nodeType);
+	}
+
+	if(!isHypothetic())
+	{
+		parent.childDetached(*this);
+	}
+	CBonusSystemNode::treeHasChanged();
+}
+
+void CBonusSystemNode::removeBonusesRecursive(const CSelector & s)
+{
+	removeBonuses(s);
+	for(CBonusSystemNode * child : children)
+		child->removeBonusesRecursive(s);
+}
+
+void CBonusSystemNode::reduceBonusDurations(const CSelector &s)
+{
+	BonusList bl;
+	exportedBonuses.getBonuses(bl, s, Selector::all);
+	for(const auto & b : bl)
+	{
+		b->turnsRemain--;
+		if(b->turnsRemain <= 0)
+			removeBonus(b);
+	}
+
+	for(CBonusSystemNode *child : children)
+		child->reduceBonusDurations(s);
+}
+
+void CBonusSystemNode::addNewBonus(const std::shared_ptr<Bonus>& b)
+{
+	//turnsRemain shouldn't be zero for following durations
+	if(Bonus::NTurns(b.get()) || Bonus::NDays(b.get()) || Bonus::OneWeek(b.get()))
+	{
+		assert(b->turnsRemain);
+	}
+
+	assert(!vstd::contains(exportedBonuses, b));
+	exportedBonuses.push_back(b);
+	exportBonus(b);
+	CBonusSystemNode::treeHasChanged();
+}
+
+void CBonusSystemNode::accumulateBonus(const std::shared_ptr<Bonus>& b)
+{
+	auto bonus = exportedBonuses.getFirst(Selector::typeSubtype(b->type, b->subtype)); //only local bonuses are interesting //TODO: what about value type?
+	if(bonus)
+		bonus->val += b->val;
+	else
+		addNewBonus(std::make_shared<Bonus>(*b)); //duplicate needed, original may get destroyed
+}
+
+void CBonusSystemNode::removeBonus(const std::shared_ptr<Bonus>& b)
+{
+	exportedBonuses -= b;
+	if(b->propagator)
+		unpropagateBonus(b);
+	else
+		bonuses -= b;
+	CBonusSystemNode::treeHasChanged();
+}
+
+void CBonusSystemNode::removeBonuses(const CSelector & selector)
+{
+	BonusList toRemove;
+	exportedBonuses.getBonuses(toRemove, selector, Selector::all);
+	for(const auto & bonus : toRemove)
+		removeBonus(bonus);
+}
+
+bool CBonusSystemNode::actsAsBonusSourceOnly() const
+{
+	switch(nodeType)
+	{
+	case CREATURE:
+	case ARTIFACT:
+	case ARTIFACT_INSTANCE:
+		return true;
+	default:
+		return false;
+	}
+}
+
+void CBonusSystemNode::propagateBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & source)
+{
+	if(b->propagator->shouldBeAttached(this))
+	{
+		auto propagated = b->propagationUpdater 
+			? source.getUpdatedBonus(b, b->propagationUpdater)
+			: b;
+		bonuses.push_back(propagated);
+		logBonus->trace("#$# %s #propagated to# %s",  propagated->Description(), nodeName());
+	}
+
+	FOREACH_RED_CHILD(child)
+		child->propagateBonus(b, source);
+}
+
+void CBonusSystemNode::unpropagateBonus(const std::shared_ptr<Bonus> & b)
+{
+	if(b->propagator->shouldBeAttached(this))
+	{
+		bonuses -= b;
+		logBonus->trace("#$# %s #is no longer propagated to# %s",  b->Description(), nodeName());
+	}
+
+	FOREACH_RED_CHILD(child)
+		child->unpropagateBonus(b);
+}
+
+void CBonusSystemNode::newChildAttached(CBonusSystemNode & child)
+{
+	assert(!vstd::contains(children, &child));
+	children.push_back(&child);
+}
+
+void CBonusSystemNode::childDetached(CBonusSystemNode & child)
+{
+	if(vstd::contains(children, &child))
+		children -= &child;
+	else
+	{
+		logBonus->error("Error on Detach. Node %s (nodeType=%d) is not a child of %s (nodeType=%d)"
+			, child.nodeShortInfo(), child.nodeType, nodeShortInfo(), nodeType);
+	}
+}
+
+void CBonusSystemNode::detachFromAll()
+{
+	while(!parents.empty())
+		detachFrom(*parents.front());
+}
+
+bool CBonusSystemNode::isIndependentNode() const
+{
+	return parents.empty() && children.empty();
+}
+
+std::string CBonusSystemNode::nodeName() const
+{
+	return !description.empty()
+		? description
+		: std::string("Bonus system node of type ") + typeid(*this).name();
+}
+
+std::string CBonusSystemNode::nodeShortInfo() const
+{
+	std::ostringstream str;
+	str << "'" << typeid(* this).name() << "'";
+	description.length() > 0 
+		? str << " (" << description << ")"
+		: str << " (no description)";
+	return str.str();
+}
+
+void CBonusSystemNode::deserializationFix()
+{
+	exportBonuses();
+
+}
+
+void CBonusSystemNode::getRedParents(TNodes & out)
+{
+	FOREACH_PARENT(pname)
+	{
+		if(pname->actsAsBonusSourceOnly())
+		{
+			out.insert(pname);
+		}
+	}
+
+	if(!actsAsBonusSourceOnly())
+	{
+		for(CBonusSystemNode *child : children)
+		{
+			out.insert(child);
+		}
+	}
+}
+
+void CBonusSystemNode::getRedChildren(TNodes &out)
+{
+	FOREACH_PARENT(pname)
+	{
+		if(!pname->actsAsBonusSourceOnly())
+		{
+			out.insert(pname);
+		}
+	}
+
+	if(actsAsBonusSourceOnly())
+	{
+		for(CBonusSystemNode *child : children)
+		{
+			out.insert(child);
+		}
+	}
+}
+
+void CBonusSystemNode::newRedDescendant(CBonusSystemNode & descendant)
+{
+	for(const auto & b : exportedBonuses)
+	{
+		if(b->propagator)
+			descendant.propagateBonus(b, *this);
+	}
+	TNodes redParents;
+	getRedAncestors(redParents); //get all red parents recursively
+
+	for(auto * parent : redParents)
+	{
+		for(const auto & b : parent->exportedBonuses)
+		{
+			if(b->propagator)
+				descendant.propagateBonus(b, *this);
+		}
+	}
+}
+
+void CBonusSystemNode::removedRedDescendant(CBonusSystemNode & descendant)
+{
+	for(const auto & b : exportedBonuses)
+		if(b->propagator)
+			descendant.unpropagateBonus(b);
+
+	TNodes redParents;
+	getRedAncestors(redParents); //get all red parents recursively
+
+	for(auto * parent : redParents)
+	{
+		for(const auto & b : parent->exportedBonuses)
+			if(b->propagator)
+				descendant.unpropagateBonus(b);
+	}
+}
+
+void CBonusSystemNode::getRedAncestors(TNodes &out)
+{
+	getRedParents(out);
+
+	TNodes redParents; 
+	getRedParents(redParents);
+
+	for(CBonusSystemNode * parent : redParents)
+		parent->getRedAncestors(out);
+}
+
+void CBonusSystemNode::exportBonus(const std::shared_ptr<Bonus> & b)
+{
+	if(b->propagator)
+		propagateBonus(b, *this);
+	else
+		bonuses.push_back(b);
+
+	CBonusSystemNode::treeHasChanged();
+}
+
+void CBonusSystemNode::exportBonuses()
+{
+	for(const auto & b : exportedBonuses)
+		exportBonus(b);
+}
+
+CBonusSystemNode::ENodeTypes CBonusSystemNode::getNodeType() const
+{
+	return nodeType;
+}
+
+const BonusList& CBonusSystemNode::getBonusList() const
+{
+	return bonuses;
+}
+
+const TNodesVector& CBonusSystemNode::getParentNodes() const
+{
+	return parents;
+}
+
+const TNodesVector& CBonusSystemNode::getChildrenNodes() const
+{
+	return children;
+}
+
+void CBonusSystemNode::setNodeType(CBonusSystemNode::ENodeTypes type)
+{
+	nodeType = type;
+}
+
+BonusList & CBonusSystemNode::getExportedBonusList()
+{
+	return exportedBonuses;
+}
+
+const BonusList & CBonusSystemNode::getExportedBonusList() const
+{
+	return exportedBonuses;
+}
+
+const std::string& CBonusSystemNode::getDescription() const
+{
+	return description;
+}
+
+void CBonusSystemNode::setDescription(const std::string &description)
+{
+	this->description = description;
+}
+
+void CBonusSystemNode::limitBonuses(const BonusList &allBonuses, BonusList &out) const
+{
+	assert(&allBonuses != &out); //todo should it work in-place?
+
+	BonusList undecided = allBonuses;
+	BonusList & accepted = out;
+
+	while(true)
+	{
+		int undecidedCount = static_cast<int>(undecided.size());
+		for(int i = 0; i < undecided.size(); i++)
+		{
+			auto b = undecided[i];
+			BonusLimitationContext context = {*b, *this, out, undecided};
+			auto decision = b->limiter ? b->limiter->limit(context) : ILimiter::EDecision::ACCEPT; //bonuses without limiters will be accepted by default
+			if(decision == ILimiter::EDecision::DISCARD)
+			{
+				undecided.erase(i);
+				i--; continue;
+			}
+			else if(decision == ILimiter::EDecision::ACCEPT)
+			{
+				accepted.push_back(b);
+				undecided.erase(i);
+				i--; continue;
+			}
+			else
+				assert(decision == ILimiter::EDecision::NOT_SURE);
+		}
+
+		if(undecided.size() == undecidedCount) //we haven't moved a single bonus -> limiters reached a stable state
+			return;
+	}
+}
+
+TBonusListPtr CBonusSystemNode::limitBonuses(const BonusList &allBonuses) const
+{
+	auto ret = std::make_shared<BonusList>();
+	limitBonuses(allBonuses, *ret);
+	return ret;
+}
+
+void CBonusSystemNode::treeHasChanged()
+{
+	treeChanged++;
+}
+
+int64_t CBonusSystemNode::getTreeVersion() const
+{
+	return treeChanged;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 140 - 0
lib/bonuses/CBonusSystemNode.h

@@ -0,0 +1,140 @@
+/*
+ * CBonusSystemNode.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "BonusList.h"
+#include "IBonusBearer.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+using TNodes = std::set<CBonusSystemNode *>;
+using TCNodes = std::set<const CBonusSystemNode *>;
+using TNodesVector = std::vector<CBonusSystemNode *>;
+
+class DLL_LINKAGE CBonusSystemNode : public virtual IBonusBearer, public boost::noncopyable
+{
+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
+	};
+private:
+	BonusList bonuses; //wielded bonuses (local or up-propagated here)
+	BonusList exportedBonuses; //bonuses coming from this node (wielded or propagated away)
+
+	TNodesVector parents; //parents -> we inherit bonuses from them, we may attach our bonuses to them
+	TNodesVector children;
+
+	ENodeTypes nodeType;
+	std::string description;
+	bool isHypotheticNode;
+
+	static const bool cachingEnabled;
+	mutable BonusList cachedBonuses;
+	mutable int64_t cachedLast;
+	static std::atomic<int64_t> treeChanged;
+
+	// Setting a value to cachingStr before getting any bonuses caches the result for later requests.
+	// This string needs to be unique, that's why it has to be setted in the following manner:
+	// [property key]_[value] => only for selector
+	mutable std::map<std::string, TBonusListPtr > cachedRequests;
+	mutable boost::mutex sync;
+
+	void getAllBonusesRec(BonusList &out, const CSelector & selector) const;
+	TConstBonusListPtr getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr) const;
+	std::shared_ptr<Bonus> getUpdatedBonus(const std::shared_ptr<Bonus> & b, const TUpdaterPtr & updater) const;
+
+public:
+	explicit CBonusSystemNode();
+	explicit CBonusSystemNode(bool isHypotetic);
+	explicit CBonusSystemNode(ENodeTypes NodeType);
+	CBonusSystemNode(CBonusSystemNode && other) noexcept;
+	virtual ~CBonusSystemNode();
+
+	void limitBonuses(const BonusList &allBonuses, BonusList &out) const; //out will bo populed with bonuses that are not limited here
+	TBonusListPtr limitBonuses(const BonusList &allBonuses) const; //same as above, returns out by val for convienence
+	TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override;
+	void getParents(TCNodes &out) const;  //retrieves list of parent nodes (nodes to inherit bonuses from),
+	std::shared_ptr<const Bonus> getBonusLocalFirst(const CSelector & selector) const;
+
+	//non-const interface
+	void getParents(TNodes &out);  //retrieves list of parent nodes (nodes to inherit bonuses from)
+
+	void getRedParents(TNodes &out);  //retrieves list of red parent nodes (nodes bonuses propagate from)
+	void getRedAncestors(TNodes &out);
+	void getRedChildren(TNodes &out);
+	void getAllParents(TCNodes & out) const;
+	static PlayerColor retrieveNodeOwner(const CBonusSystemNode * node);
+	std::shared_ptr<Bonus> getBonusLocalFirst(const CSelector & selector);
+
+	void attachTo(CBonusSystemNode & parent);
+	void detachFrom(CBonusSystemNode & parent);
+	void detachFromAll();
+	virtual void addNewBonus(const std::shared_ptr<Bonus>& b);
+	void accumulateBonus(const std::shared_ptr<Bonus>& b); //add value of bonus with same type/subtype or create new
+
+	void newChildAttached(CBonusSystemNode & child);
+	void childDetached(CBonusSystemNode & child);
+	void propagateBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & source);
+	void unpropagateBonus(const std::shared_ptr<Bonus> & b);
+	void removeBonus(const std::shared_ptr<Bonus>& b);
+	void removeBonuses(const CSelector & selector);
+	void removeBonusesRecursive(const CSelector & s);
+	void newRedDescendant(CBonusSystemNode & descendant); //propagation needed
+	void removedRedDescendant(CBonusSystemNode & descendant); //de-propagation needed
+
+	bool isIndependentNode() const; //node is independent when it has no parents nor children
+	bool actsAsBonusSourceOnly() const;
+	///updates count of remaining turns and removes outdated bonuses by selector
+	void reduceBonusDurations(const CSelector &s);
+	virtual std::string bonusToString(const std::shared_ptr<Bonus>& bonus, bool description) const {return "";}; //description or bonus name
+	virtual std::string nodeName() const;
+	virtual std::string nodeShortInfo() const;
+	bool isHypothetic() const { return isHypotheticNode; }
+
+	void deserializationFix();
+	void exportBonus(const std::shared_ptr<Bonus> & b);
+	void exportBonuses();
+
+	const BonusList &getBonusList() const;
+	BonusList & getExportedBonusList();
+	const BonusList & getExportedBonusList() const;
+	CBonusSystemNode::ENodeTypes getNodeType() const;
+	void setNodeType(CBonusSystemNode::ENodeTypes type);
+	const TNodesVector &getParentNodes() const;
+	const TNodesVector &getChildrenNodes() const;
+	const std::string &getDescription() const;
+	void setDescription(const std::string &description);
+
+	static void treeHasChanged();
+
+	int64_t getTreeVersion() const override;
+
+	virtual PlayerColor getOwner() const
+	{
+		return PlayerColor::NEUTRAL;
+	}
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+//		h & bonuses;
+		h & nodeType;
+		h & exportedBonuses;
+		h & description;
+		BONUS_TREE_DESERIALIZATION_FIX
+		//h & parents & children;
+	}
+
+	friend class CBonusProxy;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 82 - 0
lib/bonuses/IBonusBearer.cpp

@@ -0,0 +1,82 @@
+/*
+ * IBonusBearer.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+
+#include "CBonusSystemNode.h"
+#include "BonusList.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+int IBonusBearer::valOfBonuses(Bonus::BonusType type, int subtype) const
+{
+	//This part is performance-critical
+	std::string cachingStr = "type_" + std::to_string(static_cast<int>(type)) + "_" + std::to_string(subtype);
+
+	CSelector s = Selector::type()(type);
+	if(subtype != -1)
+		s = s.And(Selector::subtype()(subtype));
+
+	return valOfBonuses(s, cachingStr);
+}
+
+int IBonusBearer::valOfBonuses(const CSelector &selector, const std::string &cachingStr) const
+{
+	CSelector limit = nullptr;
+	TConstBonusListPtr hlp = getAllBonuses(selector, limit, nullptr, cachingStr);
+	return hlp->totalValue();
+}
+bool IBonusBearer::hasBonus(const CSelector &selector, const std::string &cachingStr) const
+{
+	//TODO: We don't need to count all bonuses and could break on first matching
+	return getBonuses(selector, cachingStr)->size() > 0;
+}
+
+bool IBonusBearer::hasBonus(const CSelector &selector, const CSelector &limit, const std::string &cachingStr) const
+{
+	return getBonuses(selector, limit, cachingStr)->size() > 0;
+}
+
+bool IBonusBearer::hasBonusOfType(Bonus::BonusType type, int subtype) const
+{
+	//This part is performance-ciritcal
+	std::string cachingStr = "type_" + std::to_string(static_cast<int>(type)) + "_" + std::to_string(subtype);
+
+	CSelector s = Selector::type()(type);
+	if(subtype != -1)
+		s = s.And(Selector::subtype()(subtype));
+
+	return hasBonus(s, cachingStr);
+}
+
+TConstBonusListPtr IBonusBearer::getBonuses(const CSelector &selector, const std::string &cachingStr) const
+{
+	return getAllBonuses(selector, nullptr, nullptr, cachingStr);
+}
+
+TConstBonusListPtr IBonusBearer::getBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr) const
+{
+	return getAllBonuses(selector, limit, nullptr, cachingStr);
+}
+
+bool IBonusBearer::hasBonusFrom(Bonus::BonusSource source, ui32 sourceID) const
+{
+	boost::format fmt("source_%did_%d");
+	fmt % static_cast<int>(source) % sourceID;
+
+	return hasBonus(Selector::source(source,sourceID), fmt.str());
+}
+std::shared_ptr<const Bonus> IBonusBearer::getBonus(const CSelector &selector) const
+{
+	auto bonuses = getAllBonuses(selector, Selector::all);
+	return bonuses->getFirst(Selector::all);
+}
+
+VCMI_LIB_NAMESPACE_END

+ 43 - 0
lib/bonuses/IBonusBearer.h

@@ -0,0 +1,43 @@
+
+/*
+ * IBonusBearer.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "Bonus.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class DLL_LINKAGE IBonusBearer
+{
+public:
+	//new bonusing node interface
+	// * selector is predicate that tests if HeroBonus matches our criteria
+	// * root is node on which call was made (nullptr will be replaced with this)
+	//interface
+	IBonusBearer() = default;
+	virtual ~IBonusBearer() = default;
+	virtual TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const = 0;
+	int valOfBonuses(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;
+
+	std::shared_ptr<const Bonus> getBonus(const CSelector &selector) const; //returns any bonus visible on node that matches (or nullptr if none matches)
+
+	//Optimized interface (with auto-caching)
+	int valOfBonuses(Bonus::BonusType type, int subtype = -1) const; //subtype -> subtype of bonus, if -1 then anyt;
+	bool hasBonusOfType(Bonus::BonusType type, int subtype = -1) const;//determines if hero has a bonus of given type (and optionally subtype)
+	bool hasBonusFrom(Bonus::BonusSource source, ui32 sourceID) const;
+
+	virtual int64_t getTreeVersion() const = 0;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 541 - 0
lib/bonuses/Limiters.cpp

@@ -0,0 +1,541 @@
+/*
+ * Limiters.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "Limiters.h"
+
+#include "../VCMI_Lib.h"
+#include "../spells/CSpellHandler.h"
+#include "../CCreatureHandler.h"
+#include "../CCreatureSet.h"
+#include "../CHeroHandler.h"
+#include "../CTownHandler.h"
+#include "../CGeneralTextHandler.h"
+#include "../CSkillHandler.h"
+#include "../CStack.h"
+#include "../CArtHandler.h"
+#include "../CModHandler.h"
+#include "../TerrainHandler.h"
+#include "../StringConstants.h"
+#include "../battle/BattleInfo.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+const std::map<std::string, TLimiterPtr> bonusLimiterMap =
+{
+	{"SHOOTER_ONLY", std::make_shared<HasAnotherBonusLimiter>(Bonus::SHOOTER)},
+	{"DRAGON_NATURE", std::make_shared<HasAnotherBonusLimiter>(Bonus::DRAGON_NATURE)},
+	{"IS_UNDEAD", std::make_shared<HasAnotherBonusLimiter>(Bonus::UNDEAD)},
+	{"CREATURE_NATIVE_TERRAIN", std::make_shared<CreatureTerrainLimiter>()},
+	{"CREATURE_FACTION", std::make_shared<AllOfLimiter>(std::initializer_list<TLimiterPtr>{std::make_shared<CreatureLevelLimiter>(), std::make_shared<FactionLimiter>()})},
+	{"SAME_FACTION", std::make_shared<FactionLimiter>()},
+	{"CREATURES_ONLY", std::make_shared<CreatureLevelLimiter>()},
+	{"OPPOSITE_SIDE", std::make_shared<OppositeSideLimiter>()},
+};
+
+static const CStack * retrieveStackBattle(const CBonusSystemNode * node)
+{
+	switch(node->getNodeType())
+	{
+	case CBonusSystemNode::STACK_BATTLE:
+		return dynamic_cast<const CStack *>(node);
+	default:
+		return nullptr;
+	}
+}
+
+static const CStackInstance * retrieveStackInstance(const CBonusSystemNode * node)
+{
+	switch(node->getNodeType())
+	{
+	case CBonusSystemNode::STACK_INSTANCE:
+		return (dynamic_cast<const CStackInstance *>(node));
+	case CBonusSystemNode::STACK_BATTLE:
+		return (dynamic_cast<const CStack *>(node))->base;
+	default:
+		return nullptr;
+	}
+}
+
+static const CCreature * retrieveCreature(const CBonusSystemNode *node)
+{
+	switch(node->getNodeType())
+	{
+	case CBonusSystemNode::CREATURE:
+		return (dynamic_cast<const CCreature *>(node));
+	case CBonusSystemNode::STACK_BATTLE:
+		return (dynamic_cast<const CStack *>(node))->unitType();
+	default:
+		const CStackInstance * csi = retrieveStackInstance(node);
+		if(csi)
+			return csi->type;
+		return nullptr;
+	}
+}
+
+ILimiter::EDecision ILimiter::limit(const BonusLimitationContext &context) const /*return true to drop the bonus */
+{
+	return ILimiter::EDecision::ACCEPT;
+}
+
+std::string ILimiter::toString() const
+{
+	return typeid(*this).name();
+}
+
+JsonNode ILimiter::toJsonNode() const
+{
+	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	root["type"].String() = toString();
+	return root;
+}
+
+ILimiter::EDecision CCreatureTypeLimiter::limit(const BonusLimitationContext &context) const
+{
+	const CCreature *c = retrieveCreature(&context.node);
+	if(!c)
+		return ILimiter::EDecision::DISCARD;
+	
+	auto accept =  c->getId() == creature->getId() || (includeUpgrades && creature->isMyUpgrade(c));
+	return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
+	//drop bonus if it's not our creature and (we don`t check upgrades or its not our upgrade)
+}
+
+CCreatureTypeLimiter::CCreatureTypeLimiter(const CCreature & creature_, bool IncludeUpgrades)
+	: creature(&creature_), includeUpgrades(IncludeUpgrades)
+{
+}
+
+void CCreatureTypeLimiter::setCreature(const CreatureID & id)
+{
+	creature = VLC->creh->objects[id];
+}
+
+std::string CCreatureTypeLimiter::toString() const
+{
+	boost::format fmt("CCreatureTypeLimiter(creature=%s, includeUpgrades=%s)");
+	fmt % creature->getJsonKey() % (includeUpgrades ? "true" : "false");
+	return fmt.str();
+}
+
+JsonNode CCreatureTypeLimiter::toJsonNode() const
+{
+	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+
+	root["type"].String() = "CREATURE_TYPE_LIMITER";
+	root["parameters"].Vector().push_back(JsonUtils::stringNode(creature->getJsonKey()));
+	root["parameters"].Vector().push_back(JsonUtils::boolNode(includeUpgrades));
+
+	return root;
+}
+
+HasAnotherBonusLimiter::HasAnotherBonusLimiter( Bonus::BonusType bonus )
+	: type(bonus), subtype(0), isSubtypeRelevant(false), isSourceRelevant(false), isSourceIDRelevant(false)
+{
+}
+
+HasAnotherBonusLimiter::HasAnotherBonusLimiter( Bonus::BonusType bonus, TBonusSubtype _subtype )
+	: type(bonus), subtype(_subtype), isSubtypeRelevant(true), isSourceRelevant(false), isSourceIDRelevant(false)
+{
+}
+
+HasAnotherBonusLimiter::HasAnotherBonusLimiter(Bonus::BonusType bonus, Bonus::BonusSource src)
+	: type(bonus), source(src), isSubtypeRelevant(false), isSourceRelevant(true), isSourceIDRelevant(false)
+{
+}
+
+HasAnotherBonusLimiter::HasAnotherBonusLimiter(Bonus::BonusType bonus, TBonusSubtype _subtype, Bonus::BonusSource src)
+	: type(bonus), subtype(_subtype), isSubtypeRelevant(true), source(src), isSourceRelevant(true), isSourceIDRelevant(false)
+{
+}
+
+ILimiter::EDecision HasAnotherBonusLimiter::limit(const BonusLimitationContext &context) const
+{
+	//TODO: proper selector config with parsing of JSON
+	auto mySelector = Selector::type()(type);
+
+	if(isSubtypeRelevant)
+		mySelector = mySelector.And(Selector::subtype()(subtype));
+	if(isSourceRelevant && isSourceIDRelevant)
+		mySelector = mySelector.And(Selector::source(source, sid));
+	else if (isSourceRelevant)
+		mySelector = mySelector.And(Selector::sourceTypeSel(source));
+
+	//if we have a bonus of required type accepted, limiter should accept also this bonus
+	if(context.alreadyAccepted.getFirst(mySelector))
+		return ILimiter::EDecision::ACCEPT;
+
+	//if there are no matching bonuses pending, we can (and must) reject right away
+	if(!context.stillUndecided.getFirst(mySelector))
+		return ILimiter::EDecision::DISCARD;
+
+	//do not accept for now but it may change if more bonuses gets included
+	return ILimiter::EDecision::NOT_SURE;
+}
+
+std::string HasAnotherBonusLimiter::toString() const
+{
+	std::string typeName = vstd::findKey(bonusNameMap, type);
+	if(isSubtypeRelevant)
+	{
+		boost::format fmt("HasAnotherBonusLimiter(type=%s, subtype=%d)");
+		fmt % typeName % subtype;
+		return fmt.str();
+	}
+	else
+	{
+		boost::format fmt("HasAnotherBonusLimiter(type=%s)");
+		fmt % typeName;
+		return fmt.str();
+	}
+}
+
+JsonNode HasAnotherBonusLimiter::toJsonNode() const
+{
+	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	std::string typeName = vstd::findKey(bonusNameMap, type);
+	auto sourceTypeName = vstd::findKey(bonusSourceMap, source);
+
+	root["type"].String() = "HAS_ANOTHER_BONUS_LIMITER";
+	root["parameters"].Vector().push_back(JsonUtils::stringNode(typeName));
+	if(isSubtypeRelevant)
+		root["parameters"].Vector().push_back(JsonUtils::intNode(subtype));
+	if(isSourceRelevant)
+		root["parameters"].Vector().push_back(JsonUtils::stringNode(sourceTypeName));
+
+	return root;
+}
+
+ILimiter::EDecision UnitOnHexLimiter::limit(const BonusLimitationContext &context) const
+{
+	const auto * stack = retrieveStackBattle(&context.node);
+	if(!stack)
+		return ILimiter::EDecision::DISCARD;
+
+	auto accept = false;
+	for (const auto & hex : stack->getHexes())
+		accept |= !!applicableHexes.count(hex);
+
+	return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
+}
+
+UnitOnHexLimiter::UnitOnHexLimiter(const std::set<BattleHex> & applicableHexes):
+	applicableHexes(applicableHexes)
+{
+}
+
+JsonNode UnitOnHexLimiter::toJsonNode() const
+{
+	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+
+	root["type"].String() = "UNIT_ON_HEXES";
+	for(const auto & hex : applicableHexes)
+		root["parameters"].Vector().push_back(JsonUtils::intNode(hex));
+
+	return root;
+}
+
+CreatureTerrainLimiter::CreatureTerrainLimiter()
+	: terrainType(ETerrainId::NATIVE_TERRAIN)
+{
+}
+
+CreatureTerrainLimiter::CreatureTerrainLimiter(TerrainId terrain):
+	terrainType(terrain)
+{
+}
+
+ILimiter::EDecision CreatureTerrainLimiter::limit(const BonusLimitationContext &context) const
+{
+	const CStack *stack = retrieveStackBattle(&context.node);
+	if(stack)
+	{
+		if (terrainType == ETerrainId::NATIVE_TERRAIN && stack->isOnNativeTerrain())//terrainType not specified = native
+			return ILimiter::EDecision::ACCEPT;
+
+		if(terrainType != ETerrainId::NATIVE_TERRAIN && stack->isOnTerrain(terrainType))
+			return ILimiter::EDecision::ACCEPT;
+
+	}
+	return ILimiter::EDecision::DISCARD;
+	//TODO neutral creatues
+}
+
+std::string CreatureTerrainLimiter::toString() const
+{
+	boost::format fmt("CreatureTerrainLimiter(terrainType=%s)");
+	auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->getJsonKey();
+	fmt % (terrainType == ETerrainId::NATIVE_TERRAIN ? "native" : terrainName);
+	return fmt.str();
+}
+
+JsonNode CreatureTerrainLimiter::toJsonNode() const
+{
+	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+
+	root["type"].String() = "CREATURE_TERRAIN_LIMITER";
+	auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->getJsonKey();
+	root["parameters"].Vector().push_back(JsonUtils::stringNode(terrainName));
+
+	return root;
+}
+
+FactionLimiter::FactionLimiter(FactionID creatureFaction)
+	: faction(creatureFaction)
+{
+}
+
+ILimiter::EDecision FactionLimiter::limit(const BonusLimitationContext &context) const
+{
+	const auto * bearer = dynamic_cast<const INativeTerrainProvider*>(&context.node);
+
+	if(bearer)
+	{
+		if(faction != FactionID::DEFAULT)
+			return bearer->getFaction() == faction ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
+
+		switch(context.b.source)
+		{
+			case Bonus::CREATURE_ABILITY:
+				return bearer->getFaction() == CreatureID(context.b.sid).toCreature()->getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
+			
+			case Bonus::TOWN_STRUCTURE:
+				return bearer->getFaction() == FactionID(Bonus::getHighFromSid32(context.b.sid)) ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
+
+			//TODO: other sources of bonuses
+		}
+	}
+	return ILimiter::EDecision::DISCARD; //Discard by default
+}
+
+std::string FactionLimiter::toString() const
+{
+	boost::format fmt("FactionLimiter(faction=%s)");
+	fmt % VLC->factions()->getByIndex(faction)->getJsonKey();
+	return fmt.str();
+}
+
+JsonNode FactionLimiter::toJsonNode() const
+{
+	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+
+	root["type"].String() = "FACTION_LIMITER";
+	root["parameters"].Vector().push_back(JsonUtils::stringNode(VLC->factions()->getByIndex(faction)->getJsonKey()));
+
+	return root;
+}
+
+CreatureLevelLimiter::CreatureLevelLimiter(uint32_t minLevel, uint32_t maxLevel) :
+	minLevel(minLevel),
+	maxLevel(maxLevel)
+{
+}
+
+ILimiter::EDecision CreatureLevelLimiter::limit(const BonusLimitationContext &context) const
+{
+	const auto *c = retrieveCreature(&context.node);
+	auto accept = c && (c->getLevel() < maxLevel && c->getLevel() >= minLevel);
+	return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; //drop bonus for non-creatures or non-native residents
+}
+
+std::string CreatureLevelLimiter::toString() const
+{
+	boost::format fmt("CreatureLevelLimiter(minLevel=%d,maxLevel=%d)");
+	fmt % minLevel % maxLevel;
+	return fmt.str();
+}
+
+JsonNode CreatureLevelLimiter::toJsonNode() const
+{
+	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+
+	root["type"].String() = "CREATURE_LEVEL_LIMITER";
+	root["parameters"].Vector().push_back(JsonUtils::intNode(minLevel));
+	root["parameters"].Vector().push_back(JsonUtils::intNode(maxLevel));
+
+	return root;
+}
+
+CreatureAlignmentLimiter::CreatureAlignmentLimiter(EAlignment Alignment)
+	: alignment(Alignment)
+{
+}
+
+ILimiter::EDecision CreatureAlignmentLimiter::limit(const BonusLimitationContext &context) const
+{
+	const auto * c = retrieveCreature(&context.node);
+	if(c) {
+		if(alignment == EAlignment::GOOD && c->isGood())
+			return ILimiter::EDecision::ACCEPT;
+		if(alignment == EAlignment::EVIL && c->isEvil())
+			return ILimiter::EDecision::ACCEPT;
+		if(alignment == EAlignment::NEUTRAL && !c->isEvil() && !c->isGood())
+			return ILimiter::EDecision::ACCEPT;
+	}
+
+	return ILimiter::EDecision::DISCARD;
+}
+
+std::string CreatureAlignmentLimiter::toString() const
+{
+	boost::format fmt("CreatureAlignmentLimiter(alignment=%s)");
+	fmt % GameConstants::ALIGNMENT_NAMES[vstd::to_underlying(alignment)];
+	return fmt.str();
+}
+
+JsonNode CreatureAlignmentLimiter::toJsonNode() const
+{
+	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+
+	root["type"].String() = "CREATURE_ALIGNMENT_LIMITER";
+	root["parameters"].Vector().push_back(JsonUtils::stringNode(GameConstants::ALIGNMENT_NAMES[vstd::to_underlying(alignment)]));
+
+	return root;
+}
+
+RankRangeLimiter::RankRangeLimiter(ui8 Min, ui8 Max)
+	:minRank(Min), maxRank(Max)
+{
+}
+
+RankRangeLimiter::RankRangeLimiter()
+{
+	minRank = maxRank = -1;
+}
+
+ILimiter::EDecision RankRangeLimiter::limit(const BonusLimitationContext &context) const
+{
+	const CStackInstance * csi = retrieveStackInstance(&context.node);
+	if(csi)
+	{
+		if (csi->getNodeType() == CBonusSystemNode::COMMANDER) //no stack exp bonuses for commander creatures
+			return ILimiter::EDecision::DISCARD;
+		if (csi->getExpRank() > minRank && csi->getExpRank() < maxRank)
+			return ILimiter::EDecision::ACCEPT;
+	}
+	return ILimiter::EDecision::DISCARD;
+}
+
+OppositeSideLimiter::OppositeSideLimiter(PlayerColor Owner):
+	owner(std::move(Owner))
+{
+}
+
+ILimiter::EDecision OppositeSideLimiter::limit(const BonusLimitationContext & context) const
+{
+	auto contextOwner = CBonusSystemNode::retrieveNodeOwner(& context.node);
+	auto decision = (owner == contextOwner || owner == PlayerColor::CANNOT_DETERMINE) ? ILimiter::EDecision::DISCARD : ILimiter::EDecision::ACCEPT;
+	return decision;
+}
+
+// Aggregate/Boolean Limiters
+
+AggregateLimiter::AggregateLimiter(std::vector<TLimiterPtr> limiters):
+	limiters(std::move(limiters))
+{
+}
+
+void AggregateLimiter::add(const TLimiterPtr & limiter)
+{
+	if(limiter)
+		limiters.push_back(limiter);
+}
+
+JsonNode AggregateLimiter::toJsonNode() const
+{
+	JsonNode result(JsonNode::JsonType::DATA_VECTOR);
+	result.Vector().push_back(JsonUtils::stringNode(getAggregator()));
+	for(const auto & l : limiters)
+		result.Vector().push_back(l->toJsonNode());
+	return result;
+}
+
+const std::string AllOfLimiter::aggregator = "allOf";
+const std::string & AllOfLimiter::getAggregator() const
+{
+	return aggregator;
+}
+
+AllOfLimiter::AllOfLimiter(std::vector<TLimiterPtr> limiters):
+	AggregateLimiter(limiters)
+{
+}
+
+ILimiter::EDecision AllOfLimiter::limit(const BonusLimitationContext & context) const
+{
+	bool wasntSure = false;
+
+	for(const auto & limiter : limiters)
+	{
+		auto result = limiter->limit(context);
+		if(result == ILimiter::EDecision::DISCARD)
+			return result;
+		if(result == ILimiter::EDecision::NOT_SURE)
+			wasntSure = true;
+	}
+
+	return wasntSure ? ILimiter::EDecision::NOT_SURE : ILimiter::EDecision::ACCEPT;
+}
+
+const std::string AnyOfLimiter::aggregator = "anyOf";
+const std::string & AnyOfLimiter::getAggregator() const
+{
+	return aggregator;
+}
+
+AnyOfLimiter::AnyOfLimiter(std::vector<TLimiterPtr> limiters):
+	AggregateLimiter(limiters)
+{
+}
+
+ILimiter::EDecision AnyOfLimiter::limit(const BonusLimitationContext & context) const
+{
+	bool wasntSure = false;
+
+	for(const auto & limiter : limiters)
+	{
+		auto result = limiter->limit(context);
+		if(result == ILimiter::EDecision::ACCEPT)
+			return result;
+		if(result == ILimiter::EDecision::NOT_SURE)
+			wasntSure = true;
+	}
+
+	return wasntSure ? ILimiter::EDecision::NOT_SURE : ILimiter::EDecision::DISCARD;
+}
+
+const std::string NoneOfLimiter::aggregator = "noneOf";
+const std::string & NoneOfLimiter::getAggregator() const
+{
+	return aggregator;
+}
+
+NoneOfLimiter::NoneOfLimiter(std::vector<TLimiterPtr> limiters):
+	AggregateLimiter(limiters)
+{
+}
+
+ILimiter::EDecision NoneOfLimiter::limit(const BonusLimitationContext & context) const
+{
+	bool wasntSure = false;
+
+	for(const auto & limiter : limiters)
+	{
+		auto result = limiter->limit(context);
+		if(result == ILimiter::EDecision::ACCEPT)
+			return ILimiter::EDecision::DISCARD;
+		if(result == ILimiter::EDecision::NOT_SURE)
+			wasntSure = true;
+	}
+
+	return wasntSure ? ILimiter::EDecision::NOT_SURE : ILimiter::EDecision::ACCEPT;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 264 - 0
lib/bonuses/Limiters.h

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

+ 52 - 0
lib/bonuses/Propagators.cpp

@@ -0,0 +1,52 @@
+/*
+ * Propagators.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+
+#include "Propagators.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+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
+
+bool IPropagator::shouldBeAttached(CBonusSystemNode *dest)
+{
+	return false;
+}
+
+CBonusSystemNode::ENodeTypes IPropagator::getPropagatorType() const
+{
+	return CBonusSystemNode::ENodeTypes::NONE;
+}
+
+CPropagatorNodeType::CPropagatorNodeType(CBonusSystemNode::ENodeTypes NodeType)
+	: nodeType(NodeType)
+{
+}
+
+CBonusSystemNode::ENodeTypes CPropagatorNodeType::getPropagatorType() const
+{
+	return nodeType;
+}
+
+bool CPropagatorNodeType::shouldBeAttached(CBonusSystemNode *dest)
+{
+	return nodeType == dest->getNodeType();
+}
+
+VCMI_LIB_NAMESPACE_END

+ 45 - 0
lib/bonuses/Propagators.h

@@ -0,0 +1,45 @@
+/*
+ * Propagators.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "Bonus.h"
+#include "CBonusSystemNode.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+extern DLL_LINKAGE const std::map<std::string, TPropagatorPtr> bonusPropagatorMap;
+
+class DLL_LINKAGE IPropagator
+{
+public:
+	virtual ~IPropagator() = default;
+	virtual bool shouldBeAttached(CBonusSystemNode *dest);
+	virtual CBonusSystemNode::ENodeTypes getPropagatorType() const;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{}
+};
+
+class DLL_LINKAGE CPropagatorNodeType : public IPropagator
+{
+	CBonusSystemNode::ENodeTypes nodeType;
+
+public:
+	CPropagatorNodeType(CBonusSystemNode::ENodeTypes NodeType = CBonusSystemNode::ENodeTypes::UNKNOWN);
+	bool shouldBeAttached(CBonusSystemNode *dest) override;
+	CBonusSystemNode::ENodeTypes getPropagatorType() const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & nodeType;
+	}
+};
+
+VCMI_LIB_NAMESPACE_END

+ 211 - 0
lib/bonuses/Updaters.cpp

@@ -0,0 +1,211 @@
+/*
+ * Updaters.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+
+#include "Updaters.h"
+#include "Limiters.h"
+
+#include "../mapObjects/CGHeroInstance.h"
+#include "../CStack.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+const std::map<std::string, TUpdaterPtr> bonusUpdaterMap =
+{
+	{"TIMES_HERO_LEVEL", std::make_shared<TimesHeroLevelUpdater>()},
+	{"TIMES_STACK_LEVEL", std::make_shared<TimesStackLevelUpdater>()},
+	{"ARMY_MOVEMENT", std::make_shared<ArmyMovementUpdater>()},
+	{"BONUS_OWNER_UPDATER", std::make_shared<OwnerUpdater>()}
+};
+
+std::shared_ptr<Bonus> IUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
+{
+	return b;
+}
+
+std::string IUpdater::toString() const
+{
+	return typeid(*this).name();
+}
+
+JsonNode IUpdater::toJsonNode() const
+{
+	return JsonNode(JsonNode::JsonType::DATA_NULL);
+}
+
+GrowsWithLevelUpdater::GrowsWithLevelUpdater(int valPer20, int stepSize) : valPer20(valPer20), stepSize(stepSize)
+{
+}
+
+std::shared_ptr<Bonus> GrowsWithLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
+{
+	if(context.getNodeType() == CBonusSystemNode::HERO)
+	{
+		int level = dynamic_cast<const CGHeroInstance &>(context).level;
+		int steps = stepSize ? level / stepSize : level;
+		//rounding follows format for HMM3 creature specialty bonus
+		int newVal = (valPer20 * steps + 19) / 20;
+		//return copy of bonus with updated val
+		std::shared_ptr<Bonus> newBonus = std::make_shared<Bonus>(*b);
+		newBonus->val = newVal;
+		return newBonus;
+	}
+	return b;
+}
+
+std::string GrowsWithLevelUpdater::toString() const
+{
+	return boost::str(boost::format("GrowsWithLevelUpdater(valPer20=%d, stepSize=%d)") % valPer20 % stepSize);
+}
+
+JsonNode GrowsWithLevelUpdater::toJsonNode() const
+{
+	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+
+	root["type"].String() = "GROWS_WITH_LEVEL";
+	root["parameters"].Vector().push_back(JsonUtils::intNode(valPer20));
+	if(stepSize > 1)
+		root["parameters"].Vector().push_back(JsonUtils::intNode(stepSize));
+
+	return root;
+}
+
+std::shared_ptr<Bonus> TimesHeroLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
+{
+	if(context.getNodeType() == CBonusSystemNode::HERO)
+	{
+		int level = dynamic_cast<const CGHeroInstance &>(context).level;
+		std::shared_ptr<Bonus> newBonus = std::make_shared<Bonus>(*b);
+		newBonus->val *= level;
+		return newBonus;
+	}
+	return b;
+}
+
+std::string TimesHeroLevelUpdater::toString() const
+{
+	return "TimesHeroLevelUpdater";
+}
+
+JsonNode TimesHeroLevelUpdater::toJsonNode() const
+{
+	return JsonUtils::stringNode("TIMES_HERO_LEVEL");
+}
+
+ArmyMovementUpdater::ArmyMovementUpdater():
+	base(20),
+	divider(3),
+	multiplier(10),
+	max(700)
+{
+}
+
+ArmyMovementUpdater::ArmyMovementUpdater(int base, int divider, int multiplier, int max):
+	base(base),
+	divider(divider),
+	multiplier(multiplier),
+	max(max)
+{
+}
+
+std::shared_ptr<Bonus> ArmyMovementUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
+{
+	if(b->type == Bonus::MOVEMENT && context.getNodeType() == CBonusSystemNode::HERO)
+	{
+		auto speed = static_cast<const CGHeroInstance &>(context).getLowestCreatureSpeed();
+		si32 armySpeed = speed * base / divider;
+		auto counted = armySpeed * multiplier;
+		auto newBonus = std::make_shared<Bonus>(*b);
+		newBonus->source = Bonus::ARMY;
+		newBonus->val += vstd::amin(counted, max);
+		return newBonus;
+	}
+	if(b->type != Bonus::MOVEMENT)
+		logGlobal->error("ArmyMovementUpdater should only be used for MOVEMENT bonus!");
+	return b;
+}
+
+std::string ArmyMovementUpdater::toString() const
+{
+	return "ArmyMovementUpdater";
+}
+
+JsonNode ArmyMovementUpdater::toJsonNode() const
+{
+	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+
+	root["type"].String() = "ARMY_MOVEMENT";
+	root["parameters"].Vector().push_back(JsonUtils::intNode(base));
+	root["parameters"].Vector().push_back(JsonUtils::intNode(divider));
+	root["parameters"].Vector().push_back(JsonUtils::intNode(multiplier));
+	root["parameters"].Vector().push_back(JsonUtils::intNode(max));
+
+	return root;
+}
+std::shared_ptr<Bonus> TimesStackLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
+{
+	if(context.getNodeType() == CBonusSystemNode::STACK_INSTANCE)
+	{
+		int level = dynamic_cast<const CStackInstance &>(context).getLevel();
+		std::shared_ptr<Bonus> newBonus = std::make_shared<Bonus>(*b);
+		newBonus->val *= level;
+		return newBonus;
+	}
+	else if(context.getNodeType() == CBonusSystemNode::STACK_BATTLE)
+	{
+		const auto & stack = dynamic_cast<const CStack &>(context);
+		//only update if stack doesn't have an instance (summons, war machines)
+		//otherwise we'd end up multiplying twice
+		if(stack.base == nullptr)
+		{
+			int level = stack.unitType()->getLevel();
+			std::shared_ptr<Bonus> newBonus = std::make_shared<Bonus>(*b);
+			newBonus->val *= level;
+			return newBonus;
+		}
+	}
+	return b;
+}
+
+std::string TimesStackLevelUpdater::toString() const
+{
+	return "TimesStackLevelUpdater";
+}
+
+JsonNode TimesStackLevelUpdater::toJsonNode() const
+{
+	return JsonUtils::stringNode("TIMES_STACK_LEVEL");
+}
+
+std::string OwnerUpdater::toString() const
+{
+	return "OwnerUpdater";
+}
+
+JsonNode OwnerUpdater::toJsonNode() const
+{
+	return JsonUtils::stringNode("BONUS_OWNER_UPDATER");
+}
+
+std::shared_ptr<Bonus> OwnerUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
+{
+	auto owner = CBonusSystemNode::retrieveNodeOwner(&context);
+
+	if(owner == PlayerColor::UNFLAGGABLE)
+		owner = PlayerColor::NEUTRAL;
+
+	std::shared_ptr<Bonus> updated =
+		std::make_shared<Bonus>(static_cast<Bonus::BonusDuration>(b->duration), b->type, b->source, b->val, b->sid, b->subtype, b->valType);
+	updated->limiter = std::make_shared<OppositeSideLimiter>(owner);
+	return updated;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 117 - 0
lib/bonuses/Updaters.h

@@ -0,0 +1,117 @@
+/*
+ * Updaters.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "Bonus.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+extern DLL_LINKAGE const std::map<std::string, TUpdaterPtr> bonusUpdaterMap;
+
+// observers for updating bonuses based on certain events (e.g. hero gaining level)
+
+class DLL_LINKAGE IUpdater
+{
+public:
+	virtual ~IUpdater() = default;
+
+	virtual std::shared_ptr<Bonus> createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const;
+	virtual std::string toString() const;
+	virtual JsonNode toJsonNode() const;
+
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+	}
+};
+
+class DLL_LINKAGE GrowsWithLevelUpdater : public IUpdater
+{
+public:
+	int valPer20 = 0;
+	int stepSize = 1;
+
+	GrowsWithLevelUpdater() = default;
+	GrowsWithLevelUpdater(int valPer20, int stepSize = 1);
+
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+		h & static_cast<IUpdater &>(*this);
+		h & valPer20;
+		h & stepSize;
+	}
+
+	std::shared_ptr<Bonus> createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const override;
+	virtual std::string toString() const override;
+	virtual JsonNode toJsonNode() const override;
+};
+
+class DLL_LINKAGE TimesHeroLevelUpdater : public IUpdater
+{
+public:
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+		h & static_cast<IUpdater &>(*this);
+	}
+
+	std::shared_ptr<Bonus> createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const override;
+	virtual std::string toString() const override;
+	virtual JsonNode toJsonNode() const override;
+};
+
+class DLL_LINKAGE TimesStackLevelUpdater : public IUpdater
+{
+public:
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+		h & static_cast<IUpdater &>(*this);
+	}
+
+	std::shared_ptr<Bonus> createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const override;
+	virtual std::string toString() const override;
+	virtual JsonNode toJsonNode() const override;
+};
+
+class DLL_LINKAGE ArmyMovementUpdater : public IUpdater
+{
+public:
+	si32 base;
+	si32 divider;
+	si32 multiplier;
+	si32 max;
+	ArmyMovementUpdater();
+	ArmyMovementUpdater(int base, int divider, int multiplier, int max);
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+		h & static_cast<IUpdater &>(*this);
+		h & base;
+		h & divider;
+		h & multiplier;
+		h & max;
+	}
+
+	std::shared_ptr<Bonus> createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const override;
+	virtual std::string toString() const override;
+	virtual JsonNode toJsonNode() const override;
+};
+
+class DLL_LINKAGE OwnerUpdater : public IUpdater
+{
+public:
+	template <typename Handler> void serialize(Handler& h, const int version)
+	{
+		h & static_cast<IUpdater &>(*this);
+	}
+
+	std::shared_ptr<Bonus> createUpdatedBonus(const std::shared_ptr<Bonus>& b, const CBonusSystemNode& context) const override;
+	virtual std::string toString() const override;
+	virtual JsonNode toJsonNode() const override;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 2 - 0
lib/mapObjects/CArmedInstance.h

@@ -11,6 +11,8 @@
 
 #include "CObjectHandler.h"
 #include "../CCreatureSet.h"
+#include "../bonuses/CBonusProxy.h"
+#include "../bonuses/CBonusSystemNode.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 2 - 2
lib/mapObjects/CGHeroInstance.cpp

@@ -849,12 +849,12 @@ CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &b
 			}
 		}
 		// calculate number of creatures raised - low level units contribute at 50% rate
-		const double raisedUnitHealth = VLC->creh->objects[creatureTypeRaised]->MaxHealth();
+		const double raisedUnitHealth = VLC->creh->objects[creatureTypeRaised]->getMaxHealth();
 		double raisedUnits = 0;
 		for(const auto & casualty : casualties)
 		{
 			const CCreature * c = VLC->creh->objects[casualty.first];
-			double raisedFromCasualty = std::min(c->MaxHealth() / raisedUnitHealth, 1.0) * casualty.second * necromancySkill;
+			double raisedFromCasualty = std::min(c->getMaxHealth() / raisedUnitHealth, 1.0) * casualty.second * necromancySkill;
 			if(c->getLevel() < requiredCasualtyLevel)
 				raisedFromCasualty *= 0.5;
 			raisedUnits += raisedFromCasualty;

+ 1 - 1
lib/mapObjects/CGHeroInstance.h

@@ -41,7 +41,7 @@ public:
 };
 
 
-class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator, public CArtifactSet, public spells::Caster, public IConstBonusNativeTerrainProvider
+class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator, public CArtifactSet, public spells::Caster, public AFactionMember
 {
 	// We serialize heroes into JSON for crossover
 	friend class CCampaignState;

+ 1 - 1
lib/mapObjects/CGTownInstance.cpp

@@ -12,6 +12,7 @@
 #include "CGTownInstance.h"
 #include "CObjectClassesHandler.h"
 #include "../spells/CSpellHandler.h"
+#include "../bonuses/Bonus.h"
 #include "../battle/IBattleInfoCallback.h"
 #include "../NetPacks.h"
 #include "../CConfigHandler.h"
@@ -23,7 +24,6 @@
 #include "../CPlayerState.h"
 #include "../TerrainHandler.h"
 #include "../serializer/JsonSerializeFormat.h"
-#include "../HeroBonus.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 2 - 1
lib/mapObjects/CObjectHandler.h

@@ -12,10 +12,11 @@
 #include "ObjectTemplate.h"
 
 #include "../int3.h"
-#include "../HeroBonus.h"
 #include "../NetPacksBase.h"
 #include "../ResourceSet.h"
 
+#include "../bonuses/Bonus.h"
+
 VCMI_LIB_NAMESPACE_BEGIN
 
 class CGHeroInstance;

+ 1 - 1
lib/mapObjects/MiscObjects.cpp

@@ -1895,7 +1895,7 @@ void CGSirens::onHeroVisit( const CGHeroInstance * h ) const
 			if(drown)
 			{
 				cb->changeStackCount(StackLocation(h, i->first), -drown);
-				xp += drown * i->second->type->MaxHealth();
+				xp += drown * i->second->type->getMaxHealth();
 			}
 		}
 

+ 4 - 0
lib/registerTypes/RegisterTypes.h

@@ -22,6 +22,10 @@
 #include "../mapObjects/CommonConstructors.h"
 #include "../mapObjects/MapObjects.h"
 #include "../battle/CObstacleInstance.h"
+#include "../bonuses/CBonusSystemNode.h"
+#include "../bonuses/Limiters.h"
+#include "../bonuses/Updaters.h"
+#include "../bonuses/Propagators.h"
 #include "../CStack.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 3 - 1
lib/serializer/JsonUpdater.cpp

@@ -11,7 +11,9 @@
 #include "JsonUpdater.h"
 
 #include "../JsonNode.h"
-#include "../HeroBonus.h"
+
+#include "../bonuses/CBonusSystemNode.h"
+#include "../bonuses/Bonus.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 1
lib/spells/BonusCaster.cpp

@@ -14,8 +14,8 @@
 #include <vcmi/spells/Spell.h>
 
 #include "../NetPacksBase.h"
-#include "../HeroBonus.h"
 #include "../battle/Unit.h"
+#include "../bonuses/Bonus.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 1
lib/spells/CSpellHandler.h

@@ -19,7 +19,7 @@
 #include "../int3.h"
 #include "../GameConstants.h"
 #include "../battle/BattleHex.h"
-#include "../HeroBonus.h"
+#include "../bonuses/Bonus.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 1
lib/spells/ISpellMechanics.cpp

@@ -14,7 +14,7 @@
 #include "../CRandomGenerator.h"
 #include "../VCMI_Lib.h"
 
-#include "../HeroBonus.h"
+#include "../bonuses/Bonus.h"
 #include "../battle/CBattleInfoCallback.h"
 #include "../battle/IBattleState.h"
 #include "../battle/Unit.h"

+ 1 - 1
lib/spells/ISpellMechanics.h

@@ -16,7 +16,7 @@
 #include "../battle/Destination.h"
 #include "../int3.h"
 #include "../GameConstants.h"
-#include "../HeroBonus.h"
+#include "../bonuses/Bonus.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 2 - 0
lib/spells/TargetCondition.cpp

@@ -15,6 +15,8 @@
 #include "../CBonusTypeHandler.h"
 #include "../battle/CBattleInfoCallback.h"
 #include "../battle/Unit.h"
+#include "../bonuses/BonusParams.h"
+#include "../bonuses/BonusList.h"
 
 #include "../serializer/JsonSerializeFormat.h"
 #include "../VCMI_Lib.h"

+ 2 - 2
lib/spells/effects/Damage.cpp

@@ -107,11 +107,11 @@ int64_t Damage::damageForTarget(size_t targetIndex, const Mechanics * m, const b
 	if(killByPercentage)
 	{
 		int64_t amountToKill = target->getCount() * m->getEffectValue() / 100;
-		baseDamage = amountToKill * target->MaxHealth();
+		baseDamage = amountToKill * target->getMaxHealth();
 	}
 	else if(killByCount)
 	{
-		baseDamage = m->getEffectValue() * target->MaxHealth();
+		baseDamage = m->getEffectValue() * target->getMaxHealth();
 	}
 	else
 	{

+ 1 - 1
lib/spells/effects/Heal.cpp

@@ -59,7 +59,7 @@ bool Heal::isValidTarget(const Mechanics * m, const battle::Unit * unit) const
 	if(minFullUnits > 0)
 	{
 		auto hpGained = std::min(m->getEffectValue(), injuries);
-		if(hpGained < minFullUnits * unit->MaxHealth())
+		if(hpGained < minFullUnits * unit->getMaxHealth())
 			return false;
 	}
 

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

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

部分文件因为文件数量过多而无法显示