Forráskód Böngészése

Merge branch 'develop' into rmg-no-monster-zone

Warzyw647 2 éve
szülő
commit
031671056d
100 módosított fájl, 1695 hozzáadás és 1575 törlés
  1. 1 1
      AI/BattleAI/AttackPossibility.cpp
  2. 4 4
      AI/BattleAI/BattleAI.cpp
  3. 1 1
      AI/BattleAI/BattleExchangeVariant.cpp
  4. 1 1
      AI/BattleAI/PotentialTargets.cpp
  5. 2 2
      AI/BattleAI/StackWithBonuses.cpp
  6. 1 1
      AI/Nullkiller/AIUtility.cpp
  7. 2 2
      AI/Nullkiller/Analyzers/ArmyManager.cpp
  8. 3 3
      AI/Nullkiller/Analyzers/HeroManager.cpp
  9. 6 6
      AI/Nullkiller/Engine/FuzzyEngines.cpp
  10. 8 8
      AI/Nullkiller/Engine/PriorityEvaluator.cpp
  11. 2 2
      AI/StupidAI/StupidAI.cpp
  12. 1 1
      AI/VCAI/AIUtility.cpp
  13. 6 6
      AI/VCAI/FuzzyEngines.cpp
  14. 294 274
      Mods/vcmi/config/vcmi/spanish.json
  15. 2 2
      client/CPlayerInterface.cpp
  16. 1 1
      client/NetPacksClient.cpp
  17. 6 6
      client/battle/BattleActionsController.cpp
  18. 1 1
      client/battle/BattleAnimationClasses.cpp
  19. 6 6
      client/battle/BattleEffectsController.cpp
  20. 4 4
      client/battle/BattleStacksController.cpp
  21. 2 2
      client/battle/BattleWindow.cpp
  22. 7 7
      client/widgets/MiscWidgets.cpp
  23. 1 1
      client/windows/CCastleInterface.cpp
  24. 6 6
      client/windows/CCreatureWindow.cpp
  25. 2 2
      client/windows/CHeroWindow.cpp
  26. 1 1
      client/windows/CKingdomInterface.cpp
  27. 2 0
      cmake_modules/VCMI_lib.cmake
  28. 1 1
      config/schemas/settings.json
  29. 1 1
      config/schemas/spell.json
  30. 207 78
      launcher/translation/spanish.ts
  31. 20 20
      lib/BasicTypes.cpp
  32. 2 2
      lib/BattleFieldHandler.cpp
  33. 10 10
      lib/CArtHandler.cpp
  34. 11 11
      lib/CBonusTypeHandler.cpp
  35. 1 3
      lib/CBonusTypeHandler.h
  36. 109 109
      lib/CCreatureHandler.cpp
  37. 1 1
      lib/CCreatureHandler.h
  38. 1 1
      lib/CGameInfoCallback.cpp
  39. 6 6
      lib/CGameState.cpp
  40. 5 5
      lib/CHeroHandler.cpp
  41. 36 36
      lib/CPathfinder.cpp
  42. 4 4
      lib/CPathfinder.h
  43. 2 2
      lib/CSkillHandler.cpp
  44. 7 7
      lib/CStack.cpp
  45. 11 11
      lib/CTownHandler.cpp
  46. 3 3
      lib/CTownHandler.h
  47. 14 24
      lib/JsonNode.cpp
  48. 14 14
      lib/NetPacksLib.cpp
  49. 1 1
      lib/battle/BattleAction.cpp
  50. 7 7
      lib/battle/BattleInfo.cpp
  51. 31 31
      lib/battle/CBattleInfoCallback.cpp
  52. 2 2
      lib/battle/CBattleInfoEssentials.cpp
  53. 20 20
      lib/battle/CUnitState.cpp
  54. 28 28
      lib/battle/DamageCalculator.cpp
  55. 1 1
      lib/battle/ReachabilityInfo.cpp
  56. 34 128
      lib/bonuses/Bonus.cpp
  57. 17 264
      lib/bonuses/Bonus.h
  58. 58 0
      lib/bonuses/BonusEnum.cpp
  59. 264 0
      lib/bonuses/BonusEnum.h
  60. 19 19
      lib/bonuses/BonusList.cpp
  61. 45 45
      lib/bonuses/BonusParams.cpp
  62. 3 3
      lib/bonuses/BonusParams.h
  63. 17 17
      lib/bonuses/BonusSelector.cpp
  64. 9 9
      lib/bonuses/BonusSelector.h
  65. 2 2
      lib/bonuses/CBonusProxy.cpp
  66. 3 3
      lib/bonuses/IBonusBearer.cpp
  67. 3 3
      lib/bonuses/IBonusBearer.h
  68. 9 9
      lib/bonuses/Limiters.cpp
  69. 6 6
      lib/bonuses/Limiters.h
  70. 4 4
      lib/bonuses/Updaters.cpp
  71. 6 6
      lib/mapObjects/CArmedInstance.cpp
  72. 4 4
      lib/mapObjects/CBank.cpp
  73. 1 1
      lib/mapObjects/CGDwelling.cpp
  74. 41 41
      lib/mapObjects/CGHeroInstance.cpp
  75. 2 2
      lib/mapObjects/CGPandoraBox.cpp
  76. 10 10
      lib/mapObjects/CGTownBuilding.cpp
  77. 5 5
      lib/mapObjects/CGTownInstance.cpp
  78. 4 4
      lib/mapObjects/CObjectHandler.cpp
  79. 1 1
      lib/mapObjects/CObjectHandler.h
  80. 2 2
      lib/mapObjects/CQuest.cpp
  81. 3 3
      lib/mapObjects/CRewardableConstructor.cpp
  82. 2 2
      lib/mapObjects/CRewardableObject.cpp
  83. 2 2
      lib/mapObjects/CommonConstructors.cpp
  84. 10 10
      lib/mapObjects/MiscObjects.cpp
  85. 1 1
      lib/mapping/MapFormatH3M.cpp
  86. 1 1
      lib/rewardable/Interface.cpp
  87. 1 1
      lib/spells/AbilityCaster.cpp
  88. 3 3
      lib/spells/AdventureSpellMechanics.cpp
  89. 2 2
      lib/spells/BattleSpellMechanics.cpp
  90. 24 24
      lib/spells/CSpellHandler.cpp
  91. 4 4
      lib/spells/CSpellHandler.h
  92. 6 6
      lib/spells/ISpellMechanics.cpp
  93. 2 2
      lib/spells/ISpellMechanics.h
  94. 11 11
      lib/spells/TargetCondition.cpp
  95. 1 1
      lib/spells/effects/Clone.cpp
  96. 1 1
      lib/spells/effects/Dispel.cpp
  97. 3 3
      lib/spells/effects/Moat.cpp
  98. 11 11
      lib/spells/effects/Timed.cpp
  99. 2 2
      lib/spells/effects/UnitEffect.cpp
  100. 107 107
      server/CGameHandler.cpp

+ 1 - 1
AI/BattleAI/AttackPossibility.cpp

@@ -97,7 +97,7 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInf
 	auto attacker = attackInfo.attacker;
 	auto defender = attackInfo.defender;
 	const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
-	static const auto selectorBlocksRetaliation = Selector::type()(Bonus::BLOCKS_RETALIATION);
+	static const auto selectorBlocksRetaliation = Selector::type()(BonusType::BLOCKS_RETALIATION);
 	const auto attackerSide = state.playerToSide(state.battleGetOwner(attacker));
 	const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
 

+ 4 - 4
AI/BattleAI/BattleAI.cpp

@@ -104,7 +104,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 	{
 		if(stack->creatureId() == CreatureID::CATAPULT)
 			return useCatapult(stack);
-		if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->hasBonusOfType(Bonus::HEALER))
+		if(stack->hasBonusOfType(BonusType::SIEGE_WEAPON) && stack->hasBonusOfType(BonusType::HEALER))
 		{
 			auto healingTargets = cb->battleGetStacks(CBattleInfoEssentials::ONLY_MINE);
 			std::map<int, const CStack*> woundHpToStack;
@@ -137,7 +137,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 		std::optional<PossibleSpellcast> bestSpellcast(std::nullopt);
 		//TODO: faerie dragon type spell should be selected by server
 		SpellID creatureSpellToCast = cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), stack, CBattleInfoCallback::RANDOM_AIMED);
-		if(stack->hasBonusOfType(Bonus::SPELLCASTER) && stack->canCast() && creatureSpellToCast != SpellID::NONE)
+		if(stack->hasBonusOfType(BonusType::SPELLCASTER) && stack->canCast() && creatureSpellToCast != SpellID::NONE)
 		{
 			const CSpell * spell = creatureSpellToCast.toSpell();
 
@@ -241,7 +241,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 		}
 
 		if(score <= EvaluationResult::INEFFECTIVE_SCORE
-			&& !stack->hasBonusOfType(Bonus::FLYING)
+			&& !stack->hasBonusOfType(BonusType::FLYING)
 			&& stack->unitSide() == BattleSide::ATTACKER
 			&& cb->battleGetSiegeLevel() >= CGTownInstance::CITADEL)
 		{
@@ -321,7 +321,7 @@ BattleAction CBattleAI::goTowardsNearest(const CStack * stack, std::vector<Battl
 
 	scoreEvaluator.updateReachabilityMap(hb);
 
-	if(stack->hasBonusOfType(Bonus::FLYING))
+	if(stack->hasBonusOfType(BonusType::FLYING))
 	{
 		std::set<BattleHex> obstacleHexes;
 

+ 1 - 1
AI/BattleAI/BattleExchangeVariant.cpp

@@ -65,7 +65,7 @@ int64_t BattleExchangeVariant::trackAttack(
 	bool evaluateOnly)
 {
 	const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
-	static const auto selectorBlocksRetaliation = Selector::type()(Bonus::BLOCKS_RETALIATION);
+	static const auto selectorBlocksRetaliation = Selector::type()(BonusType::BLOCKS_RETALIATION);
 	const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
 
 	DamageEstimation retaliation;

+ 1 - 1
AI/BattleAI/PotentialTargets.cpp

@@ -22,7 +22,7 @@ PotentialTargets::PotentialTargets(const battle::Unit * attacker, const Hypothet
 	const battle::Unit * forcedTarget = nullptr;
 	BattleHex forcedHex;
 
-	if(attackerInfo->hasBonusOfType(Bonus::ATTACKS_NEAREST_CREATURE))
+	if(attackerInfo->hasBonusOfType(BonusType::ATTACKS_NEAREST_CREATURE))
 	{
 		forceTarget = true;
 		auto nearest = state.getNearestStack(attackerInfo);

+ 2 - 2
AI/BattleAI/StackWithBonuses.cpp

@@ -24,7 +24,7 @@ void actualizeEffect(TBonusListPtr target, const Bonus & ef)
 {
 	for(auto & bonus : *target) //TODO: optimize
 	{
-		if(bonus->source == Bonus::SPELL_EFFECT && bonus->type == ef.type && bonus->subtype == ef.subtype)
+		if(bonus->source == BonusSource::SPELL_EFFECT && bonus->type == ef.type && bonus->subtype == ef.subtype)
 		{
 			if(bonus->turnsRemain < ef.turnsRemain)
 			{
@@ -126,7 +126,7 @@ TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, c
 	{
 		if(selector(&bonus) && (!limit || !limit(&bonus)))
 		{
-			if(ret->getFirst(Selector::source(Bonus::SPELL_EFFECT, bonus.sid).And(Selector::typeSubtype(bonus.type, bonus.subtype))))
+			if(ret->getFirst(Selector::source(BonusSource::SPELL_EFFECT, bonus.sid).And(Selector::typeSubtype(bonus.type, bonus.subtype))))
 			{
 				actualizeEffect(ret, bonus);
 			}

+ 1 - 1
AI/Nullkiller/AIUtility.cpp

@@ -307,7 +307,7 @@ bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2
 	auto art2 = a2->artType;
 
 	if(art1->price == art2->price)
-		return art1->valOfBonuses(Bonus::PRIMARY_SKILL) > art2->valOfBonuses(Bonus::PRIMARY_SKILL);
+		return art1->valOfBonuses(BonusType::PRIMARY_SKILL) > art2->valOfBonuses(BonusType::PRIMARY_SKILL);
 	else
 		return art1->price > art2->price;
 }

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

@@ -108,12 +108,12 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
 	uint64_t armyValue = 0;
 
 	TemporaryArmy newArmyInstance;
-	auto bonusModifiers = armyCarrier->getBonuses(Selector::type()(Bonus::MORALE));
+	auto bonusModifiers = armyCarrier->getBonuses(Selector::type()(BonusType::MORALE));
 
 	for(auto bonus : *bonusModifiers)
 	{
 		// army bonuses will change and object bonuses are temporary
-		if(bonus->source != Bonus::ARMY && bonus->source != Bonus::OBJECT)
+		if(bonus->source != BonusSource::ARMY && bonus->source != BonusSource::OBJECT)
 		{
 			newArmyInstance.addNewBonus(std::make_shared<Bonus>(*bonus));
 		}

+ 3 - 3
AI/Nullkiller/Analyzers/HeroManager.cpp

@@ -72,10 +72,10 @@ float HeroManager::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance *
 
 float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const
 {
-	auto heroSpecial = Selector::source(Bonus::HERO_SPECIAL, hero->type->getIndex());
-	auto secondarySkillBonus = Selector::targetSourceType()(Bonus::SECONDARY_SKILL);
+	auto heroSpecial = Selector::source(BonusSource::HERO_SPECIAL, hero->type->getIndex());
+	auto secondarySkillBonus = Selector::targetSourceType()(BonusSource::SECONDARY_SKILL);
 	auto specialSecondarySkillBonuses = hero->getBonuses(heroSpecial.And(secondarySkillBonus));
-	auto secondarySkillBonuses = hero->getBonuses(Selector::sourceTypeSel(Bonus::SECONDARY_SKILL));
+	auto secondarySkillBonuses = hero->getBonuses(Selector::sourceTypeSel(BonusSource::SECONDARY_SKILL));
 	float specialityScore = 0.0f;
 
 	for(auto bonus : *secondarySkillBonuses)

+ 6 - 6
AI/Nullkiller/Engine/FuzzyEngines.cpp

@@ -53,14 +53,14 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army)
 	double shootersStrength = 0;
 	ui32 maxSpeed = 0;
 
-	static const CSelector selectorSHOOTER = Selector::type()(Bonus::SHOOTER);
-	static const std::string keySHOOTER = "type_"+std::to_string((int32_t)Bonus::SHOOTER);
+	static const CSelector selectorSHOOTER = Selector::type()(BonusType::SHOOTER);
+	static const std::string keySHOOTER = "type_"+std::to_string((int32_t)BonusType::SHOOTER);
 
-	static const CSelector selectorFLYING = Selector::type()(Bonus::FLYING);
-	static const std::string keyFLYING = "type_"+std::to_string((int32_t)Bonus::FLYING);
+	static const CSelector selectorFLYING = Selector::type()(BonusType::FLYING);
+	static const std::string keyFLYING = "type_"+std::to_string((int32_t)BonusType::FLYING);
 
-	static const CSelector selectorSTACKS_SPEED = Selector::type()(Bonus::STACKS_SPEED);
-	static const std::string keySTACKS_SPEED = "type_"+std::to_string((int32_t)Bonus::STACKS_SPEED);
+	static const CSelector selectorSTACKS_SPEED = Selector::type()(BonusType::STACKS_SPEED);
+	static const std::string keySTACKS_SPEED = "type_"+std::to_string((int32_t)BonusType::STACKS_SPEED);
 
 	for(auto s : army->Slots())
 	{

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

@@ -210,14 +210,14 @@ uint64_t evaluateArtifactArmyValue(CArtifactInstance * art)
 		return 1500;
 
 	auto statsValue =
-		10 * art->valOfBonuses(Bonus::MOVEMENT, 1)
-		+ 1200 * art->valOfBonuses(Bonus::STACKS_SPEED)
-		+ 700 * art->valOfBonuses(Bonus::MORALE)
-		+ 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);
+		10 * art->valOfBonuses(BonusType::MOVEMENT, 1)
+		+ 1200 * art->valOfBonuses(BonusType::STACKS_SPEED)
+		+ 700 * art->valOfBonuses(BonusType::MORALE)
+		+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::ATTACK)
+		+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::DEFENSE)
+		+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::KNOWLEDGE)
+		+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::SPELL_POWER)
+		+ 500 * art->valOfBonuses(BonusType::LUCK);
 
 	auto classValue = 0;
 

+ 2 - 2
AI/StupidAI/StupidAI.cpp

@@ -107,7 +107,7 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
 
 		return attack;
 	}
-	else if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON))
+	else if(stack->hasBonusOfType(BonusType::SIEGE_WEAPON))
 	{
 		return BattleAction::makeDefend(stack);
 	}
@@ -270,7 +270,7 @@ BattleAction CStupidAI::goTowards(const CStack * stack, std::vector<BattleHex> h
 		return BattleAction::makeDefend(stack);
 	}
 
-	if(stack->hasBonusOfType(Bonus::FLYING))
+	if(stack->hasBonusOfType(BonusType::FLYING))
 	{
 		// Flying stack doesn't go hex by hex, so we can't backtrack using predecessors.
 		// We just check all available hexes and pick the one closest to the target.

+ 1 - 1
AI/VCAI/AIUtility.cpp

@@ -257,7 +257,7 @@ bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2
 	auto art2 = a2->artType;
 
 	if(art1->price == art2->price)
-		return art1->valOfBonuses(Bonus::PRIMARY_SKILL) > art2->valOfBonuses(Bonus::PRIMARY_SKILL);
+		return art1->valOfBonuses(BonusType::PRIMARY_SKILL) > art2->valOfBonuses(BonusType::PRIMARY_SKILL);
 	else
 		return art1->price > art2->price;
 }

+ 6 - 6
AI/VCAI/FuzzyEngines.cpp

@@ -52,14 +52,14 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army)
 	double shootersStrength = 0;
 	ui32 maxSpeed = 0;
 
-	static const CSelector selectorSHOOTER = Selector::type()(Bonus::SHOOTER);
-	static const std::string keySHOOTER = "type_"+std::to_string((int32_t)Bonus::SHOOTER);
+	static const CSelector selectorSHOOTER = Selector::type()(BonusType::SHOOTER);
+	static const std::string keySHOOTER = "type_"+std::to_string((int32_t)BonusType::SHOOTER);
 
-	static const CSelector selectorFLYING = Selector::type()(Bonus::FLYING);
-	static const std::string keyFLYING = "type_"+std::to_string((int32_t)Bonus::FLYING);
+	static const CSelector selectorFLYING = Selector::type()(BonusType::FLYING);
+	static const std::string keyFLYING = "type_"+std::to_string((int32_t)BonusType::FLYING);
 
-	static const CSelector selectorSTACKS_SPEED = Selector::type()(Bonus::STACKS_SPEED);
-	static const std::string keySTACKS_SPEED = "type_"+std::to_string((int32_t)Bonus::STACKS_SPEED);
+	static const CSelector selectorSTACKS_SPEED = Selector::type()(BonusType::STACKS_SPEED);
+	static const std::string keySTACKS_SPEED = "type_"+std::to_string((int32_t)BonusType::STACKS_SPEED);
 
 	for(auto s : army->Slots())
 	{

+ 294 - 274
Mods/vcmi/config/vcmi/spanish.json

@@ -1,296 +1,316 @@
 {
-    "vcmi.adventureMap.monsterThreat.title"     : "\n\n Amenaza: ",
-    "vcmi.adventureMap.monsterThreat.levels.0"  : "Sin esfuerzo",
-    "vcmi.adventureMap.monsterThreat.levels.1"  : "Muy débil",
-    "vcmi.adventureMap.monsterThreat.levels.2"  : "Débil",
-    "vcmi.adventureMap.monsterThreat.levels.3"  : "Un poco más débil",
-    "vcmi.adventureMap.monsterThreat.levels.4"  : "Igual",
-    "vcmi.adventureMap.monsterThreat.levels.5"  : "Un poco más fuerte",
-    "vcmi.adventureMap.monsterThreat.levels.6"  : "Fuerte",
-    "vcmi.adventureMap.monsterThreat.levels.7"  : "Muy fuerte",
-    "vcmi.adventureMap.monsterThreat.levels.8"  : "Desafiante",
-    "vcmi.adventureMap.monsterThreat.levels.9"  : "Abrumador",
-    "vcmi.adventureMap.monsterThreat.levels.10" : "Mortal",
-    "vcmi.adventureMap.monsterThreat.levels.11" : "Imposible",
+	"vcmi.adventureMap.monsterThreat.title"     : "\n\n Amenaza: ",
+	"vcmi.adventureMap.monsterThreat.levels.0"  : "Sin esfuerzo",
+	"vcmi.adventureMap.monsterThreat.levels.1"  : "Muy débil",
+	"vcmi.adventureMap.monsterThreat.levels.2"  : "Débil",
+	"vcmi.adventureMap.monsterThreat.levels.3"  : "Un poco más débil",
+	"vcmi.adventureMap.monsterThreat.levels.4"  : "Igual",
+	"vcmi.adventureMap.monsterThreat.levels.5"  : "Un poco más fuerte",
+	"vcmi.adventureMap.monsterThreat.levels.6"  : "Fuerte",
+	"vcmi.adventureMap.monsterThreat.levels.7"  : "Muy fuerte",
+	"vcmi.adventureMap.monsterThreat.levels.8"  : "Desafiante",
+	"vcmi.adventureMap.monsterThreat.levels.9"  : "Abrumador",
+	"vcmi.adventureMap.monsterThreat.levels.10" : "Mortal",
+	"vcmi.adventureMap.monsterThreat.levels.11" : "Imposible",
 
-    "vcmi.adventureMap.confirmRestartGame"     : "¿Estás seguro de que quieres reiniciar el juego?",
-    "vcmi.adventureMap.noTownWithMarket"       : "¡No hay mercado disponible!",
-    "vcmi.adventureMap.noTownWithTavern"       : "¡No hay pueblo disponible con taberna!",
-    "vcmi.adventureMap.spellUnknownProblem"    : "Problema desconocido con este hechizo, no hay más información disponible.",
-    "vcmi.adventureMap.playerAttacked"         : "El jugador ha sido atacado: %s",
-    "vcmi.adventureMap.moveCostDetails"        : "Puntos de movimiento - Coste: %TURNS turnos + %POINTS puntos, Puntos restantes: %REMAINING",
-    "vcmi.adventureMap.moveCostDetailsNoTurns" : "Puntos de movimiento - Coste: %POINTS puntos, Puntos restantes: %REMAINING",
+	"vcmi.adventureMap.confirmRestartGame"     : "¿Estás seguro de que quieres reiniciar el juego?",
+	"vcmi.adventureMap.noTownWithMarket"       : "¡No hay mercado disponible!",
+	"vcmi.adventureMap.noTownWithTavern"       : "¡No hay pueblo disponible con taberna!",
+	"vcmi.adventureMap.spellUnknownProblem"    : "Problema desconocido con este hechizo, no hay más información disponible.",
+	"vcmi.adventureMap.playerAttacked"         : "El jugador ha sido atacado: %s",
+	"vcmi.adventureMap.moveCostDetails"        : "Puntos de movimiento - Coste: %TURNS turnos + %POINTS puntos, Puntos restantes: %REMAINING",
+	"vcmi.adventureMap.moveCostDetailsNoTurns" : "Puntos de movimiento - Coste: %POINTS puntos, Puntos restantes: %REMAINING",
 
-    "vcmi.server.errors.existingProcess"     : "Otro proceso de vcmiserver está en ejecución, por favor termínalo primero",
-    "vcmi.server.errors.modsIncompatibility" : "Mods necesarios para cargar el juego:",
-    "vcmi.server.confirmReconnect"           : "¿Conectar a la última sesión?",
+	"vcmi.capitalColors.0" : "Rojo",
+	"vcmi.capitalColors.1" : "Azul",
+	"vcmi.capitalColors.2" : "Marron",
+	"vcmi.capitalColors.3" : "Verde",
+	"vcmi.capitalColors.4" : "Naranja",
+	"vcmi.capitalColors.5" : "Morado",
+	"vcmi.capitalColors.6" : "Turquesa",
+	"vcmi.capitalColors.7" : "Rosa",
 
-    "vcmi.settingsMainWindow.generalTab.hover" : "General",
-    "vcmi.settingsMainWindow.generalTab.help"     : "Cambiar a la pestaña de opciones generales, que contiene ajustes relacionados con el comportamiento general del juego",
-    "vcmi.settingsMainWindow.battleTab.hover" : "Batalla",
-    "vcmi.settingsMainWindow.battleTab.help"     : "Cambiar a la pestaña de opciones de batalla, que permite configurar el comportamiento del juego durante las batallas",
-    "vcmi.settingsMainWindow.adventureTab.hover" : "Mapa de Aventuras",
-    "vcmi.settingsMainWindow.adventureTab.help"  : "Cambiar a la pestaña de opciones de Mapa de Aventuras - el mapa de aventuras es parte del juego donde puedes mover a tus héroes",
+	"vcmi.server.errors.existingProcess"     : "Otro proceso de vcmiserver está en ejecución, por favor termínalo primero",
+	"vcmi.server.errors.modsIncompatibility" : "Mods necesarios para cargar el juego:",
+	"vcmi.server.confirmReconnect"           : "¿Conectar a la última sesión?",
 
+	"vcmi.settingsMainWindow.generalTab.hover" : "General",
+	"vcmi.settingsMainWindow.generalTab.help"     : "Cambiar a la pestaña de opciones generales, que contiene ajustes relacionados con el comportamiento general del juego",
+	"vcmi.settingsMainWindow.battleTab.hover" : "Batalla",
+	"vcmi.settingsMainWindow.battleTab.help"     : "Cambiar a la pestaña de opciones de batalla, que permite configurar el comportamiento del juego durante las batallas",
+	"vcmi.settingsMainWindow.adventureTab.hover" : "Mapa de Aventuras",
+	"vcmi.settingsMainWindow.adventureTab.help"  : "Cambiar a la pestaña de opciones de Mapa de Aventuras - el mapa de aventuras es parte del juego donde puedes mover a tus héroes",
 
-    "vcmi.systemOptions.videoGroup" : "Configuración de vídeo",
-    "vcmi.systemOptions.audioGroup" : "Configuración de audio",
-    "vcmi.systemOptions.otherGroup" : "Otras configuraciones", // actualmente no utilizada
-    "vcmi.systemOptions.townsGroup" : "Pantalla de la ciudad",
+	"vcmi.systemOptions.videoGroup" : "Configuración de vídeo",
+	"vcmi.systemOptions.audioGroup" : "Configuración de audio",
+	"vcmi.systemOptions.otherGroup" : "Otras configuraciones", // actualmente no utilizada
+	"vcmi.systemOptions.townsGroup" : "Pantalla de la ciudad",
 
-    "vcmi.systemOptions.fullscreenButton.hover" : "Pantalla completa",
-    "vcmi.systemOptions.fullscreenButton.help"  : "{Pantalla completa}\n\n Si se selecciona, VCMI se ejecutará en modo de pantalla completa, de lo contrario se ejecutará en ventana",
-    "vcmi.systemOptions.resolutionButton.hover" : "Resolución: %wx%h",
-    "vcmi.systemOptions.resolutionButton.help"  : "{Seleccionar resolución}\n\n Cambia la resolución de la pantalla del juego. Se requiere reiniciar el juego para aplicar la nueva resolución.",
-    "vcmi.systemOptions.resolutionMenu.hover"   : "Seleccionar resolución",
-    "vcmi.systemOptions.resolutionMenu.help"    : "Cambia la resolución de la pantalla del juego.",
-    "vcmi.systemOptions.fullscreenFailed"       : "{Pantalla completa}\n\n ¡Fallo al cambiar a modo de pantalla completa! ¡La resolución actual no es compatible con la pantalla!",
-    "vcmi.systemOptions.framerateButton.hover"  : "Mostrar FPS",
-    "vcmi.systemOptions.framerateButton.help"   : "{Mostrar FPS}\n\n Muestra el contador de Frames Por Segundo en la esquina de la ventana del juego.",
+	"vcmi.systemOptions.fullscreenButton.hover" : "Pantalla completa",
+	"vcmi.systemOptions.fullscreenButton.help"  : "{Pantalla completa}\n\n Si se selecciona, VCMI se ejecutará en modo de pantalla completa, de lo contrario se ejecutará en ventana",
+	"vcmi.systemOptions.resolutionButton.hover" : "Resolución: %wx%h",
+	"vcmi.systemOptions.resolutionButton.help"  : "{Seleccionar resolución}\n\n Cambia la resolución de la pantalla del juego. Se requiere reiniciar el juego para aplicar la nueva resolución.",
+	"vcmi.systemOptions.resolutionMenu.hover"   : "Seleccionar resolución",
+	"vcmi.systemOptions.resolutionMenu.help"    : "Cambia la resolución de la pantalla del juego.",
+	"vcmi.systemOptions.fullscreenFailed"       : "{Pantalla completa}\n\n ¡Fallo al cambiar a modo de pantalla completa! ¡La resolución actual no es compatible con la pantalla!",
+	"vcmi.systemOptions.framerateButton.hover"  : "Mostrar FPS",
+	"vcmi.systemOptions.framerateButton.help"   : "{Mostrar FPS}\n\n Muestra el contador de Frames Por Segundo en la esquina de la ventana del juego.",
 
-    "vcmi.adventureOptions.numericQuantities.hover": "Cantidades numéricas de criaturas",
-    "vcmi.adventureOptions.numericQuantities.help": "{Cantidades numéricas de criaturas}\n\n Muestra las cantidades aproximadas de criaturas enemigas en formato A-B numérico.",
-    "vcmi.adventureOptions.forceMovementInfo.hover": "Mostrar siempre el coste de movimiento",
-    "vcmi.adventureOptions.forceMovementInfo.help": "{Mostrar siempre el coste de movimiento}\n\n Reemplaza la información predeterminada de la barra de estado con datos de puntos de movimiento sin necesidad de mantener presionado el botón ALT.",
-    "vcmi.adventureOptions.showGrid.hover": "Mostrar cuadrícula",
-    "vcmi.adventureOptions.showGrid.help": "{Mostrar cuadrícula}\n\n Muestra una superposición de cuadrícula que muestra las fronteras entre las casillas del mapa de aventuras.",
-    "vcmi.adventureOptions.mapSwipe.hover": "Deslizamiento de mapa",
-    "vcmi.adventureOptions.mapSwipe.help": "{Deslizamiento de mapa}\n\n Permite el movimiento del mapa mediante el gesto de deslizamiento con el dedo en sistemas con pantalla táctil. En este momento, también se puede acceder mediante el botón izquierdo del mouse.",
-    "vcmi.adventureOptions.mapScrollSpeed1.hover": "",
-    "vcmi.adventureOptions.mapScrollSpeed5.hover": "",
-    "vcmi.adventureOptions.mapScrollSpeed6.hover": "",
-    "vcmi.adventureOptions.mapScrollSpeed1.help": "Establece la velocidad de desplazamiento del mapa como muy lenta",
-    "vcmi.adventureOptions.mapScrollSpeed5.help": "Establece la velocidad de desplazamiento del mapa como muy rápida",
-    "vcmi.adventureOptions.mapScrollSpeed6.help": "Establece la velocidad de desplazamiento del mapa como instantánea.",
+	"vcmi.adventureOptions.infoBarPick.hover" : "Mostrar mensajes en el panel de información",
+	"vcmi.adventureOptions.infoBarPick.help" : "{Mostrar mensajes en el panel de información}\n\nSiempre que sea posible, los mensajes del juego sobre los objetos del mapa que se visiten se mostrarán en el panel de información, en lugar de aparecer en una ventana separada.",
+	"vcmi.adventureOptions.numericQuantities.hover": "Cantidades numéricas de criaturas",
+	"vcmi.adventureOptions.numericQuantities.help": "{Cantidades numéricas de criaturas}\n\n Muestra las cantidades aproximadas de criaturas enemigas en formato A-B numérico.",
+	"vcmi.adventureOptions.forceMovementInfo.hover": "Mostrar siempre el coste de movimiento",
+	"vcmi.adventureOptions.forceMovementInfo.help": "{Mostrar siempre el coste de movimiento}\n\n Reemplaza la información predeterminada de la barra de estado con datos de puntos de movimiento sin necesidad de mantener presionado el botón ALT.",
+	"vcmi.adventureOptions.showGrid.hover": "Mostrar cuadrícula",
+	"vcmi.adventureOptions.showGrid.help": "{Mostrar cuadrícula}\n\n Muestra una superposición de cuadrícula que muestra las fronteras entre las casillas del mapa de aventuras.",
+	"vcmi.adventureOptions.mapSwipe.hover": "Deslizamiento de mapa",
+	"vcmi.adventureOptions.mapSwipe.help": "{Deslizamiento de mapa}\n\n Permite el movimiento del mapa mediante el gesto de deslizamiento con el dedo en sistemas con pantalla táctil. En este momento, también se puede acceder mediante el botón izquierdo del mouse.",
+	"vcmi.adventureOptions.mapScrollSpeed1.hover": "",
+	"vcmi.adventureOptions.mapScrollSpeed5.hover": "",
+	"vcmi.adventureOptions.mapScrollSpeed6.hover": "",
+	"vcmi.adventureOptions.mapScrollSpeed1.help": "Establece la velocidad de desplazamiento del mapa como muy lenta",
+	"vcmi.adventureOptions.mapScrollSpeed5.help": "Establece la velocidad de desplazamiento del mapa como muy rápida",
+	"vcmi.adventureOptions.mapScrollSpeed6.help": "Establece la velocidad de desplazamiento del mapa como instantánea.",
 
-    "vcmi.battleOptions.queueSizeLabel.hover": "Mostrar orden de turno de criaturas",
-    "vcmi.battleOptions.queueSizeNoneButton.hover": "APAGADO",
-    "vcmi.battleOptions.queueSizeAutoButton.hover": "AUTO",
-    "vcmi.battleOptions.queueSizeSmallButton.hover": "PEQUEÑO",
-    "vcmi.battleOptions.queueSizeBigButton.hover": "GRANDE",
-    "vcmi.battleOptions.queueSizeNoneButton.help": "Desactiva completamente la visibilidad del orden de turno de las criaturas en la batalla.",
-    "vcmi.battleOptions.queueSizeAutoButton.help": "Establece el tamaño del orden de turno según la resolución del juego (pequeño cuando se juega con una resolución de pantalla inferior a 700 píxeles de alto, grande en caso contrario).",
-    "vcmi.battleOptions.queueSizeSmallButton.help": "Establece el tamaño del orden de turno como pequeño.",
-    "vcmi.battleOptions.queueSizeBigButton.help": "Establece el tamaño del orden de turno como grande (no compatible si la resolución del juego es inferior a 700 píxeles de alto).",
-    "vcmi.battleOptions.animationsSpeed1.hover": "",
-    "vcmi.battleOptions.animationsSpeed5.hover": "",
-    "vcmi.battleOptions.animationsSpeed6.hover": "",
-    "vcmi.battleOptions.animationsSpeed1.help": "Establece la velocidad de animación como muy lenta.",
-    "vcmi.battleOptions.animationsSpeed5.help": "Establece la velocidad de animación como muy rápida.",
-    "vcmi.battleOptions.animationsSpeed6.help": "Establece la velocidad de animación como instantánea.",
-    "vcmi.battleOptions.skipBattleIntroMusic.hover": "Omitir música de introducción",
-    "vcmi.battleOptions.skipBattleIntroMusic.help": "{Omitir música de introducción}\n\n Omitir la breve música que se reproduce al comienzo de cada batalla antes de que comience la acción. También se puede omitir presionando la tecla ESC.",
+	"vcmi.battleOptions.queueSizeLabel.hover": "Mostrar orden de turno de criaturas",
+	"vcmi.battleOptions.queueSizeNoneButton.hover": "APAGADO",
+	"vcmi.battleOptions.queueSizeAutoButton.hover": "AUTO",
+	"vcmi.battleOptions.queueSizeSmallButton.hover": "PEQUEÑO",
+	"vcmi.battleOptions.queueSizeBigButton.hover": "GRANDE",
+	"vcmi.battleOptions.queueSizeNoneButton.help": "Desactiva completamente la visibilidad del orden de turno de las criaturas en la batalla.",
+	"vcmi.battleOptions.queueSizeAutoButton.help": "Establece el tamaño del orden de turno según la resolución del juego (pequeño cuando se juega con una resolución de pantalla inferior a 700 píxeles de alto, grande en caso contrario).",
+	"vcmi.battleOptions.queueSizeSmallButton.help": "Establece el tamaño del orden de turno como pequeño.",
+	"vcmi.battleOptions.queueSizeBigButton.help": "Establece el tamaño del orden de turno como grande (no compatible si la resolución del juego es inferior a 700 píxeles de alto).",
+	"vcmi.battleOptions.animationsSpeed1.hover": "",
+	"vcmi.battleOptions.animationsSpeed5.hover": "",
+	"vcmi.battleOptions.animationsSpeed6.hover": "",
+	"vcmi.battleOptions.animationsSpeed1.help": "Establece la velocidad de animación como muy lenta.",
+	"vcmi.battleOptions.animationsSpeed5.help": "Establece la velocidad de animación como muy rápida.",
+	"vcmi.battleOptions.animationsSpeed6.help": "Establece la velocidad de animación como instantánea.",
+	"vcmi.battleOptions.touchscreenMode.hover": "Modo pantalla táctil",
+	"vcmi.battleOptions.touchscreenMode.help": "{Modo pantalla táctil}\n\nSi está habilitado, se requiere de un segundo clic para confirmar y ejecutar la acción. Adecuado para dispositivos con pantalla táctil.",
+	"vcmi.battleOptions.movementHighlightOnHover.hover": "Resaltado de movimiento al pasar el ratón",
+	"vcmi.battleOptions.movementHighlightOnHover.help": "{Resaltado de movimiento al pasar el ratón}\n\nResalta el rango de movimiento de la unidad cuando el cursor esta sobre esta.",
+	"vcmi.battleOptions.skipBattleIntroMusic.hover": "Omitir música de introducción",
+	"vcmi.battleOptions.skipBattleIntroMusic.help": "{Omitir música de introducción}\n\n Omitir la breve música que se reproduce al comienzo de cada batalla antes de que comience la acción. También se puede omitir presionando la tecla ESC.",
+	"vcmi.battleWindow.pressKeyToSkipIntro" : "Presiona una tecla para comenzar la batalla inmediatamente.",
 
-    "vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover": "Mostrar criaturas disponibles",
-    "vcmi.otherOptions.availableCreaturesAsDwellingLabel.help": "{Mostrar criaturas disponibles}\n\n Muestra las criaturas disponibles para comprar en lugar de su crecimiento en el resumen de la ciudad (esquina inferior izquierda).",
-    "vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover": "Mostrar crecimiento semanal de criaturas",
-    "vcmi.otherOptions.creatureGrowthAsDwellingLabel.help": "{Mostrar crecimiento semanal de criaturas}\n\n Muestra el crecimiento semanal de las criaturas en lugar de la cantidad disponible en el resumen de la ciudad (esquina inferior izquierda).",
-    "vcmi.otherOptions.compactTownCreatureInfo.hover": "Información compacta de criaturas de la ciudad",
-    "vcmi.otherOptions.compactTownCreatureInfo.help": "{Información compacta de criaturas de la ciudad}\n\n Información más pequeña de las criaturas de la ciudad en el resumen de la ciudad.",
+	"vcmi.battleWindow.damageEstimation.melee" : "Ataque %CREATURE (%DAMAGE).",
+	"vcmi.battleWindow.damageEstimation.meleeKills" : "Ataque %CREATURE (%DAMAGE, %KILLS).",
+	"vcmi.battleWindow.damageEstimation.ranged" : "Disparo %CREATURE (%SHOTS, %DAMAGE).",
+	"vcmi.battleWindow.damageEstimation.rangedKills" : "Disparo %CREATURE (%SHOTS, %DAMAGE, %KILLS).",
+	"vcmi.battleWindow.damageEstimation.shots" : "%d disparos restantes",
+	"vcmi.battleWindow.damageEstimation.shots.1" : "%d disparos restantes",
+	"vcmi.battleWindow.damageEstimation.damage" : "%d daño",
+	"vcmi.battleWindow.damageEstimation.damage.1" : "%d daño",
+	"vcmi.battleWindow.damageEstimation.kills" : "%d perecerán",
+	"vcmi.battleWindow.damageEstimation.kills.1" : "%d perecerán",
 
-    "vcmi.townHall.missingBase": "El edificio base %s debe ser construido primero",
-    "vcmi.townHall.noCreaturesToRecruit": "¡No hay criaturas para reclutar!",
-    "vcmi.townHall.greetingManaVortex": "A medida que te acercas a %s, tu cuerpo se llena de nueva energía. Has duplicado tus puntos de magia normales.",
-    "vcmi.townHall.greetingKnowledge": "Estudias los glifos en %s y obtienes una visión de cómo funcionan varias magias (+1 Conocimiento).",
-    "vcmi.townHall.greetingSpellPower": "%s te enseña nuevas formas de enfocar tus poderes mágicos (+1 Poder).",
-    "vcmi.townHall.greetingExperience": "Una visita a %s te enseña muchas habilidades nuevas (+1000 experiencia).",
-    "vcmi.townHall.greetingAttack": "El tiempo pasado en %s te permite aprender habilidades de combate más efectivas (+1 habilidad de Ataque).",
-    "vcmi.townHall.greetingDefence": "Pasando tiempo en %s, los guerreros experimentados allí te enseñan habilidades defensivas adicionales (+1 Defensa).",
-    "vcmi.townHall.hasNotProduced": "%s no ha producido nada todavía.",
-    "vcmi.townHall.hasProduced": "%s produjo %d %s esta semana.",
-    "vcmi.townHall.greetingCustomBonus": "%s te da +%d %s%s",
-    "vcmi.townHall.greetingCustomUntil": " hasta la próxima batalla.",
-    "vcmi.townHall.greetingInTownMagicWell": "%s ha restaurado tus puntos de magia al máximo.",
+	"vcmi.battleResultsWindow.applyResultsLabel" : "Aplicar resultado de la batalla",
 
-    "vcmi.logicalExpressions.anyOf"  : "Cualquiera de los siguientes:",
-    "vcmi.logicalExpressions.allOf"  : "Todos los siguientes:",
-    "vcmi.logicalExpressions.noneOf" : "Ninguno de los siguientes:",
+	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover": "Mostrar criaturas disponibles",
+	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help": "{Mostrar criaturas disponibles}\n\n Muestra las criaturas disponibles para comprar en lugar de su crecimiento en el resumen de la ciudad (esquina inferior izquierda).",
+	"vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover": "Mostrar crecimiento semanal de criaturas",
+	"vcmi.otherOptions.creatureGrowthAsDwellingLabel.help": "{Mostrar crecimiento semanal de criaturas}\n\n Muestra el crecimiento semanal de las criaturas en lugar de la cantidad disponible en el resumen de la ciudad (esquina inferior izquierda).",
+	"vcmi.otherOptions.compactTownCreatureInfo.hover": "Información compacta de criaturas de la ciudad",
+	"vcmi.otherOptions.compactTownCreatureInfo.help": "{Información compacta de criaturas de la ciudad}\n\n Información más pequeña de las criaturas de la ciudad en el resumen de la ciudad.",
 
-    "vcmi.heroWindow.openCommander.hover" : "Abrir ventana de comandante",
-    "vcmi.heroWindow.openCommander.help"  : "Muestra información sobre el comandante de este héroe",
+	"vcmi.townHall.missingBase": "El edificio base %s debe ser construido primero",
+	"vcmi.townHall.noCreaturesToRecruit": "¡No hay criaturas para reclutar!",
+	"vcmi.townHall.greetingManaVortex": "A medida que te acercas a %s, tu cuerpo se llena de nueva energía. Has duplicado tus puntos de magia normales.",
+	"vcmi.townHall.greetingKnowledge": "Estudias los glifos en %s y obtienes una visión de cómo funcionan varias magias (+1 Conocimiento).",
+	"vcmi.townHall.greetingSpellPower": "%s te enseña nuevas formas de enfocar tus poderes mágicos (+1 Poder).",
+	"vcmi.townHall.greetingExperience": "Una visita a %s te enseña muchas habilidades nuevas (+1000 experiencia).",
+	"vcmi.townHall.greetingAttack": "El tiempo pasado en %s te permite aprender habilidades de combate más efectivas (+1 habilidad de Ataque).",
+	"vcmi.townHall.greetingDefence": "Pasando tiempo en %s, los guerreros experimentados allí te enseñan habilidades defensivas adicionales (+1 Defensa).",
+	"vcmi.townHall.hasNotProduced": "%s no ha producido nada todavía.",
+	"vcmi.townHall.hasProduced": "%s produjo %d %s esta semana.",
+	"vcmi.townHall.greetingCustomBonus": "%s te da +%d %s%s",
+	"vcmi.townHall.greetingCustomUntil": " hasta la próxima batalla.",
+	"vcmi.townHall.greetingInTownMagicWell": "%s ha restaurado tus puntos de magia al máximo.",
 
-    "vcmi.commanderWindow.artifactMessage" : "¿Quieres devolver este artefacto al héroe?",
+	"vcmi.logicalExpressions.anyOf"  : "Cualquiera de los siguientes:",
+	"vcmi.logicalExpressions.allOf"  : "Todos los siguientes:",
+	"vcmi.logicalExpressions.noneOf" : "Ninguno de los siguientes:",
 
-    "vcmi.creatureWindow.showBonuses.hover"    : "Cambiar a vista de bonificaciones",
-    "vcmi.creatureWindow.showBonuses.help"     : "Muestra todas las bonificaciones activas del comandante",
-    "vcmi.creatureWindow.showSkills.hover"     : "Cambiar a vista de habilidades",
-    "vcmi.creatureWindow.showSkills.help"      : "Muestra todas las habilidades aprendidas del comandante",
-    "vcmi.creatureWindow.returnArtifact.hover" : "Devolver artefacto",
-    "vcmi.creatureWindow.returnArtifact.help"  : "Usa este botón para devolver un artefacto del almacen al inventario del héroe",
+	"vcmi.heroWindow.openCommander.hover" : "Abrir ventana de comandante",
+	"vcmi.heroWindow.openCommander.help"  : "Muestra información sobre el comandante de este héroe",
 
+	"vcmi.commanderWindow.artifactMessage" : "¿Quieres devolver este artefacto al héroe?",
 
-    "vcmi.questLog.hideComplete.hover" : "Ocultar misiones completas",
-    "vcmi.questLog.hideComplete.help" : "Ocultar todas las misiones que ya han sido completadas",
+	"vcmi.creatureWindow.showBonuses.hover"    : "Cambiar a vista de bonificaciones",
+	"vcmi.creatureWindow.showBonuses.help"     : "Muestra todas las bonificaciones activas del comandante",
+	"vcmi.creatureWindow.showSkills.hover"     : "Cambiar a vista de habilidades",
+	"vcmi.creatureWindow.showSkills.help"      : "Muestra todas las habilidades aprendidas del comandante",
+	"vcmi.creatureWindow.returnArtifact.hover" : "Devolver artefacto",
+	"vcmi.creatureWindow.returnArtifact.help"  : "Usa este botón para devolver un artefacto del almacen al inventario del héroe",
 
-    "vcmi.randomMapTab.widgets.defaultTemplate"      : "(predeterminado)",
-    "vcmi.randomMapTab.widgets.templateLabel"        : "Plantilla",
-    "vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Configurar...",
-    "vcmi.randomMapTab.widgets.teamAlignmentsLabel"  : "Alineaciones de equipos",
-    "vcmi.randomMapTab.widgets.roadTypesLabel"       : "Tipos de caminos",
+	"vcmi.questLog.hideComplete.hover" : "Ocultar misiones completas",
+	"vcmi.questLog.hideComplete.help" : "Ocultar todas las misiones que ya han sido completadas",
+
+	"vcmi.randomMapTab.widgets.defaultTemplate"      : "(predeterminado)",
+	"vcmi.randomMapTab.widgets.templateLabel"        : "Plantilla",
+	"vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Configurar...",
+	"vcmi.randomMapTab.widgets.teamAlignmentsLabel"  : "Alineaciones de equipos",
+	"vcmi.randomMapTab.widgets.roadTypesLabel"       : "Tipos de caminos",
 
-	
 	// few strings from WoG used by vcmi
-    "vcmi.stackExperience.description" : "» D e t a l l e s  d e  E x p e r i e n c i a  d e l  G r u p o «\n\nTipo de Criatura ................ : %s\nRango de Experiencia ............ : %s (%i)\nPuntos de Experiencia ............ : %i\nPuntos de Experiencia para el\nSiguiente Rango ............... : %i\nExperiencia Máxima por Batalla .. : %i%% (%i)\nNúmero de Criaturas en el grupo .. : %i\nMáximo de Nuevos Reclutas sin\nPerder el Rango Actual ......... : %i\nMultiplicador de Experiencia .... : %.2f\nMultiplicador de Actualización .. : %.2f\nExperiencia después del Rango 10 : %i\nMáximo de Nuevos Reclutas para\nMantener el Rango 10 si\nEstá en la Experiencia Máxima : %i",
-    "vcmi.stackExperience.rank.0" : "Básico",
-    "vcmi.stackExperience.rank.1" : "Novato",
-    "vcmi.stackExperience.rank.2" : "Entrenado",
-    "vcmi.stackExperience.rank.3" : "Hábil",
-    "vcmi.stackExperience.rank.4" : "Probado",
-    "vcmi.stackExperience.rank.5" : "Veterano",
-    "vcmi.stackExperience.rank.6" : "Experto",
-    "vcmi.stackExperience.rank.7" : "Experto Superior",
-    "vcmi.stackExperience.rank.8" : "Élite",
-    "vcmi.stackExperience.rank.9" : "Maestro",
-    "vcmi.stackExperience.rank.10" : "As",
+	"vcmi.stackExperience.description" : "» D e t a l l e s  d e  E x p e r i e n c i a  d e l  G r u p o «\n\nTipo de Criatura ................ : %s\nRango de Experiencia ............ : %s (%i)\nPuntos de Experiencia ............ : %i\nPuntos de Experiencia para el\nSiguiente Rango ............... : %i\nExperiencia Máxima por Batalla .. : %i%% (%i)\nNúmero de Criaturas en el grupo .. : %i\nMáximo de Nuevos Reclutas sin\nPerder el Rango Actual ......... : %i\nMultiplicador de Experiencia .... : %.2f\nMultiplicador de Actualización .. : %.2f\nExperiencia después del Rango 10 : %i\nMáximo de Nuevos Reclutas para\nMantener el Rango 10 si\nEstá en la Experiencia Máxima : %i",
+	"vcmi.stackExperience.rank.0" : "Básico",
+	"vcmi.stackExperience.rank.1" : "Novato",
+	"vcmi.stackExperience.rank.2" : "Entrenado",
+	"vcmi.stackExperience.rank.3" : "Hábil",
+	"vcmi.stackExperience.rank.4" : "Probado",
+	"vcmi.stackExperience.rank.5" : "Veterano",
+	"vcmi.stackExperience.rank.6" : "Experto",
+	"vcmi.stackExperience.rank.7" : "Experto Superior",
+	"vcmi.stackExperience.rank.8" : "Élite",
+	"vcmi.stackExperience.rank.9" : "Maestro",
+	"vcmi.stackExperience.rank.10" : "As",
 
-    "core.bonus.ADDITIONAL_ATTACK.name": "Doble Ataque",
-    "core.bonus.ADDITIONAL_ATTACK.description": "Ataca dos veces",
-    "core.bonus.ADDITIONAL_RETALIATION.name": "Contraataques adicionales",
-    "core.bonus.ADDITIONAL_RETALIATION.description": "Puede contraatacar ${val} veces adicionales",
-    "core.bonus.AIR_IMMUNITY.name": "Inmunidad al Aire",
-    "core.bonus.AIR_IMMUNITY.description": "Inmune a todos los hechizos de la escuela de Aire",
-    "core.bonus.ATTACKS_ALL_ADJACENT.name": "Ataque en todas las direcciones",
-    "core.bonus.ATTACKS_ALL_ADJACENT.description": "Ataca a todos los enemigos adyacentes",
-    "core.bonus.BLOCKS_RETALIATION.name": "Sin contraataque",
-    "core.bonus.BLOCKS_RETALIATION.description": "El enemigo no puede contraatacar",
-    "core.bonus.BLOCKS_RANGED_RETALIATION.name": "Sin contraataque a distancia",
-    "core.bonus.BLOCKS_RANGED_RETALIATION.description": "El enemigo no puede contraatacar disparando",
-    "core.bonus.CATAPULT.name": "Catapulta",
-    "core.bonus.CATAPULT.description": "Ataca a las paredes de asedio",
-    "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Reducir coste del conjuro (${val})",
-    "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Reduce el coste del conjuro para el héroe",
-    "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Disminuir efecto mágico (${val})",
-    "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Aumenta el coste de los hechizos del enemigo",
-    "core.bonus.CHARGE_IMMUNITY.name": "Inmunidad a la Carga",
-    "core.bonus.CHARGE_IMMUNITY.description": "Inmune al ataque de carga del campeón",
-    "core.bonus.DARKNESS.name": "Cobertura de Oscuridad",
-    "core.bonus.DARKNESS.description": "Añade un radio de oscuridad de ${val}",
-    "core.bonus.DEATH_STARE.name": "Mirada Mortal (${val}%)",
-    "core.bonus.DEATH_STARE.description": "Tiene ${val}% de probabilidad de matar a una criatura",
-    "core.bonus.DEFENSIVE_STANCE.name": "Bonificación de Defensa",
-    "core.bonus.DEFENSIVE_STANCE.description": "+${val} de Defensa al defender",
-    "core.bonus.DESTRUCTION.name": "Destrucción",
-    "core.bonus.DESTRUCTION.description": "Tiene ${val}% de probabilidad de matar unidades extra después del ataque",
-    "core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Golpe Mortal",
-    "core.bonus.DOUBLE_DAMAGE_CHANCE.description": "${val}% de probabilidad de doble daño",
-    "core.bonus.DRAGON_NATURE.name": "Dragón",
-    "core.bonus.DRAGON_NATURE.description": "La criatura tiene la naturaleza de dragón",
-    "core.bonus.DIRECT_DAMAGE_IMMUNITY.name": "Inmunidad al Daño Directo",
-    "core.bonus.DIRECT_DAMAGE_IMMUNITY.description": "Inmune a hechizos de daño directo",
-    "core.bonus.EARTH_IMMUNITY.name": "Inmunidad a la Tierra",
-    "core.bonus.EARTH_IMMUNITY.description": "Inmune a todos los hechizos de la escuela de tierra",
-    "core.bonus.ENCHANTER.name": "Encantador",
-    "core.bonus.ENCHANTER.description": "Puede lanzar ${subtype.spell} masivo cada turno",
-    "core.bonus.ENCHANTED.name": "Encantado",
-    "core.bonus.ENCHANTED.description": "Afectado por el hechizo permanente ${subtype.spell}",
-    "core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignorar Defensa (${val}%)",
-    "core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Ignora una parte de la defensa al atacar",
-    "core.bonus.FIRE_IMMUNITY.name": "Inmunidad al Fuego",
-    "core.bonus.FIRE_IMMUNITY.description": "Inmune a todos los hechizos de la escuela de fuego",
-    "core.bonus.FIRE_SHIELD.name": "Escudo de Fuego (${val}%)",
-    "core.bonus.FIRE_SHIELD.description": "Refleja una parte del daño cuerpo a cuerpo",
-    "core.bonus.FIRST_STRIKE.name": "Primer Ataque",
-    "core.bonus.FIRST_STRIKE.description": "Esta criatura ataca primero en lugar de contraatacar",
-    "core.bonus.FEAR.name": "Miedo",
-    "core.bonus.FEAR.description": "Causa miedo a un grupo enemigo",
-    "core.bonus.FEARLESS.name": "Inmune al miedo",
-    "core.bonus.FEARLESS.description": "Inmune a la habilidad de miedo",
-    "core.bonus.FLYING.name": "Volar",
-    "core.bonus.FLYING.description": "Puede volar (ignora obstáculos)",
-    "core.bonus.FREE_SHOOTING.name": "Disparo cercano",
-    "core.bonus.FREE_SHOOTING.description": "Puede disparar en combate cercano",
-    "core.bonus.FULL_HP_REGENERATION.name": "Regeneración completa",
-    "core.bonus.FULL_HP_REGENERATION.description": "Puede regenerar hasta la salud completa",
-    "core.bonus.GARGOYLE.name": "Gárgola",
-    "core.bonus.GARGOYLE.description": "No puede ser resucitado o curado",
-    "core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Reducir daño (${val}%)",
-    "core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Reduce el daño físico de ataques a distancia o cuerpo a cuerpo",
-    "core.bonus.HATE.name": "Odio a ${subtype.creature}",
-    "core.bonus.HATE.description": "Hace un ${val}% más de daño",
-    "core.bonus.HEALER.name": "Curador",
-    "core.bonus.HEALER.description": "Cura unidades aliadas",
-    "core.bonus.HP_REGENERATION.name": "Regeneración",
-    "core.bonus.HP_REGENERATION.description": "Cura ${val} puntos de vida cada ronda",
-    "core.bonus.JOUSTING.name": "Carga de campeón",
-    "core.bonus.JOUSTING.description": "+5% de daño por hexágono recorrido",
-    "core.bonus.KING1.name": "Rey 1",
-    "core.bonus.KING1.description": "Vulnerable al SLAYER básico",
-    "core.bonus.KING2.name": "Rey 2",
-    "core.bonus.KING2.description": "Vulnerable al SLAYER avanzado",
-    "core.bonus.KING3.name": "Rey 3",
-    "core.bonus.KING3.description":"Vulnerable al SLAYER experto",
-    "core.bonus.LEVEL_SPELL_IMMUNITY.name": "Inmunidad a hechizos 1-${val}",
-    "core.bonus.LEVEL_SPELL_IMMUNITY.description": "Inmune a hechizos de nivel 1-${val}",
-    "core.bonus.LIMITED_SHOOTING_RANGE.name" : "Rango de disparo limitado",
-    "core.bonus.LIMITED_SHOOTING_RANGE.description" : "No puede disparar a objetivos más allá de ${val} hexágonos",
-    "core.bonus.LIFE_DRAIN.name": "Drenaje de vida (${val}%)",
-    "core.bonus.LIFE_DRAIN.description": "Drena ${val}% del daño causado",
-    "core.bonus.MANA_CHANNELING.name": "Canalización mágica ${val}%",
-    "core.bonus.MANA_CHANNELING.description": "Tu héroe recibe el maná gastado por el enemigo",
-    "core.bonus.MANA_DRAIN.name": "Drenaje de maná",
-    "core.bonus.MANA_DRAIN.description": "Drena ${val} de maná cada turno",
-    "core.bonus.MAGIC_MIRROR.name": "Espejo mágico (${val}%)",
-    "core.bonus.MAGIC_MIRROR.description": "Tiene una probabilidad del ${val}% de redirigir un hechizo ofensivo al enemigo",
-    "core.bonus.MAGIC_RESISTANCE.name": "Resistencia mágica (${MR}%)",
-    "core.bonus.MAGIC_RESISTANCE.description": "Tiene una probabilidad del ${MR}% de resistir el hechizo del enemigo",
-    "core.bonus.MIND_IMMUNITY.name": "Inmunidad a hechizos mentales",
-    "core.bonus.MIND_IMMUNITY.description": "Inmune a hechizos de tipo mental",
-    "core.bonus.NO_DISTANCE_PENALTY.name": "Sin penalización por distancia",
-    "core.bonus.NO_DISTANCE_PENALTY.description": "Causa el daño completo desde cualquier distancia",
-    "core.bonus.NO_MELEE_PENALTY.name": "Sin penalización de melé",
-    "core.bonus.NO_MELEE_PENALTY.description": "La criatura no tiene penalización en el combate cuerpo a cuerpo",
-    "core.bonus.NO_MORALE.name": "Moral neutral",
-    "core.bonus.NO_MORALE.description": "La criatura es inmune a los efectos de la moral",
-    "core.bonus.NO_WALL_PENALTY.name": "Sin penalización por muros",
-    "core.bonus.NO_WALL_PENALTY.description": "Daño completo durante el asedio",
-    "core.bonus.NON_LIVING.name": "No vivo",
-    "core.bonus.NON_LIVING.description": "Inmunidad a muchos efectos",
-    "core.bonus.RANDOM_SPELLCASTER.name": "Lanzador de hechizos aleatorio",
-    "core.bonus.RANDOM_SPELLCASTER.description": "Puede lanzar hechizos aleatorios",
-    "core.bonus.RANGED_RETALIATION.name": "Contraataque a distancia",
-    "core.bonus.RANGED_RETALIATION.description": "Puede realizar un contraataque a distancia",
-    "core.bonus.RECEPTIVE.name": "Receptivo",
-    "core.bonus.RECEPTIVE.description": "No tiene inmunidad a hechizos amistosos",
-    "core.bonus.REBIRTH.name": "Renacimiento (${val}%)",
-    "core.bonus.REBIRTH.description": "El ${val}% del grupo resucitará después de la muerte",
-    "core.bonus.RETURN_AFTER_STRIKE.name": "Atacar y volver",
-    "core.bonus.RETURN_AFTER_STRIKE.description": "Regresa después de un ataque cuerpo a cuerpo",
-    "core.bonus.SHOOTER.name": "A distancia",
-    "core.bonus.SHOOTER.description": "La criatura puede disparar",
-    "core.bonus.SHOOTS_ALL_ADJACENT.name": "Dispara en todas direcciones",
-    "core.bonus.SHOOTS_ALL_ADJACENT.description": "Los ataques a distancia de esta criatura impactan a todos los objetivos en un área pequeña",
-    "core.bonus.SOUL_STEAL.name": "Roba almas",
-    "core.bonus.SOUL_STEAL.description": "Gana ${val} nuevas criaturas por cada enemigo eliminado",
-    "core.bonus.SPELLCASTER.name": "Lanzador de hechizos",
-    "core.bonus.SPELLCASTER.description": "Puede lanzar ${subtype.spell}",
-    "core.bonus.SPELL_AFTER_ATTACK.name": "Lanzar después del ataque",
-    "core.bonus.SPELL_AFTER_ATTACK.description": "${val}% de probabilidad de lanzar ${subtype.spell} después del ataque",
-    "core.bonus.SPELL_BEFORE_ATTACK.name": "Lanzar antes del ataque",
-    "core.bonus.SPELL_BEFORE_ATTACK.description": "${val}% de probabilidad de lanzar ${subtype.spell} antes del ataque",
-    "core.bonus.SPELL_DAMAGE_REDUCTION.name": "Resistencia a hechizos",
-    "core.bonus.SPELL_DAMAGE_REDUCTION.description": "Reduce el daño de los hechizos en un ${val}%.",
-    "core.bonus.SPELL_IMMUNITY.name": "Inmunidad a hechizos",
-    "core.bonus.SPELL_IMMUNITY.description": "Inmune a ${subtype.spell}",
-    "core.bonus.SPELL_LIKE_ATTACK.name": "Ataque similar a hechizo",
-    "core.bonus.SPELL_LIKE_ATTACK.description": "Ataca con ${subtype.spell}",
-    "core.bonus.SPELL_RESISTANCE_AURA.name": "Aura de resistencia",
-    "core.bonus.SPELL_RESISTANCE_AURA.description": "Las unidades cercanas obtienen una resistencia mágica del ${val}%",
-    "core.bonus.SUMMON_GUARDIANS.name": "Invocar guardianes",
-    "core.bonus.SUMMON_GUARDIANS.description": "Al comienzo de la batalla invoca ${subtype.creature} (${val}%)",
-    "core.bonus.SYNERGY_TARGET.name": "Sinergia",
-    "core.bonus.SYNERGY_TARGET.description": "Esta criatura es vulnerable al efecto de sinergia",
-    "core.bonus.TWO_HEX_ATTACK_BREATH.name": "Aliento",
-    "core.bonus.TWO_HEX_ATTACK_BREATH.description": "Ataque de aliento (rango de 2 hexágonos)",
-    "core.bonus.THREE_HEADED_ATTACK.name": "Ataque de tres cabezas",
-    "core.bonus.THREE_HEADED_ATTACK.description": "Ataca a tres unidades adyacentes",
-    "core.bonus.TRANSMUTATION.name": "Transmutación",
-    "core.bonus.TRANSMUTATION.description": "${val}% de probabilidad de transformar la unidad atacada en otro tipo",
-    "core.bonus.UNDEAD.name": "No muerto",
-    "core.bonus.UNDEAD.description": "La criatura es un no muerto",
-    "core.bonus.UNLIMITED_RETALIATIONS.name": "Retaliaciones ilimitadas",
-    "core.bonus.UNLIMITED_RETALIATIONS.description": "Puede realizar un número ilimitado de ataques de represalia",
-    "core.bonus.WATER_IMMUNITY.name": "Inmunidad al agua",
-    "core.bonus.WATER_IMMUNITY.description": "Inmune a todos los hechizos de la escuela del agua",
-    "core.bonus.WIDE_BREATH.name": "Aliento amplio",
-    "core.bonus.WIDE_BREATH.description": "Ataque de aliento amplio (varios hexágonos)"
+	"core.bonus.ADDITIONAL_ATTACK.name": "Doble Ataque",
+	"core.bonus.ADDITIONAL_ATTACK.description": "Ataca dos veces",
+	"core.bonus.ADDITIONAL_RETALIATION.name": "Contraataques adicionales",
+	"core.bonus.ADDITIONAL_RETALIATION.description": "Puede contraatacar ${val} veces adicionales",
+	"core.bonus.AIR_IMMUNITY.name": "Inmunidad al Aire",
+	"core.bonus.AIR_IMMUNITY.description": "Inmune a todos los hechizos de la escuela de Aire",
+	"core.bonus.ATTACKS_ALL_ADJACENT.name": "Ataque en todas las direcciones",
+	"core.bonus.ATTACKS_ALL_ADJACENT.description": "Ataca a todos los enemigos adyacentes",
+	"core.bonus.BLOCKS_RETALIATION.name": "Sin contraataque",
+	"core.bonus.BLOCKS_RETALIATION.description": "El enemigo no puede contraatacar",
+	"core.bonus.BLOCKS_RANGED_RETALIATION.name": "Sin contraataque a distancia",
+	"core.bonus.BLOCKS_RANGED_RETALIATION.description": "El enemigo no puede contraatacar disparando",
+	"core.bonus.CATAPULT.name": "Catapulta",
+	"core.bonus.CATAPULT.description": "Ataca a las paredes de asedio",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Reducir coste del conjuro (${val})",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Reduce el coste del conjuro para el héroe",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Disminuir efecto mágico (${val})",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Aumenta el coste de los hechizos del enemigo",
+	"core.bonus.CHARGE_IMMUNITY.name": "Inmunidad a la Carga",
+	"core.bonus.CHARGE_IMMUNITY.description": "Inmune al ataque de carga del campeón",
+	"core.bonus.DARKNESS.name": "Cobertura de Oscuridad",
+	"core.bonus.DARKNESS.description": "Añade un radio de oscuridad de ${val}",
+	"core.bonus.DEATH_STARE.name": "Mirada Mortal (${val}%)",
+	"core.bonus.DEATH_STARE.description": "Tiene ${val}% de probabilidad de matar a una criatura",
+	"core.bonus.DEFENSIVE_STANCE.name": "Bonificación de Defensa",
+	"core.bonus.DEFENSIVE_STANCE.description": "+${val} de Defensa al defender",
+	"core.bonus.DESTRUCTION.name": "Destrucción",
+	"core.bonus.DESTRUCTION.description": "Tiene ${val}% de probabilidad de matar unidades extra después del ataque",
+	"core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Golpe Mortal",
+	"core.bonus.DOUBLE_DAMAGE_CHANCE.description": "${val}% de probabilidad de doble daño",
+	"core.bonus.DRAGON_NATURE.name": "Dragón",
+	"core.bonus.DRAGON_NATURE.description": "La criatura tiene la naturaleza de dragón",
+	"core.bonus.DIRECT_DAMAGE_IMMUNITY.name": "Inmunidad al Daño Directo",
+	"core.bonus.DIRECT_DAMAGE_IMMUNITY.description": "Inmune a hechizos de daño directo",
+	"core.bonus.EARTH_IMMUNITY.name": "Inmunidad a la Tierra",
+	"core.bonus.EARTH_IMMUNITY.description": "Inmune a todos los hechizos de la escuela de tierra",
+	"core.bonus.ENCHANTER.name": "Encantador",
+	"core.bonus.ENCHANTER.description": "Puede lanzar ${subtype.spell} masivo cada turno",
+	"core.bonus.ENCHANTED.name": "Encantado",
+	"core.bonus.ENCHANTED.description": "Afectado por el hechizo permanente ${subtype.spell}",
+	"core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignorar Defensa (${val}%)",
+	"core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Ignora una parte de la defensa al atacar",
+	"core.bonus.FIRE_IMMUNITY.name": "Inmunidad al Fuego",
+	"core.bonus.FIRE_IMMUNITY.description": "Inmune a todos los hechizos de la escuela de fuego",
+	"core.bonus.FIRE_SHIELD.name": "Escudo de Fuego (${val}%)",
+	"core.bonus.FIRE_SHIELD.description": "Refleja una parte del daño cuerpo a cuerpo",
+	"core.bonus.FIRST_STRIKE.name": "Primer Ataque",
+	"core.bonus.FIRST_STRIKE.description": "Esta criatura ataca primero en lugar de contraatacar",
+	"core.bonus.FEAR.name": "Miedo",
+	"core.bonus.FEAR.description": "Causa miedo a un grupo enemigo",
+	"core.bonus.FEARLESS.name": "Inmune al miedo",
+	"core.bonus.FEARLESS.description": "Inmune a la habilidad de miedo",
+	"core.bonus.FLYING.name": "Volar",
+	"core.bonus.FLYING.description": "Puede volar (ignora obstáculos)",
+	"core.bonus.FREE_SHOOTING.name": "Disparo cercano",
+	"core.bonus.FREE_SHOOTING.description": "Puede disparar en combate cercano",
+	"core.bonus.GARGOYLE.name": "Gárgola",
+	"core.bonus.GARGOYLE.description": "No puede ser resucitado o curado",
+	"core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Reducir daño (${val}%)",
+	"core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Reduce el daño físico de ataques a distancia o cuerpo a cuerpo",
+	"core.bonus.HATE.name": "Odio a ${subtype.creature}",
+	"core.bonus.HATE.description": "Hace un ${val}% más de daño",
+	"core.bonus.HEALER.name": "Curador",
+	"core.bonus.HEALER.description": "Cura unidades aliadas",
+	"core.bonus.HP_REGENERATION.name": "Regeneración",
+	"core.bonus.HP_REGENERATION.description": "Cura ${val} puntos de vida cada ronda",
+	"core.bonus.JOUSTING.name": "Carga de campeón",
+	"core.bonus.JOUSTING.description": "+5% de daño por hexágono recorrido",
+	"core.bonus.KING.name": "Rey",
+	"core.bonus.KING.description": "Vulnerable a nivel SLAYER ${val} o superior.",
+	"core.bonus.LEVEL_SPELL_IMMUNITY.name": "Inmunidad a hechizos 1-${val}",
+	"core.bonus.LEVEL_SPELL_IMMUNITY.description": "Inmune a hechizos de nivel 1-${val}",
+	"core.bonus.LIMITED_SHOOTING_RANGE.name" : "Rango de disparo limitado",
+	"core.bonus.LIMITED_SHOOTING_RANGE.description" : "No puede disparar a objetivos más allá de ${val} hexágonos",
+	"core.bonus.LIFE_DRAIN.name": "Drenaje de vida (${val}%)",
+	"core.bonus.LIFE_DRAIN.description": "Drena ${val}% del daño causado",
+	"core.bonus.MANA_CHANNELING.name": "Canalización mágica ${val}%",
+	"core.bonus.MANA_CHANNELING.description": "Tu héroe recibe el maná gastado por el enemigo",
+	"core.bonus.MANA_DRAIN.name": "Drenaje de maná",
+	"core.bonus.MANA_DRAIN.description": "Drena ${val} de maná cada turno",
+	"core.bonus.MAGIC_MIRROR.name": "Espejo mágico (${val}%)",
+	"core.bonus.MAGIC_MIRROR.description": "Tiene una probabilidad del ${val}% de redirigir un hechizo ofensivo al enemigo",
+	"core.bonus.MAGIC_RESISTANCE.name": "Resistencia mágica (${MR}%)",
+	"core.bonus.MAGIC_RESISTANCE.description": "Tiene una probabilidad del ${MR}% de resistir el hechizo del enemigo",
+	"core.bonus.MIND_IMMUNITY.name": "Inmunidad a hechizos mentales",
+	"core.bonus.MIND_IMMUNITY.description": "Inmune a hechizos de tipo mental",
+	"core.bonus.NO_DISTANCE_PENALTY.name": "Sin penalización por distancia",
+	"core.bonus.NO_DISTANCE_PENALTY.description": "Causa el daño completo desde cualquier distancia",
+	"core.bonus.NO_MELEE_PENALTY.name": "Sin penalización de melé",
+	"core.bonus.NO_MELEE_PENALTY.description": "La criatura no tiene penalización en el combate cuerpo a cuerpo",
+	"core.bonus.NO_MORALE.name": "Moral neutral",
+	"core.bonus.NO_MORALE.description": "La criatura es inmune a los efectos de la moral",
+	"core.bonus.NO_WALL_PENALTY.name": "Sin penalización por muros",
+	"core.bonus.NO_WALL_PENALTY.description": "Daño completo durante el asedio",
+	"core.bonus.NON_LIVING.name": "No vivo",
+	"core.bonus.NON_LIVING.description": "Inmunidad a muchos efectos",
+	"core.bonus.RANDOM_SPELLCASTER.name": "Lanzador de hechizos aleatorio",
+	"core.bonus.RANDOM_SPELLCASTER.description": "Puede lanzar hechizos aleatorios",
+	"core.bonus.RANGED_RETALIATION.name": "Contraataque a distancia",
+	"core.bonus.RANGED_RETALIATION.description": "Puede realizar un contraataque a distancia",
+	"core.bonus.RECEPTIVE.name": "Receptivo",
+	"core.bonus.RECEPTIVE.description": "No tiene inmunidad a hechizos amistosos",
+	"core.bonus.REBIRTH.name": "Renacimiento (${val}%)",
+	"core.bonus.REBIRTH.description": "El ${val}% del grupo resucitará después de la muerte",
+	"core.bonus.RETURN_AFTER_STRIKE.name": "Atacar y volver",
+	"core.bonus.RETURN_AFTER_STRIKE.description": "Regresa después de un ataque cuerpo a cuerpo",
+	"core.bonus.SHOOTER.name": "A distancia",
+	"core.bonus.SHOOTER.description": "La criatura puede disparar",
+	"core.bonus.SHOOTS_ALL_ADJACENT.name": "Dispara en todas direcciones",
+	"core.bonus.SHOOTS_ALL_ADJACENT.description": "Los ataques a distancia de esta criatura impactan a todos los objetivos en un área pequeña",
+	"core.bonus.SOUL_STEAL.name": "Roba almas",
+	"core.bonus.SOUL_STEAL.description": "Gana ${val} nuevas criaturas por cada enemigo eliminado",
+	"core.bonus.SPELLCASTER.name": "Lanzador de hechizos",
+	"core.bonus.SPELLCASTER.description": "Puede lanzar ${subtype.spell}",
+	"core.bonus.SPELL_AFTER_ATTACK.name": "Lanzar después del ataque",
+	"core.bonus.SPELL_AFTER_ATTACK.description": "${val}% de probabilidad de lanzar ${subtype.spell} después del ataque",
+	"core.bonus.SPELL_BEFORE_ATTACK.name": "Lanzar antes del ataque",
+	"core.bonus.SPELL_BEFORE_ATTACK.description": "${val}% de probabilidad de lanzar ${subtype.spell} antes del ataque",
+	"core.bonus.SPELL_DAMAGE_REDUCTION.name": "Resistencia a hechizos",
+	"core.bonus.SPELL_DAMAGE_REDUCTION.description": "Reduce el daño de los hechizos en un ${val}%.",
+	"core.bonus.SPELL_IMMUNITY.name": "Inmunidad a hechizos",
+	"core.bonus.SPELL_IMMUNITY.description": "Inmune a ${subtype.spell}",
+	"core.bonus.SPELL_LIKE_ATTACK.name": "Ataque similar a hechizo",
+	"core.bonus.SPELL_LIKE_ATTACK.description": "Ataca con ${subtype.spell}",
+	"core.bonus.SPELL_RESISTANCE_AURA.name": "Aura de resistencia",
+	"core.bonus.SPELL_RESISTANCE_AURA.description": "Las unidades cercanas obtienen una resistencia mágica del ${val}%",
+	"core.bonus.SUMMON_GUARDIANS.name": "Invocar guardianes",
+	"core.bonus.SUMMON_GUARDIANS.description": "Al comienzo de la batalla invoca ${subtype.creature} (${val}%)",
+	"core.bonus.SYNERGY_TARGET.name": "Sinergia",
+	"core.bonus.SYNERGY_TARGET.description": "Esta criatura es vulnerable al efecto de sinergia",
+	"core.bonus.TWO_HEX_ATTACK_BREATH.name": "Aliento",
+	"core.bonus.TWO_HEX_ATTACK_BREATH.description": "Ataque de aliento (rango de 2 hexágonos)",
+	"core.bonus.THREE_HEADED_ATTACK.name": "Ataque de tres cabezas",
+	"core.bonus.THREE_HEADED_ATTACK.description": "Ataca a tres unidades adyacentes",
+	"core.bonus.TRANSMUTATION.name": "Transmutación",
+	"core.bonus.TRANSMUTATION.description": "${val}% de probabilidad de transformar la unidad atacada en otro tipo",
+	"core.bonus.UNDEAD.name": "No muerto",
+	"core.bonus.UNDEAD.description": "La criatura es un no muerto",
+	"core.bonus.UNLIMITED_RETALIATIONS.name": "Retaliaciones ilimitadas",
+	"core.bonus.UNLIMITED_RETALIATIONS.description": "Puede realizar un número ilimitado de ataques de represalia",
+	"core.bonus.WATER_IMMUNITY.name": "Inmunidad al agua",
+	"core.bonus.WATER_IMMUNITY.description": "Inmune a todos los hechizos de la escuela del agua",
+	"core.bonus.WIDE_BREATH.name": "Aliento amplio",
+	"core.bonus.WIDE_BREATH.description": "Ataque de aliento amplio (varios hexágonos)"
 }

+ 2 - 2
client/CPlayerInterface.cpp

@@ -1225,11 +1225,11 @@ void CPlayerInterface::availableCreaturesChanged( const CGDwelling *town )
 void CPlayerInterface::heroBonusChanged( const CGHeroInstance *hero, const Bonus &bonus, bool gain )
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	if (bonus.type == Bonus::NONE)
+	if (bonus.type == BonusType::NONE)
 		return;
 
 	adventureInt->onHeroChanged(hero);
-	if ((bonus.type == Bonus::FLYING_MOVEMENT || bonus.type == Bonus::WATER_WALKING) && !gain)
+	if ((bonus.type == BonusType::FLYING_MOVEMENT || bonus.type == BonusType::WATER_WALKING) && !gain)
 	{
 		//recalculate paths because hero has lost bonus influencing pathfinding
 		localState->erasePath(hero);

+ 1 - 1
client/NetPacksClient.cpp

@@ -706,7 +706,7 @@ void ApplyClientNetPackVisitor::visitBattleSetActiveStack(BattleSetActiveStack &
 
 	const CStack *activated = gs.curB->battleGetStackByID(pack.stack);
 	PlayerColor playerToCall; //pack.player that will move activated stack
-	if (activated->hasBonusOfType(Bonus::HYPNOTIZED))
+	if (activated->hasBonusOfType(BonusType::HYPNOTIZED))
 	{
 		playerToCall = (gs.curB->sides[0].color == activated->unitOwner()
 			? gs.curB->sides[1].color

+ 6 - 6
client/battle/BattleActionsController.cpp

@@ -140,7 +140,7 @@ bool BattleActionsController::isActiveStackSpellcaster() const
 	if (!casterStack)
 		return false;
 
-	bool spellcaster = casterStack->hasBonusOfType(Bonus::SPELLCASTER);
+	bool spellcaster = casterStack->hasBonusOfType(BonusType::SPELLCASTER);
 	return (spellcaster && casterStack->canCast());
 }
 
@@ -228,7 +228,7 @@ void BattleActionsController::reorderPossibleActionsPriority(const CStack * stac
 		case PossiblePlayerBattleAction::NO_LOCATION:
 		case PossiblePlayerBattleAction::FREE_LOCATION:
 		case PossiblePlayerBattleAction::OBSTACLE:
-			if(!stack->hasBonusOfType(Bonus::NO_SPELLCAST_BY_DEFAULT) && context == MouseHoveredHexContext::OCCUPIED_HEX)
+			if(!stack->hasBonusOfType(BonusType::NO_SPELLCAST_BY_DEFAULT) && context == MouseHoveredHexContext::OCCUPIED_HEX)
 				return 1;
 			else
 				return 100;//bottom priority
@@ -349,7 +349,7 @@ void BattleActionsController::actionSetCursor(PossiblePlayerBattleAction action,
 
 		case PossiblePlayerBattleAction::MOVE_TACTICS:
 		case PossiblePlayerBattleAction::MOVE_STACK:
-			if (owner.stacksController->getActiveStack()->hasBonusOfType(Bonus::FLYING))
+			if (owner.stacksController->getActiveStack()->hasBonusOfType(BonusType::FLYING))
 				CCS->curh->set(Cursor::Combat::FLY);
 			else
 				CCS->curh->set(Cursor::Combat::MOVE);
@@ -434,7 +434,7 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
 
 		case PossiblePlayerBattleAction::MOVE_TACTICS:
 		case PossiblePlayerBattleAction::MOVE_STACK:
-			if (owner.stacksController->getActiveStack()->hasBonusOfType(Bonus::FLYING))
+			if (owner.stacksController->getActiveStack()->hasBonusOfType(BonusType::FLYING))
 				return (boost::format(CGI->generaltexth->allTexts[295]) % owner.stacksController->getActiveStack()->getName()).str(); //Fly %s here
 			else
 				return (boost::format(CGI->generaltexth->allTexts[294]) % owner.stacksController->getActiveStack()->getName()).str(); //Move %s here
@@ -863,7 +863,7 @@ void BattleActionsController::tryActivateStackSpellcasting(const CStack *casterS
 {
 	creatureSpells.clear();
 
-	bool spellcaster = casterStack->hasBonusOfType(Bonus::SPELLCASTER);
+	bool spellcaster = casterStack->hasBonusOfType(BonusType::SPELLCASTER);
 	if(casterStack->canCast() && spellcaster)
 	{
 		// faerie dragon can cast only one, randomly selected spell until their next move
@@ -874,7 +874,7 @@ void BattleActionsController::tryActivateStackSpellcasting(const CStack *casterS
 			creatureSpells.push_back(spellToCast);
 	}
 
-	TConstBonusListPtr bl = casterStack->getBonuses(Selector::type()(Bonus::SPELLCASTER));
+	TConstBonusListPtr bl = casterStack->getBonuses(Selector::type()(BonusType::SPELLCASTER));
 
 	for (auto const & bonus : *bl)
 	{

+ 1 - 1
client/battle/BattleAnimationClasses.cpp

@@ -370,7 +370,7 @@ bool MovementAnimation::init()
 	distanceX = endPosition.x - begPosition.x;
 	distanceY = endPosition.y - begPosition.y;
 
-	if (stack->hasBonus(Selector::type()(Bonus::FLYING)))
+	if (stack->hasBonus(Selector::type()(BonusType::FLYING)))
 	{
 		float distance = static_cast<float>(sqrt(distanceX * distanceX + distanceY * distanceY));
 		progressPerSecond =  AnimationControls::getFlightDistance(stack->unitType()) / distance;

+ 6 - 6
client/battle/BattleEffectsController.cpp

@@ -65,21 +65,21 @@ void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bt
 		return;
 	}
 	//don't show animation when no HP is regenerated
-	switch(bte.effect)
+	switch(static_cast<BonusType>(bte.effect))
 	{
-		case Bonus::HP_REGENERATION:
+		case BonusType::HP_REGENERATION:
 			displayEffect(EBattleEffect::REGENERATION, "REGENER", stack->getPosition());
 			break;
-		case Bonus::MANA_DRAIN:
+		case BonusType::MANA_DRAIN:
 			displayEffect(EBattleEffect::MANA_DRAIN, "MANADRAI", stack->getPosition());
 			break;
-		case Bonus::POISON:
+		case BonusType::POISON:
 			displayEffect(EBattleEffect::POISON, "POISON", stack->getPosition());
 			break;
-		case Bonus::FEAR:
+		case BonusType::FEAR:
 			displayEffect(EBattleEffect::FEAR, "FEAR", stack->getPosition());
 			break;
-		case Bonus::MORALE:
+		case BonusType::MORALE:
 		{
 			std::string hlp = CGI->generaltexth->allTexts[33];
 			boost::algorithm::replace_first(hlp,"%s",(stack->getName()));

+ 4 - 4
client/battle/BattleStacksController.cpp

@@ -107,7 +107,7 @@ BattleHex BattleStacksController::getStackCurrentPosition(const CStack * stack)
 	if ( !stackAnimation.at(stack->unitId())->isMoving())
 		return stack->getPosition();
 
-	if (stack->hasBonusOfType(Bonus::FLYING) && stackAnimation.at(stack->unitId())->getType() == ECreatureAnimType::MOVING )
+	if (stack->hasBonusOfType(BonusType::FLYING) && stackAnimation.at(stack->unitId())->getType() == ECreatureAnimType::MOVING )
 		return BattleHex::HEX_AFTER_ALL;
 
 	for (auto & anim : currentAnimations)
@@ -247,7 +247,7 @@ void BattleStacksController::setActiveStack(const CStack *stack)
 bool BattleStacksController::stackNeedsAmountBox(const CStack * stack) const
 {
 	//do not show box for singular war machines, stacked war machines with box shown are supported as extension feature
-	if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->getCount() == 1)
+	if(stack->hasBonusOfType(BonusType::SIEGE_WEAPON) && stack->getCount() == 1)
 		return false;
 
 	if(!stack->alive())
@@ -528,7 +528,7 @@ void BattleStacksController::stackMoved(const CStack *stack, std::vector<BattleH
 		addNewAnim(new MovementStartAnimation(owner, stack));
 	});
 
-	if (!stack->hasBonus(Selector::typeSubtype(Bonus::FLYING, 1)))
+	if (!stack->hasBonus(Selector::typeSubtype(BonusType::FLYING, 1)))
 	{
 		owner.addToAnimationStage(EAnimationEvents::MOVEMENT, [&]()
 		{
@@ -793,7 +793,7 @@ void BattleStacksController::removeExpiredColorFilters()
 	{
 		if (!filter.persistent)
 		{
-			if (filter.source && !filter.target->hasBonus(Selector::source(Bonus::SPELL_EFFECT, filter.source->id), Selector::all))
+			if (filter.source && !filter.target->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, filter.source->id), Selector::all))
 				return true;
 			if (filter.effect == ColorFilter::genEmptyShifter())
 				return true;

+ 2 - 2
client/battle/BattleWindow.cpp

@@ -426,11 +426,11 @@ void BattleWindow::bSpellf()
 	{
 		//TODO: move to spell mechanics, add more information to spell cast problem
 		//Handle Orb of Inhibition-like effects -> we want to display dialog with info, why casting is impossible
-		auto blockingBonus = owner.currentHero()->getBonusLocalFirst(Selector::type()(Bonus::BLOCK_ALL_MAGIC));
+		auto blockingBonus = owner.currentHero()->getBonusLocalFirst(Selector::type()(BonusType::BLOCK_ALL_MAGIC));
 		if (!blockingBonus)
 			return;
 
-		if (blockingBonus->source == Bonus::ARTIFACT)
+		if (blockingBonus->source == BonusSource::ARTIFACT)
 		{
 			const auto artID = ArtifactID(blockingBonus->sid);
 			//If we have artifact, put name of our hero. Otherwise assume it's the enemy.

+ 7 - 7
client/widgets/MiscWidgets.cpp

@@ -393,21 +393,21 @@ void MoraleLuckBox::set(const AFactionMember * node)
 	text = CGI->generaltexth->arraytxt[textId[morale]];
 	boost::algorithm::replace_first(text,"%s",CGI->generaltexth->arraytxt[neutralDescr[morale]-mrlt]);
 
-	if (morale && node && (node->getBonusBearer()->hasBonusOfType(Bonus::UNDEAD)
-			|| node->getBonusBearer()->hasBonusOfType(Bonus::NON_LIVING)))
+	if (morale && node && (node->getBonusBearer()->hasBonusOfType(BonusType::UNDEAD)
+			|| node->getBonusBearer()->hasBonusOfType(BonusType::NON_LIVING)))
 	{
 		text += CGI->generaltexth->arraytxt[113]; //unaffected by morale
 		bonusValue = 0;
 	}
-	else if(morale && node && node->getBonusBearer()->hasBonusOfType(Bonus::NO_MORALE))
+	else if(morale && node && node->getBonusBearer()->hasBonusOfType(BonusType::NO_MORALE))
 	{
-		auto noMorale = node->getBonusBearer()->getBonus(Selector::type()(Bonus::NO_MORALE));
+		auto noMorale = node->getBonusBearer()->getBonus(Selector::type()(BonusType::NO_MORALE));
 		text += "\n" + noMorale->Description();
 		bonusValue = 0;
 	}
-	else if (!morale && node && node->getBonusBearer()->hasBonusOfType(Bonus::NO_LUCK))
+	else if (!morale && node && node->getBonusBearer()->hasBonusOfType(BonusType::NO_LUCK))
 	{
-		auto noLuck = node->getBonusBearer()->getBonus(Selector::type()(Bonus::NO_LUCK));
+		auto noLuck = node->getBonusBearer()->getBonus(Selector::type()(BonusType::NO_LUCK));
 		text += "\n" + noLuck->Description();
 		bonusValue = 0;
 	}
@@ -458,7 +458,7 @@ CCreaturePic::CCreaturePic(int x, int y, const CCreature * cre, bool Big, bool A
 		bg = std::make_shared<CPicture>((*CGI->townh)[faction]->creatureBg120);
 	anim = std::make_shared<CCreatureAnim>(0, 0, cre->animDefName);
 	anim->clipRect(cre->isDoubleWide()?170:150, 155, bg->pos.w, bg->pos.h);
-	anim->startPreview(cre->hasBonusOfType(Bonus::SIEGE_WEAPON));
+	anim->startPreview(cre->hasBonusOfType(BonusType::SIEGE_WEAPON));
 
 	amount = std::make_shared<CLabel>(bg->pos.w, bg->pos.h, FONT_MEDIUM, ETextAlignment::BOTTOMRIGHT, Colors::WHITE);
 

+ 1 - 1
client/windows/CCastleInterface.cpp

@@ -1705,7 +1705,7 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance *
 		sizes.y+=20;
 		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)));
+		values.push_back(std::make_shared<LabeledValue>(sizes, CGI->generaltexth->allTexts[193], CGI->generaltexth->fcommands[4], getMyCreature()->valOfBonuses(BonusType::STACKS_SPEED)));
 		sizes.y+=20;
 		values.push_back(std::make_shared<LabeledValue>(sizes, CGI->generaltexth->allTexts[194], CGI->generaltexth->fcommands[5], town->creatureGrowth(level)));
 	}

+ 6 - 6
client/windows/CCreatureWindow.cpp

@@ -210,7 +210,7 @@ CStackWindow::ActiveSpellsSection::ActiveSpellsSection(CStackWindow * owner, int
 			spellText = CGI->generaltexth->allTexts[610]; //"%s, duration: %d rounds."
 			boost::replace_first(spellText, "%s", spell->getNameTranslated());
 			//FIXME: support permanent duration
-			int duration = battleStack->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT,effect))->turnsRemain;
+			int duration = battleStack->getBonusLocalFirst(Selector::source(BonusSource::SPELL_EFFECT,effect))->turnsRemain;
 			boost::replace_first(spellText, "%d", std::to_string(duration));
 
 			spellIcons.push_back(std::make_shared<CAnimImage>("SpellInt", effect + 1, 0, firstPos.x + offset.x * printed, firstPos.y + offset.y * printed));
@@ -510,7 +510,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
 	name = std::make_shared<CLabel>(215, 12, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, parent->info->getName());
 
 	int dmgMultiply = 1;
-	if(parent->info->owner && parent->info->stackNode->hasBonusOfType(Bonus::SIEGE_WEAPON))
+	if(parent->info->owner && parent->info->stackNode->hasBonusOfType(BonusType::SIEGE_WEAPON))
 		dmgMultiply += parent->info->owner->getPrimSkillLevel(PrimarySkill::ATTACK);
 
 	icons = std::make_shared<CPicture>("stackWindow/icons", 117, 32);
@@ -539,8 +539,8 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
 	}
 	else
 	{
-		const bool shooter = parent->info->stackNode->hasBonusOfType(Bonus::SHOOTER) && parent->info->stackNode->valOfBonuses(Bonus::SHOTS);
-		const bool caster = parent->info->stackNode->valOfBonuses(Bonus::CASTS);
+		const bool shooter = parent->info->stackNode->hasBonusOfType(BonusType::SHOOTER) && parent->info->stackNode->valOfBonuses(BonusType::SHOTS);
+		const bool caster = parent->info->stackNode->valOfBonuses(BonusType::CASTS);
 
 		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));
@@ -549,9 +549,9 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
 		addStatLabel(EStat::SPEED, parent->info->creature->speed(), parent->info->stackNode->speed());
 
 		if(shooter)
-			addStatLabel(EStat::SHOTS, parent->info->stackNode->valOfBonuses(Bonus::SHOTS));
+			addStatLabel(EStat::SHOTS, parent->info->stackNode->valOfBonuses(BonusType::SHOTS));
 		if(caster)
-			addStatLabel(EStat::MANA, parent->info->stackNode->valOfBonuses(Bonus::CASTS));
+			addStatLabel(EStat::MANA, parent->info->stackNode->valOfBonuses(BonusType::CASTS));
 
 		morale->set(parent->info->stackNode);
 		luck->set(parent->info->stackNode);

+ 2 - 2
client/windows/CHeroWindow.cpp

@@ -66,7 +66,7 @@ int64_t CHeroWithMaybePickedArtifact::getTreeVersion() const
 si32 CHeroWithMaybePickedArtifact::manaLimit() const
 {
 	//TODO: reduplicate code with CGHeroInstance
-	return si32(getPrimSkillLevel(PrimarySkill::KNOWLEDGE) * (valOfBonuses(Bonus::MANA_PER_KNOWLEDGE)));
+	return si32(getPrimSkillLevel(PrimarySkill::KNOWLEDGE) * (valOfBonuses(BonusType::MANA_PER_KNOWLEDGE)));
 }
 
 const IBonusBearer * CHeroWithMaybePickedArtifact::getBonusBearer() const 
@@ -334,7 +334,7 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded)
 
 	dismissButton->block(!!curHero->visitedTown || noDismiss);
 
-	if(curHero->valOfBonuses(Selector::type()(Bonus::BEFORE_BATTLE_REPOSITION)) == 0)
+	if(curHero->valOfBonuses(Selector::type()(BonusType::BEFORE_BATTLE_REPOSITION)) == 0)
 	{
 		tacticsButton->block(true);
 	}

+ 1 - 1
client/windows/CKingdomInterface.cpp

@@ -586,7 +586,7 @@ void CKingdomInterface::generateMinesList(const std::vector<const CGObjectInstan
 	std::vector<const CGHeroInstance*> heroes = LOCPLINT->cb->getHeroesInfo(true);
 	for(auto & heroe : heroes)
 	{
-		totalIncome += heroe->valOfBonuses(Selector::typeSubtype(Bonus::GENERATE_RESOURCE, GameResID(EGameResID::GOLD)));
+		totalIncome += heroe->valOfBonuses(Selector::typeSubtype(BonusType::GENERATE_RESOURCE, GameResID(EGameResID::GOLD)));
 	}
 
 	//Add town income of all towns

+ 2 - 0
cmake_modules/VCMI_lib.cmake

@@ -28,6 +28,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/battle/Unit.cpp
 
 		${MAIN_LIB_DIR}/bonuses/Bonus.cpp
+		${MAIN_LIB_DIR}/bonuses/BonusEnum.cpp
 		${MAIN_LIB_DIR}/bonuses/BonusList.cpp
 		${MAIN_LIB_DIR}/bonuses/BonusParams.cpp
 		${MAIN_LIB_DIR}/bonuses/BonusSelector.cpp
@@ -311,6 +312,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/battle/Unit.h
 
 		${MAIN_LIB_DIR}/bonuses/Bonus.h
+		${MAIN_LIB_DIR}/bonuses/BonusEnum.h
 		${MAIN_LIB_DIR}/bonuses/BonusList.h
 		${MAIN_LIB_DIR}/bonuses/BonusParams.h
 		${MAIN_LIB_DIR}/bonuses/BonusSelector.h

+ 1 - 1
config/schemas/settings.json

@@ -463,7 +463,7 @@
 				"repositoryURL" : {
 					"type" : "array",
 					"default" : [
-						"https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/vcmi-1.2.json"
+						"https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/vcmi-1.3.json"
 					],
 					"items" : {
 						"type" : "string"

+ 1 - 1
config/schemas/spell.json

@@ -232,7 +232,7 @@
 						},
 						"special":{
 								"type": "boolean",
-								"description": "Special spell. Can be given only by Bonus::SPELL"
+								"description": "Special spell. Can be given only by BonusType::SPELL"
 						},
 						"nonMagical":{
 							"type": "boolean",

+ 207 - 78
launcher/translation/spanish.ts

@@ -1,101 +1,101 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE TS>
-<TS version="2.1" language="en_US">
+<TS version="2.1" language="es_ES">
 <context>
     <name>CModListModel</name>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="42"/>
         <source>Translation</source>
-        <translation type="unfinished"></translation>
+        <translation>Traducción</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="43"/>
         <source>Town</source>
-        <translation type="unfinished"></translation>
+        <translation>Ciudad</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="44"/>
         <source>Test</source>
-        <translation type="unfinished"></translation>
+        <translation>Test</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="45"/>
         <source>Templates</source>
-        <translation type="unfinished"></translation>
+        <translation>Plantillas</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="46"/>
         <source>Spells</source>
-        <translation type="unfinished"></translation>
+        <translation>Hechizos</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="47"/>
         <source>Music</source>
-        <translation type="unfinished"></translation>
+        <translation>Música</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="48"/>
         <source>Sounds</source>
-        <translation type="unfinished"></translation>
+        <translation>Sonidos</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="49"/>
         <source>Skills</source>
-        <translation type="unfinished"></translation>
+        <translation>Habilidades</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="50"/>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="67"/>
         <source>Other</source>
-        <translation type="unfinished"></translation>
+        <translation>Otro</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="51"/>
         <source>Objects</source>
-        <translation type="unfinished"></translation>
+        <translation>Objetos</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="52"/>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="53"/>
         <source>Mechanics</source>
-        <translation type="unfinished"></translation>
+        <translation>Mecánicas</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="54"/>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="55"/>
         <source>Interface</source>
-        <translation type="unfinished"></translation>
+        <translation>Interfaz</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="56"/>
         <source>Heroes</source>
-        <translation type="unfinished"></translation>
+        <translation>Heroes</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="57"/>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="58"/>
         <source>Graphical</source>
-        <translation type="unfinished"></translation>
+        <translation>Gráficos</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="59"/>
         <source>Expansion</source>
-        <translation type="unfinished"></translation>
+        <translation>Expansión</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="60"/>
         <source>Creatures</source>
-        <translation type="unfinished"></translation>
+        <translation>Criaturas</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="61"/>
         <source>Artifacts</source>
-        <translation type="unfinished"></translation>
+        <translation>Artefactos</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="62"/>
         <source>AI</source>
-        <translation type="unfinished"></translation>
+        <translation>IA</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="170"/>
@@ -239,7 +239,7 @@
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="262"/>
         <source>Contact</source>
-        <translation type="unfinished"></translation>
+        <translation>Contacto</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="271"/>
@@ -265,7 +265,7 @@
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="311"/>
         <source>Languages</source>
-        <translation type="unfinished"></translation>
+        <translation>Idiomas</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="313"/>
@@ -280,27 +280,27 @@
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="319"/>
         <source>This mod can not be installed or enabled because the following dependencies are not present</source>
-        <translation type="unfinished">Este mod no se puede instalar o habilitar porque no están presentes las siguientes dependencias</translation>
+        <translation>Este mod no se puede instalar o habilitar porque no están presentes las siguientes dependencias</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="320"/>
         <source>This mod can not be enabled because the following mods are incompatible with it</source>
-        <translation type="unfinished">Este mod no se puede habilitar porque los siguientes mods son incompatibles con él</translation>
+        <translation>Este mod no se puede habilitar porque los siguientes mods son incompatibles con él</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="321"/>
         <source>This mod cannot be disabled because it is required by the following mods</source>
-        <translation type="unfinished">No se puede desactivar este mod porque es necesario para ejecutar los siguientes mods</translation>
+        <translation>No se puede desactivar este mod porque es necesario para ejecutar los siguientes mods</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="322"/>
         <source>This mod cannot be uninstalled or updated because it is required by the following mods</source>
-        <translation type="unfinished">No se puede desinstalar o actualizar este mod porque es necesario para ejecutar los siguientes mods</translation>
+        <translation>No se puede desinstalar o actualizar este mod porque es necesario para ejecutar los siguientes mods</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="323"/>
         <source>This is a submod and it cannot be installed or uninstalled separately from its parent mod</source>
-        <translation type="unfinished">Este es un submod y no se puede instalar o desinstalar por separado del mod principal</translation>
+        <translation>Este es un submod y no se puede instalar o desinstalar por separado del mod principal</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="338"/>
@@ -332,22 +332,53 @@
         <source>Open</source>
         <translation>Abrir</translation>
     </message>
+    <message>
+            <location filename="../settingsView/csettingsview_moc.ui" line="590"/>
+            <source>Adventure Map AI</source>
+            <translation>IA del Mapa de Aventuras</translation>
+    </message>
+    <message>
+            <location filename="../settingsView/csettingsview_moc.ui" line="334"/>
+            <source>User data directory</source>
+            <translation>Directorio de datos del usuario</translation>
+    </message>
+    <message>
+            <location filename="../settingsView/csettingsview_moc.ui" line="256"/>
+            <location filename="../settingsView/csettingsview_moc.ui" line="298"/>
+            <location filename="../settingsView/csettingsview_moc.ui" line="440"/>
+            <location filename="../settingsView/csettingsview_moc.ui" line="487"/>
+            <source>Off</source>
+            <translation>Desactivado</translation>
+    </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="79"/>
         <location filename="../settingsView/csettingsview_moc.ui" line="576"/>
         <source>Artificial Intelligence</source>
-        <translation type="unfinished"></translation>
+        <translation>Inteligencia Artificial</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="89"/>
         <location filename="../settingsView/csettingsview_moc.ui" line="415"/>
         <source>Mod Repositories</source>
-        <translation type="unfinished"></translation>
+        <translation>Repositorios de Mods</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="368"/>
+        <source>Update now</source>
+        <translation>Actualizar ahora</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="261"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="303"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="445"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="492"/>
+        <source>On</source>
+        <translation>Encendido</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="215"/>
         <source>Cursor</source>
-        <translation type="unfinished"></translation>
+        <translation>Cursor</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="334"/>
@@ -357,12 +388,12 @@
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="590"/>
         <source>Adventure Map AI</source>
-        <translation type="unfinished"></translation>
+        <translation>Mapa de Aventuras IA</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="597"/>
         <source>Heroes III Translation</source>
-        <translation type="unfinished"></translation>
+        <translation>Traducción de Heroes III</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="256"/>
@@ -424,7 +455,7 @@
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="368"/>
         <source>Update now</source>
-        <translation type="unfinished"></translation>
+        <translation>Actualiza ahora</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="375"/>
@@ -456,17 +487,17 @@
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="552"/>
         <source>Default</source>
-        <translation type="unfinished"></translation>
+        <translation>Defecto</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="557"/>
         <source>Hardware</source>
-        <translation type="unfinished"></translation>
+        <translation>Hardware</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="562"/>
         <source>Software</source>
-        <translation type="unfinished"></translation>
+        <translation>Software</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="583"/>
@@ -486,12 +517,12 @@
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="429"/>
         <source>Check on startup</source>
-        <translation type="unfinished"></translation>
+        <translation>Comprovar al inicio</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="500"/>
         <source>Heroes III Data Language</source>
-        <translation type="unfinished"></translation>
+        <translation>Idioma de los datos de Heroes III.</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="534"/>
@@ -501,27 +532,27 @@
     <message>
         <location filename="../settingsView/csettingsview_moc.cpp" line="385"/>
         <source>Active</source>
-        <translation type="unfinished">Activo</translation>
+        <translation>Activado</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.cpp" line="390"/>
         <source>Disabled</source>
-        <translation type="unfinished"></translation>
+        <translation>Desactivado</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.cpp" line="391"/>
         <source>Enable</source>
-        <translation type="unfinished">Activar</translation>
+        <translation>Activar</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.cpp" line="396"/>
         <source>Not Installed</source>
-        <translation type="unfinished"></translation>
+        <translation>No Instalado</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.cpp" line="397"/>
         <source>Install</source>
-        <translation type="unfinished">Instalar</translation>
+        <translation>Instalar</translation>
     </message>
 </context>
 <context>
@@ -529,12 +560,12 @@
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="28"/>
         <source>Language</source>
-        <translation type="unfinished"></translation>
+        <translation>Idioma</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="53"/>
         <source>Heroes III Data</source>
-        <translation type="unfinished"></translation>
+        <translation>Datos de Heroes III</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="78"/>
@@ -544,7 +575,7 @@
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="111"/>
         <source>Step %v out of %m</source>
-        <translation type="unfinished"></translation>
+        <translation>Paso %v de %m</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="175"/>
@@ -565,47 +596,47 @@
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="239"/>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="554"/>
         <source>Next</source>
-        <translation type="unfinished"></translation>
+        <translation>Siguiente</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="307"/>
         <source>Open help in browser</source>
-        <translation type="unfinished"></translation>
+        <translation>Abrir la ayuda en el navegador</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="320"/>
         <source>Search again</source>
-        <translation type="unfinished"></translation>
+        <translation>Buscar de nuevo</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="403"/>
         <source>Heroes III data files</source>
-        <translation type="unfinished"></translation>
+        <translation>ficheros de datos de Heroes III</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="432"/>
         <source>Copy existing data</source>
-        <translation type="unfinished"></translation>
+        <translation>Copiar datos existentes</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="445"/>
         <source>Your Heroes III data files have been successfully found.</source>
-        <translation type="unfinished"></translation>
+        <translation>Se han encontrado con éxito tus archivos de datos de Heroes III.</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="475"/>
         <source>Your Heroes III language has been successfully detected.</source>
-        <translation type="unfinished"></translation>
+        <translation>Se ha detectado con éxito el idioma de tu Heroes III.</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="146"/>
         <source>Select your language</source>
-        <translation type="unfinished"></translation>
+        <translation>Selecciona el Idioma</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="196"/>
         <source>Have a question? Found a bug? Want to help? Join us!</source>
-        <translation type="unfinished"></translation>
+        <translation>¿Tienes alguna pregunta? ¿Encontraste algún error? ¿Quieres ayudar? ¡Únete a nosotros!</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="205"/>
@@ -616,43 +647,54 @@ Before you can start playing, there are a few more steps that need to be complet
 Please keep in mind that in order to use VCMI you must own the original data files for Heroes® of Might and Magic® III: Complete or The Shadow of Death.
 
 Heroes® of Might and Magic® III HD is currently not supported!</source>
-        <translation type="unfinished"></translation>
+        <translation>¡Gracias por instalar VCMI!
+
+Antes de poder jugar, hay algunos pasos más que deben completarse.
+
+Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos originales de Heroes® of Might and Magic® III: Complete o The Shadow of Death.
+
+¡Heroes® of Might and Magic® III HD actualmente no es compatible!</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="276"/>
         <source>Locate Heroes III data files</source>
-        <translation type="unfinished"></translation>
+        <translation>Localizar los archivos de datos de Heroes III.</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="365"/>
         <source>If you don&apos;t have a copy of Heroes III installed, you can use our automatic installation tool &apos;vcmibuilder&apos;, which only requires the GoG.com Heroes III installer. Please visit our wiki for detailed instructions.</source>
-        <translation type="unfinished"></translation>
+        <translation>Si no tienes una copia de Heroes III instalada, puedes usar nuestra herramienta de instalación automática 'vcmibuilder', que solo requiere el instalador de GoG.com de Heroes III. Por favor, visita nuestra wiki para obtener instrucciones detalladas.</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="381"/>
         <source>To run VCMI, Heroes III data files need to be present in one of the specified locations. Please copy the Heroes III data to one of these directories.</source>
-        <translation type="unfinished"></translation>
+        <translation>Para ejecutar VCMI, los archivos de datos de Heroes III deben estar presentes en una de las ubicaciones especificadas. Por favor, copia los datos de Heroes III en uno de estos directorios.</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="416"/>
         <source>Alternatively, you can provide the directory where Heroes III data is installed and VCMI will copy the existing data automatically.</source>
-        <translation type="unfinished"></translation>
+        <translation>Alternativamente, puedes proporcionar el directorio donde se instaló Heroes III y VCMI copiará automáticamente los datos existentes.</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="445"/>
+        <source>Your Heroes III data files have been successfully found.</source>
+        <translation>Se han encontrado tus archivos de datos de Heroes III con éxito.</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="485"/>
         <source>The automatic detection of the Heroes III language has failed. Please select the language of your Heroes III manually</source>
-        <translation type="unfinished"></translation>
+        <translation>La detección automática del idioma de Heroes III ha fallado. Por favor, selecciona manualmente el idioma de tu Heroes III.</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="504"/>
         <source>Heroes III language</source>
-        <translation type="unfinished"></translation>
+        <translation>Idioma de Heroes III.</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="547"/>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="863"/>
         <source>Back</source>
-        <translation type="unfinished"></translation>
+        <translation>Volver</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="585"/>
@@ -667,12 +709,12 @@ Heroes® of Might and Magic® III HD is currently not supported!</source>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="657"/>
         <source>Heroes III Translation</source>
-        <translation type="unfinished"></translation>
+        <translation>Traducción de Heroes III.</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="721"/>
         <source>High Definition Support</source>
-        <translation type="unfinished"></translation>
+        <translation>Soporte para resoluciones en Alta Definición</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="743"/>
@@ -682,32 +724,119 @@ Heroes® of Might and Magic® III HD is currently not supported!</source>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="759"/>
         <source>Install a translation of Heroes III in your preferred language</source>
-        <translation type="unfinished"></translation>
+        <translation>Instalar una traducción de Heroes III en tu idioma preferido.</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="775"/>
         <source>Optionally, you can install additional mods either now, or at any point later, using the VCMI Launcher</source>
-        <translation type="unfinished"></translation>
+        <translation>Opcionalmente, puedes instalar mods adicionales ya sea ahora o en cualquier momento posterior, utilizando el lanzador de VCMI.</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="791"/>
         <source>Install support for playing Heroes III in resolutions higher than 800x600</source>
-        <translation type="unfinished"></translation>
+        <translation>Instalar soporte para jugar Heroes III en resoluciones superiores a 800x600</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="807"/>
         <source>Install compatible version of &quot;Horn of the Abyss&quot;, a fan-made Heroes III expansion ported by the VCMI team</source>
-        <translation type="unfinished"></translation>
+        <translation>Instalar la versión compatible de &quot;Horn of the Abyss&quot;, una expansión de Heroes III hecha por fans y adaptada por el equipo de VCMI.</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="823"/>
         <source>Install compatible version of &quot;In The Wake of Gods&quot;, a fan-made Heroes III expansion</source>
-        <translation type="unfinished"></translation>
+        <translation>Instalar la versión compatible de &quot;In The Wake of Gods&quot;, una expansión de Heroes III hecha por fans.</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="870"/>
         <source>Finish</source>
-        <translation type="unfinished"></translation>
+        <translation>Finalizar</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="111"/>
+        <source>Step %v out of %m</source>
+        <translation>Paso %v de %m</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="175"/>
+        <source>VCMI on Github</source>
+        <translation>VCMI en Github</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="182"/>
+        <source>VCMI on Slack</source>
+        <translation>VCMI en Slack</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="189"/>
+        <source>VCMI on Discord</source>
+        <translation>VCMI en Discord</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="239"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="554"/>
+        <source>Next</source>
+        <translation>Siguiente</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="307"/>
+        <source>Open help in browser</source>
+        <translation>Abrir ayuda en el navegador</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="320"/>
+        <source>Search again</source>
+        <translation>Buscar de nuevo</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="403"/>
+        <source>Heroes III data files</source>
+        <translation>Archivos de datos de Heroes III</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="432"/>
+        <source>Copy existing data</source>
+        <translation>Copiar datos existentes</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="475"/>
+        <source>Your Heroes III language has been successfully detected.</source>
+        <translation>Su idioma de Heroes III se ha detectado correctamente.</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="504"/>
+        <source>Heroes III language</source>
+        <translation>Idioma de Heroes III</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="547"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="863"/>
+        <source>Back</source>
+        <translation>Atrás</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="585"/>
+        <source>Install VCMI Mod Preset</source>
+        <translation>Instalar preset de mod VCMI</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="635"/>
+        <source>Horn of the Abyss</source>
+        <translation>El Cuerno del Abismo</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="657"/>
+        <source>Heroes III Translation</source>
+        <translation>Traducción de Heroes III</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="721"/>
+        <source>High Definition Support</source>
+        <translation>Resolución de alta definición</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="743"/>
+        <source>In The Wake of Gods</source>
+        <translation>In The Wake of Gods</translation>
     </message>
 </context>
 <context>
@@ -822,17 +951,17 @@ Heroes® of Might and Magic® III HD is currently not supported!</source>
     <message>
         <location filename="../lobby/lobby_moc.ui" line="274"/>
         <source>Resolve</source>
-        <translation type="unfinished"></translation>
+        <translation>Resolver</translation>
     </message>
     <message>
         <location filename="../lobby/lobby_moc.ui" line="286"/>
         <source>New game</source>
-        <translation type="unfinished"></translation>
+        <translation>Nueva partida</translation>
     </message>
     <message>
         <location filename="../lobby/lobby_moc.ui" line="293"/>
         <source>Load game</source>
-        <translation type="unfinished"></translation>
+        <translation>Cargar partida</translation>
     </message>
     <message>
         <location filename="../lobby/lobby_moc.ui" line="149"/>
@@ -842,7 +971,7 @@ Heroes® of Might and Magic® III HD is currently not supported!</source>
     <message>
         <location filename="../lobby/lobby_moc.ui" line="76"/>
         <source>Players in lobby</source>
-        <translation type="unfinished"></translation>
+        <translation>Jugadores en la sala</translation>
     </message>
     <message>
         <location filename="../lobby/lobby_moc.ui" line="159"/>
@@ -877,12 +1006,12 @@ Heroes® of Might and Magic® III HD is currently not supported!</source>
     <message>
         <location filename="../lobby/lobby_moc.cpp" line="369"/>
         <source>Disconnect</source>
-        <translation type="unfinished"></translation>
+        <translation>Desconectar</translation>
     </message>
     <message>
         <location filename="../lobby/lobby_moc.cpp" line="461"/>
         <source>No issues detected</source>
-        <translation type="unfinished"></translation>
+        <translation>No se han detectado problemas</translation>
     </message>
 </context>
 <context>
@@ -946,7 +1075,7 @@ Heroes® of Might and Magic® III HD is currently not supported!</source>
     <message>
         <location filename="../updatedialog_moc.ui" line="71"/>
         <source>You have the latest version</source>
-        <translation type="unfinished">Tienes la última versión</translation>
+        <translation>Tienes la última versión</translation>
     </message>
     <message>
         <location filename="../updatedialog_moc.ui" line="94"/>
@@ -956,7 +1085,7 @@ Heroes® of Might and Magic® III HD is currently not supported!</source>
     <message>
         <location filename="../updatedialog_moc.ui" line="101"/>
         <source>Check for updates on startup</source>
-        <translation type="unfinished">Comprobar actualizaciones al iniciar</translation>
+        <translation>Comprobar actualizaciones al iniciar</translation>
     </message>
 </context>
 </TS>

+ 20 - 20
lib/BasicTypes.cpp

@@ -34,7 +34,7 @@ TerrainId AFactionMember::getNativeTerrain() const
 {
 	constexpr auto any = TerrainId(ETerrainId::ANY_TERRAIN);
 	const std::string cachingStringNoTerrainPenalty = "type_NO_TERRAIN_PENALTY_sANY";
-	static const auto selectorNoTerrainPenalty = Selector::typeSubtype(Bonus::NO_TERRAIN_PENALTY, any);
+	static const auto selectorNoTerrainPenalty = Selector::typeSubtype(BonusType::NO_TERRAIN_PENALTY, any);
 
 	//this code is used in the CreatureTerrainLimiter::limit to setup battle bonuses
 	//and in the CGHeroInstance::getNativeTerrain() to setup movement bonuses or/and penalties.
@@ -44,7 +44,7 @@ TerrainId AFactionMember::getNativeTerrain() const
 
 int32_t AFactionMember::magicResistance() const
 {
-	si32 val = getBonusBearer()->valOfBonuses(Selector::type()(Bonus::MAGIC_RESISTANCE));
+	si32 val = getBonusBearer()->valOfBonuses(Selector::type()(BonusType::MAGIC_RESISTANCE));
 	vstd::amin (val, 100);
 	return val;
 }
@@ -53,7 +53,7 @@ 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);
+	static const auto selector = Selector::typeSubtype(BonusType::PRIMARY_SKILL, PrimarySkill::ATTACK);
 
 	return getBonusBearer()->valOfBonuses(selector, cachingStr);
 }
@@ -62,7 +62,7 @@ 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);
+	static const auto selector = Selector::typeSubtype(BonusType::PRIMARY_SKILL, PrimarySkill::DEFENSE);
 
 	return getBonusBearer()->valOfBonuses(selector, cachingStr);
 }
@@ -70,20 +70,20 @@ int AFactionMember::getDefense(bool ranged) const
 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));
+	static const auto selector = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(BonusType::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));
+	static const auto selector = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(BonusType::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 CSelector selectorAllSkills = Selector::type()(BonusType::PRIMARY_SKILL);
 	static const std::string keyAllSkills = "type_PRIMARY_SKILL";
 	auto allSkills = getBonusBearer()->getBonuses(selectorAllSkills, keyAllSkills);
 	auto ret = allSkills->valOfBonuses(Selector::subtype()(id));
@@ -93,8 +93,8 @@ int AFactionMember::getPrimSkillLevel(PrimarySkill::PrimarySkill id) const
 
 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 auto unaffectedByMoraleSelector = Selector::type()(BonusType::NON_LIVING).Or(Selector::type()(BonusType::UNDEAD))
+													.Or(Selector::type()(BonusType::SIEGE_WEAPON)).Or(Selector::type()(BonusType::NO_MORALE));
 
 	static const std::string cachingStrUn = "AFactionMember::unaffectedByMoraleSelector";
 	auto unaffected = getBonusBearer()->hasBonus(unaffectedByMoraleSelector, cachingStrUn);
@@ -105,7 +105,7 @@ int AFactionMember::moraleValAndBonusList(TConstBonusListPtr & bonusList) const
 		return 0;
 	}
 
-	static const auto moraleSelector = Selector::type()(Bonus::MORALE);
+	static const auto moraleSelector = Selector::type()(BonusType::MORALE);
 	static const std::string cachingStrMor = "type_MORALE";
 	bonusList = getBonusBearer()->getBonuses(moraleSelector, cachingStrMor);
 
@@ -117,14 +117,14 @@ int AFactionMember::moraleValAndBonusList(TConstBonusListPtr & bonusList) const
 
 int AFactionMember::luckValAndBonusList(TConstBonusListPtr & bonusList) const
 {
-	if(getBonusBearer()->hasBonusOfType(Bonus::NO_LUCK))
+	if(getBonusBearer()->hasBonusOfType(BonusType::NO_LUCK))
 	{
 		if(bonusList && !bonusList->empty())
 			bonusList = std::make_shared<const BonusList>();
 		return 0;
 	}
 
-	static const auto luckSelector = Selector::type()(Bonus::LUCK);
+	static const auto luckSelector = Selector::type()(BonusType::LUCK);
 	static const std::string cachingStrLuck = "type_LUCK";
 	bonusList = getBonusBearer()->getBonuses(luckSelector, cachingStrLuck);
 
@@ -149,7 +149,7 @@ int AFactionMember::luckVal() const
 ui32 ACreature::getMaxHealth() const
 {
 	const std::string cachingStr = "type_STACK_HEALTH";
-	static const auto selector = Selector::type()(Bonus::STACK_HEALTH);
+	static const auto selector = Selector::type()(BonusType::STACK_HEALTH);
 	auto value = getBonusBearer()->valOfBonuses(selector, cachingStr);
 	return std::max(1, value); //never 0
 }
@@ -157,26 +157,26 @@ ui32 ACreature::getMaxHealth() const
 ui32 ACreature::speed(int turn, bool useBind) const
 {
 	//war machines cannot move
-	if(getBonusBearer()->hasBonus(Selector::type()(Bonus::SIEGE_WEAPON).And(Selector::turns(turn))))
+	if(getBonusBearer()->hasBonus(Selector::type()(BonusType::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))))
+	if(useBind && getBonusBearer()->hasBonus(Selector::type()(BonusType::BIND_EFFECT).And(Selector::turns(turn))))
 	{
 		return 0;
 	}
 
-	return getBonusBearer()->valOfBonuses(Selector::type()(Bonus::STACKS_SPEED).And(Selector::turns(turn)));
+	return getBonusBearer()->valOfBonuses(Selector::type()(BonusType::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));
+	static const CSelector selector = Selector::type()(BonusType::UNDEAD)
+		.Or(Selector::type()(BonusType::NON_LIVING))
+		.Or(Selector::type()(BonusType::GARGOYLE))
+		.Or(Selector::type()(BonusType::SIEGE_WEAPON));
 
 	return !getBonusBearer()->hasBonus(selector, cachingStr);
 }

+ 2 - 2
lib/BattleFieldHandler.cpp

@@ -27,9 +27,9 @@ BattleFieldInfo * BattleFieldHandler::loadFromJson(const std::string & scope, co
 	{
 		auto bonus = JsonUtils::parseBonus(b);
 
-		bonus->source = Bonus::TERRAIN_OVERLAY;
+		bonus->source = BonusSource::TERRAIN_OVERLAY;
 		bonus->sid = info->getIndex();
-		bonus->duration = Bonus::ONE_BATTLE;
+		bonus->duration = BonusDuration::ONE_BATTLE;
 
 		info->bonuses.push_back(bonus);
 	}

+ 10 - 10
lib/CArtHandler.cpp

@@ -256,8 +256,8 @@ std::string CArtifact::nodeName() const
 
 void CArtifact::addNewBonus(const std::shared_ptr<Bonus>& b)
 {
-	b->source = Bonus::ARTIFACT;
-	b->duration = Bonus::PERMANENT;
+	b->source = BonusSource::ARTIFACT;
+	b->duration = BonusDuration::PERMANENT;
 	b->description = getNameTranslated();
 	CBonusSystemNode::addNewBonus(b);
 }
@@ -275,21 +275,21 @@ void CArtifact::serializeJson(JsonSerializeFormat & handler)
 void CGrowingArtifact::levelUpArtifact (CArtifactInstance * art)
 {
 	auto b = std::make_shared<Bonus>();
-	b->type = Bonus::LEVEL_COUNTER;
+	b->type = BonusType::LEVEL_COUNTER;
 	b->val = 1;
-	b->duration = Bonus::COMMANDER_KILLED;
+	b->duration = BonusDuration::COMMANDER_KILLED;
 	art->accumulateBonus(b);
 
 	for(const auto & bonus : bonusesPerLevel)
 	{
-		if (art->valOfBonuses(Bonus::LEVEL_COUNTER) % bonus.first == 0) //every n levels
+		if (art->valOfBonuses(BonusType::LEVEL_COUNTER) % bonus.first == 0) //every n levels
 		{
 			art->accumulateBonus(std::make_shared<Bonus>(bonus.second));
 		}
 	}
 	for(const auto & bonus : thresholdBonuses)
 	{
-		if (art->valOfBonuses(Bonus::LEVEL_COUNTER) == bonus.first) //every n levels
+		if (art->valOfBonuses(BonusType::LEVEL_COUNTER) == bonus.first) //every n levels
 		{
 			art->addNewBonus(std::make_shared<Bonus>(bonus.second));
 		}
@@ -790,7 +790,7 @@ void CArtHandler::afterLoadFinalization()
 		for(auto &bonus : art->getExportedBonusList())
 		{
 			assert(art == objects[art->id]);
-			assert(bonus->source == Bonus::ARTIFACT);
+			assert(bonus->source == BonusSource::ARTIFACT);
 			bonus->sid = art->id;
 		}
 	}
@@ -822,7 +822,7 @@ std::string CArtifactInstance::nodeName() const
 CArtifactInstance * CArtifactInstance::createScroll(const SpellID & sid)
 {
 	auto * ret = new CArtifactInstance(VLC->arth->objects[ArtifactID::SPELL_SCROLL]);
-	auto b = std::make_shared<Bonus>(Bonus::PERMANENT, Bonus::SPELL, Bonus::ARTIFACT_INSTANCE, -1, ArtifactID::SPELL_SCROLL, sid);
+	auto b = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::SPELL, BonusSource::ARTIFACT_INSTANCE, -1, ArtifactID::SPELL_SCROLL, sid);
 	ret->addNewBonus(b);
 	return ret;
 }
@@ -903,7 +903,7 @@ CArtifactInstance * CArtifactInstance::createNewArtifactInstance(CArtifact *Art)
 		if (dynamic_cast<CGrowingArtifact *>(Art))
 		{
 			auto bonus = std::make_shared<Bonus>();
-			bonus->type = Bonus::LEVEL_COUNTER;
+			bonus->type = BonusType::LEVEL_COUNTER;
 			bonus->val = 0;
 			ret->addNewBonus (bonus);
 		}
@@ -963,7 +963,7 @@ void CArtifactInstance::deserializationFix()
 
 SpellID CArtifactInstance::getScrollSpellID() const
 {
-	const auto b = getBonusLocalFirst(Selector::type()(Bonus::SPELL));
+	const auto b = getBonusLocalFirst(Selector::type()(BonusType::SPELL));
 	if(!b)
 	{
 		logMod->warn("Warning: %s doesn't bear any spell!", nodeName());

+ 11 - 11
lib/CBonusTypeHandler.cpp

@@ -66,7 +66,7 @@ CBonusTypeHandler::~CBonusTypeHandler()
 
 std::string CBonusTypeHandler::bonusToString(const std::shared_ptr<Bonus> & bonus, const IBonusBearer * bearer, bool description) const
 {
-	const CBonusType & bt = bonusTypes[bonus->type];
+	const CBonusType & bt = bonusTypes[vstd::to_underlying(bonus->type)];
 	if(bt.hidden)
 		return "";
 
@@ -92,14 +92,14 @@ std::string CBonusTypeHandler::bonusToGraphics(const std::shared_ptr<Bonus> & bo
 
 	switch(bonus->type)
 	{
-	case Bonus::SPELL_IMMUNITY:
+	case BonusType::SPELL_IMMUNITY:
 	{
 		fullPath = true;
 		const CSpell * sp = SpellID(bonus->subtype).toSpell();
 		fileName = sp->getIconImmune();
 		break;
 	}
-	case Bonus::FIRE_IMMUNITY:
+	case BonusType::FIRE_IMMUNITY:
 		switch(bonus->subtype)
 		{
 		case 0:
@@ -113,7 +113,7 @@ std::string CBonusTypeHandler::bonusToGraphics(const std::shared_ptr<Bonus> & bo
 			break;//direct damage
 		}
 		break;
-	case Bonus::WATER_IMMUNITY:
+	case BonusType::WATER_IMMUNITY:
 		switch(bonus->subtype)
 		{
 		case 0:
@@ -127,7 +127,7 @@ std::string CBonusTypeHandler::bonusToGraphics(const std::shared_ptr<Bonus> & bo
 			break;//direct damage
 		}
 		break;
-	case Bonus::AIR_IMMUNITY:
+	case BonusType::AIR_IMMUNITY:
 		switch(bonus->subtype)
 		{
 		case 0:
@@ -141,7 +141,7 @@ std::string CBonusTypeHandler::bonusToGraphics(const std::shared_ptr<Bonus> & bo
 			break;//direct damage
 		}
 		break;
-	case Bonus::EARTH_IMMUNITY:
+	case BonusType::EARTH_IMMUNITY:
 		switch(bonus->subtype)
 		{
 		case 0:
@@ -153,7 +153,7 @@ std::string CBonusTypeHandler::bonusToGraphics(const std::shared_ptr<Bonus> & bo
 			break;//not positive
 		}
 		break;
-	case Bonus::LEVEL_SPELL_IMMUNITY:
+	case BonusType::LEVEL_SPELL_IMMUNITY:
 	{
 		if(vstd::iswithin(bonus->val, 1, 5))
 		{
@@ -161,7 +161,7 @@ std::string CBonusTypeHandler::bonusToGraphics(const std::shared_ptr<Bonus> & bo
 		}
 		break;
 	}
-	case Bonus::KING:
+	case BonusType::KING:
 	{
 		if(vstd::iswithin(bonus->val, 0, 3))
 		{
@@ -169,7 +169,7 @@ std::string CBonusTypeHandler::bonusToGraphics(const std::shared_ptr<Bonus> & bo
 		}
 		break;
 	}
-	case Bonus::GENERAL_DAMAGE_REDUCTION:
+	case BonusType::GENERAL_DAMAGE_REDUCTION:
 	{
 		switch(bonus->subtype)
 		{
@@ -185,7 +185,7 @@ std::string CBonusTypeHandler::bonusToGraphics(const std::shared_ptr<Bonus> & bo
 
 	default:
 		{
-			const CBonusType & bt = bonusTypes[bonus->type];
+			const CBonusType & bt = bonusTypes[vstd::to_underlying(bonus->type)];
 			fileName = bt.icon;
 			fullPath = true;
 		}
@@ -224,7 +224,7 @@ void CBonusTypeHandler::load(const JsonNode & config)
 		}
 		else
 		{
-			CBonusType & bt = bonusTypes[it->second];
+			CBonusType & bt = bonusTypes[vstd::to_underlying(it->second)];
 
 			loadItem(node.second, bt, node.first);
 			logBonus->trace("Loaded bonus type %s", node.first);

+ 1 - 3
lib/CBonusTypeHandler.h

@@ -19,8 +19,6 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 class JsonNode;
 
-using BonusTypeID = Bonus::BonusType;
-
 class DLL_LINKAGE CBonusType
 {
 public:
@@ -67,7 +65,7 @@ private:
 	void load(const JsonNode & config);
 	void loadItem(const JsonNode & source, CBonusType & dest, const std::string & name) const;
 
-	std::vector<CBonusType> bonusTypes; //index = BonusTypeID
+	std::vector<CBonusType> bonusTypes; //index = BonusType
 };
 
 VCMI_LIB_NAMESPACE_END

+ 109 - 109
lib/CCreatureHandler.cpp

@@ -113,49 +113,49 @@ FactionID CCreature::getFaction() const
 
 int32_t CCreature::getBaseAttack() const
 {
-	static const auto SELECTOR = Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK).And(Selector::sourceTypeSel(Bonus::CREATURE_ABILITY));
+	static const auto SELECTOR = Selector::typeSubtype(BonusType::PRIMARY_SKILL, PrimarySkill::ATTACK).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));
 	return getExportedBonusList().valOfBonuses(SELECTOR);
 }
 
 int32_t CCreature::getBaseDefense() const
 {
-	static const auto SELECTOR = Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE).And(Selector::sourceTypeSel(Bonus::CREATURE_ABILITY));
+	static const auto SELECTOR = Selector::typeSubtype(BonusType::PRIMARY_SKILL, PrimarySkill::DEFENSE).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));
 	return getExportedBonusList().valOfBonuses(SELECTOR);
 }
 
 int32_t CCreature::getBaseDamageMin() const
 {
-	static const auto SELECTOR = Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 1).And(Selector::sourceTypeSel(Bonus::CREATURE_ABILITY));
+	static const auto SELECTOR = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, 1).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));
 	return getExportedBonusList().valOfBonuses(SELECTOR);
 }
 
 int32_t CCreature::getBaseDamageMax() const
 {
-	static const auto SELECTOR = Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 2).And(Selector::sourceTypeSel(Bonus::CREATURE_ABILITY));
+	static const auto SELECTOR = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, 2).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));
 	return getExportedBonusList().valOfBonuses(SELECTOR);
 }
 
 int32_t CCreature::getBaseHitPoints() const
 {
-	static const auto SELECTOR = Selector::type()(Bonus::STACK_HEALTH).And(Selector::sourceTypeSel(Bonus::CREATURE_ABILITY));
+	static const auto SELECTOR = Selector::type()(BonusType::STACK_HEALTH).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));
 	return getExportedBonusList().valOfBonuses(SELECTOR);
 }
 
 int32_t CCreature::getBaseSpellPoints() const
 {
-	static const auto SELECTOR = Selector::type()(Bonus::CASTS).And(Selector::sourceTypeSel(Bonus::CREATURE_ABILITY));
+	static const auto SELECTOR = Selector::type()(BonusType::CASTS).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));
 	return getExportedBonusList().valOfBonuses(SELECTOR);
 }
 
 int32_t CCreature::getBaseSpeed() const
 {
-	static const auto SELECTOR = Selector::type()(Bonus::STACKS_SPEED).And(Selector::sourceTypeSel(Bonus::CREATURE_ABILITY));
+	static const auto SELECTOR = Selector::type()(BonusType::STACKS_SPEED).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));
 	return getExportedBonusList().valOfBonuses(SELECTOR);
 }
 
 int32_t CCreature::getBaseShots() const
 {
-	static const auto SELECTOR = Selector::type()(Bonus::SHOTS).And(Selector::sourceTypeSel(Bonus::CREATURE_ABILITY));
+	static const auto SELECTOR = Selector::type()(BonusType::SHOTS).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));
 	return getExportedBonusList().valOfBonuses(SELECTOR);
 }
 
@@ -291,9 +291,9 @@ CCreature::CCreature()
 	fightValue = AIValue = growth = hordeGrowth = ammMin = ammMax = 0;
 }
 
-void CCreature::addBonus(int val, Bonus::BonusType type, int subtype)
+void CCreature::addBonus(int val, BonusType type, int subtype)
 {
-	auto selector = Selector::typeSubtype(type, subtype).And(Selector::source(Bonus::CREATURE_ABILITY, getIndex()));
+	auto selector = Selector::typeSubtype(type, subtype).And(Selector::source(BonusSource::CREATURE_ABILITY, getIndex()));
 	BonusList & exported = getExportedBonusList();
 
 	BonusList existing;
@@ -301,7 +301,7 @@ void CCreature::addBonus(int val, Bonus::BonusType type, int subtype)
 
 	if(existing.empty())
 	{
-		auto added = std::make_shared<Bonus>(Bonus::PERMANENT, type, Bonus::CREATURE_ABILITY, val, getIndex(), subtype, Bonus::BASE_NUMBER);
+		auto added = std::make_shared<Bonus>(BonusDuration::PERMANENT, type, BonusSource::CREATURE_ABILITY, val, getIndex(), subtype, BonusValueType::BASE_NUMBER);
 		addNewBonus(added);
 	}
 	else
@@ -339,28 +339,28 @@ void CCreature::updateFrom(const JsonNode & data)
 		serializeJson(handler);
 
 		if(!configNode["hitPoints"].isNull())
-			addBonus(configNode["hitPoints"].Integer(), Bonus::STACK_HEALTH);
+			addBonus(configNode["hitPoints"].Integer(), BonusType::STACK_HEALTH);
 
 		if(!configNode["speed"].isNull())
-			addBonus(configNode["speed"].Integer(), Bonus::STACKS_SPEED);
+			addBonus(configNode["speed"].Integer(), BonusType::STACKS_SPEED);
 
 		if(!configNode["attack"].isNull())
-			addBonus(configNode["attack"].Integer(), Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK);
+			addBonus(configNode["attack"].Integer(), BonusType::PRIMARY_SKILL, PrimarySkill::ATTACK);
 
 		if(!configNode["defense"].isNull())
-			addBonus(configNode["defense"].Integer(), Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE);
+			addBonus(configNode["defense"].Integer(), BonusType::PRIMARY_SKILL, PrimarySkill::DEFENSE);
 
 		if(!configNode["damage"]["min"].isNull())
-			addBonus(configNode["damage"]["min"].Integer(), Bonus::CREATURE_DAMAGE, 1);
+			addBonus(configNode["damage"]["min"].Integer(), BonusType::CREATURE_DAMAGE, 1);
 
 		if(!configNode["damage"]["max"].isNull())
-			addBonus(configNode["damage"]["max"].Integer(), Bonus::CREATURE_DAMAGE, 2);
+			addBonus(configNode["damage"]["max"].Integer(), BonusType::CREATURE_DAMAGE, 2);
 
 		if(!configNode["shots"].isNull())
-			addBonus(configNode["shots"].Integer(), Bonus::SHOTS);
+			addBonus(configNode["shots"].Integer(), BonusType::SHOTS);
 
 		if(!configNode["spellPoints"].isNull())
-			addBonus(configNode["spellPoints"].Integer(), Bonus::CASTS);
+			addBonus(configNode["spellPoints"].Integer(), BonusType::CASTS);
 	}
 
 
@@ -601,18 +601,18 @@ CCreature * CCreatureHandler::loadFromJson(const std::string & scope, const Json
 	VLC->generaltexth->registerString(scope, cre->getNameSingularTextID(), node["name"]["singular"].String());
 	VLC->generaltexth->registerString(scope, cre->getNamePluralTextID(), node["name"]["plural"].String());
 
-	cre->addBonus(node["hitPoints"].Integer(), Bonus::STACK_HEALTH);
-	cre->addBonus(node["speed"].Integer(), Bonus::STACKS_SPEED);
-	cre->addBonus(node["attack"].Integer(), Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK);
-	cre->addBonus(node["defense"].Integer(), Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE);
+	cre->addBonus(node["hitPoints"].Integer(), BonusType::STACK_HEALTH);
+	cre->addBonus(node["speed"].Integer(), BonusType::STACKS_SPEED);
+	cre->addBonus(node["attack"].Integer(), BonusType::PRIMARY_SKILL, PrimarySkill::ATTACK);
+	cre->addBonus(node["defense"].Integer(), BonusType::PRIMARY_SKILL, PrimarySkill::DEFENSE);
 
-	cre->addBonus(node["damage"]["min"].Integer(), Bonus::CREATURE_DAMAGE, 1);
-	cre->addBonus(node["damage"]["max"].Integer(), Bonus::CREATURE_DAMAGE, 2);
+	cre->addBonus(node["damage"]["min"].Integer(), BonusType::CREATURE_DAMAGE, 1);
+	cre->addBonus(node["damage"]["max"].Integer(), BonusType::CREATURE_DAMAGE, 2);
 
 	assert(node["damage"]["min"].Integer() <= node["damage"]["max"].Integer());
 
 	if(!node["shots"].isNull())
-		cre->addBonus(node["shots"].Integer(), Bonus::SHOTS);
+		cre->addBonus(node["shots"].Integer(), BonusType::SHOTS);
 
 	loadStackExperience(cre, node["stackExperience"]);
 	loadJsonAnimation(cre, node["graphics"]);
@@ -708,8 +708,8 @@ void CCreatureHandler::loadCrExpMod()
 			expBonParser.endLine();
 		}
 		//skeleton gets exp penalty
-		objects[56].get()->addBonus(-50, Bonus::EXP_MULTIPLIER, -1);
-		objects[57].get()->addBonus(-50, Bonus::EXP_MULTIPLIER, -1);
+		objects[56].get()->addBonus(-50, BonusType::EXP_MULTIPLIER, -1);
+		objects[57].get()->addBonus(-50, BonusType::EXP_MULTIPLIER, -1);
 		//exp for tier >7, rank 11
 		expRanks[0].push_back(147000);
 		expAfterUpgrade = 75; //percent
@@ -740,10 +740,10 @@ void CCreatureHandler::loadCrExpBon(CBonusSystemNode & globalEffects)
 		CLegacyConfigParser parser("DATA/CREXPBON.TXT");
 
 		Bonus b; //prototype with some default properties
-		b.source = Bonus::STACK_EXPERIENCE;
-		b.duration = Bonus::PERMANENT;
-		b.valType = Bonus::ADDITIVE_VALUE;
-		b.effectRange = Bonus::NO_LIMIT;
+		b.source = BonusSource::STACK_EXPERIENCE;
+		b.duration = BonusDuration::PERMANENT;
+		b.valType = BonusValueType::ADDITIVE_VALUE;
+		b.effectRange = BonusLimitEffect::NO_LIMIT;
 		b.additionalInfo = 0;
 		b.turnsRemain = 0;
 		BonusList bl;
@@ -887,9 +887,9 @@ void CCreatureHandler::loadCreatureJson(CCreature * creature, const JsonNode & c
 			if (!ability.second.isNull())
 			{
 				auto b = JsonUtils::parseBonus(ability.second);
-				b->source = Bonus::CREATURE_ABILITY;
+				b->source = BonusSource::CREATURE_ABILITY;
 				b->sid = creature->getIndex();
-				b->duration = Bonus::PERMANENT;
+				b->duration = BonusDuration::PERMANENT;
 				creature->addNewBonus(b);
 			}
 		}
@@ -905,9 +905,9 @@ void CCreatureHandler::loadCreatureJson(CCreature * creature, const JsonNode & c
 			else
 			{
 				auto b = JsonUtils::parseBonus(ability);
-				b->source = Bonus::CREATURE_ABILITY;
+				b->source = BonusSource::CREATURE_ABILITY;
 				b->sid = creature->getIndex();
-				b->duration = Bonus::PERMANENT;
+				b->duration = BonusDuration::PERMANENT;
 				creature->addNewBonus(b);
 			}
 		}
@@ -977,8 +977,8 @@ void CCreatureHandler::loadStackExperience(CCreature * creature, const JsonNode
 					// we can not create copies since identifiers resolution does not tracks copies
 					// leading to unset identifier values in copies
 					auto bonus = JsonUtils::parseBonus (exp["bonus"]);
-					bonus->source = Bonus::STACK_EXPERIENCE;
-					bonus->duration = Bonus::PERMANENT;
+					bonus->source = BonusSource::STACK_EXPERIENCE;
+					bonus->duration = BonusDuration::PERMANENT;
 					bonus->limiter = std::make_shared<RankRangeLimiter>(RankRangeLimiter(lowerLimit));
 					creature->addNewBonus (bonus);
 					break; //TODO: allow bonuses to turn off?
@@ -997,8 +997,8 @@ void CCreatureHandler::loadStackExperience(CCreature * creature, const JsonNode
 					bonusInput["val"].Float() = static_cast<int>(val.Float()) - lastVal;
 
 					auto bonus = JsonUtils::parseBonus (bonusInput);
-					bonus->source = Bonus::STACK_EXPERIENCE;
-					bonus->duration = Bonus::PERMANENT;
+					bonus->source = BonusSource::STACK_EXPERIENCE;
+					bonus->duration = BonusDuration::PERMANENT;
 					bonus->limiter.reset (new RankRangeLimiter(lowerLimit));
 					creature->addNewBonus (bonus);
 				}
@@ -1018,55 +1018,55 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars
 	switch (buf[0])
 	{
 	case 'H':
-		b.type = Bonus::STACK_HEALTH;
-		b.valType = Bonus::PERCENT_TO_BASE;
+		b.type = BonusType::STACK_HEALTH;
+		b.valType = BonusValueType::PERCENT_TO_BASE;
 		break;
 	case 'A':
-		b.type = Bonus::PRIMARY_SKILL;
+		b.type = BonusType::PRIMARY_SKILL;
 		b.subtype = PrimarySkill::ATTACK;
 		break;
 	case 'D':
-		b.type = Bonus::PRIMARY_SKILL;
+		b.type = BonusType::PRIMARY_SKILL;
 		b.subtype = PrimarySkill::DEFENSE;
 		break;
 	case 'M': //Max damage
-		b.type = Bonus::CREATURE_DAMAGE;
+		b.type = BonusType::CREATURE_DAMAGE;
 		b.subtype = 2;
 		break;
 	case 'm': //Min damage
-		b.type = Bonus::CREATURE_DAMAGE;
+		b.type = BonusType::CREATURE_DAMAGE;
 		b.subtype = 1;
 		break;
 	case 'S':
-		b.type = Bonus::STACKS_SPEED; break;
+		b.type = BonusType::STACKS_SPEED; break;
 	case 'O':
-		b.type = Bonus::SHOTS; break;
+		b.type = BonusType::SHOTS; break;
 	case 'b':
-		b.type = Bonus::ENEMY_DEFENCE_REDUCTION; break;
+		b.type = BonusType::ENEMY_DEFENCE_REDUCTION; break;
 	case 'C':
-		b.type = Bonus::CHANGES_SPELL_COST_FOR_ALLY; break;
+		b.type = BonusType::CHANGES_SPELL_COST_FOR_ALLY; break;
 	case 'd':
-		b.type = Bonus::DEFENSIVE_STANCE; break;
+		b.type = BonusType::DEFENSIVE_STANCE; break;
 	case 'e':
-		b.type = Bonus::DOUBLE_DAMAGE_CHANCE;
+		b.type = BonusType::DOUBLE_DAMAGE_CHANCE;
 		b.subtype = 0;
 		break;
 	case 'E':
-		b.type = Bonus::DEATH_STARE;
+		b.type = BonusType::DEATH_STARE;
 		b.subtype = 0; //Gorgon
 		break;
 	case 'F':
-		b.type = Bonus::FEAR; break;
+		b.type = BonusType::FEAR; break;
 	case 'g':
-		b.type = Bonus::SPELL_DAMAGE_REDUCTION;
+		b.type = BonusType::SPELL_DAMAGE_REDUCTION;
 		b.subtype = -1; //all magic schools
 		break;
 	case 'P':
-		b.type = Bonus::CASTS; break;
+		b.type = BonusType::CASTS; break;
 	case 'R':
-		b.type = Bonus::ADDITIONAL_RETALIATION; break;
+		b.type = BonusType::ADDITIONAL_RETALIATION; break;
 	case 'W':
-		b.type = Bonus::MAGIC_RESISTANCE;
+		b.type = BonusType::MAGIC_RESISTANCE;
 		b.subtype = 0; //otherwise creature window goes crazy
 		break;
 	case 'f': //on-off skill
@@ -1074,44 +1074,44 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars
 		switch (mod[0])
 		{
 			case 'A':
-				b.type = Bonus::ATTACKS_ALL_ADJACENT; break;
+				b.type = BonusType::ATTACKS_ALL_ADJACENT; break;
 			case 'b':
-				b.type = Bonus::RETURN_AFTER_STRIKE; break;
+				b.type = BonusType::RETURN_AFTER_STRIKE; break;
 			case 'B':
-				b.type = Bonus::TWO_HEX_ATTACK_BREATH; break;
+				b.type = BonusType::TWO_HEX_ATTACK_BREATH; break;
 			case 'c':
-				b.type = Bonus::JOUSTING; 
+				b.type = BonusType::JOUSTING; 
 				b.val = 5;
 				break;
 			case 'D':
-				b.type = Bonus::ADDITIONAL_ATTACK; break;
+				b.type = BonusType::ADDITIONAL_ATTACK; break;
 			case 'f':
-				b.type = Bonus::FEARLESS; break;
+				b.type = BonusType::FEARLESS; break;
 			case 'F':
-				b.type = Bonus::FLYING; break;
+				b.type = BonusType::FLYING; break;
 			case 'm':
-				b.type = Bonus::MORALE; break;
+				b.type = BonusType::MORALE; break;
 				b.val = 1;
-				b.valType = Bonus::INDEPENDENT_MAX;
+				b.valType = BonusValueType::INDEPENDENT_MAX;
 				break;
 			case 'M':
-				b.type = Bonus::NO_MORALE; break;
+				b.type = BonusType::NO_MORALE; break;
 			case 'p': //Mind spells
 			case 'P':
-				b.type = Bonus::MIND_IMMUNITY; break;
+				b.type = BonusType::MIND_IMMUNITY; break;
 			case 'r':
-				b.type = Bonus::REBIRTH; //on/off? makes sense?
+				b.type = BonusType::REBIRTH; //on/off? makes sense?
 				b.subtype = 0;
 				b.val = 20; //arbitrary value
 				break;
 			case 'R':
-				b.type = Bonus::BLOCKS_RETALIATION; break;
+				b.type = BonusType::BLOCKS_RETALIATION; break;
 			case 's':
-				b.type = Bonus::FREE_SHOOTING; break;
+				b.type = BonusType::FREE_SHOOTING; break;
 			case 'u':
-				b.type = Bonus::SPELL_RESISTANCE_AURA; break;
+				b.type = BonusType::SPELL_RESISTANCE_AURA; break;
 			case 'U':
-				b.type = Bonus::UNDEAD; break;
+				b.type = BonusType::UNDEAD; break;
 			default:
 				logGlobal->trace("Not parsed bonus %s %s", buf, mod);
 				return;
@@ -1123,42 +1123,42 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars
 		switch (mod[0])
 		{
 			case 'B': //Blind
-				b.type = Bonus::SPELL_IMMUNITY;
+				b.type = BonusType::SPELL_IMMUNITY;
 				b.subtype = SpellID::BLIND;
 				b.additionalInfo = 0;//normal immunity
 				break;
 			case 'H': //Hypnotize
-				b.type = Bonus::SPELL_IMMUNITY;
+				b.type = BonusType::SPELL_IMMUNITY;
 				b.subtype = SpellID::HYPNOTIZE;
 				b.additionalInfo = 0;//normal immunity
 				break;
 			case 'I': //Implosion
-				b.type = Bonus::SPELL_IMMUNITY;
+				b.type = BonusType::SPELL_IMMUNITY;
 				b.subtype = SpellID::IMPLOSION;
 				b.additionalInfo = 0;//normal immunity
 				break;
 			case 'K': //Berserk
-				b.type = Bonus::SPELL_IMMUNITY;
+				b.type = BonusType::SPELL_IMMUNITY;
 				b.subtype = SpellID::BERSERK;
 				b.additionalInfo = 0;//normal immunity
 				break;
 			case 'M': //Meteor Shower
-				b.type = Bonus::SPELL_IMMUNITY;
+				b.type = BonusType::SPELL_IMMUNITY;
 				b.subtype = SpellID::METEOR_SHOWER;
 				b.additionalInfo = 0;//normal immunity
 				break;
 			case 'N': //dispell beneficial spells
-				b.type = Bonus::SPELL_IMMUNITY;
+				b.type = BonusType::SPELL_IMMUNITY;
 				b.subtype = SpellID::DISPEL_HELPFUL_SPELLS;
 				b.additionalInfo = 0;//normal immunity
 				break;
 			case 'R': //Armageddon
-				b.type = Bonus::SPELL_IMMUNITY;
+				b.type = BonusType::SPELL_IMMUNITY;
 				b.subtype = SpellID::ARMAGEDDON;
 				b.additionalInfo = 0;//normal immunity
 				break;
 			case 'S': //Slow
-				b.type = Bonus::SPELL_IMMUNITY;
+				b.type = BonusType::SPELL_IMMUNITY;
 				b.subtype = SpellID::SLOW;
 				b.additionalInfo = 0;//normal immunity
 				break;
@@ -1166,61 +1166,61 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars
 			case '7':
 			case '8':
 			case '9':
-				b.type = Bonus::LEVEL_SPELL_IMMUNITY;
+				b.type = BonusType::LEVEL_SPELL_IMMUNITY;
 				b.val = std::atoi(mod.c_str()) - 5;
 				break;
 			case ':':
-				b.type = Bonus::LEVEL_SPELL_IMMUNITY;
+				b.type = BonusType::LEVEL_SPELL_IMMUNITY;
 				b.val = GameConstants::SPELL_LEVELS; //in case someone adds higher level spells?
 				break;
 			case 'F':
-				b.type = Bonus::FIRE_IMMUNITY;
+				b.type = BonusType::FIRE_IMMUNITY;
 				b.subtype = 1; //not positive
 				break;
 			case 'O':
-				b.type = Bonus::FIRE_IMMUNITY;
+				b.type = BonusType::FIRE_IMMUNITY;
 				b.subtype = 2; //only direct damage
 				break;
 			case 'f':
-				b.type = Bonus::FIRE_IMMUNITY;
+				b.type = BonusType::FIRE_IMMUNITY;
 				b.subtype = 0; //all
 				break;
 			case 'C':
-				b.type = Bonus::WATER_IMMUNITY;
+				b.type = BonusType::WATER_IMMUNITY;
 				b.subtype = 1; //not positive
 				break;
 			case 'W':
-				b.type = Bonus::WATER_IMMUNITY;
+				b.type = BonusType::WATER_IMMUNITY;
 				b.subtype = 2; //only direct damage
 				break;
 			case 'w':
-				b.type = Bonus::WATER_IMMUNITY;
+				b.type = BonusType::WATER_IMMUNITY;
 				b.subtype = 0; //all
 				break;
 			case 'E':
-				b.type = Bonus::EARTH_IMMUNITY;
+				b.type = BonusType::EARTH_IMMUNITY;
 				b.subtype = 2; //only direct damage
 				break;
 			case 'e':
-				b.type = Bonus::EARTH_IMMUNITY;
+				b.type = BonusType::EARTH_IMMUNITY;
 				b.subtype = 0; //all
 				break;
 			case 'A':
-				b.type = Bonus::AIR_IMMUNITY;
+				b.type = BonusType::AIR_IMMUNITY;
 				b.subtype = 2; //only direct damage
 				break;
 			case 'a':
-				b.type = Bonus::AIR_IMMUNITY;
+				b.type = BonusType::AIR_IMMUNITY;
 				b.subtype = 0; //all
 				break;
 			case 'D':
-				b.type = Bonus::DIRECT_DAMAGE_IMMUNITY;
+				b.type = BonusType::DIRECT_DAMAGE_IMMUNITY;
 				break;
 			case '0':
-				b.type = Bonus::RECEPTIVE;
+				b.type = BonusType::RECEPTIVE;
 				break;
 			case 'm':
-				b.type = Bonus::MIND_IMMUNITY;
+				b.type = BonusType::MIND_IMMUNITY;
 				break;
 			default:
 				logGlobal->trace("Not parsed bonus %s %s", buf, mod);
@@ -1230,38 +1230,38 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars
 
 	case 'i':
 		enable = true;
-		b.type = Bonus::NO_DISTANCE_PENALTY;
+		b.type = BonusType::NO_DISTANCE_PENALTY;
 		break;
 	case 'o':
 		enable = true;
-		b.type = Bonus::NO_WALL_PENALTY;
+		b.type = BonusType::NO_WALL_PENALTY;
 		break;
 
 	case 'a':
 	case 'c':
 	case 'K':
 	case 'k':
-		b.type = Bonus::SPELL_AFTER_ATTACK;
+		b.type = BonusType::SPELL_AFTER_ATTACK;
 		b.subtype = stringToNumber(mod);
 		break;
 	case 'h':
-		b.type= Bonus::HATE;
+		b.type = BonusType::HATE;
 		b.subtype = stringToNumber(mod);
 		break;
 	case 'p':
 	case 'J':
-		b.type = Bonus::SPELL_BEFORE_ATTACK;
+		b.type = BonusType::SPELL_BEFORE_ATTACK;
 		b.subtype = stringToNumber(mod);
 		b.additionalInfo = 3; //always expert?
 		break;
 	case 'r':
-		b.type = Bonus::HP_REGENERATION;
+		b.type = BonusType::HP_REGENERATION;
 		b.val = stringToNumber(mod);
 		break;
 	case 's':
-		b.type = Bonus::ENCHANTED;
+		b.type = BonusType::ENCHANTED;
 		b.subtype = stringToNumber(mod);
-		b.valType = Bonus::INDEPENDENT_MAX;
+		b.valType = BonusValueType::INDEPENDENT_MAX;
 		break;
 	default:
 		logGlobal->trace("Not parsed bonus %s %s", buf, mod);
@@ -1272,7 +1272,7 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars
 	{
 		case '+':
 		case '=': //should we allow percent values to stack or pick highest?
-			b.valType = Bonus::ADDITIVE_VALUE;
+			b.valType = BonusValueType::ADDITIVE_VALUE;
 			break;
 	}
 
@@ -1283,7 +1283,7 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars
 
 	if (enable) //0 and 2 means non-active, 1 - active
 	{
-		if (b.type != Bonus::REBIRTH)
+		if (b.type != BonusType::REBIRTH)
 			b.val = 0; //on-off ability, no value specified
 		parser.readNumber(); // 0 level is never active
 		for (int i = 1; i < 11; ++i)
@@ -1300,13 +1300,13 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars
 	else
 	{
 		lastVal = static_cast<si32>(parser.readNumber());
-		if (b.type == Bonus::HATE)
+		if (b.type == BonusType::HATE)
 			lastVal *= 10; //odd fix
 		//FIXME: value for zero level should be stored in our config files (independent of stack exp)
 		for (int i = 1; i < 11; ++i)
 		{
 			curVal = static_cast<si32>(parser.readNumber());
-			if (b.type == Bonus::HATE)
+			if (b.type == BonusType::HATE)
 				curVal *= 10; //odd fix
 			if (curVal > lastVal) //threshold, add new bonus
 			{

+ 1 - 1
lib/CCreatureHandler.h

@@ -202,7 +202,7 @@ public:
 
 	bool valid() const;
 
-	void addBonus(int val, Bonus::BonusType type, int subtype = -1);
+	void addBonus(int val, BonusType type, int subtype = -1);
 	std::string nodeName() const override;
 
 	template<typename RanGen>

+ 1 - 1
lib/CGameInfoCallback.cpp

@@ -314,7 +314,7 @@ bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero
 	if(getPlayerRelations(getLocalPlayer(), hero->tempOwner) == PlayerRelations::ENEMIES)
 	{
 		//todo: bonus cashing
-		int disguiseLevel = h->valOfBonuses(Selector::typeSubtype(Bonus::DISGUISED, 0));
+		int disguiseLevel = h->valOfBonuses(Selector::typeSubtype(BonusType::DISGUISED, 0));
 
 		auto doBasicDisguise = [](InfoAboutHero & info)
 		{

+ 6 - 6
lib/CGameState.cpp

@@ -954,7 +954,7 @@ void CGameState::initGlobalBonuses()
 	for(const auto & b : baseBonuses.Struct())
 	{
 		auto bonus = JsonUtils::parseBonus(b.second);
-		bonus->source = Bonus::GLOBAL;//for all
+		bonus->source = BonusSource::GLOBAL;//for all
 		bonus->sid = -1; //there is one global object
 		globalEffects.addNewBonus(bonus);
 	}
@@ -1282,9 +1282,9 @@ void CGameState::prepareCrossoverHeroes(std::vector<CGameState::CampaignHeroRepl
 		{
 			for(int g=0; g<GameConstants::PRIMARY_SKILLS; ++g)
 			{
-				auto sel = Selector::type()(Bonus::PRIMARY_SKILL)
+				auto sel = Selector::type()(BonusType::PRIMARY_SKILL)
 					.And(Selector::subtype()(g))
-					.And(Selector::sourceType()(Bonus::HERO_BASE_SKILL));
+					.And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL));
 
 				cgh->getBonusLocalFirst(sel)->val = cgh->type->heroClass->primarySkillInitial[g];
 			}
@@ -1614,7 +1614,7 @@ void CGameState::giveCampaignBonusToHero(CGHeroInstance * hero)
 					{
 						continue;
 					}
-					auto bb = std::make_shared<Bonus>(Bonus::PERMANENT, Bonus::PRIMARY_SKILL, Bonus::CAMPAIGN_BONUS, val, *scenarioOps->campState->currentMap, g);
+					auto bb = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::PRIMARY_SKILL, BonusSource::CAMPAIGN_BONUS, val, *scenarioOps->campState->currentMap, g);
 					hero->addNewBonus(bb);
 				}
 			}
@@ -2022,7 +2022,7 @@ UpgradeInfo CGameState::fillUpgradeInfo(const CStackInstance &stack) const
 		t = dynamic_cast<const CGTownInstance *>(stack.armyObj);
 	else if(h)
 	{	//hero specialty
-		TConstBonusListPtr lista = h->getBonuses(Selector::typeSubtype(Bonus::SPECIAL_UPGRADE, base->getId()));
+		TConstBonusListPtr lista = h->getBonuses(Selector::typeSubtype(BonusType::SPECIAL_UPGRADE, base->getId()));
 		for(const auto & it : *lista)
 		{
 			auto nid = CreatureID(it->additionalInfo[0]);
@@ -2590,7 +2590,7 @@ struct statsHLP
 		//Heroes can produce gold as well - skill, specialty or arts
 		for(const auto & h : ps->heroes)
 		{
-			totalIncome += h->valOfBonuses(Selector::typeSubtype(Bonus::GENERATE_RESOURCE, GameResID(EGameResID::GOLD)));
+			totalIncome += h->valOfBonuses(Selector::typeSubtype(BonusType::GENERATE_RESOURCE, GameResID(EGameResID::GOLD)));
 
 			if(!heroOrTown)
 				heroOrTown = h;

+ 5 - 5
lib/CHeroHandler.cpp

@@ -536,14 +536,14 @@ static std::vector<std::shared_ptr<Bonus>> createCreatureSpecialty(CreatureID ba
 		{
 			std::shared_ptr<Bonus> bonus = std::make_shared<Bonus>();
 			bonus->limiter.reset(new CCreatureTypeLimiter(specCreature, false));
-			bonus->type = Bonus::STACKS_SPEED;
+			bonus->type = BonusType::STACKS_SPEED;
 			bonus->val = 1;
 			result.push_back(bonus);
 		}
 
 		{
 			std::shared_ptr<Bonus> bonus = std::make_shared<Bonus>();
-			bonus->type = Bonus::PRIMARY_SKILL;
+			bonus->type = BonusType::PRIMARY_SKILL;
 			bonus->subtype = PrimarySkill::ATTACK;
 			bonus->val = 0;
 			bonus->limiter.reset(new CCreatureTypeLimiter(specCreature, false));
@@ -553,7 +553,7 @@ static std::vector<std::shared_ptr<Bonus>> createCreatureSpecialty(CreatureID ba
 
 		{
 			std::shared_ptr<Bonus> bonus = std::make_shared<Bonus>();
-			bonus->type = Bonus::PRIMARY_SKILL;
+			bonus->type = BonusType::PRIMARY_SKILL;
 			bonus->subtype = PrimarySkill::DEFENSE;
 			bonus->val = 0;
 			bonus->limiter.reset(new CCreatureTypeLimiter(specCreature, false));
@@ -600,8 +600,8 @@ void CHeroHandler::loadHeroSpecialty(CHero * hero, const JsonNode & node)
 {
 	auto prepSpec = [=](std::shared_ptr<Bonus> bonus)
 	{
-		bonus->duration = Bonus::PERMANENT;
-		bonus->source = Bonus::HERO_SPECIAL;
+		bonus->duration = BonusDuration::PERMANENT;
+		bonus->source = BonusSource::HERO_SPECIAL;
 		bonus->sid = hero->getIndex();
 		return bonus;
 	};

+ 36 - 36
lib/CPathfinder.cpp

@@ -963,7 +963,7 @@ bool CPathfinderHelper::addTeleportOneWayRandom(const CGTeleport * obj) const
 
 bool CPathfinderHelper::addTeleportWhirlpool(const CGWhirlpool * obj) const
 {
-	return options.useTeleportWhirlpool && hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION) && obj;
+	return options.useTeleportWhirlpool && hasBonusOfType(BonusType::WHIRLPOOL_PROTECTION) && obj;
 }
 
 int CPathfinderHelper::movementPointsAfterEmbark(int movement, int basicCost, bool disembark) const
@@ -992,15 +992,15 @@ TurnInfo::BonusCache::BonusCache(const TConstBonusListPtr & bl)
 	for(const auto & terrain : VLC->terrainTypeHandler->objects)
 	{
 		noTerrainPenalty.push_back(static_cast<bool>(
-				bl->getFirst(Selector::type()(Bonus::NO_TERRAIN_PENALTY).And(Selector::subtype()(terrain->getIndex())))));
+				bl->getFirst(Selector::type()(BonusType::NO_TERRAIN_PENALTY).And(Selector::subtype()(terrain->getIndex())))));
 	}
 
-	freeShipBoarding = static_cast<bool>(bl->getFirst(Selector::type()(Bonus::FREE_SHIP_BOARDING)));
-	flyingMovement = static_cast<bool>(bl->getFirst(Selector::type()(Bonus::FLYING_MOVEMENT)));
-	flyingMovementVal = bl->valOfBonuses(Selector::type()(Bonus::FLYING_MOVEMENT));
-	waterWalking = static_cast<bool>(bl->getFirst(Selector::type()(Bonus::WATER_WALKING)));
-	waterWalkingVal = bl->valOfBonuses(Selector::type()(Bonus::WATER_WALKING));
-	pathfindingVal = bl->valOfBonuses(Selector::type()(Bonus::ROUGH_TERRAIN_DISCOUNT));
+	freeShipBoarding = static_cast<bool>(bl->getFirst(Selector::type()(BonusType::FREE_SHIP_BOARDING)));
+	flyingMovement = static_cast<bool>(bl->getFirst(Selector::type()(BonusType::FLYING_MOVEMENT)));
+	flyingMovementVal = bl->valOfBonuses(Selector::type()(BonusType::FLYING_MOVEMENT));
+	waterWalking = static_cast<bool>(bl->getFirst(Selector::type()(BonusType::WATER_WALKING)));
+	waterWalkingVal = bl->valOfBonuses(Selector::type()(BonusType::WATER_WALKING));
+	pathfindingVal = bl->valOfBonuses(Selector::type()(BonusType::ROUGH_TERRAIN_DISCOUNT));
 }
 
 TurnInfo::TurnInfo(const CGHeroInstance * Hero, const int turn):
@@ -1022,7 +1022,7 @@ bool TurnInfo::isLayerAvailable(const EPathfindingLayer & layer) const
 		if(hero && hero->boat && hero->boat->layer == EPathfindingLayer::AIR)
 			break;
 			
-		if(!hasBonusOfType(Bonus::FLYING_MOVEMENT))
+		if(!hasBonusOfType(BonusType::FLYING_MOVEMENT))
 			return false;
 
 		break;
@@ -1031,7 +1031,7 @@ bool TurnInfo::isLayerAvailable(const EPathfindingLayer & layer) const
 		if(hero && hero->boat && hero->boat->layer == EPathfindingLayer::WATER)
 			break;
 			
-		if(!hasBonusOfType(Bonus::WATER_WALKING))
+		if(!hasBonusOfType(BonusType::WATER_WALKING))
 			return false;
 
 		break;
@@ -1040,17 +1040,17 @@ bool TurnInfo::isLayerAvailable(const EPathfindingLayer & layer) const
 	return true;
 }
 
-bool TurnInfo::hasBonusOfType(Bonus::BonusType type, int subtype) const
+bool TurnInfo::hasBonusOfType(BonusType type, int subtype) const
 {
 	switch(type)
 	{
-	case Bonus::FREE_SHIP_BOARDING:
+	case BonusType::FREE_SHIP_BOARDING:
 		return bonusCache->freeShipBoarding;
-	case Bonus::FLYING_MOVEMENT:
+	case BonusType::FLYING_MOVEMENT:
 		return bonusCache->flyingMovement;
-	case Bonus::WATER_WALKING:
+	case BonusType::WATER_WALKING:
 		return bonusCache->waterWalking;
-	case Bonus::NO_TERRAIN_PENALTY:
+	case BonusType::NO_TERRAIN_PENALTY:
 		return bonusCache->noTerrainPenalty[subtype];
 	}
 
@@ -1058,15 +1058,15 @@ bool TurnInfo::hasBonusOfType(Bonus::BonusType type, int subtype) const
 			bonuses->getFirst(Selector::type()(type).And(Selector::subtype()(subtype))));
 }
 
-int TurnInfo::valOfBonuses(Bonus::BonusType type, int subtype) const
+int TurnInfo::valOfBonuses(BonusType type, int subtype) const
 {
 	switch(type)
 	{
-	case Bonus::FLYING_MOVEMENT:
+	case BonusType::FLYING_MOVEMENT:
 		return bonusCache->flyingMovementVal;
-	case Bonus::WATER_WALKING:
+	case BonusType::WATER_WALKING:
 		return bonusCache->waterWalkingVal;
-	case Bonus::ROUGH_TERRAIN_DISCOUNT:
+	case BonusType::ROUGH_TERRAIN_DISCOUNT:
 		return bonusCache->pathfindingVal;
 	}
 
@@ -1083,23 +1083,23 @@ int TurnInfo::getMaxMovePoints(const EPathfindingLayer & layer) const
 	return layer == EPathfindingLayer::SAIL ? maxMovePointsWater : maxMovePointsLand;
 }
 
-void TurnInfo::updateHeroBonuses(Bonus::BonusType type, const CSelector& sel) const
+void TurnInfo::updateHeroBonuses(BonusType type, const CSelector& sel) const
 {
 	switch(type)
 	{
-	case Bonus::FREE_SHIP_BOARDING:
-		bonusCache->freeShipBoarding = static_cast<bool>(bonuses->getFirst(Selector::type()(Bonus::FREE_SHIP_BOARDING)));
+	case BonusType::FREE_SHIP_BOARDING:
+		bonusCache->freeShipBoarding = static_cast<bool>(bonuses->getFirst(Selector::type()(BonusType::FREE_SHIP_BOARDING)));
 		break;
-	case Bonus::FLYING_MOVEMENT:
-		bonusCache->flyingMovement = static_cast<bool>(bonuses->getFirst(Selector::type()(Bonus::FLYING_MOVEMENT)));
-		bonusCache->flyingMovementVal = bonuses->valOfBonuses(Selector::type()(Bonus::FLYING_MOVEMENT));
+	case BonusType::FLYING_MOVEMENT:
+		bonusCache->flyingMovement = static_cast<bool>(bonuses->getFirst(Selector::type()(BonusType::FLYING_MOVEMENT)));
+		bonusCache->flyingMovementVal = bonuses->valOfBonuses(Selector::type()(BonusType::FLYING_MOVEMENT));
 		break;
-	case Bonus::WATER_WALKING:
-		bonusCache->waterWalking = static_cast<bool>(bonuses->getFirst(Selector::type()(Bonus::WATER_WALKING)));
-		bonusCache->waterWalkingVal = bonuses->valOfBonuses(Selector::type()(Bonus::WATER_WALKING));
+	case BonusType::WATER_WALKING:
+		bonusCache->waterWalking = static_cast<bool>(bonuses->getFirst(Selector::type()(BonusType::WATER_WALKING)));
+		bonusCache->waterWalkingVal = bonuses->valOfBonuses(Selector::type()(BonusType::WATER_WALKING));
 		break;
-	case Bonus::ROUGH_TERRAIN_DISCOUNT:
-		bonusCache->pathfindingVal = bonuses->valOfBonuses(Selector::type()(Bonus::ROUGH_TERRAIN_DISCOUNT));
+	case BonusType::ROUGH_TERRAIN_DISCOUNT:
+		bonusCache->pathfindingVal = bonuses->valOfBonuses(Selector::type()(BonusType::ROUGH_TERRAIN_DISCOUNT));
 		break;
 	default:
 		bonuses = hero->getAllBonuses(Selector::days(turn), Selector::all, nullptr, "");
@@ -1162,7 +1162,7 @@ const TurnInfo * CPathfinderHelper::getTurnInfo() const
 	return turnsInfo[turn];
 }
 
-bool CPathfinderHelper::hasBonusOfType(const Bonus::BonusType type, const int subtype) const
+bool CPathfinderHelper::hasBonusOfType(const BonusType type, const int subtype) const
 {
 	return turnsInfo[turn]->hasBonusOfType(type, subtype);
 }
@@ -1248,11 +1248,11 @@ int CPathfinderHelper::getMovementCost(
 
 	bool isWaterLayer;
 	if(indeterminate(isDstWaterLayer))
-		isWaterLayer = ((hero->boat && hero->boat->layer == EPathfindingLayer::WATER) || ti->hasBonusOfType(Bonus::WATER_WALKING)) && dt->terType->isWater();
+		isWaterLayer = ((hero->boat && hero->boat->layer == EPathfindingLayer::WATER) || ti->hasBonusOfType(BonusType::WATER_WALKING)) && dt->terType->isWater();
 	else
 		isWaterLayer = static_cast<bool>(isDstWaterLayer);
 	
-	bool isAirLayer = (hero->boat && hero->boat->layer == EPathfindingLayer::AIR) || ti->hasBonusOfType(Bonus::FLYING_MOVEMENT);
+	bool isAirLayer = (hero->boat && hero->boat->layer == EPathfindingLayer::AIR) || ti->hasBonusOfType(BonusType::FLYING_MOVEMENT);
 
 	int ret = hero->getTileCost(*dt, *ct, ti);
 	if(isSailLayer)
@@ -1261,9 +1261,9 @@ int CPathfinderHelper::getMovementCost(
 			ret = static_cast<int>(ret * 2.0 / 3);
 	}
 	else if(isAirLayer)
-		vstd::amin(ret, GameConstants::BASE_MOVEMENT_COST + ti->valOfBonuses(Bonus::FLYING_MOVEMENT));
-	else if(isWaterLayer && ti->hasBonusOfType(Bonus::WATER_WALKING))
-		ret = static_cast<int>(ret * (100.0 + ti->valOfBonuses(Bonus::WATER_WALKING)) / 100.0);
+		vstd::amin(ret, GameConstants::BASE_MOVEMENT_COST + ti->valOfBonuses(BonusType::FLYING_MOVEMENT));
+	else if(isWaterLayer && ti->hasBonusOfType(BonusType::WATER_WALKING))
+		ret = static_cast<int>(ret * (100.0 + ti->valOfBonuses(BonusType::WATER_WALKING)) / 100.0);
 
 	if(src.x != dst.x && src.y != dst.y) //it's diagonal move
 	{

+ 4 - 4
lib/CPathfinder.h

@@ -530,9 +530,9 @@ struct DLL_LINKAGE TurnInfo
 
 	TurnInfo(const CGHeroInstance * Hero, const int Turn = 0);
 	bool isLayerAvailable(const EPathfindingLayer & layer) const;
-	bool hasBonusOfType(const Bonus::BonusType type, const int subtype = -1) const;
-	int valOfBonuses(const Bonus::BonusType type, const int subtype = -1) const;
-	void updateHeroBonuses(Bonus::BonusType type, const CSelector& sel) const;
+	bool hasBonusOfType(const BonusType type, const int subtype = -1) const;
+	int valOfBonuses(const BonusType type, const int subtype = -1) const;
+	void updateHeroBonuses(BonusType type, const CSelector& sel) const;
 	int getMaxMovePoints(const EPathfindingLayer & layer) const;
 };
 
@@ -561,7 +561,7 @@ public:
 	void updateTurnInfo(const int turn = 0);
 	bool isLayerAvailable(const EPathfindingLayer & layer) const;
 	const TurnInfo * getTurnInfo() const;
-	bool hasBonusOfType(const Bonus::BonusType type, const int subtype = -1) const;
+	bool hasBonusOfType(const BonusType type, const int subtype = -1) const;
 	int getMaxMovePoints(const EPathfindingLayer & layer) const;
 
 	std::vector<int3> getCastleGates(const PathNodeInfo & source) const;

+ 2 - 2
lib/CSkillHandler.cpp

@@ -90,9 +90,9 @@ SecondarySkill CSkill::getId() const
 
 void CSkill::addNewBonus(const std::shared_ptr<Bonus> & b, int level)
 {
-	b->source = Bonus::SECONDARY_SKILL;
+	b->source = BonusSource::SECONDARY_SKILL;
 	b->sid = id;
-	b->duration = Bonus::PERMANENT;
+	b->duration = BonusDuration::PERMANENT;
 	b->description = getNameTranslated();
 	levels[level-1].effects.push_back(b);
 }

+ 7 - 7
lib/CStack.cpp

@@ -96,7 +96,7 @@ si32 CStack::magicResistance() const
 	for(const auto * one : battle->battleAdjacentUnits(this))
 	{
 		if(one->unitOwner() == owner)
-			vstd::amax(auraBonus, one->valOfBonuses(Bonus::SPELL_RESISTANCE_AURA)); //max value
+			vstd::amax(auraBonus, one->valOfBonuses(BonusType::SPELL_RESISTANCE_AURA)); //max value
 	}
 	vstd::abetween(auraBonus, 0, 100);
 	vstd::abetween(magicResistance, 0, 100);
@@ -125,11 +125,11 @@ std::vector<si32> CStack::activeSpells() const
 	std::vector<si32> ret;
 
 	std::stringstream cachingStr;
-	cachingStr << "!type_" << Bonus::NONE << "source_" << Bonus::SPELL_EFFECT;
-	CSelector selector = Selector::sourceType()(Bonus::SPELL_EFFECT)
+	cachingStr << "!type_" << vstd::to_underlying(BonusType::NONE) << "source_" << vstd::to_underlying(BonusSource::SPELL_EFFECT);
+	CSelector selector = Selector::sourceType()(BonusSource::SPELL_EFFECT)
 						 .And(CSelector([](const Bonus * b)->bool
 	{
-		return b->type != Bonus::NONE && SpellID(b->sid).toSpell() && !SpellID(b->sid).toSpell()->isAdventure();
+		return b->type != BonusType::NONE && SpellID(b->sid).toSpell() && !SpellID(b->sid).toSpell()->isAdventure();
 	}));
 
 	TConstBonusListPtr spellEffects = getBonuses(selector, Selector::all, cachingStr.str());
@@ -198,7 +198,7 @@ void CStack::prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand, const
 	{
 		bsa.flags |= BattleStackAttacked::KILLED;
 
-		auto resurrectValue = customState->valOfBonuses(Bonus::REBIRTH);
+		auto resurrectValue = customState->valOfBonuses(BonusType::REBIRTH);
 
 		if(resurrectValue > 0 && customState->canCast()) //there must be casts left
 		{
@@ -220,7 +220,7 @@ void CStack::prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand, const
 					resurrectedCount += 1;
 			}
 
-			if(customState->hasBonusOfType(Bonus::REBIRTH, 1))
+			if(customState->hasBonusOfType(BonusType::REBIRTH, 1))
 			{
 				// resurrect at least one Sacred Phoenix
 				vstd::amax(resurrectedCount, 1);
@@ -308,7 +308,7 @@ std::string CStack::getName() const
 
 bool CStack::canBeHealed() const
 {
-	return getFirstHPleft() < static_cast<int32_t>(getMaxHealth()) && isValidTarget() && !hasBonusOfType(Bonus::SIEGE_WEAPON);
+	return getFirstHPleft() < static_cast<int32_t>(getMaxHealth()) && isValidTarget() && !hasBonusOfType(BonusType::SIEGE_WEAPON);
 }
 
 bool CStack::isOnNativeTerrain() const

+ 11 - 11
lib/CTownHandler.cpp

@@ -500,7 +500,7 @@ void CTownHandler::addBonusesForVanilaBuilding(CBuilding * building) const
 	{
 		if(building->bid == BuildingID::TAVERN)
 		{
-			b = createBonus(building, Bonus::MORALE, +1);
+			b = createBonus(building, BonusType::MORALE, +1);
 		}
 	}
 	else
@@ -508,23 +508,23 @@ void CTownHandler::addBonusesForVanilaBuilding(CBuilding * building) const
 		switch(building->subId)
 		{
 		case BuildingSubID::BROTHERHOOD_OF_SWORD:
-			b = createBonus(building, Bonus::MORALE, +2);
+			b = createBonus(building, BonusType::MORALE, +2);
 			building->overrideBids.insert(BuildingID::TAVERN);
 			break;
 		case BuildingSubID::FOUNTAIN_OF_FORTUNE:
-			b = createBonus(building, Bonus::LUCK, +2);
+			b = createBonus(building, BonusType::LUCK, +2);
 			break;
 		case BuildingSubID::SPELL_POWER_GARRISON_BONUS:
-			b = createBonus(building, Bonus::PRIMARY_SKILL, +2, PrimarySkill::SPELL_POWER);
+			b = createBonus(building, BonusType::PRIMARY_SKILL, +2, PrimarySkill::SPELL_POWER);
 			break;
 		case BuildingSubID::ATTACK_GARRISON_BONUS:
-			b = createBonus(building, Bonus::PRIMARY_SKILL, +2, PrimarySkill::ATTACK);
+			b = createBonus(building, BonusType::PRIMARY_SKILL, +2, PrimarySkill::ATTACK);
 			break;
 		case BuildingSubID::DEFENSE_GARRISON_BONUS:
-			b = createBonus(building, Bonus::PRIMARY_SKILL, +2, PrimarySkill::DEFENSE);
+			b = createBonus(building, BonusType::PRIMARY_SKILL, +2, PrimarySkill::DEFENSE);
 			break;
 		case BuildingSubID::LIGHTHOUSE:
-			b = createBonus(building, Bonus::MOVEMENT, +500, playerPropagator, 0);
+			b = createBonus(building, BonusType::MOVEMENT, +500, playerPropagator, 0);
 			break;
 		}
 	}
@@ -532,12 +532,12 @@ void CTownHandler::addBonusesForVanilaBuilding(CBuilding * building) const
 		building->addNewBonus(b, building->buildingBonuses);
 }
 
-std::shared_ptr<Bonus> CTownHandler::createBonus(CBuilding * build, Bonus::BonusType type, int val, int subtype) const
+std::shared_ptr<Bonus> CTownHandler::createBonus(CBuilding * build, BonusType type, int val, int subtype) const
 {
 	return createBonus(build, type, val, emptyPropagator(), subtype);
 }
 
-std::shared_ptr<Bonus> CTownHandler::createBonus(CBuilding * build, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype) const
+std::shared_ptr<Bonus> CTownHandler::createBonus(CBuilding * build, BonusType type, int val, TPropagatorPtr & prop, int subtype) const
 {
 	std::ostringstream descr;
 	descr << build->getNameTranslated();
@@ -545,13 +545,13 @@ std::shared_ptr<Bonus> CTownHandler::createBonus(CBuilding * build, Bonus::Bonus
 }
 
 std::shared_ptr<Bonus> CTownHandler::createBonusImpl(const BuildingID & building,
-													 Bonus::BonusType type,
+													 BonusType type,
 													 int val,
 													 TPropagatorPtr & prop,
 													 const std::string & description,
 													 int subtype) const
 {
-	auto b = std::make_shared<Bonus>(Bonus::PERMANENT, type, Bonus::TOWN_STRUCTURE, val, building, description, subtype);
+	auto b = std::make_shared<Bonus>(BonusDuration::PERMANENT, type, BonusSource::TOWN_STRUCTURE, val, building, description, subtype);
 
 	if(prop)
 		b->addPropagator(prop);

+ 3 - 3
lib/CTownHandler.h

@@ -376,10 +376,10 @@ class DLL_LINKAGE CTownHandler : public CHandlerBase<FactionID, Faction, CFactio
 	void loadBuilding(CTown * town, const std::string & stringID, const JsonNode & source);
 	void loadBuildings(CTown * town, const JsonNode & source);
 
-	std::shared_ptr<Bonus> createBonus(CBuilding * build, Bonus::BonusType type, int val, int subtype = -1) const;
-	std::shared_ptr<Bonus> createBonus(CBuilding * build, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype = -1) const;
+	std::shared_ptr<Bonus> createBonus(CBuilding * build, BonusType type, int val, int subtype = -1) const;
+	std::shared_ptr<Bonus> createBonus(CBuilding * build, BonusType type, int val, TPropagatorPtr & prop, int subtype = -1) const;
 	std::shared_ptr<Bonus> createBonusImpl(const BuildingID & building,
-												  Bonus::BonusType type,
+												  BonusType type,
 												  int val,
 												  TPropagatorPtr & prop,
 												  const std::string & description,

+ 14 - 24
lib/JsonNode.cpp

@@ -485,7 +485,7 @@ void JsonUtils::parseTypedBonusShort(const JsonVector & source, const std::share
 	dest->val = static_cast<si32>(source[1].Float());
 	resolveIdentifier(source[2],dest->subtype);
 	dest->additionalInfo = static_cast<si32>(source[3].Float());
-	dest->duration = Bonus::PERMANENT; //TODO: handle flags (as integer)
+	dest->duration = BonusDuration::PERMANENT; //TODO: handle flags (as integer)
 	dest->turnsRemain = 0;
 }
 
@@ -821,11 +821,11 @@ std::shared_ptr<Bonus> JsonUtils::parseBonus(const JsonNode &ability)
 
 std::shared_ptr<Bonus> JsonUtils::parseBuildingBonus(const JsonNode & ability, const BuildingID & building, const std::string & description)
 {
-	/*	duration = Bonus::PERMANENT
-		source = Bonus::TOWN_STRUCTURE
+	/*	duration = BonusDuration::PERMANENT
+		source = BonusSource::TOWN_STRUCTURE
 		bonusType, val, subtype - get from json
 	*/
-	auto b = std::make_shared<Bonus>(Bonus::PERMANENT, Bonus::NONE, Bonus::TOWN_STRUCTURE, 0, building, description, -1);
+	auto b = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::NONE, BonusSource::TOWN_STRUCTURE, 0, building, description, -1);
 
 	if(!parseBonus(ability, b.get()))
 		return nullptr;
@@ -846,14 +846,14 @@ static BonusParams convertDeprecatedBonus(const JsonNode &ability)
 				params.val = static_cast<si32>(ability["val"].Float());
 				params.valRelevant = true;
 			}
-			Bonus::ValueType valueType = Bonus::ADDITIVE_VALUE;
+			BonusValueType valueType = BonusValueType::ADDITIVE_VALUE;
 			if(!ability["valueType"].isNull())
 				valueType = bonusValueMap.find(ability["valueType"].String())->second;
 
-			if(ability["type"].String() == "SECONDARY_SKILL_PREMY" && valueType == Bonus::PERCENT_TO_BASE) //assume secondary skill special
+			if(ability["type"].String() == "SECONDARY_SKILL_PREMY" && valueType == BonusValueType::PERCENT_TO_BASE) //assume secondary skill special
 			{
-				params.valueType = Bonus::PERCENT_TO_TARGET_TYPE;
-				params.targetType = Bonus::SECONDARY_SKILL;
+				params.valueType = BonusValueType::PERCENT_TO_TARGET_TYPE;
+				params.targetType = BonusSource::SECONDARY_SKILL;
 				params.targetTypeRelevant = true;
 			}
 
@@ -946,7 +946,7 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
 
 		value = &ability["valueType"];
 		if (!value->isNull())
-			b->valType = static_cast<Bonus::ValueType>(parseByMapN(bonusValueMap, value, "value type "));
+			b->valType = static_cast<BonusValueType>(parseByMapN(bonusValueMap, value, "value type "));
 	}
 
 	b->stacking = ability["stacking"].String();
@@ -967,7 +967,7 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
 
 	value = &ability["effectRange"];
 	if (!value->isNull())
-		b->effectRange = static_cast<Bonus::LimitEffect>(parseByMapN(bonusLimitEffect, value, "effect range "));
+		b->effectRange = static_cast<BonusLimitEffect>(parseByMapN(bonusLimitEffect, value, "effect range "));
 
 	value = &ability["duration"];
 	if (!value->isNull())
@@ -975,17 +975,7 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
 		switch (value->getType())
 		{
 		case JsonNode::JsonType::DATA_STRING:
-			b->duration = static_cast<Bonus::BonusDuration>(parseByMap(bonusDurationMap, value, "duration type "));
-			break;
-		case JsonNode::JsonType::DATA_VECTOR:
-			{
-				ui16 dur = 0;
-				for (const JsonNode & d : value->Vector())
-				{
-					dur |= parseByMapN(bonusDurationMap, &d, "duration type ");
-				}
-				b->duration = static_cast<Bonus::BonusDuration>(dur);
-			}
+			b->duration = static_cast<BonusDuration>(parseByMap(bonusDurationMap, value, "duration type "));
 			break;
 		default:
 			logMod->error("Error! Wrong bonus duration format.");
@@ -994,11 +984,11 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
 
 	value = &ability["sourceType"];
 	if (!value->isNull())
-		b->source = static_cast<Bonus::BonusSource>(parseByMap(bonusSourceMap, value, "source type "));
+		b->source = static_cast<BonusSource>(parseByMap(bonusSourceMap, value, "source type "));
 
 	value = &ability["targetSourceType"];
 	if (!value->isNull())
-		b->targetSourceType = static_cast<Bonus::BonusSource>(parseByMap(bonusSourceMap, value, "target type "));
+		b->targetSourceType = static_cast<BonusSource>(parseByMap(bonusSourceMap, value, "target type "));
 
 	value = &ability["limiters"];
 	if (!value->isNull())
@@ -1075,7 +1065,7 @@ CSelector JsonUtils::parseSelector(const JsonNode & ability)
 		ret = ret.And(Selector::subtype()(subtype));
 	}
 	value = &ability["sourceType"];
-	Bonus::BonusSource src = Bonus::OTHER; //Fixes for GCC false maybe-uninitialized
+	BonusSource src = BonusSource::OTHER; //Fixes for GCC false maybe-uninitialized
 	si32 id = 0;
 	auto sourceIDRelevant = false;
 	auto sourceTypeRelevant = false;

+ 14 - 14
lib/NetPacksLib.cpp

@@ -982,13 +982,13 @@ void GiveBonus::applyGs(CGameState *gs)
 
 	std::string &descr = b->description;
 
-	if(bdescr.message.empty() && (bonus.type == Bonus::LUCK || bonus.type == Bonus::MORALE))
+	if(bdescr.message.empty() && (bonus.type == BonusType::LUCK || bonus.type == BonusType::MORALE))
 	{
-		if (bonus.source == Bonus::OBJECT)
+		if (bonus.source == BonusSource::OBJECT)
 		{
 			descr = VLC->generaltexth->arraytxt[bonus.val > 0 ? 110 : 109]; //+/-%d Temporary until next battle"
 		}
-		else if(bonus.source == Bonus::TOWN_STRUCTURE)
+		else if(bonus.source == BonusSource::TOWN_STRUCTURE)
 		{
 			descr = bonus.description;
 			return;
@@ -1124,7 +1124,7 @@ void RemoveBonus::applyGs(CGameState *gs)
 
 	for(const auto & b : bonuses)
 	{
-		if(b->source == source && b->sid == id)
+		if(vstd::to_underlying(b->source) == source && b->sid == id)
 		{
 			bonus = *b; //backup bonus (to show to interfaces later)
 			node->removeBonus(b);
@@ -2155,15 +2155,15 @@ void BattleTriggerEffect::applyGs(CGameState * gs) const
 {
 	CStack * st = gs->curB->getStack(stackID);
 	assert(st);
-	switch(effect)
+	switch(static_cast<BonusType>(effect))
 	{
-	case Bonus::HP_REGENERATION:
+	case BonusType::HP_REGENERATION:
 	{
 		int64_t toHeal = val;
 		st->heal(toHeal, EHealLevel::HEAL, EHealPower::PERMANENT);
 		break;
 	}
-	case Bonus::MANA_DRAIN:
+	case BonusType::MANA_DRAIN:
 	{
 		CGHeroInstance * h = gs->getHero(ObjectInstanceID(additionalInfo));
 		st->drainedMana = true;
@@ -2171,18 +2171,18 @@ void BattleTriggerEffect::applyGs(CGameState * gs) const
 		vstd::amax(h->mana, 0);
 		break;
 	}
-	case Bonus::POISON:
+	case BonusType::POISON:
 	{
-		auto b = st->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT, SpellID::POISON)
-				.And(Selector::type()(Bonus::STACK_HEALTH)));
+		auto b = st->getBonusLocalFirst(Selector::source(BonusSource::SPELL_EFFECT, SpellID::POISON)
+				.And(Selector::type()(BonusType::STACK_HEALTH)));
 		if (b)
 			b->val = val;
 		break;
 	}
-	case Bonus::ENCHANTER:
-	case Bonus::MORALE:
+	case BonusType::ENCHANTER:
+	case BonusType::MORALE:
 		break;
-	case Bonus::FEAR:
+	case BonusType::FEAR:
 		st->fear = true;
 		break;
 	default:
@@ -2477,7 +2477,7 @@ void BattleSetStackProperty::applyGs(CGameState * gs) const
 		}
 		case UNBIND:
 		{
-			stack->removeBonusesRecursive(Selector::type()(Bonus::BIND_EFFECT));
+			stack->removeBonusesRecursive(Selector::type()(BonusType::BIND_EFFECT));
 			break;
 		}
 		case CLONED:

+ 1 - 1
lib/battle/BattleAction.cpp

@@ -52,7 +52,7 @@ BattleAction BattleAction::makeMeleeAttack(const battle::Unit * stack, BattleHex
 	ba.stackNumber = stack->unitId();
 	ba.aimToHex(attackFrom);
 	ba.aimToHex(destination);
-	if(returnAfterAttack && stack->hasBonusOfType(Bonus::RETURN_AFTER_STRIKE))
+	if(returnAfterAttack && stack->hasBonusOfType(BonusType::RETURN_AFTER_STRIKE))
 		ba.aimToHex(stack->getPosition());
 	return ba;
 }

+ 7 - 7
lib/battle/BattleInfo.cpp

@@ -473,9 +473,9 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
 	//native terrain bonuses
 	static auto nativeTerrain = std::make_shared<CreatureTerrainLimiter>();
 	
-	curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::STACKS_SPEED, Bonus::TERRAIN_NATIVE, 1, 0, 0)->addLimiter(nativeTerrain));
-	curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::PRIMARY_SKILL, Bonus::TERRAIN_NATIVE, 1, 0, PrimarySkill::ATTACK)->addLimiter(nativeTerrain));
-	curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::PRIMARY_SKILL, Bonus::TERRAIN_NATIVE, 1, 0, PrimarySkill::DEFENSE)->addLimiter(nativeTerrain));
+	curB->addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::STACKS_SPEED, BonusSource::TERRAIN_NATIVE, 1, 0, 0)->addLimiter(nativeTerrain));
+	curB->addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::PRIMARY_SKILL, BonusSource::TERRAIN_NATIVE, 1, 0, PrimarySkill::ATTACK)->addLimiter(nativeTerrain));
+	curB->addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::PRIMARY_SKILL, BonusSource::TERRAIN_NATIVE, 1, 0, PrimarySkill::DEFENSE)->addLimiter(nativeTerrain));
 	//////////////////////////////////////////////////////////////////////////
 
 	//tactics
@@ -489,8 +489,8 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
 	{
 		if(heroes[i])
 		{
-			battleRepositionHex[i] += heroes[i]->valOfBonuses(Selector::type()(Bonus::BEFORE_BATTLE_REPOSITION));
-			battleRepositionHexBlock[i] += heroes[i]->valOfBonuses(Selector::type()(Bonus::BEFORE_BATTLE_REPOSITION_BLOCK));
+			battleRepositionHex[i] += heroes[i]->valOfBonuses(Selector::type()(BonusType::BEFORE_BATTLE_REPOSITION));
+			battleRepositionHexBlock[i] += heroes[i]->valOfBonuses(Selector::type()(BonusType::BEFORE_BATTLE_REPOSITION_BLOCK));
 		}
 	}
 	int tacticsSkillDiffAttacker = battleRepositionHex[BattleSide::ATTACKER] - battleRepositionHexBlock[BattleSide::DEFENDER];
@@ -803,7 +803,7 @@ void BattleInfo::setUnitState(uint32_t id, const JsonNode & data, int64_t health
 		auto selector = [](const Bonus * b)
 		{
 			//Special case: DISRUPTING_RAY is absolutely permanent
-			return b->source == Bonus::SPELL_EFFECT && b->sid != SpellID::DISRUPTING_RAY;
+			return b->source == BonusSource::SPELL_EFFECT && b->sid != SpellID::DISRUPTING_RAY;
 		};
 		changedStack->removeBonusesRecursive(selector);
 	}
@@ -929,7 +929,7 @@ uint32_t BattleInfo::nextUnitId() const
 
 void BattleInfo::addOrUpdateUnitBonus(CStack * sta, const Bonus & value, bool forceAdd)
 {
-	if(forceAdd || !sta->hasBonus(Selector::source(Bonus::SPELL_EFFECT, value.sid).And(Selector::typeSubtype(value.type, value.subtype))))
+	if(forceAdd || !sta->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, value.sid).And(Selector::typeSubtype(value.type, value.subtype))))
 	{
 		//no such effect or cumulative - add new
 		logBonus->trace("%s receives a new bonus: %s", sta->nodeName(), value.Description());

+ 31 - 31
lib/battle/CBattleInfoCallback.cpp

@@ -126,7 +126,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(con
 
 		if(!hero)
 			return ESpellCastProblem::NO_HERO_TO_CAST_SPELL;
-		if(hero->hasBonusOfType(Bonus::BLOCK_ALL_MAGIC))
+		if(hero->hasBonusOfType(BonusType::BLOCK_ALL_MAGIC))
 			return ESpellCastProblem::MAGIC_IS_BLOCKED;
 	}
 		break;
@@ -204,7 +204,7 @@ bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, Bat
 		return false;
 
 	const std::string cachingStrNoWallPenalty = "type_NO_WALL_PENALTY";
-	static const auto selectorNoWallPenalty = Selector::type()(Bonus::NO_WALL_PENALTY);
+	static const auto selectorNoWallPenalty = Selector::type()(BonusType::NO_WALL_PENALTY);
 
 	if(shooter->hasBonus(selectorNoWallPenalty, cachingStrNoWallPenalty))
 		return false;
@@ -227,7 +227,7 @@ std::vector<PossiblePlayerBattleAction> CBattleInfoCallback::getClientActionsFor
 	{
 		if(stack->canCast()) //TODO: check for battlefield effects that prevent casting?
 		{
-			if(stack->hasBonusOfType(Bonus::SPELLCASTER))
+			if(stack->hasBonusOfType(BonusType::SPELLCASTER))
 			{
 				for(const auto & spellID : data.creatureSpellsToCast)
 				{
@@ -236,12 +236,12 @@ std::vector<PossiblePlayerBattleAction> CBattleInfoCallback::getClientActionsFor
 					allowedActionList.push_back(act);
 				}
 			}
-			if(stack->hasBonusOfType(Bonus::RANDOM_SPELLCASTER))
+			if(stack->hasBonusOfType(BonusType::RANDOM_SPELLCASTER))
 				allowedActionList.push_back(PossiblePlayerBattleAction::RANDOM_GENIE_SPELL);
 		}
 		if(stack->canShoot())
 			allowedActionList.push_back(PossiblePlayerBattleAction::SHOOT);
-		if(stack->hasBonusOfType(Bonus::RETURN_AFTER_STRIKE))
+		if(stack->hasBonusOfType(BonusType::RETURN_AFTER_STRIKE))
 			allowedActionList.push_back(PossiblePlayerBattleAction::ATTACK_AND_RETURN);
 
 		allowedActionList.push_back(PossiblePlayerBattleAction::ATTACK); //all active stacks can attack
@@ -251,9 +251,9 @@ std::vector<PossiblePlayerBattleAction> CBattleInfoCallback::getClientActionsFor
 			allowedActionList.push_back(PossiblePlayerBattleAction::MOVE_STACK);
 
 		const auto * siegedTown = battleGetDefendedTown();
-		if(siegedTown && siegedTown->hasFort() && stack->hasBonusOfType(Bonus::CATAPULT)) //TODO: check shots
+		if(siegedTown && siegedTown->hasFort() && stack->hasBonusOfType(BonusType::CATAPULT)) //TODO: check shots
 			allowedActionList.push_back(PossiblePlayerBattleAction::CATAPULT);
-		if(stack->hasBonusOfType(Bonus::HEALER))
+		if(stack->hasBonusOfType(BonusType::HEALER))
 			allowedActionList.push_back(PossiblePlayerBattleAction::HEAL);
 	}
 
@@ -686,10 +686,10 @@ bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker) const
 		return false;
 
 	//forgetfulness
-	TConstBonusListPtr forgetfulList = attacker->getBonuses(Selector::type()(Bonus::FORGETFULL));
+	TConstBonusListPtr forgetfulList = attacker->getBonuses(Selector::type()(BonusType::FORGETFULL));
 	if(!forgetfulList->empty())
 	{
-		int forgetful = forgetfulList->valOfBonuses(Selector::type()(Bonus::FORGETFULL));
+		int forgetful = forgetfulList->valOfBonuses(Selector::type()(BonusType::FORGETFULL));
 
 		//advanced+ level
 		if(forgetful > 1)
@@ -697,7 +697,7 @@ bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker) const
 	}
 
 	return attacker->canShoot()	&& (!battleIsUnitBlocked(attacker)
-			|| attacker->hasBonusOfType(Bonus::FREE_SHOOTING));
+			|| attacker->hasBonusOfType(BonusType::FREE_SHOOTING));
 }
 
 bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker, BattleHex dest) const
@@ -712,7 +712,7 @@ bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker, BattleHe
 	{
 		if(battleCanShoot(attacker))
 		{
-			auto limitedRangeBonus = attacker->getBonus(Selector::type()(Bonus::LIMITED_SHOOTING_RANGE));
+			auto limitedRangeBonus = attacker->getBonus(Selector::type()(BonusType::LIMITED_SHOOTING_RANGE));
 			if(limitedRangeBonus == nullptr)
 			{
 				return true;
@@ -1239,11 +1239,11 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes(const battle:
 	{
 		attackOriginHex = attacker->occupiedHex(attackOriginHex); //the other hex stack stands on
 	}
-	if(attacker->hasBonusOfType(Bonus::ATTACKS_ALL_ADJACENT))
+	if(attacker->hasBonusOfType(BonusType::ATTACKS_ALL_ADJACENT))
 	{
 		boost::copy(attacker->getSurroundingHexes(attackerPos), vstd::set_inserter(at.hostileCreaturePositions));
 	}
-	if(attacker->hasBonusOfType(Bonus::THREE_HEADED_ATTACK))
+	if(attacker->hasBonusOfType(BonusType::THREE_HEADED_ATTACK))
 	{
 		std::vector<BattleHex> hexes = attacker->getSurroundingHexes(attackerPos);
 		for(BattleHex tile : hexes)
@@ -1256,7 +1256,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes(const battle:
 			}
 		}
 	}
-	if(attacker->hasBonusOfType(Bonus::WIDE_BREATH))
+	if(attacker->hasBonusOfType(BonusType::WIDE_BREATH))
 	{
 		std::vector<BattleHex> hexes = destinationTile.neighbouringTiles();
 		for(int i = 0; i<hexes.size(); i++)
@@ -1275,7 +1275,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes(const battle:
 				at.friendlyCreaturePositions.insert(tile);
 		}
 	}
-	else if(attacker->hasBonusOfType(Bonus::TWO_HEX_ATTACK_BREATH))
+	else if(attacker->hasBonusOfType(BonusType::TWO_HEX_ATTACK_BREATH))
 	{
 		auto direction = BattleHex::mutualPosition(attackOriginHex, destinationTile);
 		if(direction != BattleHex::NONE) //only adjacent hexes are subject of dragon breath calculation
@@ -1312,7 +1312,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyShootableHexes(const battle::
 	AttackableTiles at;
 	RETURN_IF_NOT_BATTLE(at);
 
-	if(attacker->hasBonusOfType(Bonus::SHOOTS_ALL_ADJACENT) && !vstd::contains(attackerPos.neighbouringTiles(), destinationTile))
+	if(attacker->hasBonusOfType(BonusType::SHOOTS_ALL_ADJACENT) && !vstd::contains(attackerPos.neighbouringTiles(), destinationTile))
 	{
 		std::vector<BattleHex> targetHexes = destinationTile.neighbouringTiles();
 		targetHexes.push_back(destinationTile);
@@ -1448,7 +1448,7 @@ bool CBattleInfoCallback::battleHasDistancePenalty(const IBonusBearer * shooter,
 	RETURN_IF_NOT_BATTLE(false);
 
 	const std::string cachingStrNoDistancePenalty = "type_NO_DISTANCE_PENALTY";
-	static const auto selectorNoDistancePenalty = Selector::type()(Bonus::NO_DISTANCE_PENALTY);
+	static const auto selectorNoDistancePenalty = Selector::type()(BonusType::NO_DISTANCE_PENALTY);
 
 	if(shooter->hasBonus(selectorNoDistancePenalty, cachingStrNoDistancePenalty))
 		return false;
@@ -1458,7 +1458,7 @@ bool CBattleInfoCallback::battleHasDistancePenalty(const IBonusBearer * shooter,
 		//If any hex of target creature is within range, there is no penalty
 		int range = GameConstants::BATTLE_PENALTY_DISTANCE;
 
-		auto bonus = shooter->getBonus(Selector::type()(Bonus::LIMITED_SHOOTING_RANGE));
+		auto bonus = shooter->getBonus(Selector::type()(BonusType::LIMITED_SHOOTING_RANGE));
 		if(bonus != nullptr && bonus->additionalInfo != CAddInfo::NONE)
 			range = bonus->additionalInfo[0];
 
@@ -1542,13 +1542,13 @@ int32_t CBattleInfoCallback::battleGetSpellCost(const spells::Spell * sp, const
 
 	for(const auto * unit : battleAliveUnits())
 	{
-		if(unit->unitOwner() == caster->tempOwner && unit->hasBonusOfType(Bonus::CHANGES_SPELL_COST_FOR_ALLY))
+		if(unit->unitOwner() == caster->tempOwner && unit->hasBonusOfType(BonusType::CHANGES_SPELL_COST_FOR_ALLY))
 		{
-			vstd::amax(manaReduction, unit->valOfBonuses(Bonus::CHANGES_SPELL_COST_FOR_ALLY));
+			vstd::amax(manaReduction, unit->valOfBonuses(BonusType::CHANGES_SPELL_COST_FOR_ALLY));
 		}
-		if(unit->unitOwner() != caster->tempOwner && unit->hasBonusOfType(Bonus::CHANGES_SPELL_COST_FOR_ENEMY))
+		if(unit->unitOwner() != caster->tempOwner && unit->hasBonusOfType(BonusType::CHANGES_SPELL_COST_FOR_ENEMY))
 		{
-			vstd::amax(manaIncrease, unit->valOfBonuses(Bonus::CHANGES_SPELL_COST_FOR_ENEMY));
+			vstd::amax(manaIncrease, unit->valOfBonuses(BonusType::CHANGES_SPELL_COST_FOR_ENEMY));
 		}
 	}
 
@@ -1564,7 +1564,7 @@ bool CBattleInfoCallback::battleIsUnitBlocked(const battle::Unit * unit) const
 {
 	RETURN_IF_NOT_BATTLE(false);
 
-	if(unit->hasBonusOfType(Bonus::SIEGE_WEAPON)) //siege weapons cannot be blocked
+	if(unit->hasBonusOfType(BonusType::SIEGE_WEAPON)) //siege weapons cannot be blocked
 		return false;
 
 	for(const auto * adjacent : battleAdjacentUnits(unit))
@@ -1635,9 +1635,9 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, c
 	for(const SpellID& spellID : allPossibleSpells)
 	{
 		std::stringstream cachingStr;
-		cachingStr << "source_" << Bonus::SPELL_EFFECT << "id_" << spellID.num;
+		cachingStr << "source_" << vstd::to_underlying(BonusSource::SPELL_EFFECT) << "id_" << spellID.num;
 
-		if(subject->hasBonus(Selector::source(Bonus::SPELL_EFFECT, spellID), Selector::all, cachingStr.str())
+		if(subject->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, spellID), Selector::all, cachingStr.str())
 		 //TODO: this ability has special limitations
 		|| !(spellID.toSpell()->canBeCast(this, spells::Mode::CREATURE_ACTIVE, subject)))
 			continue;
@@ -1702,7 +1702,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, c
 		{
 			const auto * kingMonster = getAliveEnemy([&](const CStack * stack) -> bool //look for enemy, non-shooting stack
 			{
-				const auto isKing = Selector::type()(Bonus::KING);
+				const auto isKing = Selector::type()(BonusType::KING);
 
 				return stack->hasBonus(isKing);
 			});
@@ -1729,7 +1729,7 @@ SpellID CBattleInfoCallback::getRandomCastedSpell(CRandomGenerator & rand,const
 {
 	RETURN_IF_NOT_BATTLE(SpellID::NONE);
 
-	TConstBonusListPtr bl = caster->getBonuses(Selector::type()(Bonus::SPELLCASTER));
+	TConstBonusListPtr bl = caster->getBonuses(Selector::type()(BonusType::SPELLCASTER));
 	if (!bl->size())
 		return SpellID::NONE;
 	int totalWeight = 0;
@@ -1772,7 +1772,7 @@ int CBattleInfoCallback::battleGetSurrenderCost(const PlayerColor & Player) cons
 		ret += unit->getRawSurrenderCost();
 
 	if(const CGHeroInstance * h = battleGetFightingHero(side))
-		discount += h->valOfBonuses(Bonus::SURRENDER_DISCOUNT);
+		discount += h->valOfBonuses(BonusType::SURRENDER_DISCOUNT);
 
 	ret = static_cast<int>(ret * (100.0 - discount) / 100.0);
 	vstd::amax(ret, 0); //no negative costs for >100% discounts (impossible in original H3 mechanics, but some day...)
@@ -1790,7 +1790,7 @@ si8 CBattleInfoCallback::battleMinSpellLevel(ui8 side) const
 	if(!node)
 		return 0;
 
-	auto b = node->getBonuses(Selector::type()(Bonus::BLOCK_MAGIC_BELOW));
+	auto b = node->getBonuses(Selector::type()(BonusType::BLOCK_MAGIC_BELOW));
 	if(b->size())
 		return b->totalValue();
 
@@ -1809,7 +1809,7 @@ si8 CBattleInfoCallback::battleMaxSpellLevel(ui8 side) const
 		return GameConstants::SPELL_LEVELS;
 
 	//We can't "just get value" - it'd be 0 if there are bonuses (and all would be blocked)
-	auto b = node->getBonuses(Selector::type()(Bonus::BLOCK_MAGIC_ABOVE));
+	auto b = node->getBonuses(Selector::type()(BonusType::BLOCK_MAGIC_ABOVE));
 	if(b->size())
 		return b->totalValue();
 
@@ -1820,7 +1820,7 @@ std::optional<int> CBattleInfoCallback::battleIsFinished() const
 {
 	auto units = battleGetUnitsIf([=](const battle::Unit * unit)
 	{
-		return unit->alive() && !unit->isTurret() && !unit->hasBonusOfType(Bonus::SIEGE_WEAPON);
+		return unit->alive() && !unit->isTurret() && !unit->hasBonusOfType(BonusType::SIEGE_WEAPON);
 	});
 
 	std::array<bool, 2> hasUnit = {false, false}; //index is BattleSide

+ 2 - 2
lib/battle/CBattleInfoEssentials.cpp

@@ -271,7 +271,7 @@ bool CBattleInfoEssentials::battleCanFlee(const PlayerColor & player) const
 		return false;
 
 	//eg. one of heroes is wearing shakles of war
-	if(myHero->hasBonusOfType(Bonus::BATTLE_NO_FLEEING))
+	if(myHero->hasBonusOfType(BonusType::BATTLE_NO_FLEEING))
 		return false;
 
 	//we are besieged defender
@@ -394,7 +394,7 @@ PlayerColor CBattleInfoEssentials::battleGetOwner(const battle::Unit * unit) con
 
 	PlayerColor initialOwner = getBattle()->getSidePlayer(unit->unitSide());
 
-	static CSelector selector = Selector::type()(Bonus::HYPNOTIZED);
+	static CSelector selector = Selector::type()(BonusType::HYPNOTIZED);
 	static std::string cachingString = "type_103s-1";
 
 	if(unit->hasBonus(selector, cachingString))

+ 20 - 20
lib/battle/CUnitState.cpp

@@ -85,8 +85,8 @@ void CAmmo::serializeJson(JsonSerializeFormat & handler)
 
 ///CShots
 CShots::CShots(const battle::Unit * Owner)
-	: CAmmo(Owner, Selector::type()(Bonus::SHOTS)),
-	shooter(Owner, Selector::type()(Bonus::SHOOTER))
+	: CAmmo(Owner, Selector::type()(BonusType::SHOTS)),
+	shooter(Owner, Selector::type()(BonusType::SHOOTER))
 {
 }
 
@@ -117,16 +117,16 @@ int32_t CShots::total() const
 
 ///CCasts
 CCasts::CCasts(const battle::Unit * Owner):
-	CAmmo(Owner, Selector::type()(Bonus::CASTS))
+	CAmmo(Owner, Selector::type()(BonusType::CASTS))
 {
 }
 
 ///CRetaliations
 CRetaliations::CRetaliations(const battle::Unit * Owner)
-	: CAmmo(Owner, Selector::type()(Bonus::ADDITIONAL_RETALIATION)),
+	: CAmmo(Owner, Selector::type()(BonusType::ADDITIONAL_RETALIATION)),
 	totalCache(0),
-	noRetaliation(Owner, Selector::type()(Bonus::SIEGE_WEAPON).Or(Selector::type()(Bonus::HYPNOTIZED)).Or(Selector::type()(Bonus::NO_RETALIATION))),
-	unlimited(Owner, Selector::type()(Bonus::UNLIMITED_RETALIATIONS))
+	noRetaliation(Owner, Selector::type()(BonusType::SIEGE_WEAPON).Or(Selector::type()(BonusType::HYPNOTIZED)).Or(Selector::type()(BonusType::NO_RETALIATION))),
+	unlimited(Owner, Selector::type()(BonusType::UNLIMITED_RETALIATIONS))
 {
 }
 
@@ -339,13 +339,13 @@ CUnitState::CUnitState():
 	counterAttacks(this),
 	health(this),
 	shots(this),
-	totalAttacks(this, Selector::type()(Bonus::ADDITIONAL_ATTACK), 1),
-	minDamage(this, Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 1)), 0),
-	maxDamage(this, Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 2)), 0),
-	attack(this, Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK), 0),
-	defence(this, Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE), 0),
-	inFrenzy(this, Selector::type()(Bonus::IN_FRENZY)),
-	cloneLifetimeMarker(this, Selector::type()(Bonus::NONE).And(Selector::source(Bonus::SPELL_EFFECT, SpellID::CLONE))),
+	totalAttacks(this, Selector::type()(BonusType::ADDITIONAL_ATTACK), 1),
+	minDamage(this, Selector::typeSubtype(BonusType::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, 1)), 0),
+	maxDamage(this, Selector::typeSubtype(BonusType::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, 2)), 0),
+	attack(this, Selector::typeSubtype(BonusType::PRIMARY_SKILL, PrimarySkill::ATTACK), 0),
+	defence(this, Selector::typeSubtype(BonusType::PRIMARY_SKILL, PrimarySkill::DEFENSE), 0),
+	inFrenzy(this, Selector::type()(BonusType::IN_FRENZY)),
+	cloneLifetimeMarker(this, Selector::type()(BonusType::NONE).And(Selector::source(BonusSource::SPELL_EFFECT, SpellID::CLONE))),
 	cloneID(-1)
 {
 
@@ -430,7 +430,7 @@ const CGHeroInstance * CUnitState::getHeroCaster() const
 
 int32_t CUnitState::getSpellSchoolLevel(const spells::Spell * spell, int32_t * outSelectedSchool) const
 {
-	int32_t skill = valOfBonuses(Selector::typeSubtype(Bonus::SPELLCASTER, spell->getIndex()));
+	int32_t skill = valOfBonuses(Selector::typeSubtype(BonusType::SPELLCASTER, spell->getIndex()));
 	vstd::abetween(skill, 0, 3);
 	return skill;
 }
@@ -453,12 +453,12 @@ int32_t CUnitState::getEffectLevel(const spells::Spell * spell) const
 
 int32_t CUnitState::getEffectPower(const spells::Spell * spell) const
 {
-	return valOfBonuses(Bonus::CREATURE_SPELL_POWER) * getCount() / 100;
+	return valOfBonuses(BonusType::CREATURE_SPELL_POWER) * getCount() / 100;
 }
 
 int32_t CUnitState::getEnchantPower(const spells::Spell * spell) const
 {
-	int32_t res = valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
+	int32_t res = valOfBonuses(BonusType::CREATURE_ENCHANT_POWER);
 	if(res <= 0)
 		res = 3;//default for creatures
 	return res;
@@ -466,7 +466,7 @@ int32_t CUnitState::getEnchantPower(const spells::Spell * spell) const
 
 int64_t CUnitState::getEffectValue(const spells::Spell * spell) const
 {
-	return static_cast<int64_t>(getCount()) * valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spell->getIndex());
+	return static_cast<int64_t>(getCount()) * valOfBonuses(BonusType::SPECIFIC_SPELL_POWER, spell->getIndex());
 }
 
 PlayerColor CUnitState::getCasterOwner() const
@@ -511,7 +511,7 @@ bool CUnitState::isGhost() const
 
 bool CUnitState::isFrozen() const
 {
-	return hasBonus(Selector::source(Bonus::SPELL_EFFECT, SpellID::STONE_GAZE), Selector::all);
+	return hasBonus(Selector::source(BonusSource::SPELL_EFFECT, SpellID::STONE_GAZE), Selector::all);
 }
 
 bool CUnitState::isValidTarget(bool allowDead) const
@@ -588,12 +588,12 @@ void CUnitState::setPosition(BattleHex hex)
 
 int32_t CUnitState::getInitiative(int turn) const
 {
-	return valOfBonuses(Selector::type()(Bonus::STACKS_SPEED).And(Selector::turns(turn)));
+	return valOfBonuses(Selector::type()(BonusType::STACKS_SPEED).And(Selector::turns(turn)));
 }
 
 bool CUnitState::canMove(int turn) const
 {
-	return alive() && !hasBonus(Selector::type()(Bonus::NOT_ACTIVE).And(Selector::turns(turn))); //eg. Ammo Cart or blinded creature
+	return alive() && !hasBonus(Selector::type()(BonusType::NOT_ACTIVE).And(Selector::turns(turn))); //eg. Ammo Cart or blinded creature
 }
 
 bool CUnitState::defended(int turn) const

+ 28 - 28
lib/battle/DamageCalculator.cpp

@@ -46,13 +46,13 @@ DamageRange DamageCalculator::getBaseDamageSingle() const
 	}
 
 	const std::string cachingStrSiedgeWeapon = "type_SIEGE_WEAPON";
-	static const auto selectorSiedgeWeapon = Selector::type()(Bonus::SIEGE_WEAPON);
+	static const auto selectorSiedgeWeapon = Selector::type()(BonusType::SIEGE_WEAPON);
 
 	if(info.attacker->hasBonus(selectorSiedgeWeapon, cachingStrSiedgeWeapon) && info.attacker->creatureIndex() != CreatureID::ARROW_TOWERS)
 	{
 		auto retrieveHeroPrimSkill = [&](int skill) -> int
 		{
-			std::shared_ptr<const Bonus> b = info.attacker->getBonus(Selector::sourceTypeSel(Bonus::HERO_BASE_SKILL).And(Selector::typeSubtype(Bonus::PRIMARY_SKILL, skill)));
+			std::shared_ptr<const Bonus> b = info.attacker->getBonus(Selector::sourceTypeSel(BonusSource::HERO_BASE_SKILL).And(Selector::typeSubtype(BonusType::PRIMARY_SKILL, skill)));
 			return b ? b->val : 0;
 		};
 
@@ -66,10 +66,10 @@ DamageRange DamageCalculator::getBaseDamageSingle() const
 DamageRange DamageCalculator::getBaseDamageBlessCurse() const
 {
 	const std::string cachingStrForcedMinDamage = "type_ALWAYS_MINIMUM_DAMAGE";
-	static const auto selectorForcedMinDamage = Selector::type()(Bonus::ALWAYS_MINIMUM_DAMAGE);
+	static const auto selectorForcedMinDamage = Selector::type()(BonusType::ALWAYS_MINIMUM_DAMAGE);
 
 	const std::string cachingStrForcedMaxDamage = "type_ALWAYS_MAXIMUM_DAMAGE";
-	static const auto selectorForcedMaxDamage = Selector::type()(Bonus::ALWAYS_MAXIMUM_DAMAGE);
+	static const auto selectorForcedMaxDamage = Selector::type()(BonusType::ALWAYS_MAXIMUM_DAMAGE);
 
 	TConstBonusListPtr curseEffects = info.attacker->getBonuses(selectorForcedMinDamage, cachingStrForcedMinDamage);
 	TConstBonusListPtr blessEffects = info.attacker->getBonuses(selectorForcedMaxDamage, cachingStrForcedMaxDamage);
@@ -130,10 +130,10 @@ int DamageCalculator::getActorAttackEffective() const
 int DamageCalculator::getActorAttackSlayer() const
 {
 	const std::string cachingStrSlayer = "type_SLAYER";
-	static const auto selectorSlayer = Selector::type()(Bonus::SLAYER);
+	static const auto selectorSlayer = Selector::type()(BonusType::SLAYER);
 
 	auto slayerEffects = info.attacker->getBonuses(selectorSlayer, cachingStrSlayer);
-	auto slayerAffected = info.defender->unitType()->valOfBonuses(Selector::type()(Bonus::KING));
+	auto slayerAffected = info.defender->unitType()->valOfBonuses(Selector::type()(BonusType::KING));
 
 	if(std::shared_ptr<const Bonus> slayerEffect = slayerEffects->getFirst(Selector::all))
 	{
@@ -143,7 +143,7 @@ int DamageCalculator::getActorAttackSlayer() const
 		if(isAffected)
 		{
 			int attackBonus = SpellID(SpellID::SLAYER).toSpell()->getLevelPower(spLevel);
-			if(info.attacker->hasBonusOfType(Bonus::SPECIAL_PECULIAR_ENCHANT, SpellID::SLAYER))
+			if(info.attacker->hasBonusOfType(BonusType::SPECIAL_PECULIAR_ENCHANT, SpellID::SLAYER))
 			{
 				ui8 attackerTier = info.attacker->unitType()->getLevel();
 				ui8 specialtyBonus = std::max(5 - attackerTier, 0);
@@ -167,7 +167,7 @@ int DamageCalculator::getTargetDefenseEffective() const
 
 int DamageCalculator::getTargetDefenseIgnored() const
 {
-	double multDefenceReduction = battleBonusValue(info.attacker, Selector::type()(Bonus::ENEMY_DEFENCE_REDUCTION)) / 100.0;
+	double multDefenceReduction = battleBonusValue(info.attacker, Selector::type()(BonusType::ENEMY_DEFENCE_REDUCTION)) / 100.0;
 
 	if(multDefenceReduction > 0)
 	{
@@ -195,7 +195,7 @@ double DamageCalculator::getAttackSkillFactor() const
 double DamageCalculator::getAttackBlessFactor() const
 {
 	const std::string cachingStrDamage = "type_GENERAL_DAMAGE_PREMY";
-	static const auto selectorDamage = Selector::type()(Bonus::GENERAL_DAMAGE_PREMY);
+	static const auto selectorDamage = Selector::type()(BonusType::GENERAL_DAMAGE_PREMY);
 	return info.attacker->valOfBonuses(selectorDamage, cachingStrDamage) / 100.0;
 }
 
@@ -205,11 +205,11 @@ double DamageCalculator::getAttackOffenseArcheryFactor() const
 	if(info.shooting)
 	{
 		const std::string cachingStrArchery = "type_PERCENTAGE_DAMAGE_BOOSTs_1";
-		static const auto selectorArchery = Selector::typeSubtype(Bonus::PERCENTAGE_DAMAGE_BOOST, 1);
+		static const auto selectorArchery = Selector::typeSubtype(BonusType::PERCENTAGE_DAMAGE_BOOST, 1);
 		return info.attacker->valOfBonuses(selectorArchery, cachingStrArchery) / 100.0;
 	}
 	const std::string cachingStrOffence = "type_PERCENTAGE_DAMAGE_BOOSTs_0";
-	static const auto selectorOffence = Selector::typeSubtype(Bonus::PERCENTAGE_DAMAGE_BOOST, 0);
+	static const auto selectorOffence = Selector::typeSubtype(BonusType::PERCENTAGE_DAMAGE_BOOST, 0);
 	return info.attacker->valOfBonuses(selectorOffence, cachingStrOffence) / 100.0;
 }
 
@@ -231,7 +231,7 @@ double DamageCalculator::getAttackDoubleDamageFactor() const
 {
 	if(info.doubleDamage) {
 		const auto cachingStr = "type_BONUS_DAMAGE_PERCENTAGEs_" + std::to_string(info.attacker->creatureIndex());
-		const auto selector = Selector::typeSubtype(Bonus::BONUS_DAMAGE_PERCENTAGE, info.attacker->creatureIndex());
+		const auto selector = Selector::typeSubtype(BonusType::BONUS_DAMAGE_PERCENTAGE, info.attacker->creatureIndex());
 		return info.attacker->valOfBonuses(selector, cachingStr) / 100.0;
 	}
 	return 0.0;
@@ -240,10 +240,10 @@ double DamageCalculator::getAttackDoubleDamageFactor() const
 double DamageCalculator::getAttackJoustingFactor() const
 {
 	const std::string cachingStrJousting = "type_JOUSTING";
-	static const auto selectorJousting = Selector::type()(Bonus::JOUSTING);
+	static const auto selectorJousting = Selector::type()(BonusType::JOUSTING);
 
 	const std::string cachingStrChargeImmunity = "type_CHARGE_IMMUNITY";
-	static const auto selectorChargeImmunity = Selector::type()(Bonus::CHARGE_IMMUNITY);
+	static const auto selectorChargeImmunity = Selector::type()(BonusType::CHARGE_IMMUNITY);
 
 	//applying jousting bonus
 	if(info.chargeDistance > 0 && info.attacker->hasBonus(selectorJousting, cachingStrJousting) && !info.defender->hasBonus(selectorChargeImmunity, cachingStrChargeImmunity))
@@ -255,7 +255,7 @@ double DamageCalculator::getAttackHateFactor() const
 {
 	//assume that unit have only few HATE features and cache them all
 	const std::string cachingStrHate = "type_HATE";
-	static const auto selectorHate = Selector::type()(Bonus::HATE);
+	static const auto selectorHate = Selector::type()(BonusType::HATE);
 
 	auto allHateEffects = info.attacker->getBonuses(selectorHate, cachingStrHate);
 
@@ -281,7 +281,7 @@ double DamageCalculator::getDefenseSkillFactor() const
 double DamageCalculator::getDefenseArmorerFactor() const
 {
 	const std::string cachingStrArmorer = "type_GENERAL_DAMAGE_REDUCTIONs_N1_NsrcSPELL_EFFECT";
-	static const auto selectorArmorer = Selector::typeSubtype(Bonus::GENERAL_DAMAGE_REDUCTION, -1).And(Selector::sourceTypeSel(Bonus::SPELL_EFFECT).Not());
+	static const auto selectorArmorer = Selector::typeSubtype(BonusType::GENERAL_DAMAGE_REDUCTION, -1).And(Selector::sourceTypeSel(BonusSource::SPELL_EFFECT).Not());
 	return info.defender->valOfBonuses(selectorArmorer, cachingStrArmorer) / 100.0;
 
 }
@@ -289,10 +289,10 @@ double DamageCalculator::getDefenseArmorerFactor() const
 double DamageCalculator::getDefenseMagicShieldFactor() const
 {
 	const std::string cachingStrMeleeReduction = "type_GENERAL_DAMAGE_REDUCTIONs_0";
-	static const auto selectorMeleeReduction = Selector::typeSubtype(Bonus::GENERAL_DAMAGE_REDUCTION, 0);
+	static const auto selectorMeleeReduction = Selector::typeSubtype(BonusType::GENERAL_DAMAGE_REDUCTION, 0);
 
 	const std::string cachingStrRangedReduction = "type_GENERAL_DAMAGE_REDUCTIONs_1";
-	static const auto selectorRangedReduction = Selector::typeSubtype(Bonus::GENERAL_DAMAGE_REDUCTION, 1);
+	static const auto selectorRangedReduction = Selector::typeSubtype(BonusType::GENERAL_DAMAGE_REDUCTION, 1);
 
 	//handling spell effects - shield and air shield
 	if(info.shooting)
@@ -311,7 +311,7 @@ double DamageCalculator::getDefenseRangePenaltiesFactor() const
 		const std::string cachingStrAdvAirShield = "isAdvancedAirShield";
 		auto isAdvancedAirShield = [](const Bonus* bonus)
 		{
-			return bonus->source == Bonus::SPELL_EFFECT
+			return bonus->source == BonusSource::SPELL_EFFECT
 					&& bonus->sid == SpellID::AIR_SHIELD
 					&& bonus->val >= SecSkillLevel::ADVANCED;
 		};
@@ -325,7 +325,7 @@ double DamageCalculator::getDefenseRangePenaltiesFactor() const
 	else
 	{
 		const std::string cachingStrNoMeleePenalty = "type_NO_MELEE_PENALTY";
-		static const auto selectorNoMeleePenalty = Selector::type()(Bonus::NO_MELEE_PENALTY);
+		static const auto selectorNoMeleePenalty = Selector::type()(BonusType::NO_MELEE_PENALTY);
 
 		if(info.attacker->isShooter() && !info.attacker->hasBonus(selectorNoMeleePenalty, cachingStrNoMeleePenalty))
 			return 0.5;
@@ -356,7 +356,7 @@ double DamageCalculator::getDefenseUnluckyFactor() const
 
 double DamageCalculator::getDefenseBlindParalysisFactor() const
 {
-	double multAttackReduction = battleBonusValue(info.attacker, Selector::type()(Bonus::GENERAL_ATTACK_REDUCTION)) / 100.0;
+	double multAttackReduction = battleBonusValue(info.attacker, Selector::type()(BonusType::GENERAL_ATTACK_REDUCTION)) / 100.0;
 	return multAttackReduction;
 }
 
@@ -366,7 +366,7 @@ double DamageCalculator::getDefenseForgetfulnessFactor() const
 	{
 		//todo: set actual percentage in spell bonus configuration instead of just level; requires non trivial backward compatibility handling
 		//get list first, total value of 0 also counts
-		TConstBonusListPtr forgetfulList = info.attacker->getBonuses(Selector::type()(Bonus::FORGETFULL),"type_FORGETFULL");
+		TConstBonusListPtr forgetfulList = info.attacker->getBonuses(Selector::type()(BonusType::FORGETFULL),"type_FORGETFULL");
 
 		if(!forgetfulList->empty())
 		{
@@ -386,7 +386,7 @@ double DamageCalculator::getDefensePetrificationFactor() const
 {
 	// Creatures that are petrified by a Basilisk's Petrifying attack or a Medusa's Stone gaze take 50% damage (R8 = 0.50) from ranged and melee attacks. Taking damage also deactivates the effect.
 	const std::string cachingStrAllReduction = "type_GENERAL_DAMAGE_REDUCTIONs_N1_srcSPELL_EFFECT";
-	static const auto selectorAllReduction = Selector::typeSubtype(Bonus::GENERAL_DAMAGE_REDUCTION, -1).And(Selector::sourceTypeSel(Bonus::SPELL_EFFECT));
+	static const auto selectorAllReduction = Selector::typeSubtype(BonusType::GENERAL_DAMAGE_REDUCTION, -1).And(Selector::sourceTypeSel(BonusSource::SPELL_EFFECT));
 
 	return info.defender->valOfBonuses(selectorAllReduction, cachingStrAllReduction) / 100.0;
 }
@@ -397,7 +397,7 @@ double DamageCalculator::getDefenseMagicFactor() const
 	if(info.attacker->creatureIndex() == CreatureID::MAGIC_ELEMENTAL)
 	{
 		const std::string cachingStrMagicImmunity = "type_LEVEL_SPELL_IMMUNITY";
-		static const auto selectorMagicImmunity = Selector::type()(Bonus::LEVEL_SPELL_IMMUNITY);
+		static const auto selectorMagicImmunity = Selector::type()(BonusType::LEVEL_SPELL_IMMUNITY);
 
 		if(info.defender->valOfBonuses(selectorMagicImmunity, cachingStrMagicImmunity) >= 5)
 			return 0.5;
@@ -411,7 +411,7 @@ double DamageCalculator::getDefenseMindFactor() const
 	if(info.attacker->creatureIndex() == CreatureID::PSYCHIC_ELEMENTAL)
 	{
 		const std::string cachingStrMindImmunity = "type_MIND_IMMUNITY";
-		static const auto selectorMindImmunity = Selector::type()(Bonus::MIND_IMMUNITY);
+		static const auto selectorMindImmunity = Selector::type()(BonusType::MIND_IMMUNITY);
 
 		if(info.defender->hasBonus(selectorMindImmunity, cachingStrMindImmunity))
 			return 0.5;
@@ -471,10 +471,10 @@ int64_t DamageCalculator::getCasualties(int64_t damageDealt) const
 
 int DamageCalculator::battleBonusValue(const IBonusBearer * bearer, const CSelector & selector) const
 {
-	auto noLimit = Selector::effectRange()(Bonus::NO_LIMIT);
+	auto noLimit = Selector::effectRange()(BonusLimitEffect::NO_LIMIT);
 	auto limitMatches = info.shooting
-						? Selector::effectRange()(Bonus::ONLY_DISTANCE_FIGHT)
-						: Selector::effectRange()(Bonus::ONLY_MELEE_FIGHT);
+						? Selector::effectRange()(BonusLimitEffect::ONLY_DISTANCE_FIGHT)
+						: Selector::effectRange()(BonusLimitEffect::ONLY_MELEE_FIGHT);
 
 	//any regular bonuses or just ones for melee/ranged
 	return bearer->getBonuses(selector, noLimit.Or(limitMatches))->totalValue();

+ 1 - 1
lib/battle/ReachabilityInfo.cpp

@@ -19,7 +19,7 @@ ReachabilityInfo::Parameters::Parameters(const battle::Unit * Stack, BattleHex S
 	startPosition(StartPosition),
 	doubleWide(Stack->doubleWide()),
 	side(Stack->unitSide()),
-	flying(Stack->hasBonusOfType(Bonus::FLYING))
+	flying(Stack->hasBonusOfType(BonusType::FLYING))
 {
 	knownAccessible = battle::Unit::getHexes(startPosition, doubleWide, side);
 }

+ 34 - 128
lib/bonuses/Bonus.cpp

@@ -23,7 +23,6 @@
 #include "../CTownHandler.h"
 #include "../CGeneralTextHandler.h"
 #include "../CSkillHandler.h"
-#include "../CStack.h"
 #include "../CArtHandler.h"
 #include "../CModHandler.h"
 #include "../TerrainHandler.h"
@@ -32,44 +31,6 @@
 
 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",
@@ -158,20 +119,20 @@ std::string Bonus::Description(std::optional<si32> customValue) const
 		{
 			switch(source)
 			{
-			case ARTIFACT:
+			case BonusSource::ARTIFACT:
 				str << ArtifactID(sid).toArtifact(VLC->artifacts())->getNameTranslated();
 				break;
-			case SPELL_EFFECT:
+			case BonusSource::SPELL_EFFECT:
 				str << SpellID(sid).toSpell(VLC->spells())->getNameTranslated();
 				break;
-			case CREATURE_ABILITY:
-				str << VLC->creh->objects[sid]->getNamePluralTranslated();
+			case BonusSource::CREATURE_ABILITY:
+				str << CreatureID(sid).toCreature(VLC->creatures())->getNamePluralTranslated();
 				break;
-			case SECONDARY_SKILL:
-				str << VLC->skillh->getByIndex(sid)->getNameTranslated();
+			case BonusSource::SECONDARY_SKILL:
+				str << VLC->skills()->getByIndex(sid)->getNameTranslated();
 				break;
-			case HERO_SPECIAL:
-				str << VLC->heroh->objects[sid]->getNameTranslated();
+			case BonusSource::HERO_SPECIAL:
+				str << VLC->heroTypes()->getByIndex(sid)->getNameTranslated();
 				break;
 			default:
 				//todo: handle all possible sources
@@ -193,61 +154,40 @@ std::string Bonus::Description(std::optional<si32> customValue) const
 	return str.str();
 }
 
-JsonNode subtypeToJson(Bonus::BonusType type, int subtype)
+JsonNode subtypeToJson(BonusType type, int subtype)
 {
 	switch(type)
 	{
-	case Bonus::PRIMARY_SKILL:
+	case BonusType::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:
+	case BonusType::SPECIAL_SPELL_LEV:
+	case BonusType::SPECIFIC_SPELL_DAMAGE:
+	case BonusType::SPELL:
+	case BonusType::SPECIAL_PECULIAR_ENCHANT:
+	case BonusType::SPECIAL_ADD_VALUE_ENCHANT:
+	case BonusType::SPECIAL_FIXED_VALUE_ENCHANT:
 		return JsonUtils::stringNode(CModHandler::makeFullIdentifier("", "spell", SpellID::encode(subtype)));
-	case Bonus::IMPROVED_NECROMANCY:
-	case Bonus::SPECIAL_UPGRADE:
+	case BonusType::IMPROVED_NECROMANCY:
+	case BonusType::SPECIAL_UPGRADE:
 		return JsonUtils::stringNode(CModHandler::makeFullIdentifier("", "creature", CreatureID::encode(subtype)));
-	case Bonus::GENERATE_RESOURCE:
+	case BonusType::GENERATE_RESOURCE:
 		return JsonUtils::stringNode("resource." + GameConstants::RESOURCE_NAMES[subtype]);
 	default:
 		return JsonUtils::intNode(subtype);
 	}
 }
 
-JsonNode additionalInfoToJson(Bonus::BonusType type, CAddInfo addInfo)
+JsonNode additionalInfoToJson(BonusType type, CAddInfo addInfo)
 {
 	switch(type)
 	{
-	case Bonus::SPECIAL_UPGRADE:
+	case BonusType::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);
@@ -257,36 +197,26 @@ JsonNode Bonus::toJsonNode() const
 		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)
+	if(source != BonusSource::OTHER)
 		root["sourceType"].String() = vstd::findKey(bonusSourceMap, source);
-	if(targetSourceType != OTHER)
+	if(targetSourceType != BonusSource::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)
+	if(valType != BonusValueType::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)
+	if(effectRange != BonusLimitEffect::NO_LIMIT)
 		root["effectRange"].String() = vstd::findKey(bonusLimitEffect, effectRange);
-	if(duration != PERMANENT)
-		root["duration"] = durationToJson(duration);
+	if(duration != BonusDuration::PERMANENT)
+		root["duration"].String() = vstd::findKey(bonusDurationMap, duration);
 	if(turnsRemain)
 		root["turns"].Integer() = turnsRemain;
 	if(limiter)
@@ -298,32 +228,8 @@ JsonNode Bonus::toJsonNode() const
 	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)),
+Bonus::Bonus(BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::string Desc, si32 Subtype):
+	duration(Duration),
 	type(Type),
 	subtype(Subtype),
 	source(Src),
@@ -332,11 +238,11 @@ Bonus::Bonus(Bonus::BonusDuration Duration, BonusType Type, BonusSource Src, si3
 	description(std::move(Desc))
 {
 	boost::algorithm::trim(description);
-	targetSourceType = OTHER;
+	targetSourceType = BonusSource::OTHER;
 }
 
-Bonus::Bonus(Bonus::BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, si32 Subtype, ValueType ValType):
-	duration(static_cast<ui16>(Duration)),
+Bonus::Bonus(BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, si32 Subtype, BonusValueType ValType):
+	duration(Duration),
 	type(Type),
 	subtype(Subtype),
 	source(Src),
@@ -345,8 +251,8 @@ Bonus::Bonus(Bonus::BonusDuration Duration, BonusType Type, BonusSource Src, si3
 	valType(ValType)
 {
 	turnsRemain = 0;
-	effectRange = NO_LIMIT;
-	targetSourceType = OTHER;
+	effectRange = BonusLimitEffect::NO_LIMIT;
+	targetSourceType = BonusSource::OTHER;
 }
 
 std::shared_ptr<Bonus> Bonus::addPropagator(const TPropagatorPtr & Propagator)

+ 17 - 264
lib/bonuses/Bonus.h

@@ -9,6 +9,7 @@
  */
 #pragma once
 
+#include "BonusEnum.h"
 #include "../JsonNode.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -48,266 +49,24 @@ public:
 
 #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
+	BonusDuration duration = BonusDuration::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
+	BonusType type = BonusType::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 source = BonusSource::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;
+	BonusValueType valType = BonusValueType::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
+	BonusLimitEffect effectRange = BonusLimitEffect::NO_LIMIT; //if not NO_LIMIT, bonus will be omitted by default
 
 	TLimiterPtr limiter;
 	TPropagatorPtr propagator;
@@ -317,7 +76,7 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
 	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(BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, si32 Subtype=-1, BonusValueType ValType = BonusValueType::ADDITIVE_VALUE);
 	Bonus() = default;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
@@ -348,43 +107,43 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
 	}
 	static bool NDays(const Bonus *hb)
 	{
-		return hb->duration & Bonus::N_DAYS;
+		return hb->duration == BonusDuration::N_DAYS;
 	}
 	static bool NTurns(const Bonus *hb)
 	{
-		return hb->duration & Bonus::N_TURNS;
+		return hb->duration == BonusDuration::N_TURNS;
 	}
 	static bool OneDay(const Bonus *hb)
 	{
-		return hb->duration & Bonus::ONE_DAY;
+		return hb->duration == BonusDuration::ONE_DAY;
 	}
 	static bool OneWeek(const Bonus *hb)
 	{
-		return hb->duration & Bonus::ONE_WEEK;
+		return hb->duration == BonusDuration::ONE_WEEK;
 	}
 	static bool OneBattle(const Bonus *hb)
 	{
-		return hb->duration & Bonus::ONE_BATTLE;
+		return hb->duration == BonusDuration::ONE_BATTLE;
 	}
 	static bool Permanent(const Bonus *hb)
 	{
-		return hb->duration & Bonus::PERMANENT;
+		return hb->duration == BonusDuration::PERMANENT;
 	}
 	static bool UntilGetsTurn(const Bonus *hb)
 	{
-		return hb->duration & Bonus::STACK_GETS_TURN;
+		return hb->duration == BonusDuration::STACK_GETS_TURN;
 	}
 	static bool UntilAttack(const Bonus *hb)
 	{
-		return hb->duration & Bonus::UNTIL_ATTACK;
+		return hb->duration == BonusDuration::UNTIL_ATTACK;
 	}
 	static bool UntilBeingAttacked(const Bonus *hb)
 	{
-		return hb->duration & Bonus::UNTIL_BEING_ATTACKED;
+		return hb->duration == BonusDuration::UNTIL_BEING_ATTACKED;
 	}
 	static bool UntilCommanderKilled(const Bonus *hb)
 	{
-		return hb->duration & Bonus::COMMANDER_KILLED;
+		return hb->duration == BonusDuration::COMMANDER_KILLED;
 	}
 	inline bool operator == (const BonusType & cf) const
 	{
@@ -411,7 +170,6 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
 
 	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
@@ -420,11 +178,6 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
 
 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

+ 58 - 0
lib/bonuses/BonusEnum.cpp

@@ -0,0 +1,58 @@
+/*
+ * BonusEnum.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 "BonusEnum.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+#define BONUS_NAME(x) { #x, BonusType::x },
+	const std::map<std::string, BonusType> bonusNameMap = {
+		BONUS_LIST
+	};
+#undef BONUS_NAME
+
+#define BONUS_VALUE(x) { #x, BonusValueType::x },
+	const std::map<std::string, BonusValueType> bonusValueMap = { BONUS_VALUE_LIST };
+#undef BONUS_VALUE
+
+#define BONUS_SOURCE(x) { #x, BonusSource::x },
+	const std::map<std::string, BonusSource> bonusSourceMap = { BONUS_SOURCE_LIST };
+#undef BONUS_SOURCE
+
+#define BONUS_ITEM(x) { #x, BonusDuration::x },
+const std::map<std::string, BonusDuration> 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", BonusDuration::UNTIL_BEING_ATTACKED }//typo, but used in some mods
+};
+#undef BONUS_ITEM
+
+#define BONUS_ITEM(x) { #x, BonusLimitEffect::x },
+const std::map<std::string, BonusLimitEffect> bonusLimitEffect =
+{
+	BONUS_ITEM(NO_LIMIT)
+	BONUS_ITEM(ONLY_DISTANCE_FIGHT)
+	BONUS_ITEM(ONLY_MELEE_FIGHT)
+};
+#undef BONUS_ITEM
+
+VCMI_LIB_NAMESPACE_END

+ 264 - 0
lib/bonuses/BonusEnum.h

@@ -0,0 +1,264 @@
+/*
+ * BonusEnum.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
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+#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
+
+
+enum class BonusType
+{
+#define BONUS_NAME(x) x,
+    BONUS_LIST
+#undef BONUS_NAME
+};
+enum class BonusDuration : uint16_t //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 class 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 class BonusLimitEffect
+{
+    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 class BonusValueType
+{
+#define BONUS_VALUE(x) x,
+    BONUS_VALUE_LIST
+#undef BONUS_VALUE
+};
+
+extern DLL_LINKAGE const std::map<std::string, BonusType> bonusNameMap;
+extern DLL_LINKAGE const std::map<std::string, BonusValueType> bonusValueMap;
+extern DLL_LINKAGE const std::map<std::string, BonusSource> bonusSourceMap;
+extern DLL_LINKAGE const std::map<std::string, BonusDuration> bonusDurationMap;
+extern DLL_LINKAGE const std::map<std::string, BonusLimitEffect> bonusLimitEffect;
+
+VCMI_LIB_NAMESPACE_END

+ 19 - 19
lib/bonuses/BonusList.cpp

@@ -98,7 +98,7 @@ int BonusList::totalValue() const
 	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 = {};
+	std::array <BonusCollection, vstd::to_underlying(BonusSource::NUM_BONUS_SOURCE)> sources = {};
 	BonusCollection any;
 	bool hasIndepMax = false;
 	bool hasIndepMin = false;
@@ -107,31 +107,31 @@ int BonusList::totalValue() const
 	{
 		switch(b->valType)
 		{
-		case Bonus::BASE_NUMBER:
-			sources[b->source].base += b->val;
+		case BonusValueType::BASE_NUMBER:
+			sources[vstd::to_underlying(b->source)].base += b->val;
 			break;
-		case Bonus::PERCENT_TO_ALL:
-			sources[b->source].percentToAll += b->val;
+		case BonusValueType::PERCENT_TO_ALL:
+			sources[vstd::to_underlying(b->source)].percentToAll += b->val;
 			break;
-		case Bonus::PERCENT_TO_BASE:
-			sources[b->source].percentToBase += b->val;
+		case BonusValueType::PERCENT_TO_BASE:
+			sources[vstd::to_underlying(b->source)].percentToBase += b->val;
 			break;
-		case Bonus::PERCENT_TO_SOURCE:
-			sources[b->source].percentToSource += b->val;
+		case BonusValueType::PERCENT_TO_SOURCE:
+			sources[vstd::to_underlying(b->source)].percentToSource += b->val;
 			break;
-		case Bonus::PERCENT_TO_TARGET_TYPE:
-			sources[b->targetSourceType].percentToSource += b->val;
+		case BonusValueType::PERCENT_TO_TARGET_TYPE:
+			sources[vstd::to_underlying(b->targetSourceType)].percentToSource += b->val;
 			break;
-		case Bonus::ADDITIVE_VALUE:
-			sources[b->source].additive += b->val;
+		case BonusValueType::ADDITIVE_VALUE:
+			sources[vstd::to_underlying(b->source)].additive += b->val;
 			break;
-		case Bonus::INDEPENDENT_MAX:
+		case BonusValueType::INDEPENDENT_MAX:
 			hasIndepMax = true;
-			vstd::amax(sources[b->source].indepMax, b->val);
+			vstd::amax(sources[vstd::to_underlying(b->source)].indepMax, b->val);
 			break;
-		case Bonus::INDEPENDENT_MIN:
+		case BonusValueType::INDEPENDENT_MIN:
 			hasIndepMin = true;
-			vstd::amin(sources[b->source].indepMin, b->val);
+			vstd::amin(sources[vstd::to_underlying(b->source)].indepMin, b->val);
 			break;
 		}
 	}
@@ -155,7 +155,7 @@ int BonusList::totalValue() const
 
 	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;
+		return b->valType != BonusValueType::INDEPENDENT_MAX && b->valType != BonusValueType::INDEPENDENT_MIN;
 	}));
 
 	if(notIndepBonuses)
@@ -190,7 +190,7 @@ void BonusList::getBonuses(BonusList & out, const CSelector &selector, const CSe
 	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;
+		auto noFightLimit = b->effectRange == BonusLimitEffect::NO_LIMIT;
 		if(selector(b.get()) && ((!limit && noFightLimit) || ((bool)limit && limit(b.get()))))
 			out.push_back(b);
 	}

+ 45 - 45
lib/bonuses/BonusParams.cpp

@@ -23,112 +23,112 @@ BonusParams::BonusParams(std::string deprecatedTypeStr, std::string deprecatedSu
 	if(deprecatedTypeStr == "SECONDARY_SKILL_PREMY" || deprecatedTypeStr == "SPECIAL_SECONDARY_SKILL")
 	{
 		if(deprecatedSubtype == SecondarySkill::PATHFINDING || deprecatedSubtypeStr == "skill.pathfinding")
-			type = Bonus::ROUGH_TERRAIN_DISCOUNT;
+			type = BonusType::ROUGH_TERRAIN_DISCOUNT;
 		else if(deprecatedSubtype == SecondarySkill::DIPLOMACY || deprecatedSubtypeStr == "skill.diplomacy")
-			type = Bonus::WANDERING_CREATURES_JOIN_BONUS;
+			type = BonusType::WANDERING_CREATURES_JOIN_BONUS;
 		else if(deprecatedSubtype == SecondarySkill::WISDOM || deprecatedSubtypeStr == "skill.wisdom")
-			type = Bonus::MAX_LEARNABLE_SPELL_LEVEL;
+			type = BonusType::MAX_LEARNABLE_SPELL_LEVEL;
 		else if(deprecatedSubtype == SecondarySkill::MYSTICISM || deprecatedSubtypeStr == "skill.mysticism")
-			type = Bonus::MANA_REGENERATION;
+			type = BonusType::MANA_REGENERATION;
 		else if(deprecatedSubtype == SecondarySkill::NECROMANCY || deprecatedSubtypeStr == "skill.necromancy")
-			type = Bonus::UNDEAD_RAISE_PERCENTAGE;
+			type = BonusType::UNDEAD_RAISE_PERCENTAGE;
 		else if(deprecatedSubtype == SecondarySkill::LEARNING || deprecatedSubtypeStr == "skill.learning")
-			type = Bonus::HERO_EXPERIENCE_GAIN_PERCENT;
+			type = BonusType::HERO_EXPERIENCE_GAIN_PERCENT;
 		else if(deprecatedSubtype == SecondarySkill::RESISTANCE || deprecatedSubtypeStr == "skill.resistance")
-			type = Bonus::MAGIC_RESISTANCE;
+			type = BonusType::MAGIC_RESISTANCE;
 		else if(deprecatedSubtype == SecondarySkill::EAGLE_EYE || deprecatedSubtypeStr == "skill.eagleEye")
-			type = Bonus::LEARN_BATTLE_SPELL_CHANCE;
+			type = BonusType::LEARN_BATTLE_SPELL_CHANCE;
 		else if(deprecatedSubtype == SecondarySkill::SCOUTING || deprecatedSubtypeStr == "skill.scouting")
-			type = Bonus::SIGHT_RADIUS;
+			type = BonusType::SIGHT_RADIUS;
 		else if(deprecatedSubtype == SecondarySkill::INTELLIGENCE || deprecatedSubtypeStr == "skill.intelligence")
 		{
-			type = Bonus::MANA_PER_KNOWLEDGE;
-			valueType = Bonus::PERCENT_TO_BASE;
+			type = BonusType::MANA_PER_KNOWLEDGE;
+			valueType = BonusValueType::PERCENT_TO_BASE;
 			valueTypeRelevant = true;
 		}
 		else if(deprecatedSubtype == SecondarySkill::SORCERY || deprecatedSubtypeStr == "skill.sorcery")
-			type = Bonus::SPELL_DAMAGE;
+			type = BonusType::SPELL_DAMAGE;
 		else if(deprecatedSubtype == SecondarySkill::SCHOLAR || deprecatedSubtypeStr == "skill.scholar")
-			type = Bonus::LEARN_MEETING_SPELL_LIMIT;
+			type = BonusType::LEARN_MEETING_SPELL_LIMIT;
 		else if(deprecatedSubtype == SecondarySkill::ARCHERY|| deprecatedSubtypeStr == "skill.archery")
 		{
 			subtype = 1;
 			subtypeRelevant = true;
-			type = Bonus::PERCENTAGE_DAMAGE_BOOST;
+			type = BonusType::PERCENTAGE_DAMAGE_BOOST;
 		}
 		else if(deprecatedSubtype == SecondarySkill::OFFENCE || deprecatedSubtypeStr == "skill.offence")
 		{
 			subtype = 0;
 			subtypeRelevant = true;
-			type = Bonus::PERCENTAGE_DAMAGE_BOOST;
+			type = BonusType::PERCENTAGE_DAMAGE_BOOST;
 		}
 		else if(deprecatedSubtype == SecondarySkill::ARMORER || deprecatedSubtypeStr == "skill.armorer")
 		{
 			subtype = -1;
 			subtypeRelevant = true;
-			type = Bonus::GENERAL_DAMAGE_REDUCTION;
+			type = BonusType::GENERAL_DAMAGE_REDUCTION;
 		}
 		else if(deprecatedSubtype == SecondarySkill::NAVIGATION || deprecatedSubtypeStr == "skill.navigation")
 		{
 			subtype = 0;
 			subtypeRelevant = true;
-			valueType = Bonus::PERCENT_TO_BASE;
+			valueType = BonusValueType::PERCENT_TO_BASE;
 			valueTypeRelevant = true;
-			type = Bonus::MOVEMENT;
+			type = BonusType::MOVEMENT;
 		}
 		else if(deprecatedSubtype == SecondarySkill::LOGISTICS || deprecatedSubtypeStr == "skill.logistics")
 		{
 			subtype = 1;
 			subtypeRelevant = true;
-			valueType = Bonus::PERCENT_TO_BASE;
+			valueType = BonusValueType::PERCENT_TO_BASE;
 			valueTypeRelevant = true;
-			type = Bonus::MOVEMENT;
+			type = BonusType::MOVEMENT;
 		}
 		else if(deprecatedSubtype == SecondarySkill::ESTATES || deprecatedSubtypeStr == "skill.estates")
 		{
-			type = Bonus::GENERATE_RESOURCE;
+			type = BonusType::GENERATE_RESOURCE;
 			subtype = GameResID(EGameResID::GOLD);
 			subtypeRelevant = true;
 		}
 		else if(deprecatedSubtype == SecondarySkill::AIR_MAGIC || deprecatedSubtypeStr == "skill.airMagic")
 		{
-			type = Bonus::MAGIC_SCHOOL_SKILL;
+			type = BonusType::MAGIC_SCHOOL_SKILL;
 			subtypeRelevant = true;
 			subtype = 4;
 		}
 		else if(deprecatedSubtype == SecondarySkill::WATER_MAGIC || deprecatedSubtypeStr == "skill.waterMagic")
 		{
-			type = Bonus::MAGIC_SCHOOL_SKILL;
+			type = BonusType::MAGIC_SCHOOL_SKILL;
 			subtypeRelevant = true;
 			subtype = 1;
 		}
 		else if(deprecatedSubtype == SecondarySkill::FIRE_MAGIC || deprecatedSubtypeStr == "skill.fireMagic")
 		{
-			type = Bonus::MAGIC_SCHOOL_SKILL;
+			type = BonusType::MAGIC_SCHOOL_SKILL;
 			subtypeRelevant = true;
 			subtype = 2;
 		}
 		else if(deprecatedSubtype == SecondarySkill::EARTH_MAGIC || deprecatedSubtypeStr == "skill.earthMagic")
 		{
-			type = Bonus::MAGIC_SCHOOL_SKILL;
+			type = BonusType::MAGIC_SCHOOL_SKILL;
 			subtypeRelevant = true;
 			subtype = 8;
 		}
 		else if (deprecatedSubtype == SecondarySkill::ARTILLERY || deprecatedSubtypeStr == "skill.artillery")
 		{
-			type = Bonus::BONUS_DAMAGE_CHANCE;
+			type = BonusType::BONUS_DAMAGE_CHANCE;
 			subtypeRelevant = true;
 			subtypeStr = "core:creature.ballista";
 		}
 		else if (deprecatedSubtype == SecondarySkill::FIRST_AID || deprecatedSubtypeStr == "skill.firstAid")
 		{
-			type = Bonus::SPECIFIC_SPELL_POWER;
+			type = BonusType::SPECIFIC_SPELL_POWER;
 			subtypeRelevant = true;
 			subtypeStr = "core:spell.firstAid";
 		}
 		else if (deprecatedSubtype == SecondarySkill::BALLISTICS || deprecatedSubtypeStr == "skill.ballistics")
 		{
-			type = Bonus::CATAPULT_EXTRA_SHOTS;
+			type = BonusType::CATAPULT_EXTRA_SHOTS;
 			subtypeRelevant = true;
 			subtypeStr = "core:spell.catapultShot";
 		}
@@ -138,10 +138,10 @@ BonusParams::BonusParams(std::string deprecatedTypeStr, std::string deprecatedSu
 	else if (deprecatedTypeStr == "SECONDARY_SKILL_VAL2")
 	{
 		if(deprecatedSubtype == SecondarySkill::EAGLE_EYE || deprecatedSubtypeStr == "skill.eagleEye")
-			type = Bonus::LEARN_BATTLE_SPELL_LEVEL_LIMIT;
+			type = BonusType::LEARN_BATTLE_SPELL_LEVEL_LIMIT;
 		else if (deprecatedSubtype == SecondarySkill::ARTILLERY || deprecatedSubtypeStr == "skill.artillery")
 		{
-			type = Bonus::HERO_GRANTS_ATTACKS;
+			type = BonusType::HERO_GRANTS_ATTACKS;
 			subtypeRelevant = true;
 			subtypeStr = "core:creature.ballista";
 		}
@@ -152,68 +152,68 @@ BonusParams::BonusParams(std::string deprecatedTypeStr, std::string deprecatedSu
 	{
 		subtype = 0;
 		subtypeRelevant = true;
-		valueType = Bonus::ADDITIVE_VALUE;
+		valueType = BonusValueType::ADDITIVE_VALUE;
 		valueTypeRelevant = true;
-		type = Bonus::MOVEMENT;
+		type = BonusType::MOVEMENT;
 	}
 	else if (deprecatedTypeStr == "LAND_MOVEMENT")
 	{
 		subtype = 1;
 		subtypeRelevant = true;
-		valueType = Bonus::ADDITIVE_VALUE;
+		valueType = BonusValueType::ADDITIVE_VALUE;
 		valueTypeRelevant = true;
-		type = Bonus::MOVEMENT;
+		type = BonusType::MOVEMENT;
 	}
 	else if (deprecatedTypeStr == "MAXED_SPELL")
 	{
-		type = Bonus::SPELL;
+		type = BonusType::SPELL;
 		subtypeStr = deprecatedSubtypeStr;
 		subtypeRelevant = true;
-		valueType = Bonus::INDEPENDENT_MAX;
+		valueType = BonusValueType::INDEPENDENT_MAX;
 		valueTypeRelevant = true;
 		val = 3;
 		valRelevant = true;
 	}
 	else if (deprecatedTypeStr == "FULL_HP_REGENERATION")
 	{
-		type = Bonus::HP_REGENERATION;
+		type = BonusType::HP_REGENERATION;
 		val = 100000; //very high value to always chose stack health
 		valRelevant = true;
 	}
 	else if (deprecatedTypeStr == "KING1")
 	{
-		type = Bonus::KING;
+		type = BonusType::KING;
 		val = 0;
 		valRelevant = true;
 	}
 	else if (deprecatedTypeStr == "KING2")
 	{
-		type = Bonus::KING;
+		type = BonusType::KING;
 		val = 2;
 		valRelevant = true;
 	}
 	else if (deprecatedTypeStr == "KING3")
 	{
-		type = Bonus::KING;
+		type = BonusType::KING;
 		val = 3;
 		valRelevant = true;
 	}
 	else if (deprecatedTypeStr == "SIGHT_RADIOUS")
-		type = Bonus::SIGHT_RADIUS;
+		type = BonusType::SIGHT_RADIUS;
 	else if (deprecatedTypeStr == "SELF_MORALE")
 	{
-		type = Bonus::MORALE;
+		type = BonusType::MORALE;
 		val = 1;
 		valRelevant = true;
-		valueType = Bonus::INDEPENDENT_MAX;
+		valueType = BonusValueType::INDEPENDENT_MAX;
 		valueTypeRelevant = true;
 	}
 	else if (deprecatedTypeStr == "SELF_LUCK")
 	{
-		type = Bonus::LUCK;
+		type = BonusType::LUCK;
 		val = 1;
 		valRelevant = true;
-		valueType = Bonus::INDEPENDENT_MAX;
+		valueType = BonusValueType::INDEPENDENT_MAX;
 		valueTypeRelevant = true;
 	}
 	else

+ 3 - 3
lib/bonuses/BonusParams.h

@@ -18,15 +18,15 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 struct DLL_LINKAGE BonusParams {
 	bool isConverted;
-	Bonus::BonusType type = Bonus::NONE;
+	BonusType type = BonusType::NONE;
 	TBonusSubtype subtype = -1;
 	std::string subtypeStr;
 	bool subtypeRelevant = false;
-	Bonus::ValueType valueType = Bonus::BASE_NUMBER;
+	BonusValueType valueType = BonusValueType::BASE_NUMBER;
 	bool valueTypeRelevant = false;
 	si32 val = 0;
 	bool valRelevant = false;
-	Bonus::BonusSource targetType = Bonus::SECONDARY_SKILL;
+	BonusSource targetType = BonusSource::SECONDARY_SKILL;
 	bool targetTypeRelevant = false;
 
 	BonusParams(bool isConverted = true) : isConverted(isConverted) {};

+ 17 - 17
lib/bonuses/BonusSelector.cpp

@@ -15,9 +15,9 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 namespace Selector
 {
-	DLL_LINKAGE CSelectFieldEqual<Bonus::BonusType> & type()
+	DLL_LINKAGE CSelectFieldEqual<BonusType> & type()
 	{
-		static CSelectFieldEqual<Bonus::BonusType> stype(&Bonus::type);
+		static CSelectFieldEqual<BonusType> stype(&Bonus::type);
 		return stype;
 	}
 
@@ -33,53 +33,53 @@ namespace Selector
 		return sinfo;
 	}
 
-	DLL_LINKAGE CSelectFieldEqual<Bonus::BonusSource> & sourceType()
+	DLL_LINKAGE CSelectFieldEqual<BonusSource> & sourceType()
 	{
-		static CSelectFieldEqual<Bonus::BonusSource> ssourceType(&Bonus::source);
+		static CSelectFieldEqual<BonusSource> ssourceType(&Bonus::source);
 		return ssourceType;
 	}
 
-	DLL_LINKAGE CSelectFieldEqual<Bonus::BonusSource> & targetSourceType()
+	DLL_LINKAGE CSelectFieldEqual<BonusSource> & targetSourceType()
 	{
-		static CSelectFieldEqual<Bonus::BonusSource> ssourceType(&Bonus::targetSourceType);
+		static CSelectFieldEqual<BonusSource> ssourceType(&Bonus::targetSourceType);
 		return ssourceType;
 	}
 
-	DLL_LINKAGE CSelectFieldEqual<Bonus::LimitEffect> & effectRange()
+	DLL_LINKAGE CSelectFieldEqual<BonusLimitEffect> & effectRange()
 	{
-		static CSelectFieldEqual<Bonus::LimitEffect> seffectRange(&Bonus::effectRange);
+		static CSelectFieldEqual<BonusLimitEffect> seffectRange(&Bonus::effectRange);
 		return seffectRange;
 	}
 
 	DLL_LINKAGE CWillLastTurns turns;
 	DLL_LINKAGE CWillLastDays days;
 
-	CSelector DLL_LINKAGE typeSubtype(Bonus::BonusType Type, TBonusSubtype Subtype)
+	CSelector DLL_LINKAGE typeSubtype(BonusType Type, TBonusSubtype Subtype)
 	{
 		return type()(Type).And(subtype()(Subtype));
 	}
 
-	CSelector DLL_LINKAGE typeSubtypeInfo(Bonus::BonusType type, TBonusSubtype subtype, const CAddInfo & info)
+	CSelector DLL_LINKAGE typeSubtypeInfo(BonusType type, TBonusSubtype subtype, const CAddInfo & info)
 	{
-		return CSelectFieldEqual<Bonus::BonusType>(&Bonus::type)(type)
+		return CSelectFieldEqual<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)
+	CSelector DLL_LINKAGE source(BonusSource source, ui32 sourceID)
 	{
-		return CSelectFieldEqual<Bonus::BonusSource>(&Bonus::source)(source)
+		return CSelectFieldEqual<BonusSource>(&Bonus::source)(source)
 			.And(CSelectFieldEqual<ui32>(&Bonus::sid)(sourceID));
 	}
 
-	CSelector DLL_LINKAGE sourceTypeSel(Bonus::BonusSource source)
+	CSelector DLL_LINKAGE sourceTypeSel(BonusSource source)
 	{
-		return CSelectFieldEqual<Bonus::BonusSource>(&Bonus::source)(source);
+		return CSelectFieldEqual<BonusSource>(&Bonus::source)(source);
 	}
 
-	CSelector DLL_LINKAGE valueType(Bonus::ValueType valType)
+	CSelector DLL_LINKAGE valueType(BonusValueType valType)
 	{
-		return CSelectFieldEqual<Bonus::ValueType>(&Bonus::valType)(valType);
+		return CSelectFieldEqual<BonusValueType>(&Bonus::valType)(valType);
 	}
 
 	DLL_LINKAGE CSelector all([](const Bonus * b){return true;});

+ 9 - 9
lib/bonuses/BonusSelector.h

@@ -125,20 +125,20 @@ public:
 
 namespace Selector
 {
-	extern DLL_LINKAGE CSelectFieldEqual<Bonus::BonusType> & type();
+	extern DLL_LINKAGE CSelectFieldEqual<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 CSelectFieldEqual<BonusSource> & sourceType();
+	extern DLL_LINKAGE CSelectFieldEqual<BonusSource> & targetSourceType();
+	extern DLL_LINKAGE CSelectFieldEqual<BonusLimitEffect> & 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);
+	CSelector DLL_LINKAGE typeSubtype(BonusType Type, TBonusSubtype Subtype);
+	CSelector DLL_LINKAGE typeSubtypeInfo(BonusType type, TBonusSubtype subtype, const CAddInfo & info);
+	CSelector DLL_LINKAGE source(BonusSource source, ui32 sourceID);
+	CSelector DLL_LINKAGE sourceTypeSel(BonusSource source);
+	CSelector DLL_LINKAGE valueType(BonusValueType valType);
 
 	/**
 	 * Selects all bonuses

+ 2 - 2
lib/bonuses/CBonusProxy.cpp

@@ -152,7 +152,7 @@ int CTotalsProxy::getValueAndList(TConstBonusListPtr & outBonusList) const
 
 int CTotalsProxy::getMeleeValue() const
 {
-	static const auto limit = Selector::effectRange()(Bonus::NO_LIMIT).Or(Selector::effectRange()(Bonus::ONLY_MELEE_FIGHT));
+	static const auto limit = Selector::effectRange()(BonusLimitEffect::NO_LIMIT).Or(Selector::effectRange()(BonusLimitEffect::ONLY_MELEE_FIGHT));
 
 	const auto treeVersion = target->getTreeVersion();
 
@@ -168,7 +168,7 @@ int CTotalsProxy::getMeleeValue() const
 
 int CTotalsProxy::getRangedValue() const
 {
-	static const auto limit = Selector::effectRange()(Bonus::NO_LIMIT).Or(Selector::effectRange()(Bonus::ONLY_DISTANCE_FIGHT));
+	static const auto limit = Selector::effectRange()(BonusLimitEffect::NO_LIMIT).Or(Selector::effectRange()(BonusLimitEffect::ONLY_DISTANCE_FIGHT));
 
 	const auto treeVersion = target->getTreeVersion();
 

+ 3 - 3
lib/bonuses/IBonusBearer.cpp

@@ -15,7 +15,7 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-int IBonusBearer::valOfBonuses(Bonus::BonusType type, int subtype) const
+int IBonusBearer::valOfBonuses(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);
@@ -44,7 +44,7 @@ bool IBonusBearer::hasBonus(const CSelector &selector, const CSelector &limit, c
 	return getBonuses(selector, limit, cachingStr)->size() > 0;
 }
 
-bool IBonusBearer::hasBonusOfType(Bonus::BonusType type, int subtype) const
+bool IBonusBearer::hasBonusOfType(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);
@@ -66,7 +66,7 @@ TConstBonusListPtr IBonusBearer::getBonuses(const CSelector &selector, const CSe
 	return getAllBonuses(selector, limit, nullptr, cachingStr);
 }
 
-bool IBonusBearer::hasBonusFrom(Bonus::BonusSource source, ui32 sourceID) const
+bool IBonusBearer::hasBonusFrom(BonusSource source, ui32 sourceID) const
 {
 	boost::format fmt("source_%did_%d");
 	fmt % static_cast<int>(source) % sourceID;

+ 3 - 3
lib/bonuses/IBonusBearer.h

@@ -33,9 +33,9 @@ public:
 	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;
+	int valOfBonuses(BonusType type, int subtype = -1) const; //subtype -> subtype of bonus, if -1 then anyt;
+	bool hasBonusOfType(BonusType type, int subtype = -1) const;//determines if hero has a bonus of given type (and optionally subtype)
+	bool hasBonusFrom(BonusSource source, ui32 sourceID) const;
 
 	virtual int64_t getTreeVersion() const = 0;
 };

+ 9 - 9
lib/bonuses/Limiters.cpp

@@ -30,9 +30,9 @@ 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)},
+	{"SHOOTER_ONLY", std::make_shared<HasAnotherBonusLimiter>(BonusType::SHOOTER)},
+	{"DRAGON_NATURE", std::make_shared<HasAnotherBonusLimiter>(BonusType::DRAGON_NATURE)},
+	{"IS_UNDEAD", std::make_shared<HasAnotherBonusLimiter>(BonusType::UNDEAD)},
 	{"CREATURE_NATIVE_TERRAIN", std::make_shared<CreatureTerrainLimiter>()},
 	{"CREATURE_FACTION", std::make_shared<AllOfLimiter>(std::initializer_list<TLimiterPtr>{std::make_shared<CreatureLevelLimiter>(), std::make_shared<FactionLimiter>()})},
 	{"SAME_FACTION", std::make_shared<FactionLimiter>()},
@@ -136,22 +136,22 @@ JsonNode CCreatureTypeLimiter::toJsonNode() const
 	return root;
 }
 
-HasAnotherBonusLimiter::HasAnotherBonusLimiter( Bonus::BonusType bonus )
+HasAnotherBonusLimiter::HasAnotherBonusLimiter( BonusType bonus )
 	: type(bonus), subtype(0), isSubtypeRelevant(false), isSourceRelevant(false), isSourceIDRelevant(false)
 {
 }
 
-HasAnotherBonusLimiter::HasAnotherBonusLimiter( Bonus::BonusType bonus, TBonusSubtype _subtype )
+HasAnotherBonusLimiter::HasAnotherBonusLimiter( BonusType bonus, TBonusSubtype _subtype )
 	: type(bonus), subtype(_subtype), isSubtypeRelevant(true), isSourceRelevant(false), isSourceIDRelevant(false)
 {
 }
 
-HasAnotherBonusLimiter::HasAnotherBonusLimiter(Bonus::BonusType bonus, Bonus::BonusSource src)
+HasAnotherBonusLimiter::HasAnotherBonusLimiter(BonusType bonus, BonusSource src)
 	: type(bonus), source(src), isSubtypeRelevant(false), isSourceRelevant(true), isSourceIDRelevant(false)
 {
 }
 
-HasAnotherBonusLimiter::HasAnotherBonusLimiter(Bonus::BonusType bonus, TBonusSubtype _subtype, Bonus::BonusSource src)
+HasAnotherBonusLimiter::HasAnotherBonusLimiter(BonusType bonus, TBonusSubtype _subtype, BonusSource src)
 	: type(bonus), subtype(_subtype), isSubtypeRelevant(true), source(src), isSourceRelevant(true), isSourceIDRelevant(false)
 {
 }
@@ -303,10 +303,10 @@ ILimiter::EDecision FactionLimiter::limit(const BonusLimitationContext &context)
 
 		switch(context.b.source)
 		{
-			case Bonus::CREATURE_ABILITY:
+			case BonusSource::CREATURE_ABILITY:
 				return bearer->getFaction() == CreatureID(context.b.sid).toCreature()->getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
 			
-			case Bonus::TOWN_STRUCTURE:
+			case BonusSource::TOWN_STRUCTURE:
 				return bearer->getFaction() == FactionID(Bonus::getHighFromSid32(context.b.sid)) ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
 
 			//TODO: other sources of bonuses

+ 6 - 6
lib/bonuses/Limiters.h

@@ -111,18 +111,18 @@ public:
 class DLL_LINKAGE HasAnotherBonusLimiter : public ILimiter //applies only to nodes that have another bonus working
 {
 public:
-	Bonus::BonusType type;
+	BonusType type;
 	TBonusSubtype subtype;
-	Bonus::BonusSource source;
+	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);
+	HasAnotherBonusLimiter(BonusType bonus = BonusType::NONE);
+	HasAnotherBonusLimiter(BonusType bonus, TBonusSubtype _subtype);
+	HasAnotherBonusLimiter(BonusType bonus, BonusSource src);
+	HasAnotherBonusLimiter(BonusType bonus, TBonusSubtype _subtype, BonusSource src);
 
 	EDecision limit(const BonusLimitationContext &context) const override;
 	std::string toString() const override;

+ 4 - 4
lib/bonuses/Updaters.cpp

@@ -118,17 +118,17 @@ ArmyMovementUpdater::ArmyMovementUpdater(int base, int divider, int multiplier,
 
 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)
+	if(b->type == BonusType::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->source = BonusSource::ARMY;
 		newBonus->val += vstd::amin(counted, max);
 		return newBonus;
 	}
-	if(b->type != Bonus::MOVEMENT)
+	if(b->type != BonusType::MOVEMENT)
 		logGlobal->error("ArmyMovementUpdater should only be used for MOVEMENT bonus!");
 	return b;
 }
@@ -203,7 +203,7 @@ std::shared_ptr<Bonus> OwnerUpdater::createUpdatedBonus(const std::shared_ptr<Bo
 		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);
+		std::make_shared<Bonus>(b->duration, b->type, b->source, b->val, b->sid, b->subtype, b->valType);
 	updated->limiter = std::make_shared<OppositeSideLimiter>(owner);
 	return updated;
 }

+ 6 - 6
lib/mapObjects/CArmedInstance.cpp

@@ -37,7 +37,7 @@ void CArmedInstance::randomizeArmy(int type)
 }
 
 // Take Angelic Alliance troop-mixing freedom of non-evil units into account.
-CSelector CArmedInstance::nonEvilAlignmentMixSelector = Selector::type()(Bonus::NONEVIL_ALIGNMENT_MIX);
+CSelector CArmedInstance::nonEvilAlignmentMixSelector = Selector::type()(BonusType::NONEVIL_ALIGNMENT_MIX);
 
 CArmedInstance::CArmedInstance()
 	:CArmedInstance(false)
@@ -56,10 +56,10 @@ void CArmedInstance::updateMoraleBonusFromArmy()
 	if(!validTypes(false)) //object not randomized, don't bother
 		return;
 
-	auto b = getExportedBonusList().getFirst(Selector::sourceType()(Bonus::ARMY).And(Selector::type()(Bonus::MORALE)));
+	auto b = getExportedBonusList().getFirst(Selector::sourceType()(BonusSource::ARMY).And(Selector::type()(BonusType::MORALE)));
  	if(!b)
 	{
-		b = std::make_shared<Bonus>(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, 0, -1);
+		b = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::MORALE, BonusSource::ARMY, 0, -1);
 		addNewBonus(b);
 	}
 
@@ -68,7 +68,7 @@ void CArmedInstance::updateMoraleBonusFromArmy()
 	bool hasUndead = false;
 
 	const std::string undeadCacheKey = "type_UNDEAD";
-	static const CSelector undeadSelector = Selector::type()(Bonus::UNDEAD);
+	static const CSelector undeadSelector = Selector::type()(BonusType::UNDEAD);
 
 	for(const auto & slot : Slots())
 	{
@@ -121,12 +121,12 @@ void CArmedInstance::updateMoraleBonusFromArmy()
 
 	//-1 modifier for any Undead unit in army
 	const ui8 UNDEAD_MODIFIER_ID = -2;
-	auto undeadModifier = getExportedBonusList().getFirst(Selector::source(Bonus::ARMY, UNDEAD_MODIFIER_ID));
+	auto undeadModifier = getExportedBonusList().getFirst(Selector::source(BonusSource::ARMY, UNDEAD_MODIFIER_ID));
  	if(hasUndead)
 	{
 		if(!undeadModifier)
 		{
-			undeadModifier = std::make_shared<Bonus>(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, -1, UNDEAD_MODIFIER_ID, VLC->generaltexth->arraytxt[116]);
+			undeadModifier = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::MORALE, BonusSource::ARMY, -1, UNDEAD_MODIFIER_ID, VLC->generaltexth->arraytxt[116]);
 			undeadModifier->description = undeadModifier->description.substr(0, undeadModifier->description.size()-2);//trim value
 			addNewBonus(undeadModifier);
 		}

+ 4 - 4
lib/mapObjects/CBank.cpp

@@ -169,10 +169,10 @@ void CBank::doVisit(const CGHeroInstance * hero) const
 		{
 			GiveBonus gbonus;
 			gbonus.id = hero->id.getNum();
-			gbonus.bonus.duration = Bonus::ONE_BATTLE;
-			gbonus.bonus.source = Bonus::OBJECT;
+			gbonus.bonus.duration = BonusDuration::ONE_BATTLE;
+			gbonus.bonus.source = BonusSource::OBJECT;
 			gbonus.bonus.sid = ID;
-			gbonus.bonus.type = Bonus::MORALE;
+			gbonus.bonus.type = BonusType::MORALE;
 			gbonus.bonus.val = -1;
 			switch (ID)
 			{
@@ -197,7 +197,7 @@ void CBank::doVisit(const CGHeroInstance * hero) const
 		case Obj::PYRAMID:
 		{
 			GiveBonus gb;
-			gb.bonus = Bonus(Bonus::ONE_BATTLE, Bonus::LUCK, Bonus::OBJECT, -2, id.getNum(), VLC->generaltexth->arraytxt[70]);
+			gb.bonus = Bonus(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, -2, id.getNum(), VLC->generaltexth->arraytxt[70]);
 			gb.id = hero->id.getNum();
 			cb->giveHeroBonus(&gb);
 			textID = 107;

+ 1 - 1
lib/mapObjects/CGDwelling.cpp

@@ -253,7 +253,7 @@ void CGDwelling::newTurn(CRandomGenerator & rand) const
 				creaturesAccumulate = VLC->settings()->getBoolean(EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL);
 
 			CCreature *cre = VLC->creh->objects[creatures[i].second[0]];
-			TQuantity amount = cre->getGrowth() * (1 + cre->valOfBonuses(Bonus::CREATURE_GROWTH_PERCENT)/100) + cre->valOfBonuses(Bonus::CREATURE_GROWTH);
+			TQuantity amount = cre->getGrowth() * (1 + cre->valOfBonuses(BonusType::CREATURE_GROWTH_PERCENT)/100) + cre->valOfBonuses(BonusType::CREATURE_GROWTH);
 			if (creaturesAccumulate && ID != Obj::REFUGEE_CAMP) //camp should not try to accumulate different kinds of creatures
 				sac.creatures[i].first += amount;
 			else

+ 41 - 41
lib/mapObjects/CGHeroInstance.cpp

@@ -39,8 +39,8 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 static int lowestSpeed(const CGHeroInstance * chi)
 {
-	static const CSelector selectorSTACKS_SPEED = Selector::type()(Bonus::STACKS_SPEED);
-	static const std::string keySTACKS_SPEED = "type_" + std::to_string(static_cast<si32>(Bonus::STACKS_SPEED));
+	static const CSelector selectorSTACKS_SPEED = Selector::type()(BonusType::STACKS_SPEED);
+	static const std::string keySTACKS_SPEED = "type_" + std::to_string(static_cast<si32>(BonusType::STACKS_SPEED));
 
 	if(!chi->stacksCount())
 	{
@@ -73,11 +73,11 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile & dest, const TerrainTile & f
 	}
 	else if(ti->nativeTerrain != from.terType->getId() &&//the terrain is not native
 			ti->nativeTerrain != ETerrainId::ANY_TERRAIN && //no special creature bonus
-			!ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType->getIndex())) //no special movement bonus
+			!ti->hasBonusOfType(BonusType::NO_TERRAIN_PENALTY, from.terType->getIndex())) //no special movement bonus
 	{
 
 		ret = VLC->terrainTypeHandler->getById(dest.terType->getId())->moveCost;
-		ret -= ti->valOfBonuses(Bonus::ROUGH_TERRAIN_DISCOUNT);
+		ret -= ti->valOfBonuses(BonusType::ROUGH_TERRAIN_DISCOUNT);
 		if(ret < GameConstants::BASE_MOVEMENT_COST)
 			ret = GameConstants::BASE_MOVEMENT_COST;
 	}
@@ -212,14 +212,14 @@ void CGHeroInstance::updateArmyMovementBonus(bool onLand, const TurnInfo * ti) c
 		lowestCreatureSpeed = realLowestSpeed;
 		//Let updaters run again
 		treeHasChanged();
-		ti->updateHeroBonuses(Bonus::MOVEMENT, Selector::subtype()(!!onLand));
+		ti->updateHeroBonuses(BonusType::MOVEMENT, Selector::subtype()(!!onLand));
 	}
 }
 
 int CGHeroInstance::maxMovePointsCached(bool onLand, const TurnInfo * ti) const
 {
 	updateArmyMovementBonus(onLand, ti);
-	return ti->valOfBonuses(Bonus::MOVEMENT, !!onLand);
+	return ti->valOfBonuses(BonusType::MOVEMENT, !!onLand);
 }
 
 CGHeroInstance::CGHeroInstance():
@@ -286,7 +286,7 @@ void CGHeroInstance::initHero(CRandomGenerator & rand)
 
 	if(portrait < 0 || portrait == 255)
 		portrait = type->imageIndex;
-	if(!hasBonus(Selector::sourceType()(Bonus::HERO_BASE_SKILL)))
+	if(!hasBonus(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)))
 	{
 		for(int g=0; g<GameConstants::PRIMARY_SKILLS; ++g)
 		{
@@ -327,9 +327,9 @@ void CGHeroInstance::initHero(CRandomGenerator & rand)
 	for(const auto & b : baseBonuses.Struct())
 	{
 		auto bonus = JsonUtils::parseBonus(b.second);
-		bonus->source = Bonus::HERO_BASE_SKILL;
+		bonus->source = BonusSource::HERO_BASE_SKILL;
 		bonus->sid = id.getNum();
-		bonus->duration = Bonus::PERMANENT;
+		bonus->duration = BonusDuration::PERMANENT;
 		addNewBonus(bonus);
 	}
 
@@ -529,7 +529,7 @@ void CGHeroInstance::initObj(CRandomGenerator & rand)
 
 void CGHeroInstance::recreateSecondarySkillsBonuses()
 {
-	auto secondarySkillsBonuses = getBonuses(Selector::sourceType()(Bonus::SECONDARY_SKILL));
+	auto secondarySkillsBonuses = getBonuses(Selector::sourceType()(BonusSource::SECONDARY_SKILL));
 	for(const auto & bonus : *secondarySkillsBonuses)
 		removeBonus(bonus);
 
@@ -540,7 +540,7 @@ void CGHeroInstance::recreateSecondarySkillsBonuses()
 
 void CGHeroInstance::updateSkillBonus(const SecondarySkill & which, int val)
 {
-	removeBonuses(Selector::source(Bonus::SECONDARY_SKILL, which));
+	removeBonuses(Selector::source(BonusSource::SECONDARY_SKILL, which));
 	auto skillBonus = (*VLC->skillh)[which]->at(val).effects;
 	for(const auto & b : skillBonus)
 		addNewBonus(std::make_shared<Bonus>(*b));
@@ -575,7 +575,7 @@ ui64 CGHeroInstance::getTotalStrength() const
 
 TExpType CGHeroInstance::calculateXp(TExpType exp) const
 {
-	return static_cast<TExpType>(exp * (valOfBonuses(Bonus::HERO_EXPERIENCE_GAIN_PERCENT)) / 100.0);
+	return static_cast<TExpType>(exp * (valOfBonuses(BonusType::HERO_EXPERIENCE_GAIN_PERCENT)) / 100.0);
 }
 
 int32_t CGHeroInstance::getCasterUnitId() const
@@ -589,7 +589,7 @@ int32_t CGHeroInstance::getSpellSchoolLevel(const spells::Spell * spell, int32_t
 
 	spell->forEachSchool([&, this](const spells::SchoolInfo & cnf, bool & stop)
 	{
-		int32_t thisSchool = valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 1 << (static_cast<ui8>(cnf.id))); //FIXME: Bonus shouldn't be additive (Witchking Artifacts : Crown of Skies)
+		int32_t thisSchool = valOfBonuses(BonusType::MAGIC_SCHOOL_SKILL, 1 << (static_cast<ui8>(cnf.id))); //FIXME: Bonus shouldn't be additive (Witchking Artifacts : Crown of Skies)
 		if(thisSchool > skill)
 		{
 			skill = thisSchool;
@@ -598,8 +598,8 @@ int32_t CGHeroInstance::getSpellSchoolLevel(const spells::Spell * spell, int32_t
 		}
 	});
 
-	vstd::amax(skill, valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 0)); //any school bonus
-	vstd::amax(skill, valOfBonuses(Bonus::SPELL, spell->getIndex())); //given by artifact or other effect
+	vstd::amax(skill, valOfBonuses(BonusType::MAGIC_SCHOOL_SKILL, 0)); //any school bonus
+	vstd::amax(skill, valOfBonuses(BonusType::SPELL, spell->getIndex())); //given by artifact or other effect
 
 	vstd::amax(skill, 0); //in case we don't know any school
 	vstd::amin(skill, 3);
@@ -611,9 +611,9 @@ int64_t CGHeroInstance::getSpellBonus(const spells::Spell * spell, int64_t base,
 	//applying sorcery secondary skill
 
 	if(spell->isMagical())
-		base = static_cast<int64_t>(base * (valOfBonuses(Bonus::SPELL_DAMAGE)) / 100.0);
+		base = static_cast<int64_t>(base * (valOfBonuses(BonusType::SPELL_DAMAGE)) / 100.0);
 
-	base = static_cast<int64_t>(base * (100 + valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, spell->getIndex())) / 100.0);
+	base = static_cast<int64_t>(base * (100 + valOfBonuses(BonusType::SPECIFIC_SPELL_DAMAGE, spell->getIndex())) / 100.0);
 
 	int maxSchoolBonus = 0;
 
@@ -625,14 +625,14 @@ int64_t CGHeroInstance::getSpellBonus(const spells::Spell * spell, int64_t base,
 	base = static_cast<int64_t>(base * (100 + maxSchoolBonus) / 100.0);
 
 	if(affectedStack && affectedStack->creatureLevel() > 0) //Hero specials like Solmyr, Deemer
-		base = static_cast<int64_t>(base * static_cast<double>(100 + valOfBonuses(Bonus::SPECIAL_SPELL_LEV, spell->getIndex()) / affectedStack->creatureLevel()) / 100.0);
+		base = static_cast<int64_t>(base * static_cast<double>(100 + valOfBonuses(BonusType::SPECIAL_SPELL_LEV, spell->getIndex()) / affectedStack->creatureLevel()) / 100.0);
 
 	return base;
 }
 
 int64_t CGHeroInstance::getSpecificSpellBonus(const spells::Spell * spell, int64_t base) const
 {
-	base = static_cast<int64_t>(base * (100 + valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, spell->getIndex())) / 100.0);
+	base = static_cast<int64_t>(base * (100 + valOfBonuses(BonusType::SPECIFIC_SPELL_DAMAGE, spell->getIndex())) / 100.0);
 	return base;
 }
 
@@ -648,7 +648,7 @@ int32_t CGHeroInstance::getEffectPower(const spells::Spell * spell) const
 
 int32_t CGHeroInstance::getEnchantPower(const spells::Spell * spell) const
 {
-	return getPrimSkillLevel(PrimarySkill::SPELL_POWER) + valOfBonuses(Bonus::SPELL_DURATION);
+	return getPrimSkillLevel(PrimarySkill::SPELL_POWER) + valOfBonuses(BonusType::SPELL_DURATION);
 }
 
 int64_t CGHeroInstance::getEffectValue(const spells::Spell * spell) const
@@ -702,7 +702,7 @@ bool CGHeroInstance::canCastThisSpell(const spells::Spell * spell) const
 	const bool isAllowed = IObjectInterface::cb->isAllowed(0, spell->getIndex());
 
 	const bool inSpellBook = vstd::contains(spells, spell->getId()) && hasSpellbook();
-	const bool specificBonus = hasBonusOfType(Bonus::SPELL, spell->getIndex());
+	const bool specificBonus = hasBonusOfType(BonusType::SPELL, spell->getIndex());
 
 	bool schoolBonus = false;
 
@@ -714,7 +714,7 @@ bool CGHeroInstance::canCastThisSpell(const spells::Spell * spell) const
 		}
 	});
 
-	const bool levelBonus = hasBonusOfType(Bonus::SPELLS_OF_LEVEL, spell->getLevel());
+	const bool levelBonus = hasBonusOfType(BonusType::SPELLS_OF_LEVEL, spell->getLevel());
 
 	if(spell->isSpecial())
 	{
@@ -781,19 +781,19 @@ bool CGHeroInstance::canLearnSpell(const spells::Spell * spell) const
  */
 CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &battleResult) const
 {
-	bool hasImprovedNecromancy = hasBonusOfType(Bonus::IMPROVED_NECROMANCY);
+	bool hasImprovedNecromancy = hasBonusOfType(BonusType::IMPROVED_NECROMANCY);
 
 	// need skill or cloak of undead king - lesser artifacts don't work without skill
 	if (hasImprovedNecromancy)
 	{
-		double necromancySkill = valOfBonuses(Bonus::UNDEAD_RAISE_PERCENTAGE) / 100.0;
-		const ui8 necromancyLevel = valOfBonuses(Bonus::IMPROVED_NECROMANCY);
+		double necromancySkill = valOfBonuses(BonusType::UNDEAD_RAISE_PERCENTAGE) / 100.0;
+		const ui8 necromancyLevel = valOfBonuses(BonusType::IMPROVED_NECROMANCY);
 		vstd::amin(necromancySkill, 1.0); //it's impossible to raise more creatures than all...
 		const std::map<ui32,si32> &casualties = battleResult.casualties[!battleResult.winner];
 		// figure out what to raise - pick strongest creature meeting requirements
 		auto creatureTypeRaised = CreatureID::NONE; //now we always have IMPROVED_NECROMANCY, no need for hardcode
 		int requiredCasualtyLevel = 1;
-		TConstBonusListPtr improvedNecromancy = getBonuses(Selector::type()(Bonus::IMPROVED_NECROMANCY));
+		TConstBonusListPtr improvedNecromancy = getBonuses(Selector::type()(BonusType::IMPROVED_NECROMANCY));
 		if(!improvedNecromancy->empty())
 		{
 			auto getCreatureID = [](const std::shared_ptr<Bonus> & bonus) -> CreatureID
@@ -899,15 +899,15 @@ int3 CGHeroInstance::getSightCenter() const
 
 int CGHeroInstance::getSightRadius() const
 {
-	return valOfBonuses(Bonus::SIGHT_RADIUS); // scouting gives SIGHT_RADIUS bonus
+	return valOfBonuses(BonusType::SIGHT_RADIUS); // scouting gives SIGHT_RADIUS bonus
 }
 
 si32 CGHeroInstance::manaRegain() const
 {
-	if (hasBonusOfType(Bonus::FULL_MANA_REGENERATION))
+	if (hasBonusOfType(BonusType::FULL_MANA_REGENERATION))
 		return manaLimit();
 
-	return valOfBonuses(Bonus::MANA_REGENERATION);
+	return valOfBonuses(BonusType::MANA_REGENERATION);
 }
 
 si32 CGHeroInstance::getManaNewTurn() const
@@ -963,9 +963,9 @@ int32_t CGHeroInstance::getSpellCost(const spells::Spell * sp) const
 
 void CGHeroInstance::pushPrimSkill( PrimarySkill::PrimarySkill which, int val )
 {
-	assert(!hasBonus(Selector::typeSubtype(Bonus::PRIMARY_SKILL, which)
-						.And(Selector::sourceType()(Bonus::HERO_BASE_SKILL))));
-	addNewBonus(std::make_shared<Bonus>(Bonus::PERMANENT, Bonus::PRIMARY_SKILL, Bonus::HERO_BASE_SKILL, val, id.getNum(), which));
+	assert(!hasBonus(Selector::typeSubtype(BonusType::PRIMARY_SKILL, which)
+						.And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL))));
+	addNewBonus(std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::PRIMARY_SKILL, BonusSource::HERO_BASE_SKILL, val, id.getNum(), which));
 }
 
 EAlignment CGHeroInstance::getAlignment() const
@@ -986,7 +986,7 @@ std::string CGHeroInstance::nodeName() const
 si32 CGHeroInstance::manaLimit() const
 {
 	return si32(getPrimSkillLevel(PrimarySkill::KNOWLEDGE)
-		* (valOfBonuses(Bonus::MANA_PER_KNOWLEDGE)));
+		* (valOfBonuses(BonusType::MANA_PER_KNOWLEDGE)));
 }
 
 std::string CGHeroInstance::getNameTranslated() const
@@ -1070,7 +1070,7 @@ const std::set<SpellID> & CGHeroInstance::getSpellsInSpellbook() const
 
 int CGHeroInstance::maxSpellLevel() const
 {
-	return std::min(GameConstants::SPELL_LEVELS, valOfBonuses(Selector::type()(Bonus::MAX_LEARNABLE_SPELL_LEVEL)));
+	return std::min(GameConstants::SPELL_LEVELS, valOfBonuses(Selector::type()(BonusType::MAX_LEARNABLE_SPELL_LEVEL)));
 }
 
 void CGHeroInstance::deserializationFix()
@@ -1118,7 +1118,7 @@ int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool
 		ti = turnInfoLocal.get();
 	}
 
-	if(!ti->hasBonusOfType(Bonus::FREE_SHIP_BOARDING))
+	if(!ti->hasBonusOfType(BonusType::FREE_SHIP_BOARDING))
 		return 0; // take all MPs by default
 	
 	auto boatLayer = boat ? boat->layer : EPathfindingLayer::SAIL;
@@ -1313,9 +1313,9 @@ void CGHeroInstance::setPrimarySkill(PrimarySkill::PrimarySkill primarySkill, si
 {
 	if(primarySkill < PrimarySkill::EXPERIENCE)
 	{
-		auto skill = getBonusLocalFirst(Selector::type()(Bonus::PRIMARY_SKILL)
+		auto skill = getBonusLocalFirst(Selector::type()(BonusType::PRIMARY_SKILL)
 			.And(Selector::subtype()(primarySkill))
-			.And(Selector::sourceType()(Bonus::HERO_BASE_SKILL)));
+			.And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)));
 		assert(skill);
 
 		if(abs)
@@ -1389,9 +1389,9 @@ bool CGHeroInstance::hasVisions(const CGObjectInstance * target, const int subty
 {
 	//VISIONS spell support
 
-	const std::string cached = boost::to_string((boost::format("type_%d__subtype_%d") % Bonus::VISIONS % subtype));
+	const auto cached = "type_" + std::to_string(vstd::to_underlying(BonusType::VISIONS)) + "__subtype_" + std::to_string(subtype);
 
-	const int visionsMultiplier = valOfBonuses(Selector::typeSubtype(Bonus::VISIONS,subtype), cached);
+	const int visionsMultiplier = valOfBonuses(Selector::typeSubtype(BonusType::VISIONS,subtype), cached);
 
 	int visionsRange =  visionsMultiplier * getPrimSkillLevel(PrimarySkill::SPELL_POWER);
 
@@ -1505,7 +1505,7 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler)
 	//primary skills
 	if(handler.saving)
 	{
-		const bool haveSkills = hasBonus(Selector::type()(Bonus::PRIMARY_SKILL).And(Selector::sourceType()(Bonus::HERO_BASE_SKILL)));
+		const bool haveSkills = hasBonus(Selector::type()(BonusType::PRIMARY_SKILL).And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)));
 
 		if(haveSkills)
 		{
@@ -1513,7 +1513,7 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler)
 
 			for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
 			{
-				int value = valOfBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, i).And(Selector::sourceType()(Bonus::HERO_BASE_SKILL)));
+				int value = valOfBonuses(Selector::typeSubtype(BonusType::PRIMARY_SKILL, i).And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)));
 
 				handler.serializeInt(PrimarySkill::names[i], value, 0);
 			}

+ 2 - 2
lib/mapObjects/CGPandoraBox.cpp

@@ -183,7 +183,7 @@ void CGPandoraBox::giveContentsAfterExp(const CGHeroInstance *h) const
 		iw.components.emplace_back(Component::EComponentType::MORALE, 0, moraleDiff, 0);
 		cb->showInfoDialog(&iw);
 		GiveBonus gb;
-		gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::MORALE,Bonus::OBJECT,moraleDiff,id.getNum(),"");
+		gb.bonus = Bonus(BonusDuration::ONE_BATTLE,BonusType::MORALE,BonusSource::OBJECT,moraleDiff,id.getNum(),"");
 		gb.id = h->id.getNum();
 		cb->giveHeroBonus(&gb);
 	}
@@ -194,7 +194,7 @@ void CGPandoraBox::giveContentsAfterExp(const CGHeroInstance *h) const
 		iw.components.emplace_back(Component::EComponentType::LUCK, 0, luckDiff, 0);
 		cb->showInfoDialog(&iw);
 		GiveBonus gb;
-		gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::LUCK,Bonus::OBJECT,luckDiff,id.getNum(),"");
+		gb.bonus = Bonus(BonusDuration::ONE_BATTLE,BonusType::LUCK,BonusSource::OBJECT,luckDiff,id.getNum(),"");
 		gb.id = h->id.getNum();
 		cb->giveHeroBonus(&gb);
 	}

+ 10 - 10
lib/mapObjects/CGTownBuilding.cpp

@@ -84,7 +84,7 @@ std::string CGTownBuilding::getVisitingBonusGreeting() const
 
 std::string CGTownBuilding::getCustomBonusGreeting(const Bonus & bonus) const
 {
-	if(bonus.type == Bonus::TOWN_MAGIC_WELL)
+	if(bonus.type == BonusType::TOWN_MAGIC_WELL)
 	{
 		auto bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingInTownMagicWell"));
 		auto buildingName = town->getTown()->getSpecialBuilding(bType)->getNameTranslated();
@@ -95,12 +95,12 @@ std::string CGTownBuilding::getCustomBonusGreeting(const Bonus & bonus) const
 	std::string param;
 	std::string until;
 
-	if(bonus.type == Bonus::MORALE)
+	if(bonus.type == BonusType::MORALE)
 		param = VLC->generaltexth->allTexts[384];
-	else if(bonus.type == Bonus::LUCK)
+	else if(bonus.type == BonusType::LUCK)
 		param = VLC->generaltexth->allTexts[385];
 
-	until = bonus.duration == static_cast<ui16>(Bonus::ONE_BATTLE)
+	until = bonus.duration == BonusDuration::ONE_BATTLE
 			? VLC->generaltexth->translate("vcmi.townHall.greetingCustomUntil")
 			: ".";
 
@@ -142,10 +142,10 @@ void COPWBonus::onHeroVisit (const CGHeroInstance * h) const
 		switch (this->bType)
 		{
 		case BuildingSubID::STABLES:
-			if(!h->hasBonusFrom(Bonus::OBJECT, Obj::STABLES)) //does not stack with advMap Stables
+			if(!h->hasBonusFrom(BonusSource::OBJECT, Obj::STABLES)) //does not stack with advMap Stables
 			{
 				GiveBonus gb;
-				gb.bonus = Bonus(Bonus::ONE_WEEK, Bonus::MOVEMENT, Bonus::OBJECT, 600, 94, VLC->generaltexth->arraytxt[100], 1);
+				gb.bonus = Bonus(BonusDuration::ONE_WEEK, BonusType::MOVEMENT, BonusSource::OBJECT, 600, 94, VLC->generaltexth->arraytxt[100], 1);
 				gb.id = heroID.getNum();
 				cb->giveHeroBonus(&gb);
 
@@ -234,7 +234,7 @@ void CTownBonus::onHeroVisit (const CGHeroInstance * h) const
 
 		case BuildingSubID::CUSTOM_VISITING_BONUS:
 			const auto building = town->getTown()->buildings.at(bID);
-			if(!h->hasBonusFrom(Bonus::TOWN_STRUCTURE, Bonus::getSid32(building->town->faction->getIndex(), building->bid)))
+			if(!h->hasBonusFrom(BonusSource::TOWN_STRUCTURE, Bonus::getSid32(building->town->faction->getIndex(), building->bid)))
 			{
 				const auto & bonuses = building->onVisitBonuses;
 				applyBonuses(const_cast<CGHeroInstance *>(h), bonuses);
@@ -262,18 +262,18 @@ void CTownBonus::applyBonuses(CGHeroInstance * h, const BonusList & bonuses) con
 		GiveBonus gb;
 		InfoWindow iw;
 
-		if(bonus->type == Bonus::TOWN_MAGIC_WELL)
+		if(bonus->type == BonusType::TOWN_MAGIC_WELL)
 		{
 			if(h->mana >= h->manaLimit())
 				return;
 			cb->setManaPoints(h->id, h->manaLimit());
-			bonus->duration = Bonus::ONE_DAY;
+			bonus->duration = BonusDuration::ONE_DAY;
 		}
 		gb.bonus = * bonus;
 		gb.id = h->id.getNum();
 		cb->giveHeroBonus(&gb);
 
-		if(bonus->duration == Bonus::PERMANENT)
+		if(bonus->duration == BonusDuration::PERMANENT)
 			addToVisitors = true;
 
 		iw.player = cb->getOwner(h->id);

+ 5 - 5
lib/mapObjects/CGTownInstance.cpp

@@ -147,7 +147,7 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const
 			ret.entries.emplace_back(subID, BuildingID::HORDE_2, creature->getHorde());
 
 	//statue-of-legion-like bonus: % to base+castle
-	TConstBonusListPtr bonuses2 = getBonuses(Selector::type()(Bonus::CREATURE_GROWTH_PERCENT));
+	TConstBonusListPtr bonuses2 = getBonuses(Selector::type()(BonusType::CREATURE_GROWTH_PERCENT));
 	for(const auto & b : *bonuses2)
 	{
 		const auto growth = b->val * (base + castleBonus) / 100;
@@ -155,7 +155,7 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const
 	}
 
 	//other *-of-legion-like bonuses (%d to growth cumulative with grail)
-	TConstBonusListPtr bonuses = getBonuses(Selector::type()(Bonus::CREATURE_GROWTH).And(Selector::subtype()(level)));
+	TConstBonusListPtr bonuses = getBonuses(Selector::type()(BonusType::CREATURE_GROWTH).And(Selector::subtype()(level)));
 	for(const auto & b : *bonuses)
 		ret.entries.emplace_back(b->val, b->Description());
 
@@ -764,10 +764,10 @@ void CGTownInstance::deserializationFix()
 
 void CGTownInstance::updateMoraleBonusFromArmy()
 {
-	auto b = getExportedBonusList().getFirst(Selector::sourceType()(Bonus::ARMY).And(Selector::type()(Bonus::MORALE)));
+	auto b = getExportedBonusList().getFirst(Selector::sourceType()(BonusSource::ARMY).And(Selector::type()(BonusType::MORALE)));
 	if(!b)
 	{
-		b = std::make_shared<Bonus>(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, 0, -1);
+		b = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::MORALE, BonusSource::ARMY, 0, -1);
 		addNewBonus(b);
 	}
 
@@ -783,7 +783,7 @@ void CGTownInstance::updateMoraleBonusFromArmy()
 void CGTownInstance::recreateBuildingsBonuses()
 {
 	BonusList bl;
-	getExportedBonusList().getBonuses(bl, Selector::sourceType()(Bonus::TOWN_STRUCTURE));
+	getExportedBonusList().getBonuses(bl, Selector::sourceType()(BonusSource::TOWN_STRUCTURE));
 
 	for(const auto & b : bl)
 		removeBonus(b);

+ 4 - 4
lib/mapObjects/CObjectHandler.cpp

@@ -272,13 +272,13 @@ int3 CGObjectInstance::getVisitableOffset() const
 	return appearance->getVisitableOffset();
 }
 
-void CGObjectInstance::giveDummyBonus(const ObjectInstanceID & heroID, ui8 duration) const
+void CGObjectInstance::giveDummyBonus(const ObjectInstanceID & heroID, BonusDuration duration) const
 {
 	GiveBonus gbonus;
-	gbonus.bonus.type = Bonus::NONE;
+	gbonus.bonus.type = BonusType::NONE;
 	gbonus.id = heroID.getNum();
-	gbonus.bonus.duration = static_cast<Bonus::BonusDuration>(duration);
-	gbonus.bonus.source = Bonus::OBJECT;
+	gbonus.bonus.duration = duration;
+	gbonus.bonus.source = BonusSource::OBJECT;
 	gbonus.bonus.sid = ID;
 	cb->giveHeroBonus(&gbonus);
 }

+ 1 - 1
lib/mapObjects/CObjectHandler.h

@@ -232,7 +232,7 @@ protected:
 	virtual void setPropertyDer(ui8 what, ui32 val);
 
 	/// Gives dummy bonus from this object to hero. Can be used to track visited state
-	void giveDummyBonus(const ObjectInstanceID & heroID, ui8 duration = Bonus::ONE_DAY) const;
+	void giveDummyBonus(const ObjectInstanceID & heroID, BonusDuration duration = BonusDuration::ONE_DAY) const;
 
 	///Serialize object-type specific options
 	virtual void serializeJsonOptions(JsonSerializeFormat & handler);

+ 2 - 2
lib/mapObjects/CQuest.cpp

@@ -850,8 +850,8 @@ void CGSeerHut::completeQuest (const CGHeroInstance * h) const //reward
 		}
 		case MORALE_BONUS: case LUCK_BONUS:
 		{
-			Bonus hb(Bonus::ONE_WEEK, (rewardType == 3 ? Bonus::MORALE : Bonus::LUCK),
-				Bonus::OBJECT, rVal, h->id.getNum(), "", -1);
+			Bonus hb(BonusDuration::ONE_WEEK, (rewardType == 3 ? BonusType::MORALE : BonusType::LUCK),
+				BonusSource::OBJECT, rVal, h->id.getNum(), "", -1);
 			GiveBonus gb;
 			gb.id = h->id.getNum();
 			gb.bonus = hb;

+ 3 - 3
lib/mapObjects/CRewardableConstructor.cpp

@@ -48,12 +48,12 @@ void CRewardableConstructor::configureObject(CGObjectInstance * object, CRandomG
 		{
 			for (auto & bonus : rewardInfo.reward.bonuses)
 			{
-				bonus.source = Bonus::OBJECT;
+				bonus.source = BonusSource::OBJECT;
 				bonus.sid = rewardableObject->ID;
 				//TODO: bonus.description = object->getObjectName();
-				if (bonus.type == Bonus::MORALE)
+				if (bonus.type == BonusType::MORALE)
 					rewardInfo.reward.extraComponents.emplace_back(Component::EComponentType::MORALE, 0, bonus.val, 0);
-				if (bonus.type == Bonus::LUCK)
+				if (bonus.type == BonusType::LUCK)
 					rewardInfo.reward.extraComponents.emplace_back(Component::EComponentType::LUCK, 0, bonus.val, 0);
 			}
 		}

+ 2 - 2
lib/mapObjects/CRewardableObject.cpp

@@ -182,7 +182,7 @@ bool CRewardableObject::wasVisitedBefore(const CGHeroInstance * contextHero) con
 		case Rewardable::VISIT_PLAYER:
 			return vstd::contains(cb->getPlayerState(contextHero->getOwner())->visitedObjects, ObjectInstanceID(id));
 		case Rewardable::VISIT_BONUS:
-			return contextHero->hasBonusFrom(Bonus::OBJECT, ID);
+			return contextHero->hasBonusFrom(BonusSource::OBJECT, ID);
 		case Rewardable::VISIT_HERO:
 			return contextHero->visitedObjects.count(ObjectInstanceID(id));
 		default:
@@ -211,7 +211,7 @@ bool CRewardableObject::wasVisited(const CGHeroInstance * h) const
 	switch (configuration.visitMode)
 	{
 		case Rewardable::VISIT_BONUS:
-			return h->hasBonusFrom(Bonus::OBJECT, ID);
+			return h->hasBonusFrom(BonusSource::OBJECT, ID);
 		case Rewardable::VISIT_HERO:
 			return h->visitedObjects.count(ObjectInstanceID(id));
 		default:

+ 2 - 2
lib/mapObjects/CommonConstructors.cpp

@@ -435,12 +435,12 @@ static void addStackToArmy(IObjectInfo::CArmyStructure & army, const CCreature *
 	army.totalStrength += crea->getFightValue() * amount;
 
 	bool walker = true;
-	if(crea->hasBonusOfType(Bonus::SHOOTER))
+	if(crea->hasBonusOfType(BonusType::SHOOTER))
 	{
 		army.shootersStrength += crea->getFightValue() * amount;
 		walker = false;
 	}
-	if(crea->hasBonusOfType(Bonus::FLYING))
+	if(crea->hasBonusOfType(BonusType::FLYING))
 	{
 		army.flyersStrength += crea->getFightValue() * amount;
 		walker = false;

+ 10 - 10
lib/mapObjects/MiscObjects.cpp

@@ -312,7 +312,7 @@ int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const
 	if(count*2 > totalCount)
 		sympathy++; // 2 - hero have similar creatures more that 50%
 
-	int diplomacy = h->valOfBonuses(Bonus::WANDERING_CREATURES_JOIN_BONUS);
+	int diplomacy = h->valOfBonuses(BonusType::WANDERING_CREATURES_JOIN_BONUS);
 	int charisma = powerFactor + diplomacy + sympathy;
 
 	if(charisma < character)
@@ -1232,7 +1232,7 @@ void CGWhirlpool::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer
 
 bool CGWhirlpool::isProtected(const CGHeroInstance * h)
 {
-	return h->hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION)
+	return h->hasBonusOfType(BonusType::WHIRLPOOL_PROTECTION)
 	|| (h->stacksCount() == 1 && h->Slots().begin()->second->count == 1);
 }
 
@@ -1384,7 +1384,7 @@ void CGArtifact::serializeJsonOptions(JsonSerializeFormat& handler)
 
 	if(handler.saving && ID == Obj::SPELL_SCROLL)
 	{
-		const std::shared_ptr<Bonus> b = storedArtifact->getBonusLocalFirst(Selector::type()(Bonus::SPELL));
+		const std::shared_ptr<Bonus> b = storedArtifact->getBonusLocalFirst(Selector::type()(BonusType::SPELL));
 		SpellID spellId(b->subtype);
 
 		handler.serializeId("spell", spellId, SpellID::NONE);
@@ -1866,21 +1866,21 @@ void CGSirens::initObj(CRandomGenerator & rand)
 
 std::string CGSirens::getHoverText(const CGHeroInstance * hero) const
 {
-	return getObjectName() + " " + visitedTxt(hero->hasBonusFrom(Bonus::OBJECT,ID));
+	return getObjectName() + " " + visitedTxt(hero->hasBonusFrom(BonusSource::OBJECT,ID));
 }
 
 void CGSirens::onHeroVisit( const CGHeroInstance * h ) const
 {
 	InfoWindow iw;
 	iw.player = h->tempOwner;
-	if(h->hasBonusFrom(Bonus::OBJECT,ID)) //has already visited Sirens
+	if(h->hasBonusFrom(BonusSource::OBJECT,ID)) //has already visited Sirens
 	{
 		iw.type = EInfoWindowMode::AUTO;
 		iw.text.addTxt(MetaString::ADVOB_TXT,133);
 	}
 	else
 	{
-		giveDummyBonus(h->id, Bonus::ONE_BATTLE);
+		giveDummyBonus(h->id, BonusDuration::ONE_BATTLE);
 		TExpType xp = 0;
 
 		for (auto i = h->Slots().begin(); i != h->Slots().end(); i++)
@@ -2120,7 +2120,7 @@ void CGLighthouse::onHeroVisit( const CGHeroInstance * h ) const
 		{
 			RemoveBonus rb(GiveBonus::ETarget::PLAYER);
 			rb.whoID = oldOwner.getNum();
-			rb.source = Bonus::OBJECT;
+			rb.source = vstd::to_underlying(BonusSource::OBJECT);
 			rb.id = id.getNum();
 			cb->sendAndApply(&rb);
 		}
@@ -2139,11 +2139,11 @@ void CGLighthouse::initObj(CRandomGenerator & rand)
 void CGLighthouse::giveBonusTo(const PlayerColor & player, bool onInit) const
 {
 	GiveBonus gb(GiveBonus::ETarget::PLAYER);
-	gb.bonus.type = Bonus::MOVEMENT;
+	gb.bonus.type = BonusType::MOVEMENT;
 	gb.bonus.val = 500;
 	gb.id = player.getNum();
-	gb.bonus.duration = Bonus::PERMANENT;
-	gb.bonus.source = Bonus::OBJECT;
+	gb.bonus.duration = BonusDuration::PERMANENT;
+	gb.bonus.source = BonusSource::OBJECT;
 	gb.bonus.sid = id.getNum();
 	gb.bonus.subtype = 0;
 

+ 1 - 1
lib/mapping/MapFormatH3M.cpp

@@ -1720,7 +1720,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec
 		bool hasCustomPrimSkills = reader->readBool();
 		if(hasCustomPrimSkills)
 		{
-			auto ps = object->getAllBonuses(Selector::type()(Bonus::PRIMARY_SKILL).And(Selector::sourceType()(Bonus::HERO_BASE_SKILL)), nullptr);
+			auto ps = object->getAllBonuses(Selector::type()(BonusType::PRIMARY_SKILL).And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)), nullptr);
 			if(ps->size())
 			{
 				logGlobal->warn("Hero %s subID=%d has set primary skills twice (in map properties and on adventure map instance). Using the latter set...", object->getNameTranslated(), object->subID );

+ 1 - 1
lib/rewardable/Interface.cpp

@@ -91,7 +91,7 @@ void Rewardable::Interface::grantRewardAfterLevelup(IGameCallback * cb, const Re
 
 	for(const Bonus & bonus : info.reward.bonuses)
 	{
-		assert(bonus.source == Bonus::OBJECT);
+		assert(bonus.source == BonusSource::OBJECT);
 		GiveBonus gb;
 		gb.who = GiveBonus::ETarget::HERO;
 		gb.bonus = bonus;

+ 1 - 1
lib/spells/AbilityCaster.cpp

@@ -35,7 +35,7 @@ int32_t AbilityCaster::getSpellSchoolLevel(const Spell * spell, int32_t * outSel
 
 	if(spell->getLevel() > 0)
 	{
-		vstd::amax(skill, unit->valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 0));
+		vstd::amax(skill, unit->valOfBonuses(BonusType::MAGIC_SCHOOL_SKILL, 0));
 	}
 
 	vstd::amax(skill, 0);

+ 3 - 3
lib/spells/AdventureSpellMechanics.cpp

@@ -307,9 +307,9 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironm
 	const int movementCost = GameConstants::BASE_MOVEMENT_COST * ((schoolLevel >= 3) ? 2 : 3);
 
 	std::stringstream cachingStr;
-	cachingStr << "source_" << Bonus::SPELL_EFFECT << "id_" << owner->id.num;
+	cachingStr << "source_" << vstd::to_underlying(BonusSource::SPELL_EFFECT) << "id_" << owner->id.num;
 
-	if(parameters.caster->getHeroCaster()->getBonuses(Selector::source(Bonus::SPELL_EFFECT, owner->id), Selector::all, cachingStr.str())->size() >= owner->getLevelPower(schoolLevel)) //limit casts per turn
+	if(parameters.caster->getHeroCaster()->getBonuses(Selector::source(BonusSource::SPELL_EFFECT, owner->id), Selector::all, cachingStr.str())->size() >= owner->getLevelPower(schoolLevel)) //limit casts per turn
 	{
 		InfoWindow iw;
 		iw.player = parameters.caster->getCasterOwner();
@@ -321,7 +321,7 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironm
 
 	GiveBonus gb;
 	gb.id = parameters.caster->getCasterUnitId();
-	gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::NONE, Bonus::SPELL_EFFECT, 0, owner->id);
+	gb.bonus = Bonus(BonusDuration::ONE_DAY, BonusType::NONE, BonusSource::SPELL_EFFECT, 0, owner->id);
 	env->apply(&gb);
 
 	if(!dest->isClear(curr)) //wrong dest tile

+ 2 - 2
lib/spells/BattleSpellMechanics.cpp

@@ -283,7 +283,7 @@ void BattleSpellMechanics::cast(ServerCallback * server, const Target & target)
 			{
 				if(stack->unitOwner() == otherHero->tempOwner)
 				{
-					vstd::amax(manaChannel, stack->valOfBonuses(Bonus::MANA_CHANNELING));
+					vstd::amax(manaChannel, stack->valOfBonuses(BonusType::MANA_CHANNELING));
 				}
 			}
 			sc.manaGained = (manaChannel * spellCost) / 100;
@@ -467,7 +467,7 @@ void BattleSpellMechanics::doRemoveEffects(ServerCallback * server, const std::v
 
 bool BattleSpellMechanics::counteringSelector(const Bonus * bonus) const
 {
-	if(bonus->source != Bonus::SPELL_EFFECT)
+	if(bonus->source != BonusSource::SPELL_EFFECT)
 		return false;
 
 	for(const SpellID & id : owner->counteredSpells)

+ 24 - 24
lib/spells/CSpellHandler.cpp

@@ -42,35 +42,35 @@ static const spells::SchoolInfo SCHOOL[4] =
 {
 	{
 		ESpellSchool::AIR,
-		Bonus::AIR_SPELL_DMG_PREMY,
-		Bonus::AIR_IMMUNITY,
+		BonusType::AIR_SPELL_DMG_PREMY,
+		BonusType::AIR_IMMUNITY,
 		"air",
 		SecondarySkill::AIR_MAGIC,
-		Bonus::AIR_SPELLS
+		BonusType::AIR_SPELLS
 	},
 	{
 		ESpellSchool::FIRE,
-		Bonus::FIRE_SPELL_DMG_PREMY,
-		Bonus::FIRE_IMMUNITY,
+		BonusType::FIRE_SPELL_DMG_PREMY,
+		BonusType::FIRE_IMMUNITY,
 		"fire",
 		SecondarySkill::FIRE_MAGIC,
-		Bonus::FIRE_SPELLS
+		BonusType::FIRE_SPELLS
 	},
 	{
 		ESpellSchool::WATER,
-		Bonus::WATER_SPELL_DMG_PREMY,
-		Bonus::WATER_IMMUNITY,
+		BonusType::WATER_SPELL_DMG_PREMY,
+		BonusType::WATER_IMMUNITY,
 		"water",
 		SecondarySkill::WATER_MAGIC,
-		Bonus::WATER_SPELLS
+		BonusType::WATER_SPELLS
 	},
 	{
 		ESpellSchool::EARTH,
-		Bonus::EARTH_SPELL_DMG_PREMY,
-		Bonus::EARTH_IMMUNITY,
+		BonusType::EARTH_SPELL_DMG_PREMY,
+		BonusType::EARTH_IMMUNITY,
 		"earth",
 		SecondarySkill::EARTH_MAGIC,
-		Bonus::EARTH_SPELLS
+		BonusType::EARTH_SPELLS
 	}
 };
 
@@ -393,15 +393,15 @@ int64_t CSpell::adjustRawDamage(const spells::Caster * caster, const battle::Uni
 		//applying protections - when spell has more then one elements, only one protection should be applied (I think)
 		forEachSchool([&](const spells::SchoolInfo & cnf, bool & stop)
 		{
-			if(bearer->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, static_cast<ui8>(cnf.id)))
+			if(bearer->hasBonusOfType(BonusType::SPELL_DAMAGE_REDUCTION, static_cast<ui8>(cnf.id)))
 			{
-				ret *= 100 - bearer->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, static_cast<ui8>(cnf.id));
+				ret *= 100 - bearer->valOfBonuses(BonusType::SPELL_DAMAGE_REDUCTION, static_cast<ui8>(cnf.id));
 				ret /= 100;
 				stop = true; //only bonus from one school is used
 			}
 		});
 
-		CSelector selector = Selector::type()(Bonus::SPELL_DAMAGE_REDUCTION).And(Selector::subtype()(-1));
+		CSelector selector = Selector::type()(BonusType::SPELL_DAMAGE_REDUCTION).And(Selector::subtype()(-1));
 
 		//general spell dmg reduction, works only on magical effects
 		if(bearer->hasBonus(selector) && isMagical())
@@ -411,9 +411,9 @@ int64_t CSpell::adjustRawDamage(const spells::Caster * caster, const battle::Uni
 		}
 
 		//dmg increasing
-		if(bearer->hasBonusOfType(Bonus::MORE_DAMAGE_FROM_SPELL, id))
+		if(bearer->hasBonusOfType(BonusType::MORE_DAMAGE_FROM_SPELL, id))
 		{
-			ret *= 100 + bearer->valOfBonuses(Bonus::MORE_DAMAGE_FROM_SPELL, id.toEnum());
+			ret *= 100 + bearer->valOfBonuses(BonusType::MORE_DAMAGE_FROM_SPELL, id.toEnum());
 			ret /= 100;
 		}
 	}
@@ -452,8 +452,8 @@ JsonNode CSpell::convertTargetCondition(const BTVector & immunity, const BTVecto
 	static const std::string CONDITION_NORMAL = "normal";
 	static const std::string CONDITION_ABSOLUTE = "absolute";
 
-#define BONUS_NAME(x) { Bonus::x, #x },
-	static const std::map<Bonus::BonusType, std::string> bonusNameRMap = { BONUS_LIST };
+#define BONUS_NAME(x) { BonusType::x, #x },
+	static const std::map<BonusType, std::string> bonusNameRMap = { BONUS_LIST };
 #undef BONUS_NAME
 
 	JsonNode res;
@@ -791,7 +791,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode &
 
 	spell->special = flags["special"].Bool();
 
-	auto findBonus = [&](const std::string & name, std::vector<Bonus::BonusType> & vec)
+	auto findBonus = [&](const std::string & name, std::vector<BonusType> & vec)
 	{
 		auto it = bonusNameMap.find(name);
 		if(it == bonusNameMap.end())
@@ -800,11 +800,11 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode &
 		}
 		else
 		{
-			vec.push_back(static_cast<Bonus::BonusType>(it->second));
+			vec.push_back(static_cast<BonusType>(it->second));
 		}
 	};
 
-	auto readBonusStruct = [&](const std::string & name, std::vector<Bonus::BonusType> & vec)
+	auto readBonusStruct = [&](const std::string & name, std::vector<BonusType> & vec)
 	{
 		for(auto bonusData: json[name].Struct())
 		{
@@ -933,7 +933,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode &
 			const bool usePowerAsValue = bonusNode["val"].isNull();
 
 			b->sid = spell->id; //for all
-			b->source = Bonus::SPELL_EFFECT;//for all
+			b->source = BonusSource::SPELL_EFFECT;//for all
 
 			if(usePowerAsValue)
 				b->val = levelPower;
@@ -948,7 +948,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode &
 			const bool usePowerAsValue = bonusNode["val"].isNull();
 
 			b->sid = spell->id; //for all
-			b->source = Bonus::SPELL_EFFECT;//for all
+			b->source = BonusSource::SPELL_EFFECT;//for all
 
 			if(usePowerAsValue)
 				b->val = levelPower;

+ 4 - 4
lib/spells/CSpellHandler.h

@@ -44,11 +44,11 @@ class IBattleCast;
 struct SchoolInfo
 {
 	ESpellSchool id; //backlink
-	Bonus::BonusType damagePremyBonus;
-	Bonus::BonusType immunityBonus;
+	BonusType damagePremyBonus;
+	BonusType immunityBonus;
 	std::string jsonName;
 	SecondarySkill::ESecondarySkill skill;
-	Bonus::BonusType knoledgeBonus;
+	BonusType knoledgeBonus;
 };
 
 }
@@ -184,7 +184,7 @@ public:
 		TargetInfo(const CSpell * spell, const int32_t level, spells::Mode mode);
 	};
 
-	using BTVector = std::vector<Bonus::BonusType>;
+	using BTVector = std::vector<BonusType>;
 
 	si32 level;
 

+ 6 - 6
lib/spells/ISpellMechanics.cpp

@@ -274,7 +274,7 @@ void BattleCast::cast(ServerCallback * server, Target target)
 	if(tryMagicMirror)
 	{
 		const std::string magicMirrorCacheStr = "type_MAGIC_MIRROR";
-		static const auto magicMirrorSelector = Selector::type()(Bonus::MAGIC_MIRROR);
+		static const auto magicMirrorSelector = Selector::type()(BonusType::MAGIC_MIRROR);
 
 		auto rangeGen = server->getRNG()->getInt64Range(0, 99);
 
@@ -484,9 +484,9 @@ bool BaseMechanics::adaptProblem(ESpellCastProblem::ESpellCastProblem source, Pr
 				return adaptGenericProblem(target);
 
 			//Recanter's Cloak or similar effect. Try to retrieve bonus
-			const auto b = hero->getBonusLocalFirst(Selector::type()(Bonus::BLOCK_MAGIC_ABOVE));
+			const auto b = hero->getBonusLocalFirst(Selector::type()(BonusType::BLOCK_MAGIC_ABOVE));
 			//TODO what about other values and non-artifact sources?
-			if(b && b->val == 2 && b->source == Bonus::ARTIFACT)
+			if(b && b->val == 2 && b->source == BonusSource::ARTIFACT)
 			{
 				//The %s prevents %s from casting 3rd level or higher spells.
 				text.addTxt(MetaString::GENERAL_TXT, 536);
@@ -494,7 +494,7 @@ bool BaseMechanics::adaptProblem(ESpellCastProblem::ESpellCastProblem source, Pr
 				caster->getCasterName(text);
 				target.add(std::move(text), spells::Problem::NORMAL);
 			}
-			else if(b && b->source == Bonus::TERRAIN_OVERLAY && VLC->battlefields()->getByIndex(b->sid)->identifier == "cursed_ground")
+			else if(b && b->source == BonusSource::TERRAIN_OVERLAY && VLC->battlefields()->getByIndex(b->sid)->identifier == "cursed_ground")
 			{
 				text.addTxt(MetaString::GENERAL_TXT, 537);
 				target.add(std::move(text), spells::Problem::NORMAL);
@@ -620,9 +620,9 @@ int64_t BaseMechanics::calculateRawEffectValue(int32_t basePowerMultiplier, int3
 	return owner->calculateRawEffectValue(getEffectLevel(), basePowerMultiplier, levelPowerMultiplier);
 }
 
-std::vector<Bonus::BonusType> BaseMechanics::getElementalImmunity() const
+std::vector<BonusType> BaseMechanics::getElementalImmunity() const
 {
-	std::vector<Bonus::BonusType> ret;
+	std::vector<BonusType> ret;
 
 	owner->forEachSchool([&](const SchoolInfo & cnf, bool & stop)
 	{

+ 2 - 2
lib/spells/ISpellMechanics.h

@@ -235,7 +235,7 @@ public:
 	virtual int64_t applySpecificSpellBonus(int64_t value) const = 0;
 	virtual int64_t calculateRawEffectValue(int32_t basePowerMultiplier, int32_t levelPowerMultiplier) const = 0;
 
-	virtual std::vector<Bonus::BonusType> getElementalImmunity() const = 0;
+	virtual std::vector<BonusType> getElementalImmunity() const = 0;
 
 	//Battle facade
 	virtual bool ownerMatches(const battle::Unit * unit) const = 0;
@@ -296,7 +296,7 @@ public:
 	int64_t applySpecificSpellBonus(int64_t value) const override;
 	int64_t calculateRawEffectValue(int32_t basePowerMultiplier, int32_t levelPowerMultiplier) const override;
 
-	std::vector<Bonus::BonusType> getElementalImmunity() const override;
+	std::vector<BonusType> getElementalImmunity() const override;
 
 	bool ownerMatches(const battle::Unit * unit) const override;
 	bool ownerMatches(const battle::Unit * unit, const boost::logic::tribool positivness) const override;

+ 11 - 11
lib/spells/TargetCondition.cpp

@@ -121,9 +121,9 @@ protected:
 			return true;
 
 		std::stringstream cachingStr;
-		cachingStr << "type_" << Bonus::LEVEL_SPELL_IMMUNITY << "addInfo_1";
+		cachingStr << "type_" << vstd::to_underlying(BonusType::LEVEL_SPELL_IMMUNITY) << "addInfo_1";
 
-		TConstBonusListPtr levelImmunities = target->getBonuses(Selector::type()(Bonus::LEVEL_SPELL_IMMUNITY).And(Selector::info()(1)), cachingStr.str());
+		TConstBonusListPtr levelImmunities = target->getBonuses(Selector::type()(BonusType::LEVEL_SPELL_IMMUNITY).And(Selector::info()(1)), cachingStr.str());
 		return (levelImmunities->size() == 0 || levelImmunities->totalValue() < m->getSpellLevel() || m->getSpellLevel() <= 0);
 	}
 };
@@ -141,8 +141,8 @@ protected:
 	bool check(const Mechanics * m, const battle::Unit * target) const override
 	{
 		std::stringstream cachingStr;
-		cachingStr << "type_" << Bonus::SPELL_IMMUNITY << "subtype_" << m->getSpellIndex() << "addInfo_1";
-		return !target->hasBonus(Selector::typeSubtypeInfo(Bonus::SPELL_IMMUNITY, m->getSpellIndex(), 1), cachingStr.str());
+		cachingStr << "type_" << vstd::to_underlying(BonusType::SPELL_IMMUNITY) << "subtype_" << m->getSpellIndex() << "addInfo_1";
+		return !target->hasBonus(Selector::typeSubtypeInfo(BonusType::SPELL_IMMUNITY, m->getSpellIndex(), 1), cachingStr.str());
 	}
 };
 
@@ -197,7 +197,7 @@ protected:
 	{
 		if(!m->isMagicalEffect()) //Always pass on non-magical
 			return true;
-		TConstBonusListPtr levelImmunities = target->getBonuses(Selector::type()(Bonus::LEVEL_SPELL_IMMUNITY));
+		TConstBonusListPtr levelImmunities = target->getBonuses(Selector::type()(BonusType::LEVEL_SPELL_IMMUNITY));
 		return levelImmunities->size() == 0 ||
 		levelImmunities->totalValue() < m->getSpellLevel() ||
 		m->getSpellLevel() <= 0;
@@ -216,7 +216,7 @@ public:
 protected:
 	bool check(const Mechanics * m, const battle::Unit * target) const override
 	{
-		return !target->hasBonusOfType(Bonus::SPELL_IMMUNITY, m->getSpellIndex());
+		return !target->hasBonusOfType(BonusType::SPELL_IMMUNITY, m->getSpellIndex());
 	}
 };
 
@@ -241,10 +241,10 @@ public:
 	SpellEffectCondition(const SpellID & spellID_): spellID(spellID_)
 	{
 		std::stringstream builder;
-		builder << "source_" << Bonus::SPELL_EFFECT << "id_" << spellID.num;
+		builder << "source_" << vstd::to_underlying(BonusSource::SPELL_EFFECT) << "id_" << spellID.num;
 		cachingString = builder.str();
 
-		selector = Selector::source(Bonus::SPELL_EFFECT, spellID.num);
+		selector = Selector::source(BonusSource::SPELL_EFFECT, spellID.num);
 	}
 
 protected:
@@ -268,7 +268,7 @@ protected:
 	}
 
 private:
-	CSelector selector = Selector::type()(Bonus::RECEPTIVE);
+	CSelector selector = Selector::type()(BonusType::RECEPTIVE);
 	std::string cachingString = "type_RECEPTIVE";
 };
 
@@ -277,8 +277,8 @@ class ImmunityNegationCondition : public TargetConditionItemBase
 protected:
 	bool check(const Mechanics * m, const battle::Unit * target) const override
 	{
-		const bool battleWideNegation = target->hasBonusOfType(Bonus::NEGATE_ALL_NATURAL_IMMUNITIES, 0);
-		const bool heroNegation = target->hasBonusOfType(Bonus::NEGATE_ALL_NATURAL_IMMUNITIES, 1);
+		const bool battleWideNegation = target->hasBonusOfType(BonusType::NEGATE_ALL_NATURAL_IMMUNITIES, 0);
+		const bool heroNegation = target->hasBonusOfType(BonusType::NEGATE_ALL_NATURAL_IMMUNITIES, 1);
 		//anyone can cast on artifact holder`s stacks
 		if(heroNegation)
 		{

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

@@ -89,7 +89,7 @@ void Clone::apply(ServerCallback * server, const Mechanics * m, const EffectTarg
 		server->apply(&cloneFlags);
 
 		SetStackEffect sse;
-		Bonus lifeTimeMarker(Bonus::N_TURNS, Bonus::NONE, Bonus::SPELL_EFFECT, 0, SpellID::CLONE); //TODO: use special bonus type
+		Bonus lifeTimeMarker(BonusDuration::N_TURNS, BonusType::NONE, BonusSource::SPELL_EFFECT, 0, SpellID::CLONE); //TODO: use special bonus type
 		lifeTimeMarker.turnsRemain = m->getEffectDuration();
 		std::vector<Bonus> buffer;
 		buffer.push_back(lifeTimeMarker);

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

@@ -85,7 +85,7 @@ std::shared_ptr<const BonusList> Dispel::getBonuses(const Mechanics * m, const b
 {
 	auto sel = [=](const Bonus * bonus)
 	{
-		if(bonus->source == Bonus::SPELL_EFFECT)
+		if(bonus->source == BonusSource::SPELL_EFFECT)
 		{
 			const Spell * sourceSpell = SpellID(bonus->sid).toSpell(m->spells());
 			if(!sourceSpell)

+ 3 - 3
lib/spells/effects/Moat.cpp

@@ -80,17 +80,17 @@ void Moat::convertBonus(const Mechanics * m, std::vector<Bonus> & converted) con
 		Bonus nb(*b);
 
 		//Moat battlefield effect is always permanent
-		nb.duration = Bonus::ONE_BATTLE;
+		nb.duration = BonusDuration::ONE_BATTLE;
 
 		if(m->battle()->battleGetDefendedTown() && m->battle()->battleGetSiegeLevel() >= CGTownInstance::CITADEL)
 		{
 			nb.sid = Bonus::getSid32(m->battle()->battleGetDefendedTown()->getFaction(), BuildingID::CITADEL);
-			nb.source = Bonus::TOWN_STRUCTURE;
+			nb.source = BonusSource::TOWN_STRUCTURE;
 		}
 		else
 		{
 			nb.sid = m->getSpellIndex(); //for all
-			nb.source = Bonus::SPELL_EFFECT;//for all
+			nb.source = BonusSource::SPELL_EFFECT;//for all
 		}
 		std::set<BattleHex> flatMoatHexes;
 

+ 11 - 11
lib/spells/effects/Timed.cpp

@@ -45,7 +45,7 @@ static void describeEffect(std::vector<MetaString> & log, const Mechanics * m, c
 	{
 		switch(bonus.type)
 		{
-		case Bonus::NOT_ACTIVE:
+		case BonusType::NOT_ACTIVE:
 			{
 				switch(bonus.subtype)
 				{
@@ -60,17 +60,17 @@ static void describeEffect(std::vector<MetaString> & log, const Mechanics * m, c
 				}
 			}
 			break;
-		case Bonus::POISON:
+		case BonusType::POISON:
 			addLogLine(561, boost::logic::indeterminate);
 			return;
-		case Bonus::BIND_EFFECT:
+		case BonusType::BIND_EFFECT:
 			addLogLine(-560, true);
 			return;
-		case Bonus::STACK_HEALTH:
+		case BonusType::STACK_HEALTH:
 			{
 				if(bonus.val < 0)
 				{
-					BonusList unitHealth = *target->getBonuses(Selector::type()(Bonus::STACK_HEALTH));
+					BonusList unitHealth = *target->getBonuses(Selector::type()(BonusType::STACK_HEALTH));
 
 					auto oldHealth = unitHealth.totalValue();
 					unitHealth.push_back(std::make_shared<Bonus>(bonus));
@@ -108,9 +108,9 @@ void Timed::apply(ServerCallback * server, const Mechanics * m, const EffectTarg
 	const auto *casterHero = dynamic_cast<const CGHeroInstance *>(m->caster);
 	if(casterHero)
 	{ 
-		peculiarBonus = casterHero->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, m->getSpellIndex()));
-		addedValueBonus = casterHero->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_ADD_VALUE_ENCHANT, m->getSpellIndex()));
-		fixedValueBonus = casterHero->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_FIXED_VALUE_ENCHANT, m->getSpellIndex()));
+		peculiarBonus = casterHero->getBonusLocalFirst(Selector::typeSubtype(BonusType::SPECIAL_PECULIAR_ENCHANT, m->getSpellIndex()));
+		addedValueBonus = casterHero->getBonusLocalFirst(Selector::typeSubtype(BonusType::SPECIAL_ADD_VALUE_ENCHANT, m->getSpellIndex()));
+		fixedValueBonus = casterHero->getBonusLocalFirst(Selector::typeSubtype(BonusType::SPECIAL_FIXED_VALUE_ENCHANT, m->getSpellIndex()));
 	}	
 	//TODO: does hero specialty should affects his stack casting spells?
 
@@ -220,13 +220,13 @@ void Timed::convertBonus(const Mechanics * m, int32_t & duration, std::vector<Bo
 		vstd::amax(maxDuration, nb.turnsRemain);
 
 		nb.sid = m->getSpellIndex(); //for all
-		nb.source = Bonus::SPELL_EFFECT;//for all
+		nb.source = BonusSource::SPELL_EFFECT;//for all
 
 		//fix to original config: shield should display damage reduction
-		if((nb.sid == SpellID::SHIELD || nb.sid == SpellID::AIR_SHIELD) && (nb.type == Bonus::GENERAL_DAMAGE_REDUCTION))
+		if((nb.sid == SpellID::SHIELD || nb.sid == SpellID::AIR_SHIELD) && (nb.type == BonusType::GENERAL_DAMAGE_REDUCTION))
 			nb.val = 100 - nb.val;
 		//we need to know who cast Bind
-		else if(nb.sid == SpellID::BIND && nb.type == Bonus::BIND_EFFECT && m->caster->getHeroCaster() == nullptr)
+		else if(nb.sid == SpellID::BIND && nb.type == BonusType::BIND_EFFECT && m->caster->getHeroCaster() == nullptr)
 			nb.additionalInfo = m->caster->getCasterUnitId();
 
 		converted.push_back(nb);

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

@@ -252,8 +252,8 @@ bool UnitEffect::isReceptive(const Mechanics * m, const battle::Unit * unit) con
 
 		//SPELL_IMMUNITY absolute case
 		std::stringstream cachingStr;
-		cachingStr << "type_" << Bonus::SPELL_IMMUNITY << "subtype_" << m->getSpellIndex() << "addInfo_1";
-		return !unit->hasBonus(Selector::typeSubtypeInfo(Bonus::SPELL_IMMUNITY, m->getSpellIndex(), 1), cachingStr.str());
+		cachingStr << "type_" << vstd::to_underlying(BonusType::SPELL_IMMUNITY) << "subtype_" << m->getSpellIndex() << "addInfo_1";
+		return !unit->hasBonus(Selector::typeSubtypeInfo(BonusType::SPELL_IMMUNITY, m->getSpellIndex(), 1), cachingStr.str());
 	}
 	else
 	{

+ 107 - 107
server/CGameHandler.cpp

@@ -136,7 +136,7 @@ static void giveExp(BattleResult &r)
 	r.exp[1] = 0;
 	for (auto i = r.casualties[!r.winner].begin(); i!=r.casualties[!r.winner].end(); i++)
 	{
-		r.exp[r.winner] += VLC->creh->objects.at(i->first)->valOfBonuses(Bonus::STACK_HEALTH) * i->second;
+		r.exp[r.winner] += VLC->creh->objects.at(i->first)->valOfBonuses(BonusType::STACK_HEALTH) * i->second;
 	}
 }
 
@@ -361,10 +361,10 @@ void CGameHandler::levelUpCommander (const CCommanderInstance * c, int skill)
 
 	scp.accumulatedBonus.subtype = 0;
 	scp.accumulatedBonus.additionalInfo = 0;
-	scp.accumulatedBonus.duration = Bonus::PERMANENT;
+	scp.accumulatedBonus.duration = BonusDuration::PERMANENT;
 	scp.accumulatedBonus.turnsRemain = 0;
-	scp.accumulatedBonus.source = Bonus::COMMANDER;
-	scp.accumulatedBonus.valType = Bonus::BASE_NUMBER;
+	scp.accumulatedBonus.source = BonusSource::COMMANDER;
+	scp.accumulatedBonus.valType = BonusValueType::BASE_NUMBER;
 	if (skill <= ECommander::SPELL_POWER)
 	{
 		scp.which = SetCommanderProperty::BONUS;
@@ -378,36 +378,36 @@ void CGameHandler::levelUpCommander (const CCommanderInstance * c, int skill)
 		switch (skill)
 		{
 			case ECommander::ATTACK:
-				scp.accumulatedBonus.type = Bonus::PRIMARY_SKILL;
+				scp.accumulatedBonus.type = BonusType::PRIMARY_SKILL;
 				scp.accumulatedBonus.subtype = PrimarySkill::ATTACK;
 				break;
 			case ECommander::DEFENSE:
-				scp.accumulatedBonus.type = Bonus::PRIMARY_SKILL;
+				scp.accumulatedBonus.type = BonusType::PRIMARY_SKILL;
 				scp.accumulatedBonus.subtype = PrimarySkill::DEFENSE;
 				break;
 			case ECommander::HEALTH:
-				scp.accumulatedBonus.type = Bonus::STACK_HEALTH;
-				scp.accumulatedBonus.valType = Bonus::PERCENT_TO_BASE;
+				scp.accumulatedBonus.type = BonusType::STACK_HEALTH;
+				scp.accumulatedBonus.valType = BonusValueType::PERCENT_TO_BASE;
 				break;
 			case ECommander::DAMAGE:
-				scp.accumulatedBonus.type = Bonus::CREATURE_DAMAGE;
+				scp.accumulatedBonus.type = BonusType::CREATURE_DAMAGE;
 				scp.accumulatedBonus.subtype = 0;
-				scp.accumulatedBonus.valType = Bonus::PERCENT_TO_BASE;
+				scp.accumulatedBonus.valType = BonusValueType::PERCENT_TO_BASE;
 				break;
 			case ECommander::SPEED:
-				scp.accumulatedBonus.type = Bonus::STACKS_SPEED;
+				scp.accumulatedBonus.type = BonusType::STACKS_SPEED;
 				break;
 			case ECommander::SPELL_POWER:
-				scp.accumulatedBonus.type = Bonus::MAGIC_RESISTANCE;
+				scp.accumulatedBonus.type = BonusType::MAGIC_RESISTANCE;
 				scp.accumulatedBonus.val = difference (VLC->creh->skillLevels, c->secondarySkills, ECommander::RESISTANCE);
 				sendAndApply (&scp); //additional pack
-				scp.accumulatedBonus.type = Bonus::CREATURE_SPELL_POWER;
+				scp.accumulatedBonus.type = BonusType::CREATURE_SPELL_POWER;
 				scp.accumulatedBonus.val = difference (VLC->creh->skillLevels, c->secondarySkills, ECommander::SPELL_POWER) * 100; //like hero with spellpower = ability level
 				sendAndApply (&scp); //additional pack
-				scp.accumulatedBonus.type = Bonus::CASTS;
+				scp.accumulatedBonus.type = BonusType::CASTS;
 				scp.accumulatedBonus.val = difference (VLC->creh->skillLevels, c->secondarySkills, ECommander::CASTS);
 				sendAndApply (&scp); //additional pack
-				scp.accumulatedBonus.type = Bonus::CREATURE_ENCHANT_POWER; //send normally
+				scp.accumulatedBonus.type = BonusType::CREATURE_ENCHANT_POWER; //send normally
 				break;
 		}
 
@@ -632,9 +632,9 @@ void CGameHandler::endBattleConfirm(const BattleInfo * battleInfo)
 
 	if(!finishingBattle->isDraw() && finishingBattle->winnerHero)
 	{
-		if (int eagleEyeLevel = finishingBattle->winnerHero->valOfBonuses(Bonus::LEARN_BATTLE_SPELL_LEVEL_LIMIT, -1))
+		if (int eagleEyeLevel = finishingBattle->winnerHero->valOfBonuses(BonusType::LEARN_BATTLE_SPELL_LEVEL_LIMIT, -1))
 		{
-			double eagleEyeChance = finishingBattle->winnerHero->valOfBonuses(Bonus::LEARN_BATTLE_SPELL_CHANCE, 0);
+			double eagleEyeChance = finishingBattle->winnerHero->valOfBonuses(BonusType::LEARN_BATTLE_SPELL_CHANCE, 0);
 			for(auto & spellId : battleInfo->sides.at(!battleResult.data->winner).usedSpellsHistory)
 			{
 				auto spell = spellId.toSpell(VLC->spells());
@@ -946,7 +946,7 @@ void CGameHandler::makeAttack(const CStack * attacker, const CStack * defender,
 			bat.flags |= BattleAttack::UNLUCKY;
 	}
 
-	if (getRandomGenerator().nextInt(99) < attacker->valOfBonuses(Bonus::DOUBLE_DAMAGE_CHANCE))
+	if (getRandomGenerator().nextInt(99) < attacker->valOfBonuses(BonusType::DOUBLE_DAMAGE_CHANCE))
 	{
 		bat.flags |= BattleAttack::DEATH_BLOW;
 	}
@@ -954,7 +954,7 @@ void CGameHandler::makeAttack(const CStack * attacker, const CStack * defender,
 	const auto * owner = gs->curB->getHero(attacker->unitOwner());
 	if(owner)
 	{
-		int chance = owner->valOfBonuses(Bonus::BONUS_DAMAGE_CHANCE, attacker->creatureIndex());
+		int chance = owner->valOfBonuses(BonusType::BONUS_DAMAGE_CHANCE, attacker->creatureIndex());
 		if (chance > getRandomGenerator().nextInt(99))
 			bat.flags |= BattleAttack::BALLISTA_DOUBLE_DMG;
 	}
@@ -973,7 +973,7 @@ void CGameHandler::makeAttack(const CStack * attacker, const CStack * defender,
 			drainedLife += applyBattleEffects(bat, attackerState, fireShield, stack, distance, true);
 	}
 
-	std::shared_ptr<const Bonus> bonus = attacker->getBonusLocalFirst(Selector::type()(Bonus::SPELL_LIKE_ATTACK));
+	std::shared_ptr<const Bonus> bonus = attacker->getBonusLocalFirst(Selector::type()(BonusType::SPELL_LIKE_ATTACK));
 	if(bonus && ranged) //TODO: make it work in melee?
 	{
 		//this is need for displaying hit animation
@@ -1142,23 +1142,23 @@ int64_t CGameHandler::applyBattleEffects(BattleAttack & bat, std::shared_ptr<bat
 	int64_t drainedLife = 0;
 
 	//life drain handling
-	if(attackerState->hasBonusOfType(Bonus::LIFE_DRAIN) && def->isLiving())
+	if(attackerState->hasBonusOfType(BonusType::LIFE_DRAIN) && def->isLiving())
 	{
-		int64_t toHeal = bsa.damageAmount * attackerState->valOfBonuses(Bonus::LIFE_DRAIN) / 100;
+		int64_t toHeal = bsa.damageAmount * attackerState->valOfBonuses(BonusType::LIFE_DRAIN) / 100;
 		attackerState->heal(toHeal, EHealLevel::RESURRECT, EHealPower::PERMANENT);
 		drainedLife += toHeal;
 	}
 
 	//soul steal handling
-	if(attackerState->hasBonusOfType(Bonus::SOUL_STEAL) && def->isLiving())
+	if(attackerState->hasBonusOfType(BonusType::SOUL_STEAL) && def->isLiving())
 	{
 		//we can have two bonuses - one with subtype 0 and another with subtype 1
 		//try to use permanent first, use only one of two
 		for(si32 subtype = 1; subtype >= 0; subtype--)
 		{
-			if(attackerState->hasBonusOfType(Bonus::SOUL_STEAL, subtype))
+			if(attackerState->hasBonusOfType(BonusType::SOUL_STEAL, subtype))
 			{
-				int64_t toHeal = bsa.killedAmount * attackerState->valOfBonuses(Bonus::SOUL_STEAL, subtype) * attackerState->getMaxHealth();
+				int64_t toHeal = bsa.killedAmount * attackerState->valOfBonuses(BonusType::SOUL_STEAL, subtype) * attackerState->getMaxHealth();
 				attackerState->heal(toHeal, EHealLevel::OVERHEAL, ((subtype == 0) ? EHealPower::ONE_BATTLE : EHealPower::PERMANENT));
 				drainedLife += toHeal;
 				break;
@@ -1170,13 +1170,13 @@ int64_t CGameHandler::applyBattleEffects(BattleAttack & bat, std::shared_ptr<bat
 	//fire shield handling
 	if(!bat.shot() &&
 		!def->isClone() &&
-		def->hasBonusOfType(Bonus::FIRE_SHIELD) &&
-		!attackerState->hasBonusOfType(Bonus::FIRE_IMMUNITY) &&
+		def->hasBonusOfType(BonusType::FIRE_SHIELD) &&
+		!attackerState->hasBonusOfType(BonusType::FIRE_IMMUNITY) &&
 		CStack::isMeleeAttackPossible(attackerState.get(), def) // attacked needs to be adjacent to defender for fire shield to trigger (e.g. Dragon Breath attack)
 			)
 	{
 		//TODO: use damage with bonus but without penalties
-		auto fireShieldDamage = (std::min<int64_t>(def->getAvailableHealth(), bsa.damageAmount) * def->valOfBonuses(Bonus::FIRE_SHIELD)) / 100;
+		auto fireShieldDamage = (std::min<int64_t>(def->getAvailableHealth(), bsa.damageAmount) * def->valOfBonuses(BonusType::FIRE_SHIELD)) / 100;
 		fireShield.push_back(std::make_pair(def, fireShieldDamage));
 	}
 
@@ -1371,7 +1371,7 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
 		return false;
 	};
 
-	if (curStack->hasBonusOfType(Bonus::FLYING))
+	if (curStack->hasBonusOfType(BonusType::FLYING))
 	{
 		if (path.second <= creSpeed && path.first.size() > 0)
 		{
@@ -1825,7 +1825,7 @@ void CGameHandler::newTurn()
 			{
 				for(auto stack : hero->stacks)
 				{
-					if(stack.second->hasBonusOfType(Bonus::SPECIAL_CRYSTAL_GENERATION))
+					if(stack.second->hasBonusOfType(BonusType::SPECIAL_CRYSTAL_GENERATION))
 					{
 						hasCrystalGenCreature = true;
 						break;
@@ -1838,7 +1838,7 @@ void CGameHandler::newTurn()
 				{
 					for(auto stack : town->stacks)
 					{
-						if(stack.second->hasBonusOfType(Bonus::SPECIAL_CRYSTAL_GENERATION))
+						if(stack.second->hasBonusOfType(BonusType::SPECIAL_CRYSTAL_GENERATION))
 						{
 							hasCrystalGenCreature = true;
 							break;
@@ -1868,7 +1868,7 @@ void CGameHandler::newTurn()
 			{
 				for (int k = 0; k < GameConstants::RESOURCE_QUANTITY; k++)
 				{
-					n.res[elem.first][k] += h->valOfBonuses(Bonus::GENERATE_RESOURCE, k);
+					n.res[elem.first][k] += h->valOfBonuses(BonusType::GENERATE_RESOURCE, k);
 				}
 			}
 		}
@@ -1951,13 +1951,13 @@ void CGameHandler::newTurn()
 				sendAndApply (&fw);
 			}
 		}
-		if (t->hasBonusOfType (Bonus::DARKNESS))
+		if (t->hasBonusOfType (BonusType::DARKNESS))
 		{
 			for (auto & player : gs->players)
 			{
 				if (getPlayerStatus(player.first) == EPlayerStatus::INGAME &&
 					getPlayerRelations(player.first, t->tempOwner) == PlayerRelations::ENEMIES)
-					changeFogOfWar(t->visitablePos(), t->getBonusLocalFirst(Selector::type()(Bonus::DARKNESS))->val, player.first, true);
+					changeFogOfWar(t->visitablePos(), t->getBonusLocalFirst(Selector::type()(BonusType::DARKNESS))->val, player.first, true);
 			}
 		}
 	}
@@ -2281,8 +2281,8 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
 	auto pathfinderHelper = std::make_unique<CPathfinderHelper>(gs, h, PathfinderOptions());
 	auto ti = pathfinderHelper->getTurnInfo();
 
-	const bool canFly = pathfinderHelper->hasBonusOfType(Bonus::FLYING_MOVEMENT) || (h->boat && h->boat->layer == EPathfindingLayer::AIR);
-	const bool canWalkOnSea = pathfinderHelper->hasBonusOfType(Bonus::WATER_WALKING) || (h->boat && h->boat->layer == EPathfindingLayer::WATER);
+	const bool canFly = pathfinderHelper->hasBonusOfType(BonusType::FLYING_MOVEMENT) || (h->boat && h->boat->layer == EPathfindingLayer::AIR);
+	const bool canWalkOnSea = pathfinderHelper->hasBonusOfType(BonusType::WATER_WALKING) || (h->boat && h->boat->layer == EPathfindingLayer::WATER);
 	const int cost = pathfinderHelper->getMovementCost(h->visitablePos(), hmpos, nullptr, nullptr, h->movement);
 
 	//it's a rock or blocked and not visitable tile
@@ -2751,8 +2751,8 @@ void CGameHandler::useScholarSkill(ObjectInstanceID fromHero, ObjectInstanceID t
 {
 	const CGHeroInstance * h1 = getHero(fromHero);
 	const CGHeroInstance * h2 = getHero(toHero);
-	int h1_scholarSpellLevel = h1->valOfBonuses(Bonus::LEARN_MEETING_SPELL_LIMIT, -1);
-	int h2_scholarSpellLevel = h2->valOfBonuses(Bonus::LEARN_MEETING_SPELL_LIMIT, -1);
+	int h1_scholarSpellLevel = h1->valOfBonuses(BonusType::LEARN_MEETING_SPELL_LIMIT, -1);
+	int h2_scholarSpellLevel = h2->valOfBonuses(BonusType::LEARN_MEETING_SPELL_LIMIT, -1);
 
 	if (h1_scholarSpellLevel < h2_scholarSpellLevel)
 	{
@@ -3636,7 +3636,7 @@ bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid)
 // 	{
 // 		RemoveBonus rb(RemoveBonus::TOWN);
 // 		rb.whoID = t->id;
-// 		rb.source = Bonus::TOWN_STRUCTURE;
+// 		rb.source = BonusSource::TOWN_STRUCTURE;
 // 		rb.id = 17;
 // 		sendAndApply(&rb);
 // 	}
@@ -4322,8 +4322,8 @@ bool CGameHandler::transformInUndead(const IMarket *market, const CGHeroInstance
 	//resulting creature - bone dragons or skeletons
 	CreatureID resCreature = CreatureID::SKELETON;
 
-	if ((s.hasBonusOfType(Bonus::DRAGON_NATURE)
-			&& !(s.hasBonusOfType(Bonus::UNDEAD)))
+	if ((s.hasBonusOfType(BonusType::DRAGON_NATURE)
+			&& !(s.hasBonusOfType(BonusType::UNDEAD)))
 			|| (s.getCreatureID() == CreatureID::HYDRA)
 			|| (s.getCreatureID() == CreatureID::CHAOS_HYDRA))
 		resCreature = CreatureID::BONE_DRAGON;
@@ -4619,12 +4619,12 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
 		{
 			//defensive stance, TODO: filter out spell boosts from bonus (stone skin etc.)
 			SetStackEffect sse;
-			Bonus defenseBonusToAdd(Bonus::STACK_GETS_TURN, Bonus::PRIMARY_SKILL, Bonus::OTHER, 20, -1, PrimarySkill::DEFENSE, Bonus::PERCENT_TO_ALL);
-			Bonus bonus2(Bonus::STACK_GETS_TURN, Bonus::PRIMARY_SKILL, Bonus::OTHER, stack->valOfBonuses(Bonus::DEFENSIVE_STANCE),
-				 -1, PrimarySkill::DEFENSE, Bonus::ADDITIVE_VALUE);
-			Bonus alternativeWeakCreatureBonus(Bonus::STACK_GETS_TURN, Bonus::PRIMARY_SKILL, Bonus::OTHER, 1, -1, PrimarySkill::DEFENSE, Bonus::ADDITIVE_VALUE);
+			Bonus defenseBonusToAdd(BonusDuration::STACK_GETS_TURN, BonusType::PRIMARY_SKILL, BonusSource::OTHER, 20, -1, PrimarySkill::DEFENSE, BonusValueType::PERCENT_TO_ALL);
+			Bonus bonus2(BonusDuration::STACK_GETS_TURN, BonusType::PRIMARY_SKILL, BonusSource::OTHER, stack->valOfBonuses(BonusType::DEFENSIVE_STANCE),
+				 -1, PrimarySkill::DEFENSE, BonusValueType::ADDITIVE_VALUE);
+			Bonus alternativeWeakCreatureBonus(BonusDuration::STACK_GETS_TURN, BonusType::PRIMARY_SKILL, BonusSource::OTHER, 1, -1, PrimarySkill::DEFENSE, BonusValueType::ADDITIVE_VALUE);
 
-			BonusList defence = *stack->getBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE));
+			BonusList defence = *stack->getBonuses(Selector::typeSubtype(BonusType::PRIMARY_SKILL, PrimarySkill::DEFENSE));
 			int oldDefenceValue = defence.totalValue();
 
 			defence.push_back(std::make_shared<Bonus>(defenseBonusToAdd));
@@ -4757,11 +4757,11 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
 			const auto * attackingHero = gs->curB->battleGetFightingHero(ba.side);
 			if(attackingHero)
 			{
-				totalAttacks += attackingHero->valOfBonuses(Bonus::HERO_GRANTS_ATTACKS, stack->creatureIndex());
+				totalAttacks += attackingHero->valOfBonuses(BonusType::HERO_GRANTS_ATTACKS, stack->creatureIndex());
 			}
 
 
-			const bool firstStrike = destinationStack->hasBonusOfType(Bonus::FIRST_STRIKE);
+			const bool firstStrike = destinationStack->hasBonusOfType(BonusType::FIRST_STRIKE);
 			const bool retaliation = destinationStack->ableToRetaliate();
 			for (int i = 0; i < totalAttacks; ++i)
 			{
@@ -4772,7 +4772,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
 				}
 
 				//move can cause death, eg. by walking into the moat, first strike can cause death or paralysis/petrification
-				if(stack->alive() && !stack->hasBonusOfType(Bonus::NOT_ACTIVE) && destinationStack->alive())
+				if(stack->alive() && !stack->hasBonusOfType(BonusType::NOT_ACTIVE) && destinationStack->alive())
 				{
 					makeAttack(stack, destinationStack, (i ? 0 : distance), destinationTile, i==0, false, false);//no distance travelled on second attack
 				}
@@ -4780,7 +4780,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
 				//counterattack
 				//we check retaliation twice, so if it unblocked during attack it will work only on next attack
 				if(stack->alive()
-					&& !stack->hasBonusOfType(Bonus::BLOCKS_RETALIATION)
+					&& !stack->hasBonusOfType(BonusType::BLOCKS_RETALIATION)
 					&& (i == 0 && !firstStrike)
 					&& retaliation && destinationStack->ableToRetaliate())
 				{
@@ -4789,7 +4789,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
 			}
 
 			//return
-			if(stack->hasBonusOfType(Bonus::RETURN_AFTER_STRIKE)
+			if(stack->hasBonusOfType(BonusType::RETURN_AFTER_STRIKE)
 				&& target.size() == 3
 				&& startingPos != stack->getPosition()
 				&& startingPos == target.at(2).hexValue
@@ -4829,8 +4829,8 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
 			makeAttack(stack, destinationStack, 0, destination, true, true, false);
 
 			//ranged counterattack
-			if (destinationStack->hasBonusOfType(Bonus::RANGED_RETALIATION)
-				&& !stack->hasBonusOfType(Bonus::BLOCKS_RANGED_RETALIATION)
+			if (destinationStack->hasBonusOfType(BonusType::RANGED_RETALIATION)
+				&& !stack->hasBonusOfType(BonusType::BLOCKS_RANGED_RETALIATION)
 				&& destinationStack->ableToRetaliate()
 				&& gs->curB->battleCanShoot(destinationStack, stack->getPosition())
 				&& stack->alive()) //attacker may have died (fire shield)
@@ -4845,7 +4845,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
 			const auto * attackingHero = gs->curB->battleGetFightingHero(ba.side);
 			if(attackingHero)
 			{
-				totalRangedAttacks += attackingHero->valOfBonuses(Bonus::HERO_GRANTS_ATTACKS, stack->creatureIndex());
+				totalRangedAttacks += attackingHero->valOfBonuses(BonusType::HERO_GRANTS_ATTACKS, stack->creatureIndex());
 			}
 
 
@@ -4866,7 +4866,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
 		{
 			auto wrapper = wrapAction(ba);
 			const CStack * shooter = gs->curB->battleGetStackByID(ba.stackNumber);
-			std::shared_ptr<const Bonus> catapultAbility = stack->getBonusLocalFirst(Selector::type()(Bonus::CATAPULT));
+			std::shared_ptr<const Bonus> catapultAbility = stack->getBonusLocalFirst(Selector::type()(BonusType::CATAPULT));
 			if(!catapultAbility || catapultAbility->subtype < 0)
 			{
 				complain("We do not know how to shoot :P");
@@ -4875,7 +4875,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
 			{
 				const CSpell * spell = SpellID(catapultAbility->subtype).toSpell();
 				spells::BattleCast parameters(gs->curB, shooter, spells::Mode::SPELL_LIKE_ATTACK, spell); //We can shot infinitely by catapult
-				auto shotLevel = stack->valOfBonuses(Selector::typeSubtype(Bonus::CATAPULT_EXTRA_SHOTS, catapultAbility->subtype));
+				auto shotLevel = stack->valOfBonuses(Selector::typeSubtype(BonusType::CATAPULT_EXTRA_SHOTS, catapultAbility->subtype));
 				parameters.setSpellLevel(shotLevel);
 				parameters.cast(spellEnv, target);
 			}
@@ -4895,7 +4895,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
 			}
 
 			const battle::Unit * destStack = nullptr;
-			std::shared_ptr<const Bonus> healerAbility = stack->getBonusLocalFirst(Selector::type()(Bonus::HEALER));
+			std::shared_ptr<const Bonus> healerAbility = stack->getBonusLocalFirst(Selector::type()(BonusType::HEALER));
 
 			if(target.at(0).unitValue)
 				destStack = target.at(0).unitValue;
@@ -4923,8 +4923,8 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
 			const CStack * stack = gs->curB->battleGetStackByID(ba.stackNumber);
 			SpellID spellID = SpellID(ba.actionSubtype);
 
-			std::shared_ptr<const Bonus> randSpellcaster = stack->getBonus(Selector::type()(Bonus::RANDOM_SPELLCASTER));
-			std::shared_ptr<const Bonus> spellcaster = stack->getBonus(Selector::typeSubtype(Bonus::SPELLCASTER, spellID));
+			std::shared_ptr<const Bonus> randSpellcaster = stack->getBonus(Selector::type()(BonusType::RANDOM_SPELLCASTER));
+			std::shared_ptr<const Bonus> spellcaster = stack->getBonus(Selector::typeSubtype(BonusType::SPELLCASTER, spellID));
 
 			//TODO special bonus for genies ability
 			if (randSpellcaster && battleGetRandomStackSpell(getRandomGenerator(), stack, CBattleInfoCallback::RANDOM_AIMED) < 0)
@@ -5148,7 +5148,7 @@ bool CGameHandler::makeCustomAction(BattleAction & ba)
 
 void CGameHandler::stackEnchantedTrigger(const CStack * st)
 {
-	auto bl = *(st->getBonuses(Selector::type()(Bonus::ENCHANTED)));
+	auto bl = *(st->getBonuses(Selector::type()(BonusType::ENCHANTED)));
 	for(auto b : bl)
 	{
 		const CSpell * sp = SpellID(b->subtype).toSpell();
@@ -5188,10 +5188,10 @@ void CGameHandler::stackTurnTrigger(const CStack *st)
 	if (st->alive())
 	{
 		//unbind
-		if (st->hasBonus(Selector::type()(Bonus::BIND_EFFECT)))
+		if (st->hasBonus(Selector::type()(BonusType::BIND_EFFECT)))
 		{
 			bool unbind = true;
-			BonusList bl = *(st->getBonuses(Selector::type()(Bonus::BIND_EFFECT)));
+			BonusList bl = *(st->getBonuses(Selector::type()(BonusType::BIND_EFFECT)));
 			auto adjacent = gs->curB->battleAdjacentUnits(st);
 
 			for (auto b : bl)
@@ -5219,42 +5219,42 @@ void CGameHandler::stackTurnTrigger(const CStack *st)
 			}
 		}
 
-		if (st->hasBonusOfType(Bonus::POISON))
+		if (st->hasBonusOfType(BonusType::POISON))
 		{
-			std::shared_ptr<const Bonus> b = st->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT, SpellID::POISON).And(Selector::type()(Bonus::STACK_HEALTH)));
+			std::shared_ptr<const Bonus> b = st->getBonusLocalFirst(Selector::source(BonusSource::SPELL_EFFECT, SpellID::POISON).And(Selector::type()(BonusType::STACK_HEALTH)));
 			if (b) //TODO: what if not?...
 			{
-				bte.val = std::max (b->val - 10, -(st->valOfBonuses(Bonus::POISON)));
+				bte.val = std::max (b->val - 10, -(st->valOfBonuses(BonusType::POISON)));
 				if (bte.val < b->val) //(negative) poison effect increases - update it
 				{
-					bte.effect = Bonus::POISON;
+					bte.effect = vstd::to_underlying(BonusType::POISON);
 					sendAndApply(&bte);
 				}
 			}
 		}
-		if(st->hasBonusOfType(Bonus::MANA_DRAIN) && !st->drainedMana)
+		if(st->hasBonusOfType(BonusType::MANA_DRAIN) && !st->drainedMana)
 		{
 			const PlayerColor opponent = gs->curB->otherPlayer(gs->curB->battleGetOwner(st));
 			const CGHeroInstance * opponentHero = gs->curB->getHero(opponent);
 			if(opponentHero)
 			{
-				ui32 manaDrained = st->valOfBonuses(Bonus::MANA_DRAIN);
+				ui32 manaDrained = st->valOfBonuses(BonusType::MANA_DRAIN);
 				vstd::amin(manaDrained, opponentHero->mana);
 				if(manaDrained)
 				{
-					bte.effect = Bonus::MANA_DRAIN;
+					bte.effect = vstd::to_underlying(BonusType::MANA_DRAIN);
 					bte.val = manaDrained;
 					bte.additionalInfo = opponentHero->id.getNum(); //for sanity
 					sendAndApply(&bte);
 				}
 			}
 		}
-		if (st->isLiving() && !st->hasBonusOfType(Bonus::FEARLESS))
+		if (st->isLiving() && !st->hasBonusOfType(BonusType::FEARLESS))
 		{
 			bool fearsomeCreature = false;
 			for (CStack * stack : gs->curB->stacks)
 			{
-				if (battleMatchOwner(st, stack) && stack->alive() && stack->hasBonusOfType(Bonus::FEAR))
+				if (battleMatchOwner(st, stack) && stack->alive() && stack->hasBonusOfType(BonusType::FEAR))
 				{
 					fearsomeCreature = true;
 					break;
@@ -5264,12 +5264,12 @@ void CGameHandler::stackTurnTrigger(const CStack *st)
 			{
 				if (getRandomGenerator().nextInt(99) < 10) //fixed 10%
 				{
-					bte.effect = Bonus::FEAR;
+					bte.effect = vstd::to_underlying(BonusType::FEAR);
 					sendAndApply(&bte);
 				}
 			}
 		}
-		BonusList bl = *(st->getBonuses(Selector::type()(Bonus::ENCHANTER)));
+		BonusList bl = *(st->getBonuses(Selector::type()(BonusType::ENCHANTER)));
 		int side = gs->curB->whatSide(st->unitOwner());
 		if(st->canCast() && gs->curB->battleGetEnchanterCounter(side) == 0)
 		{
@@ -5829,7 +5829,7 @@ bool CGameHandler::dig(const CGHeroInstance *h)
 	return true;
 }
 
-void CGameHandler::attackCasting(bool ranged, Bonus::BonusType attackMode, const battle::Unit * attacker, const battle::Unit * defender)
+void CGameHandler::attackCasting(bool ranged, BonusType attackMode, const battle::Unit * attacker, const battle::Unit * defender)
 {
 	if(attacker->hasBonusOfType(attackMode))
 	{
@@ -5899,7 +5899,7 @@ void CGameHandler::attackCasting(bool ranged, Bonus::BonusType attackMode, const
 
 void CGameHandler::handleAttackBeforeCasting(bool ranged, const CStack * attacker, const CStack * defender)
 {
-	attackCasting(ranged, Bonus::SPELL_BEFORE_ATTACK, attacker, defender); //no death stare / acid breath needed?
+	attackCasting(ranged, BonusType::SPELL_BEFORE_ATTACK, attacker, defender); //no death stare / acid breath needed?
 }
 
 void CGameHandler::handleAfterAttackCasting(bool ranged, const CStack * attacker, const CStack * defender)
@@ -5907,7 +5907,7 @@ void CGameHandler::handleAfterAttackCasting(bool ranged, const CStack * attacker
 	if(!attacker->alive() || !defender->alive()) // can be already dead
 		return;
 
-	attackCasting(ranged, Bonus::SPELL_AFTER_ATTACK, attacker, defender);
+	attackCasting(ranged, BonusType::SPELL_AFTER_ATTACK, attacker, defender);
 
 	if(!defender->alive())
 	{
@@ -5915,13 +5915,13 @@ void CGameHandler::handleAfterAttackCasting(bool ranged, const CStack * attacker
 		return;
 	}
 
-	if(attacker->hasBonusOfType(Bonus::DEATH_STARE))
+	if(attacker->hasBonusOfType(BonusType::DEATH_STARE))
 	{
 		// mechanics of Death Stare as in H3:
 		// each gorgon have 10% chance to kill (counted separately in H3) -> binomial distribution
 		//original formula x = min(x, (gorgons_count + 9)/10);
 
-		double chanceToKill = attacker->valOfBonuses(Bonus::DEATH_STARE, 0) / 100.0f;
+		double chanceToKill = attacker->valOfBonuses(BonusType::DEATH_STARE, 0) / 100.0f;
 		vstd::amin(chanceToKill, 1); //cap at 100%
 
 		std::binomial_distribution<> distribution(attacker->getCount(), chanceToKill);
@@ -5932,7 +5932,7 @@ void CGameHandler::handleAfterAttackCasting(bool ranged, const CStack * attacker
 		int maxToKill = static_cast<int>((attacker->getCount() + cap - 1) / cap); //not much more than chance * count
 		vstd::amin(staredCreatures, maxToKill);
 
-		staredCreatures += (attacker->level() * attacker->valOfBonuses(Bonus::DEATH_STARE, 1)) / defender->level();
+		staredCreatures += (attacker->level() * attacker->valOfBonuses(BonusType::DEATH_STARE, 1)) / defender->level();
 		if(staredCreatures)
 		{
 			//TODO: death stare was not originally available for multiple-hex attacks, but...
@@ -5952,7 +5952,7 @@ void CGameHandler::handleAfterAttackCasting(bool ranged, const CStack * attacker
 		return;
 
 	int64_t acidDamage = 0;
-	TConstBonusListPtr acidBreath = attacker->getBonuses(Selector::type()(Bonus::ACID_BREATH));
+	TConstBonusListPtr acidBreath = attacker->getBonuses(Selector::type()(BonusType::ACID_BREATH));
 	for(const auto & b : *acidBreath)
 	{
 		if(b->additionalInfo[0] > getRandomGenerator().nextInt(99))
@@ -5977,15 +5977,15 @@ void CGameHandler::handleAfterAttackCasting(bool ranged, const CStack * attacker
 	if(!defender->alive())
 		return;
 
-	if(attacker->hasBonusOfType(Bonus::TRANSMUTATION) && defender->isLiving()) //transmutation mechanics, similar to WoG werewolf ability
+	if(attacker->hasBonusOfType(BonusType::TRANSMUTATION) && defender->isLiving()) //transmutation mechanics, similar to WoG werewolf ability
 	{
-		double chanceToTrigger = attacker->valOfBonuses(Bonus::TRANSMUTATION) / 100.0f;
+		double chanceToTrigger = attacker->valOfBonuses(BonusType::TRANSMUTATION) / 100.0f;
 		vstd::amin(chanceToTrigger, 1); //cap at 100%
 
 		if(getRandomGenerator().getDoubleRange(0, 1)() > chanceToTrigger)
 			return;
 
-		int bonusAdditionalInfo = attacker->getBonus(Selector::type()(Bonus::TRANSMUTATION))->additionalInfo[0];
+		int bonusAdditionalInfo = attacker->getBonus(Selector::type()(BonusType::TRANSMUTATION))->additionalInfo[0];
 
 		if(defender->unitType()->getId() == bonusAdditionalInfo ||
 			(bonusAdditionalInfo == CAddInfo::NONE && defender->unitType()->getId() == attacker->unitType()->getId()))
@@ -6002,9 +6002,9 @@ void CGameHandler::handleAfterAttackCasting(bool ranged, const CStack * attacker
 		else
 			resurrectInfo.type = attacker->creatureId();
 
-		if(attacker->hasBonusOfType((Bonus::TRANSMUTATION), 0))
+		if(attacker->hasBonusOfType((BonusType::TRANSMUTATION), 0))
 			resurrectInfo.count = std::max((defender->getCount() * defender->getMaxHealth()) / resurrectInfo.type.toCreature()->getMaxHealth(), 1u);
-		else if (attacker->hasBonusOfType((Bonus::TRANSMUTATION), 1))
+		else if (attacker->hasBonusOfType((BonusType::TRANSMUTATION), 1))
 			resurrectInfo.count = defender->getCount();
 		else
 			return; //wrong subtype
@@ -6019,21 +6019,21 @@ void CGameHandler::handleAfterAttackCasting(bool ranged, const CStack * attacker
 		sendAndApply(&addUnits);
 	}
 
-	if(attacker->hasBonusOfType(Bonus::DESTRUCTION, 0) || attacker->hasBonusOfType(Bonus::DESTRUCTION, 1))
+	if(attacker->hasBonusOfType(BonusType::DESTRUCTION, 0) || attacker->hasBonusOfType(BonusType::DESTRUCTION, 1))
 	{
 		double chanceToTrigger = 0;
 		int amountToDie = 0;
 
-		if(attacker->hasBonusOfType(Bonus::DESTRUCTION, 0)) //killing by percentage
+		if(attacker->hasBonusOfType(BonusType::DESTRUCTION, 0)) //killing by percentage
 		{
-			chanceToTrigger = attacker->valOfBonuses(Bonus::DESTRUCTION, 0) / 100.0f;
-			int percentageToDie = attacker->getBonus(Selector::type()(Bonus::DESTRUCTION).And(Selector::subtype()(0)))->additionalInfo[0];
+			chanceToTrigger = attacker->valOfBonuses(BonusType::DESTRUCTION, 0) / 100.0f;
+			int percentageToDie = attacker->getBonus(Selector::type()(BonusType::DESTRUCTION).And(Selector::subtype()(0)))->additionalInfo[0];
 			amountToDie = static_cast<int>(defender->getCount() * percentageToDie * 0.01f);
 		}
-		else if(attacker->hasBonusOfType(Bonus::DESTRUCTION, 1)) //killing by count
+		else if(attacker->hasBonusOfType(BonusType::DESTRUCTION, 1)) //killing by count
 		{
-			chanceToTrigger = attacker->valOfBonuses(Bonus::DESTRUCTION, 1) / 100.0f;
-			amountToDie = attacker->getBonus(Selector::type()(Bonus::DESTRUCTION).And(Selector::subtype()(1)))->additionalInfo[0];
+			chanceToTrigger = attacker->valOfBonuses(BonusType::DESTRUCTION, 1) / 100.0f;
+			amountToDie = attacker->getBonus(Selector::type()(BonusType::DESTRUCTION).And(Selector::subtype()(1)))->additionalInfo[0];
 		}
 
 		vstd::amin(chanceToTrigger, 1); //cap trigger chance at 100%
@@ -6371,9 +6371,9 @@ void CGameHandler::runBattle()
 
 	for (CStack * stack : initialStacks)
 	{
-		if (stack->hasBonusOfType(Bonus::SUMMON_GUARDIANS))
+		if (stack->hasBonusOfType(BonusType::SUMMON_GUARDIANS))
 		{
-			std::shared_ptr<const Bonus> summonInfo = stack->getBonus(Selector::type()(Bonus::SUMMON_GUARDIANS));
+			std::shared_ptr<const Bonus> summonInfo = stack->getBonus(Selector::type()(BonusType::SUMMON_GUARDIANS));
 			auto accessibility = getAccesibility();
 			CreatureID creatureData = CreatureID(summonInfo->subtype);
 			std::vector<BattleHex> targetHexes;
@@ -6418,7 +6418,7 @@ void CGameHandler::runBattle()
 		auto h = gs->curB->battleGetFightingHero(i);
 		if (h)
 		{
-			TConstBonusListPtr bl = h->getBonuses(Selector::type()(Bonus::OPENING_BATTLE_SPELL));
+			TConstBonusListPtr bl = h->getBonuses(Selector::type()(BonusType::OPENING_BATTLE_SPELL));
 
 			for (auto b : *bl)
 			{
@@ -6486,11 +6486,11 @@ void CGameHandler::runBattle()
 					{
 						BattleTriggerEffect bte;
 						bte.stackID = stack->unitId();
-						bte.effect = Bonus::HP_REGENERATION;
+						bte.effect = vstd::to_underlying(BonusType::HP_REGENERATION);
 
 						const int32_t lostHealth = stack->getMaxHealth() - stack->getFirstHPleft();
-						if(stack->hasBonusOfType(Bonus::HP_REGENERATION))
-							bte.val = std::min(lostHealth, stack->valOfBonuses(Bonus::HP_REGENERATION));
+						if(stack->hasBonusOfType(BonusType::HP_REGENERATION))
+							bte.val = std::min(lostHealth, stack->valOfBonuses(BonusType::HP_REGENERATION));
 
 						if(bte.val) // anything to heal
 							sendAndApply(&bte);
@@ -6537,7 +6537,7 @@ void CGameHandler::runBattle()
 				}
 			}
 
-			if (next->hasBonusOfType(Bonus::ATTACKS_NEAREST_CREATURE)) //while in berserk
+			if (next->hasBonusOfType(BonusType::ATTACKS_NEAREST_CREATURE)) //while in berserk
 			{
 				logGlobal->trace("Handle Berserk effect");
 				std::pair<const battle::Unit *, BattleHex> attackInfo = curB.getNearestStack(next);
@@ -6565,7 +6565,7 @@ void CGameHandler::runBattle()
 			const int stackCreatureId = next->unitType()->getId();
 
 			if ((stackCreatureId == CreatureID::ARROW_TOWERS || stackCreatureId == CreatureID::BALLISTA)
-				&& (!curOwner || getRandomGenerator().nextInt(99) >= curOwner->valOfBonuses(Bonus::MANUAL_CONTROL, stackCreatureId)))
+				&& (!curOwner || getRandomGenerator().nextInt(99) >= curOwner->valOfBonuses(BonusType::MANUAL_CONTROL, stackCreatureId)))
 			{
 				BattleAction attack;
 				attack.actionType = EActionType::SHOOT;
@@ -6610,7 +6610,7 @@ void CGameHandler::runBattle()
 					continue;
 				}
 
-				if (!curOwner || getRandomGenerator().nextInt(99) >= curOwner->valOfBonuses(Bonus::MANUAL_CONTROL, CreatureID::CATAPULT))
+				if (!curOwner || getRandomGenerator().nextInt(99) >= curOwner->valOfBonuses(BonusType::MANUAL_CONTROL, CreatureID::CATAPULT))
 				{
 					BattleAction attack;
 					attack.actionType = EActionType::CATAPULT;
@@ -6635,7 +6635,7 @@ void CGameHandler::runBattle()
 					continue;
 				}
 
-				if (!curOwner || getRandomGenerator().nextInt(99) >= curOwner->valOfBonuses(Bonus::MANUAL_CONTROL, CreatureID::FIRST_AID_TENT))
+				if (!curOwner || getRandomGenerator().nextInt(99) >= curOwner->valOfBonuses(BonusType::MANUAL_CONTROL, CreatureID::FIRST_AID_TENT))
 				{
 					RandomGeneratorUtil::randomShuffle(possibleStacks, getRandomGenerator());
 					const CStack * toBeHealed = possibleStacks.front();
@@ -6720,7 +6720,7 @@ void CGameHandler::runBattle()
 						{
 							BattleTriggerEffect bte;
 							bte.stackID = next->unitId();
-							bte.effect = Bonus::MORALE;
+							bte.effect = vstd::to_underlying(BonusType::MORALE);
 							bte.val = 1;
 							bte.additionalInfo = 0;
 							sendAndApply(&bte); //play animation
@@ -6872,7 +6872,7 @@ void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, cons
 		///Give all spells with bonus (to allow banned spells)
 		GiveBonus giveBonus(GiveBonus::ETarget::HERO);
 		giveBonus.id = hero->id.getNum();
-		giveBonus.bonus = Bonus(Bonus::PERMANENT, Bonus::SPELLS_OF_LEVEL, Bonus::OTHER, 0, 0);
+		giveBonus.bonus = Bonus(BonusDuration::PERMANENT, BonusType::SPELLS_OF_LEVEL, BonusSource::OTHER, 0, 0);
 		//start with level 0 to skip abilities
 		for (int level = 1; level <= GameConstants::SPELL_LEVELS; level++)
 		{
@@ -7017,9 +7017,9 @@ void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, cons
 		sendAndApply(&smp);
 
 		GiveBonus gb(GiveBonus::ETarget::HERO);
-		gb.bonus.type = Bonus::FREE_SHIP_BOARDING;
-		gb.bonus.duration = Bonus::ONE_DAY;
-		gb.bonus.source = Bonus::OTHER;
+		gb.bonus.type = BonusType::FREE_SHIP_BOARDING;
+		gb.bonus.duration = BonusDuration::ONE_DAY;
+		gb.bonus.source = BonusSource::OTHER;
 		gb.id = hero->id.getNum();
 		giveHeroBonus(&gb);
 	}

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott