瀏覽代碼

Merge pull request #3064 from IvanSavenko/bonus_metaidentifier

Type-safe bonus system
Ivan Savenko 2 年之前
父節點
當前提交
3867e512f7
共有 100 個文件被更改,包括 1448 次插入835 次删除
  1. 1 1
      AI/Nullkiller/Analyzers/ArmyManager.cpp
  2. 5 5
      AI/Nullkiller/Analyzers/HeroManager.cpp
  3. 6 6
      AI/Nullkiller/Engine/PriorityEvaluator.cpp
  4. 4 4
      AI/Nullkiller/Pathfinding/AINodeStorage.cpp
  5. 1 1
      AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp
  6. 1 1
      AI/VCAI/Pathfinding/AINodeStorage.cpp
  7. 1 1
      AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp
  8. 2 2
      client/NetPacksClient.cpp
  9. 1 1
      client/battle/BattleActionsController.cpp
  10. 3 3
      client/battle/BattleStacksController.cpp
  11. 1 1
      client/battle/BattleWindow.cpp
  12. 4 4
      client/windows/CCreatureWindow.cpp
  13. 1 1
      client/windows/CKingdomInterface.cpp
  14. 4 0
      cmake_modules/VCMI_lib.cmake
  15. 104 115
      config/artifacts.json
  16. 0 3
      config/battlefields.json
  17. 17 17
      config/commanders.json
  18. 1 1
      config/creatures/fortress.json
  19. 2 2
      config/creatures/inferno.json
  20. 2 4
      config/creatures/necropolis.json
  21. 3 3
      config/creatures/neutral.json
  22. 1 1
      config/factions/dungeon.json
  23. 2 2
      config/factions/fortress.json
  24. 1 1
      config/factions/stronghold.json
  25. 1 1
      config/factions/tower.json
  26. 2 2
      config/gameConfig.json
  27. 3 4
      config/heroes/castle.json
  28. 18 18
      config/heroes/conflux.json
  29. 1 2
      config/heroes/dungeon.json
  30. 2 3
      config/heroes/fortress.json
  31. 0 1
      config/heroes/necropolis.json
  32. 2 3
      config/heroes/rampart.json
  33. 12 12
      config/heroes/special.json
  34. 2 3
      config/heroes/stronghold.json
  35. 1 2
      config/heroes/tower.json
  36. 2 2
      config/objects/rewardableBonusing.json
  37. 1 4
      config/schemas/bonus.json
  38. 5 8
      config/skills.json
  39. 3 3
      config/spells/ability.json
  40. 7 8
      config/spells/adventure.json
  41. 7 7
      config/spells/moats.json
  42. 10 12
      config/spells/timed.json
  43. 64 25
      docs/modders/Bonus/Bonus_Types.md
  44. 2 2
      docs/modders/Game_Identifiers.md
  45. 1 1
      include/vcmi/FactionMember.h
  46. 1 1
      lib/ArtifactUtils.cpp
  47. 6 6
      lib/BasicTypes.cpp
  48. 1 1
      lib/BattleFieldHandler.cpp
  49. 1 1
      lib/CArtHandler.cpp
  50. 1 1
      lib/CArtifactInstance.cpp
  51. 20 23
      lib/CBonusTypeHandler.cpp
  52. 54 51
      lib/CCreatureHandler.cpp
  53. 2 1
      lib/CCreatureHandler.h
  54. 3 3
      lib/CGameInfoCallback.cpp
  55. 4 4
      lib/CHeroHandler.cpp
  56. 1 1
      lib/CSkillHandler.cpp
  57. 6 6
      lib/CStack.cpp
  58. 1 1
      lib/CStack.h
  59. 43 13
      lib/CTownHandler.cpp
  60. 7 6
      lib/CTownHandler.h
  61. 258 67
      lib/JsonNode.cpp
  62. 1 10
      lib/JsonNode.h
  63. 2 2
      lib/NetPacks.h
  64. 4 4
      lib/NetPacksLib.cpp
  65. 4 4
      lib/battle/BattleInfo.cpp
  66. 3 3
      lib/battle/CBattleInfoCallback.cpp
  67. 8 8
      lib/battle/CUnitState.cpp
  68. 14 13
      lib/battle/DamageCalculator.cpp
  69. 25 36
      lib/bonuses/Bonus.cpp
  70. 12 19
      lib/bonuses/Bonus.h
  71. 74 0
      lib/bonuses/BonusCustomTypes.cpp
  72. 75 0
      lib/bonuses/BonusCustomTypes.h
  73. 2 2
      lib/bonuses/BonusEnum.h
  74. 1 1
      lib/bonuses/BonusList.cpp
  75. 1 1
      lib/bonuses/BonusList.h
  76. 38 41
      lib/bonuses/BonusParams.cpp
  77. 2 3
      lib/bonuses/BonusParams.h
  78. 8 8
      lib/bonuses/BonusSelector.cpp
  79. 5 5
      lib/bonuses/BonusSelector.h
  80. 8 8
      lib/bonuses/IBonusBearer.cpp
  81. 4 4
      lib/bonuses/IBonusBearer.h
  82. 8 8
      lib/bonuses/Limiters.cpp
  83. 4 4
      lib/bonuses/Limiters.h
  84. 0 6
      lib/campaign/CampaignConstants.h
  85. 129 2
      lib/constants/EntityIdentifiers.cpp
  86. 84 73
      lib/constants/EntityIdentifiers.h
  87. 2 12
      lib/constants/Enumerations.h
  88. 64 0
      lib/constants/IdentifierBase.h
  89. 80 0
      lib/constants/VariantIdentifier.h
  90. 2 2
      lib/gameState/CGameState.cpp
  91. 13 15
      lib/gameState/CGameStateCampaign.cpp
  92. 5 0
      lib/mapObjectConstructors/CObjectClassesHandler.cpp
  93. 2 0
      lib/mapObjectConstructors/CObjectClassesHandler.h
  94. 2 2
      lib/mapObjectConstructors/CRewardableConstructor.cpp
  95. 3 4
      lib/mapObjects/CArmedInstance.cpp
  96. 3 3
      lib/mapObjects/CBank.cpp
  97. 1 1
      lib/mapObjects/CGCreature.cpp
  98. 29 39
      lib/mapObjects/CGHeroInstance.cpp
  99. 1 1
      lib/mapObjects/CGHeroInstance.h
  100. 2 2
      lib/mapObjects/CGObjectInstance.cpp

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

@@ -153,7 +153,7 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
 	for(auto bonus : *bonusModifiers)
 	{
 		// army bonuses will change and object bonuses are temporary
-		if(bonus->source != BonusSource::ARMY && bonus->source != BonusSource::OBJECT)
+		if(bonus->source != BonusSource::ARMY && bonus->source != BonusSource::OBJECT_INSTANCE && bonus->source != BonusSource::OBJECT_TYPE)
 		{
 			newArmyInstance.addNewBonus(std::make_shared<Bonus>(*bonus));
 		}

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

@@ -71,7 +71,7 @@ float HeroManager::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance *
 
 float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const
 {
-	auto heroSpecial = Selector::source(BonusSource::HERO_SPECIAL, hero->type->getIndex());
+	auto heroSpecial = Selector::source(BonusSource::HERO_SPECIAL, BonusSourceID(hero->type->getId()));
 	auto secondarySkillBonus = Selector::targetSourceType()(BonusSource::SECONDARY_SKILL);
 	auto specialSecondarySkillBonuses = hero->getBonuses(heroSpecial.And(secondarySkillBonus));
 	auto secondarySkillBonuses = hero->getBonuses(Selector::sourceTypeSel(BonusSource::SECONDARY_SKILL));
@@ -83,7 +83,7 @@ float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const
 
 		if(hasBonus)
 		{
-			SecondarySkill bonusSkill = SecondarySkill(bonus->sid);
+			SecondarySkill bonusSkill = bonus->sid.as<SecondarySkill>();
 			float bonusScore = wariorSkillsScores.evaluateSecSkill(hero, bonusSkill);
 
 			if(bonusScore > 0)
@@ -313,7 +313,7 @@ void ExistingSkillRule::evaluateScore(const CGHeroInstance * hero, SecondarySkil
 		if(heroSkill.first == skill)
 			return;
 
-		upgradesLeft += SecSkillLevel::EXPERT - heroSkill.second;
+		upgradesLeft += MasteryLevel::EXPERT - heroSkill.second;
 	}
 
 	if(score >= 2 || (score >= 1 && upgradesLeft <= 1))
@@ -327,7 +327,7 @@ void WisdomRule::evaluateScore(const CGHeroInstance * hero, SecondarySkill skill
 
 	auto wisdomLevel = hero->getSecSkillLevel(SecondarySkill::WISDOM);
 
-	if(hero->level > 10 && wisdomLevel == SecSkillLevel::NONE)
+	if(hero->level > 10 && wisdomLevel == MasteryLevel::NONE)
 		score += 1.5;
 }
 
@@ -345,7 +345,7 @@ void AtLeastOneMagicRule::evaluateScore(const CGHeroInstance * hero, SecondarySk
 	
 	bool heroHasAnyMagic = vstd::contains_if(magicSchools, [&](SecondarySkill skill) -> bool
 	{
-		return hero->getSecSkillLevel(skill) > SecSkillLevel::NONE;
+		return hero->getSecSkillLevel(skill) > MasteryLevel::NONE;
 	});
 
 	if(!heroHasAnyMagic)

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

@@ -242,13 +242,13 @@ uint64_t evaluateArtifactArmyValue(CArtifactInstance * art)
 		return 1500;
 
 	auto statsValue =
-		10 * art->valOfBonuses(BonusType::MOVEMENT, 1)
+		10 * art->valOfBonuses(BonusType::MOVEMENT, BonusCustomSubtype::heroMovementLand)
 		+ 1200 * art->valOfBonuses(BonusType::STACKS_SPEED)
 		+ 700 * art->valOfBonuses(BonusType::MORALE)
-		+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, static_cast<int>(PrimarySkill::ATTACK))
-		+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, static_cast<int>(PrimarySkill::DEFENSE))
-		+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, static_cast<int>(PrimarySkill::KNOWLEDGE))
-		+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, static_cast<int>(PrimarySkill::SPELL_POWER))
+		+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK))
+		+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::DEFENSE))
+		+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::KNOWLEDGE))
+		+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::SPELL_POWER))
 		+ 500 * art->valOfBonuses(BonusType::LUCK);
 
 	auto classValue = 0;
@@ -538,7 +538,7 @@ float RewardEvaluator::evaluateWitchHutSkillScore(const CGObjectInstance * hut,
 	if(!hut->wasVisited(hero->tempOwner))
 		return role == HeroRole::SCOUT ? 2 : 0;
 
-	if(hero->getSecSkillLevel(skill) != SecSkillLevel::NONE
+	if(hero->getSecSkillLevel(skill) != MasteryLevel::NONE
 		|| hero->secSkills.size() >= GameConstants::SKILL_PER_HERO)
 		return 0;
 

+ 4 - 4
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -984,7 +984,7 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
 struct TowmPortalFinder
 {
 	const std::vector<CGPathNode *> & initialNodes;
-	SecSkillLevel::SecSkillLevel townPortalSkillLevel;
+	MasteryLevel::Type townPortalSkillLevel;
 	uint64_t movementNeeded;
 	const ChainActor * actor;
 	const CGHeroInstance * hero;
@@ -1006,8 +1006,8 @@ struct TowmPortalFinder
 		townPortal = spellID.toSpell();
 
 		// TODO: Copy/Paste from TownPortalMechanics
-		townPortalSkillLevel = SecSkillLevel::SecSkillLevel(hero->getSpellSchoolLevel(townPortal));
-		movementNeeded = GameConstants::BASE_MOVEMENT_COST * (townPortalSkillLevel >= SecSkillLevel::EXPERT ? 2 : 3);
+		townPortalSkillLevel = MasteryLevel::Type(hero->getSpellSchoolLevel(townPortal));
+		movementNeeded = GameConstants::BASE_MOVEMENT_COST * (townPortalSkillLevel >= MasteryLevel::EXPERT ? 2 : 3);
 	}
 
 	bool actorCanCastTownPortal()
@@ -1028,7 +1028,7 @@ struct TowmPortalFinder
 				continue;
 			}
 
-			if(townPortalSkillLevel < SecSkillLevel::ADVANCED)
+			if(townPortalSkillLevel < MasteryLevel::ADVANCED)
 			{
 				const CGTownInstance * nearestTown = *vstd::minElementByFun(targetTowns, [&](const CGTownInstance * t) -> int
 				{

+ 1 - 1
AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp

@@ -139,7 +139,7 @@ namespace AIPathfinding
 			auto summonBoatSpell = SpellID(SpellID::SUMMON_BOAT).toSpell();
 
 			if(hero->canCastThisSpell(summonBoatSpell)
-				&& hero->getSpellSchoolLevel(summonBoatSpell) >= SecSkillLevel::ADVANCED)
+				&& hero->getSpellSchoolLevel(summonBoatSpell) >= MasteryLevel::ADVANCED)
 			{
 				// TODO: For lower school level we might need to check the existance of some boat
 				summonableVirtualBoats[hero] = std::make_shared<SummonBoatAction>();

+ 1 - 1
AI/VCAI/Pathfinding/AINodeStorage.cpp

@@ -250,7 +250,7 @@ void AINodeStorage::calculateTownPortalTeleportations(
 			return;
 		}
 
-		if(skillLevel < SecSkillLevel::ADVANCED)
+		if(skillLevel < MasteryLevel::ADVANCED)
 		{
 			const CGTownInstance * nearestTown = *vstd::minElementByFun(towns, [&](const CGTownInstance * t) -> int
 			{

+ 1 - 1
AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp

@@ -77,7 +77,7 @@ namespace AIPathfinding
 		auto summonBoatSpell = SpellID(SpellID::SUMMON_BOAT).toSpell();
 
 		if(hero->canCastThisSpell(summonBoatSpell)
-			&& hero->getSpellSchoolLevel(summonBoatSpell) >= SecSkillLevel::ADVANCED)
+			&& hero->getSpellSchoolLevel(summonBoatSpell) >= MasteryLevel::ADVANCED)
 		{
 			// TODO: For lower school level we might need to check the existance of some boat
 			summonableVirtualBoat.reset(new SummonBoatAction());

+ 2 - 2
client/NetPacksClient.cpp

@@ -433,14 +433,14 @@ void ApplyClientNetPackVisitor::visitRemoveBonus(RemoveBonus & pack)
 	{
 	case GiveBonus::ETarget::HERO:
 		{
-			const CGHeroInstance *h = gs.getHero(ObjectInstanceID(pack.id));
+			const CGHeroInstance *h = gs.getHero(ObjectInstanceID(pack.whoID));
 			callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroBonusChanged, h, pack.bonus, false);
 		}
 		break;
 	case GiveBonus::ETarget::PLAYER:
 		{
 			//const PlayerState *p = gs.getPlayerState(pack.id);
-			callInterfaceIfPresent(cl, PlayerColor(pack.id), &IGameEventsReceiver::playerBonusChanged, pack.bonus, false);
+			callInterfaceIfPresent(cl, PlayerColor(pack.whoID), &IGameEventsReceiver::playerBonusChanged, pack.bonus, false);
 		}
 		break;
 	}

+ 1 - 1
client/battle/BattleActionsController.cpp

@@ -898,7 +898,7 @@ void BattleActionsController::tryActivateStackSpellcasting(const CStack *casterS
 	for(const auto & bonus : *bl)
 	{
 		if (bonus->additionalInfo[0] <= 0)
-			creatureSpells.push_back(SpellID(bonus->subtype).toSpell());
+			creatureSpells.push_back(bonus->subtype.as<SpellID>().toSpell());
 	}
 }
 

+ 3 - 3
client/battle/BattleStacksController.cpp

@@ -264,7 +264,7 @@ bool BattleStacksController::stackNeedsAmountBox(const CStack * stack) const
 
 std::shared_ptr<IImage> BattleStacksController::getStackAmountBox(const CStack * stack)
 {
-	std::vector<si32> activeSpells = stack->activeSpells();
+	std::vector<SpellID> activeSpells = stack->activeSpells();
 
 	if ( activeSpells.empty())
 		return amountNormal;
@@ -534,7 +534,7 @@ void BattleStacksController::stackMoved(const CStack *stack, std::vector<BattleH
 		addNewAnim(new MovementStartAnimation(owner, stack));
 	});
 
-	if (!stack->hasBonus(Selector::typeSubtype(BonusType::FLYING, 1)))
+	if (!stack->hasBonus(Selector::typeSubtype(BonusType::FLYING, BonusCustomSubtype::movementFlying)))
 	{
 		owner.addToAnimationStage(EAnimationEvents::MOVEMENT, [&]()
 		{
@@ -797,7 +797,7 @@ void BattleStacksController::removeExpiredColorFilters()
 	{
 		if (!filter.persistent)
 		{
-			if (filter.source && !filter.target->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, filter.source->id), Selector::all))
+			if (filter.source && !filter.target->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(filter.source->id)), Selector::all))
 				return true;
 			if (filter.effect == ColorFilter::genEmptyShifter())
 				return true;

+ 1 - 1
client/battle/BattleWindow.cpp

@@ -548,7 +548,7 @@ void BattleWindow::bSpellf()
 
 		if (blockingBonus->source == BonusSource::ARTIFACT)
 		{
-			const auto artID = ArtifactID(blockingBonus->sid);
+			const auto artID = blockingBonus->sid.as<ArtifactID>();
 			//If we have artifact, put name of our hero. Otherwise assume it's the enemy.
 			//TODO check who *really* is source of bonus
 			std::string heroName = myHero->hasArt(artID) ? myHero->getNameTranslated() : owner.enemyHero().name;

+ 4 - 4
client/windows/CCreatureWindow.cpp

@@ -208,10 +208,10 @@ CStackWindow::ActiveSpellsSection::ActiveSpellsSection(CStackWindow * owner, int
 
 	//spell effects
 	int printed=0; //how many effect pics have been printed
-	std::vector<si32> spells = battleStack->activeSpells();
-	for(si32 effect : spells)
+	std::vector<SpellID> spells = battleStack->activeSpells();
+	for(SpellID effect : spells)
 	{
-		const spells::Spell * spell = CGI->spells()->getByIndex(effect);
+		const spells::Spell * spell = CGI->spells()->getById(effect);
 
 		std::string spellText;
 
@@ -224,7 +224,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(BonusSource::SPELL_EFFECT,effect))->turnsRemain;
+			int duration = battleStack->getBonusLocalFirst(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(effect)))->turnsRemain;
 			boost::replace_first(spellText, "%d", std::to_string(duration));
 
 			spellIcons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SpellInt"), effect + 1, 0, firstPos.x + offset.x * printed, firstPos.y + offset.y * printed));

+ 1 - 1
client/windows/CKingdomInterface.cpp

@@ -579,7 +579,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(BonusType::GENERATE_RESOURCE, GameResID(EGameResID::GOLD)));
+		totalIncome += heroe->valOfBonuses(Selector::typeSubtype(BonusType::GENERATE_RESOURCE, BonusSubtypeID(GameResID(EGameResID::GOLD))));
 	}
 
 	//Add town income of all towns

+ 4 - 0
cmake_modules/VCMI_lib.cmake

@@ -31,6 +31,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/bonuses/BonusList.cpp
 		${MAIN_LIB_DIR}/bonuses/BonusParams.cpp
 		${MAIN_LIB_DIR}/bonuses/BonusSelector.cpp
+		${MAIN_LIB_DIR}/bonuses/BonusCustomTypes.cpp
 		${MAIN_LIB_DIR}/bonuses/CBonusProxy.cpp
 		${MAIN_LIB_DIR}/bonuses/CBonusSystemNode.cpp
 		${MAIN_LIB_DIR}/bonuses/IBonusBearer.cpp
@@ -357,6 +358,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/bonuses/BonusList.h
 		${MAIN_LIB_DIR}/bonuses/BonusParams.h
 		${MAIN_LIB_DIR}/bonuses/BonusSelector.h
+		${MAIN_LIB_DIR}/bonuses/BonusCustomTypes.h
 		${MAIN_LIB_DIR}/bonuses/CBonusProxy.h
 		${MAIN_LIB_DIR}/bonuses/CBonusSystemNode.h
 		${MAIN_LIB_DIR}/bonuses/IBonusBearer.h
@@ -371,6 +373,8 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 
 		${MAIN_LIB_DIR}/constants/EntityIdentifiers.h
 		${MAIN_LIB_DIR}/constants/Enumerations.h
+		${MAIN_LIB_DIR}/constants/IdentifierBase.h
+		${MAIN_LIB_DIR}/constants/VariantIdentifier.h
 		${MAIN_LIB_DIR}/constants/NumericConstants.h
 		${MAIN_LIB_DIR}/constants/StringConstants.h
 

+ 104 - 115
config/artifacts.json

@@ -42,7 +42,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.attack",
+				"subtype" : "primarySkill.attack",
 				"type" : "PRIMARY_SKILL",
 				"val" : 2,
 				"valueType" : "BASE_NUMBER"
@@ -55,7 +55,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.attack",
+				"subtype" : "primarySkill.attack",
 				"type" : "PRIMARY_SKILL",
 				"val" : 3,
 				"valueType" : "BASE_NUMBER"
@@ -68,7 +68,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.attack",
+				"subtype" : "primarySkill.attack",
 				"type" : "PRIMARY_SKILL",
 				"val" : 4,
 				"valueType" : "BASE_NUMBER"
@@ -81,7 +81,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.attack",
+				"subtype" : "primarySkill.attack",
 				"type" : "PRIMARY_SKILL",
 				"val" : 5,
 				"valueType" : "BASE_NUMBER"
@@ -94,7 +94,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.attack",
+				"subtype" : "primarySkill.attack",
 				"type" : "PRIMARY_SKILL",
 				"val" : 6,
 				"valueType" : "BASE_NUMBER"
@@ -107,13 +107,13 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.attack",
+				"subtype" : "primarySkill.attack",
 				"type" : "PRIMARY_SKILL",
 				"val" : 12,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.defence",
+				"subtype" : "primarySkill.defence",
 				"type" : "PRIMARY_SKILL",
 				"val" : -3,
 				"valueType" : "BASE_NUMBER"
@@ -126,7 +126,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.defence",
+				"subtype" : "primarySkill.defence",
 				"type" : "PRIMARY_SKILL",
 				"val" : 2,
 				"valueType" : "BASE_NUMBER"
@@ -139,7 +139,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.defence",
+				"subtype" : "primarySkill.defence",
 				"type" : "PRIMARY_SKILL",
 				"val" : 3,
 				"valueType" : "BASE_NUMBER"
@@ -152,7 +152,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.defence",
+				"subtype" : "primarySkill.defence",
 				"type" : "PRIMARY_SKILL",
 				"val" : 4,
 				"valueType" : "BASE_NUMBER"
@@ -165,7 +165,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.defence",
+				"subtype" : "primarySkill.defence",
 				"type" : "PRIMARY_SKILL",
 				"val" : 5,
 				"valueType" : "BASE_NUMBER"
@@ -178,7 +178,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.defence",
+				"subtype" : "primarySkill.defence",
 				"type" : "PRIMARY_SKILL",
 				"val" : 6,
 				"valueType" : "BASE_NUMBER"
@@ -191,13 +191,13 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.defence",
+				"subtype" : "primarySkill.defence",
 				"type" : "PRIMARY_SKILL",
 				"val" : 12,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : 0,
+				"subtype" : "primarySkill.attack",
 				"type" : "PRIMARY_SKILL",
 				"val" : -3,
 				"valueType" : "BASE_NUMBER"
@@ -210,7 +210,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.knowledge",
+				"subtype" : "primarySkill.knowledge",
 				"type" : "PRIMARY_SKILL",
 				"val" : 1,
 				"valueType" : "BASE_NUMBER"
@@ -223,7 +223,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.knowledge",
+				"subtype" : "primarySkill.knowledge",
 				"type" : "PRIMARY_SKILL",
 				"val" : 2,
 				"valueType" : "BASE_NUMBER"
@@ -236,7 +236,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.knowledge",
+				"subtype" : "primarySkill.knowledge",
 				"type" : "PRIMARY_SKILL",
 				"val" : 3,
 				"valueType" : "BASE_NUMBER"
@@ -249,7 +249,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.knowledge",
+				"subtype" : "primarySkill.knowledge",
 				"type" : "PRIMARY_SKILL",
 				"val" : 4,
 				"valueType" : "BASE_NUMBER"
@@ -262,7 +262,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.knowledge",
+				"subtype" : "primarySkill.knowledge",
 				"type" : "PRIMARY_SKILL",
 				"val" : 5,
 				"valueType" : "BASE_NUMBER"
@@ -275,13 +275,13 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.knowledge",
+				"subtype" : "primarySkill.knowledge",
 				"type" : "PRIMARY_SKILL",
 				"val" : 10,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.spellpower",
+				"subtype" : "primarySkill.spellpower",
 				"type" : "PRIMARY_SKILL",
 				"val" : -2,
 				"valueType" : "BASE_NUMBER"
@@ -294,7 +294,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.spellpower",
+				"subtype" : "primarySkill.spellpower",
 				"type" : "PRIMARY_SKILL",
 				"val" : 1,
 				"valueType" : "BASE_NUMBER"
@@ -307,7 +307,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.spellpower",
+				"subtype" : "primarySkill.spellpower",
 				"type" : "PRIMARY_SKILL",
 				"val" : 2,
 				"valueType" : "BASE_NUMBER"
@@ -320,7 +320,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.spellpower",
+				"subtype" : "primarySkill.spellpower",
 				"type" : "PRIMARY_SKILL",
 				"val" : 3,
 				"valueType" : "BASE_NUMBER"
@@ -333,7 +333,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.spellpower",
+				"subtype" : "primarySkill.spellpower",
 				"type" : "PRIMARY_SKILL",
 				"val" : 4,
 				"valueType" : "BASE_NUMBER"
@@ -346,7 +346,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.spellpower",
+				"subtype" : "primarySkill.spellpower",
 				"type" : "PRIMARY_SKILL",
 				"val" : 5,
 				"valueType" : "BASE_NUMBER"
@@ -359,13 +359,13 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.spellpower",
+				"subtype" : "primarySkill.spellpower",
 				"type" : "PRIMARY_SKILL",
 				"val" : 10,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.knowledge",
+				"subtype" : "primarySkill.knowledge",
 				"type" : "PRIMARY_SKILL",
 				"val" : -2,
 				"valueType" : "BASE_NUMBER"
@@ -378,25 +378,25 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.attack",
+				"subtype" : "primarySkill.attack",
 				"type" : "PRIMARY_SKILL",
 				"val" : 1,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.defence",
+				"subtype" : "primarySkill.defence",
 				"type" : "PRIMARY_SKILL",
 				"val" : 1,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.spellpower",
+				"subtype" : "primarySkill.spellpower",
 				"type" : "PRIMARY_SKILL",
 				"val" : 1,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.knowledge",
+				"subtype" : "primarySkill.knowledge",
 				"type" : "PRIMARY_SKILL",
 				"val" : 1,
 				"valueType" : "BASE_NUMBER"
@@ -409,25 +409,25 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.attack",
+				"subtype" : "primarySkill.attack",
 				"type" : "PRIMARY_SKILL",
 				"val" : 2,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.defence",
+				"subtype" : "primarySkill.defence",
 				"type" : "PRIMARY_SKILL",
 				"val" : 2,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.spellpower",
+				"subtype" : "primarySkill.spellpower",
 				"type" : "PRIMARY_SKILL",
 				"val" : 2,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.knowledge",
+				"subtype" : "primarySkill.knowledge",
 				"type" : "PRIMARY_SKILL",
 				"val" : 2,
 				"valueType" : "BASE_NUMBER"
@@ -440,25 +440,25 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.attack",
+				"subtype" : "primarySkill.attack",
 				"type" : "PRIMARY_SKILL",
 				"val" : 3,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.defence",
+				"subtype" : "primarySkill.defence",
 				"type" : "PRIMARY_SKILL",
 				"val" : 3,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.spellpower",
+				"subtype" : "primarySkill.spellpower",
 				"type" : "PRIMARY_SKILL",
 				"val" : 3,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.knowledge",
+				"subtype" : "primarySkill.knowledge",
 				"type" : "PRIMARY_SKILL",
 				"val" : 3,
 				"valueType" : "BASE_NUMBER"
@@ -471,25 +471,25 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.attack",
+				"subtype" : "primarySkill.attack",
 				"type" : "PRIMARY_SKILL",
 				"val" : 4,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.defence",
+				"subtype" : "primarySkill.defence",
 				"type" : "PRIMARY_SKILL",
 				"val" : 4,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.spellpower",
+				"subtype" : "primarySkill.spellpower",
 				"type" : "PRIMARY_SKILL",
 				"val" : 4,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.knowledge",
+				"subtype" : "primarySkill.knowledge",
 				"type" : "PRIMARY_SKILL",
 				"val" : 4,
 				"valueType" : "BASE_NUMBER"
@@ -502,25 +502,25 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.attack",
+				"subtype" : "primarySkill.attack",
 				"type" : "PRIMARY_SKILL",
 				"val" : 5,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.defence",
+				"subtype" : "primarySkill.defence",
 				"type" : "PRIMARY_SKILL",
 				"val" : 5,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.spellpower",
+				"subtype" : "primarySkill.spellpower",
 				"type" : "PRIMARY_SKILL",
 				"val" : 5,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.knowledge",
+				"subtype" : "primarySkill.knowledge",
 				"type" : "PRIMARY_SKILL",
 				"val" : 5,
 				"valueType" : "BASE_NUMBER"
@@ -533,25 +533,25 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.attack",
+				"subtype" : "primarySkill.attack",
 				"type" : "PRIMARY_SKILL",
 				"val" : 6,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.defence",
+				"subtype" : "primarySkill.defence",
 				"type" : "PRIMARY_SKILL",
 				"val" : 6,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.spellpower",
+				"subtype" : "primarySkill.spellpower",
 				"type" : "PRIMARY_SKILL",
 				"val" : 6,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.knowledge",
+				"subtype" : "primarySkill.knowledge",
 				"type" : "PRIMARY_SKILL",
 				"val" : 6,
 				"valueType" : "BASE_NUMBER"
@@ -564,13 +564,13 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.attack",
+				"subtype" : "primarySkill.attack",
 				"type" : "PRIMARY_SKILL",
 				"val" : 1,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.defence",
+				"subtype" : "primarySkill.defence",
 				"type" : "PRIMARY_SKILL",
 				"val" : 1,
 				"valueType" : "BASE_NUMBER"
@@ -583,13 +583,13 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.attack",
+				"subtype" : "primarySkill.attack",
 				"type" : "PRIMARY_SKILL",
 				"val" : 2,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.defence",
+				"subtype" : "primarySkill.defence",
 				"type" : "PRIMARY_SKILL",
 				"val" : 2,
 				"valueType" : "BASE_NUMBER"
@@ -602,13 +602,13 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.attack",
+				"subtype" : "primarySkill.attack",
 				"type" : "PRIMARY_SKILL",
 				"val" : 3,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.defence",
+				"subtype" : "primarySkill.defence",
 				"type" : "PRIMARY_SKILL",
 				"val" : 3,
 				"valueType" : "BASE_NUMBER"
@@ -621,13 +621,13 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.attack",
+				"subtype" : "primarySkill.attack",
 				"type" : "PRIMARY_SKILL",
 				"val" : 4,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.defence",
+				"subtype" : "primarySkill.defence",
 				"type" : "PRIMARY_SKILL",
 				"val" : 4,
 				"valueType" : "BASE_NUMBER"
@@ -640,13 +640,13 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.spellpower",
+				"subtype" : "primarySkill.spellpower",
 				"type" : "PRIMARY_SKILL",
 				"val" : 1,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.knowledge",
+				"subtype" : "primarySkill.knowledge",
 				"type" : "PRIMARY_SKILL",
 				"val" : 1,
 				"valueType" : "BASE_NUMBER"
@@ -659,13 +659,13 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.spellpower",
+				"subtype" : "primarySkill.spellpower",
 				"type" : "PRIMARY_SKILL",
 				"val" : 2,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.knowledge",
+				"subtype" : "primarySkill.knowledge",
 				"type" : "PRIMARY_SKILL",
 				"val" : 2,
 				"valueType" : "BASE_NUMBER"
@@ -678,13 +678,13 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.spellpower",
+				"subtype" : "primarySkill.spellpower",
 				"type" : "PRIMARY_SKILL",
 				"val" : 3,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.knowledge",
+				"subtype" : "primarySkill.knowledge",
 				"type" : "PRIMARY_SKILL",
 				"val" : 3,
 				"valueType" : "BASE_NUMBER"
@@ -697,13 +697,13 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : "primSkill.spellpower",
+				"subtype" : "primarySkill.spellpower",
 				"type" : "PRIMARY_SKILL",
 				"val" : 4,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.knowledge",
+				"subtype" : "primarySkill.knowledge",
 				"type" : "PRIMARY_SKILL",
 				"val" : 4,
 				"valueType" : "BASE_NUMBER"
@@ -870,7 +870,6 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : 0,
 				"type" : "MAGIC_RESISTANCE",
 				"val" : 5,
 				"valueType" : "BASE_NUMBER"
@@ -883,7 +882,6 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : 0,
 				"type" : "MAGIC_RESISTANCE",
 				"val" : 10,
 				"valueType" : "BASE_NUMBER"
@@ -896,7 +894,6 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : 0,
 				"type" : "MAGIC_RESISTANCE",
 				"val" : 15,
 				"valueType" : "BASE_NUMBER"
@@ -910,7 +907,7 @@
 		"bonuses" : [
 			{
 				"type" : "PERCENTAGE_DAMAGE_BOOST",
-				"subtype" : 1,
+				"subtype" : "damageTypeRanged",
 				"val" : 5,
 				"valueType" : "ADDITIVE_VALUE",
 				"limiters" : [
@@ -918,10 +915,10 @@
 						"type" : "HAS_ANOTHER_BONUS_LIMITER",
 						"parameters" : [
 							"PERCENTAGE_DAMAGE_BOOST",
-							1,
+							"damageTypeRanged",
 							{
 								"type" : "SECONDARY_SKILL",
-								"id" : "skill.archery"
+								"id" : "secondarySkill.archery"
 							}
 						]
 					}
@@ -936,7 +933,7 @@
 		"bonuses" : [
 			{
 				"type" : "PERCENTAGE_DAMAGE_BOOST",
-				"subtype" : 1,
+				"subtype" : "damageTypeRanged",
 				"val" : 10,
 				"valueType" : "ADDITIVE_VALUE",
 				"limiters" : [
@@ -944,10 +941,10 @@
 						"type" : "HAS_ANOTHER_BONUS_LIMITER",
 						"parameters" : [
 							"PERCENTAGE_DAMAGE_BOOST",
-							1,
+							"damageTypeRanged",
 							{
 								"type" : "SECONDARY_SKILL",
-								"id" : "skill.archery"
+								"id" : "secondarySkill.archery"
 							}
 						]
 					}
@@ -962,7 +959,7 @@
 		"bonuses" : [
 			{
 				"type" : "PERCENTAGE_DAMAGE_BOOST",
-				"subtype" : 1,
+				"subtype" : "damageTypeRanged",
 				"val" : 15,
 				"valueType" : "ADDITIVE_VALUE",
 				"limiters" : [
@@ -970,10 +967,10 @@
 						"type" : "HAS_ANOTHER_BONUS_LIMITER",
 						"parameters" : [
 							"PERCENTAGE_DAMAGE_BOOST",
-							1,
+							"damageTypeRanged",
 							{
 								"type" : "SECONDARY_SKILL",
-								"id" : "skill.archery"
+								"id" : "secondarySkill.archery"
 							}
 						]
 					}
@@ -987,7 +984,6 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : 0,
 				"type" : "LEARN_BATTLE_SPELL_CHANCE",
 				"val" : 5,
 				"valueType" : "ADDITIVE_VALUE"
@@ -1000,7 +996,6 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : 0,
 				"type" : "LEARN_BATTLE_SPELL_CHANCE",
 				"val" : 10,
 				"valueType" : "ADDITIVE_VALUE"
@@ -1013,7 +1008,6 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : 0,
 				"type" : "LEARN_BATTLE_SPELL_CHANCE",
 				"val" : 15,
 				"valueType" : "ADDITIVE_VALUE"
@@ -1075,7 +1069,7 @@
 		"bonuses" : [
 			{
 				"type" : "MOVEMENT",
-				"subtype" : 1,
+				"subtype" : "heroMovementLand",
 				"val" : 300,
 				"valueType" : "ADDITIVE_VALUE"
 			}
@@ -1089,7 +1083,7 @@
 		"bonuses" : [
 			{
 				"type" : "MOVEMENT",
-				"subtype" : 0,
+				"subtype" : "heroMovementSea",
 				"val" : 1000,
 				"valueType" : "ADDITIVE_VALUE"
 			}
@@ -1277,7 +1271,7 @@
 		"bonuses" : [
 			{
 				"type" : "SPELLS_OF_SCHOOL",
-				"subtype" : 1,
+				"subtype" : "fire",
 				"val" : 0,
 				"valueType" : "BASE_NUMBER"
 			}
@@ -1290,7 +1284,7 @@
 		"bonuses" : [
 			{
 				"type" : "SPELLS_OF_SCHOOL",
-				"subtype" : 0,
+				"subtype" : "air",
 				"val" : 0,
 				"valueType" : "BASE_NUMBER"
 			}
@@ -1303,7 +1297,7 @@
 		"bonuses" : [
 			{
 				"type" : "SPELLS_OF_SCHOOL",
-				"subtype" : 2,
+				"subtype" : "water",
 				"val" : 0,
 				"valueType" : "BASE_NUMBER"
 			}
@@ -1316,7 +1310,7 @@
 		"bonuses" : [
 			{
 				"type" : "SPELLS_OF_SCHOOL",
-				"subtype" : 3,
+				"subtype" : "earth",
 				"val" : 0,
 				"valueType" : "BASE_NUMBER"
 			}
@@ -1342,14 +1336,12 @@
 		"bonuses" : [
 			{
 				"limiters" : ["SHOOTER_ONLY"],
-				"subtype" : 0,
 				"type" : "NO_DISTANCE_PENALTY",
 				"val" : 0,
 				"valueType" : "ADDITIVE_VALUE"
 			},
 			{
 				"limiters" : ["SHOOTER_ONLY"],
-				"subtype" : 0,
 				"type" : "NO_WALL_PENALTY",
 				"val" : 0,
 				"valueType" : "ADDITIVE_VALUE"
@@ -1362,7 +1354,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : 35,
+				"subtype" : "dispel",
 				"type" : "SPELL_IMMUNITY",
 				"val" : 0,
 				"valueType" : "BASE_NUMBER",
@@ -1377,14 +1369,14 @@
 		"bonuses" : [
 			{
 				"type" : "NEGATE_ALL_NATURAL_IMMUNITIES",
-				"subtype" : 0,
+				"subtype" : "immunityBattleWide",
 				"val" : 0,
 				"valueType" : "BASE_NUMBER",
 				"propagator": "BATTLE_WIDE"
 			},
 			{
 				"type" : "NEGATE_ALL_NATURAL_IMMUNITIES",
-				"subtype" : 1,
+				"subtype" : "immunityEnemyHero",
 				"val" : 0,
 				"valueType" : "BASE_NUMBER"
 			},
@@ -1457,7 +1449,7 @@
 		"bonuses" : [
 			{
 				"type" : "MOVEMENT",
-				"subtype" : 1,
+				"subtype" : "heroMovementLand",
 				"val" : 600,
 				"valueType" : "ADDITIVE_VALUE"
 			}
@@ -1738,7 +1730,7 @@
 		"bonuses" : [
 			{
 				"type" : "CREATURE_GROWTH",
-				"subtype" : 1,
+				"subtype" : "creatureLevel1",
 				"val" : 5,
 				"propagator": "VISITED_TOWN_AND_VISITOR"
 			}
@@ -1751,7 +1743,7 @@
 		"bonuses" : [
 			{
 				"type" : "CREATURE_GROWTH",
-				"subtype" : 2,
+				"subtype" : "creatureLevel2",
 				"val" : 4,
 				"propagator": "VISITED_TOWN_AND_VISITOR"
 			}
@@ -1764,7 +1756,7 @@
 		"bonuses" : [
 			{
 				"type" : "CREATURE_GROWTH",
-				"subtype" : 3,
+				"subtype" : "creatureLevel3",
 				"val" : 3,
 				"propagator": "VISITED_TOWN_AND_VISITOR"
 			}
@@ -1777,7 +1769,7 @@
 		"bonuses" : [
 			{
 				"type" : "CREATURE_GROWTH",
-				"subtype" : 4,
+				"subtype" : "creatureLevel4",
 				"val" : 2,
 				"propagator": "VISITED_TOWN_AND_VISITOR"
 			}
@@ -1790,7 +1782,7 @@
 		"bonuses" : [
 			{
 				"type" : "CREATURE_GROWTH",
-				"subtype" : 5,
+				"subtype" : "creatureLevel5",
 				"val" : 1,
 				"propagator": "VISITED_TOWN_AND_VISITOR"
 			}
@@ -1807,7 +1799,7 @@
 			},
 			{
 				"type" : "MOVEMENT",
-				"subtype" : 0,
+				"subtype" : "heroMovementSea",
 				"val" : 500,
 				"valueType" : "ADDITIVE_VALUE"
 			},
@@ -1831,7 +1823,7 @@
 	{
 		"bonuses" : [
 			{
-				"subtype" : 5,
+				"subtype" : "spellLevel5",
 				"type" : "SPELLS_OF_LEVEL",
 				"val" : 3,
 				"valueType" : "BASE_NUMBER"
@@ -1867,14 +1859,14 @@
 		"bonuses" : [
 			{
 				"limiters" : ["DRAGON_NATURE"],
-				"subtype" : "primSkill.attack",
+				"subtype" : "primarySkill.attack",
 				"type" : "PRIMARY_SKILL",
 				"val" : 5,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
 				"limiters" : ["DRAGON_NATURE"],
-				"subtype" : "primSkill.defence",
+				"subtype" : "primarySkill.defence",
 				"type" : "PRIMARY_SKILL",
 				"val" : 5,
 				"valueType" : "BASE_NUMBER"
@@ -1900,25 +1892,25 @@
 				"addInfo" : 1
 			},
 			{
-				"subtype" : "primSkill.attack",
+				"subtype" : "primarySkill.attack",
 				"type" : "PRIMARY_SKILL",
 				"val" : 3,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.defence",
+				"subtype" : "primarySkill.defence",
 				"type" : "PRIMARY_SKILL",
 				"val" : 3,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.spellpower",
+				"subtype" : "primarySkill.spellpower",
 				"type" : "PRIMARY_SKILL",
 				"val" : 3,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.knowledge",
+				"subtype" : "primarySkill.knowledge",
 				"type" : "PRIMARY_SKILL",
 				"val" : 6,
 				"valueType" : "BASE_NUMBER"
@@ -2103,25 +2095,25 @@
 				"valueType" : "INDEPENDENT_MAX"
 			},
 			{
-				"subtype" : "primSkill.attack",
+				"subtype" : "primarySkill.attack",
 				"type" : "PRIMARY_SKILL",
 				"val" : 6,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.defence",
+				"subtype" : "primarySkill.defence",
 				"type" : "PRIMARY_SKILL",
 				"val" : 6,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.spellpower",
+				"subtype" : "primarySkill.spellpower",
 				"type" : "PRIMARY_SKILL",
 				"val" : 6,
 				"valueType" : "BASE_NUMBER"
 			},
 			{
-				"subtype" : "primSkill.knowledge",
+				"subtype" : "primarySkill.knowledge",
 				"type" : "PRIMARY_SKILL",
 				"val" : 6,
 				"valueType" : "BASE_NUMBER"
@@ -2183,21 +2175,18 @@
 		"bonuses" : [
 			{
 				"limiters" : ["SHOOTER_ONLY"],
-				"subtype" : 0,
 				"type" : "NO_DISTANCE_PENALTY",
 				"val" : 0,
 				"valueType" : "ADDITIVE_VALUE"
 			},
 			{
 				"limiters" : ["SHOOTER_ONLY"],
-				"subtype" : 0,
 				"type" : "NO_WALL_PENALTY",
 				"val" : 0,
 				"valueType" : "ADDITIVE_VALUE"
 			},
 			{
 				"limiters" : ["SHOOTER_ONLY"],
-				"subtype" : 0,
 				"type" : "FREE_SHOOTING",
 				"val" : 0,
 				"valueType" : "ADDITIVE_VALUE"

+ 0 - 3
config/battlefields.json

@@ -130,21 +130,18 @@
 		"bonuses": [
 			{
 				"type" : "NO_MORALE",
-				"subtype" : 0,
 				"val" : 0,
 				"valueType" : "INDEPENDENT_MIN",
 				"description" : "Creatures on Cursed Ground"
 			},
 			{
 				"type" : "NO_LUCK",
-				"subtype" : 0,
 				"val" : 0,
 				"valueType" : "INDEPENDENT_MIN",
 				"description" : "Creatures on Cursed Ground"
 			},
 			{
 				"type" : "BLOCK_MAGIC_ABOVE",
-				"subtype" : 0,
 				"val" : 1,
 				"valueType" : "INDEPENDENT_MIN"
 			}

+ 17 - 17
config/commanders.json

@@ -3,9 +3,9 @@
 	//Commander receives these bonuses on level-up
 	"bonusPerLevel":
 	[
-		["CREATURE_DAMAGE", 1, 1, 0 ], //+1 minimum damage
-		["CREATURE_DAMAGE", 2, 2, 0 ], //+2 maximum damage
-		["STACK_HEALTH", 5, 0, 0 ] //+5 hp
+		["CREATURE_DAMAGE", 1, "creatureDamageMin", 0 ], //+1 minimum damage
+		["CREATURE_DAMAGE", 2, "creatureDamageMax", 0 ], //+2 maximum damage
+		["STACK_HEALTH", 5, null, 0 ] //+5 hp
 	],
 	//Value of bonuses given by each skill level
 	"skillLevels":
@@ -22,20 +22,20 @@
 	"abilityRequirements":
 	//Two secondary skills needed for each special ability
 	[
-		{"ability": ["ENEMY_DEFENCE_REDUCTION", 50, 0, 0 ], "skills": [0, 1]},
-		{"ability": ["FEAR", 0, 0, 0 ], "skills": [0, 2]},
-		{"ability": ["ALWAYS_MAXIMUM_DAMAGE", 0, -1, 0 ], "skills": [0, 3]},
-		{"ability": ["SHOOTER", 0, 0, 0 ], "skills": [0, 4]},
-		{"ability": ["BLOCKS_RETALIATION", 0, 1, 0 ], "skills": [0,5]},
-		{"ability": ["UNLIMITED_RETALIATIONS", 0, 0, 0 ], "skills": [1, 2]},
-		{"ability": ["ATTACKS_ALL_ADJACENT", 0, 0, 0 ], "skills": [1, 3]},
-		{"ability": ["NONE", 30, 0, 0 ], "skills": [1, 4]}, // TODO: Implement bonus that gives chance to completely block one enemy attack per turn
-		{"ability": ["FIRE_SHIELD", 1, 1, 0 ], "skills": [1, 5]},
-		{"ability": ["ADDITIONAL_ATTACK", 1, 0, 0 ], "skills": [2, 3]},
-		{"ability": ["HP_REGENERATION", 50, 0, 0 ], "skills": [2, 4]},
+		{"ability": ["ENEMY_DEFENCE_REDUCTION", 50, null, 0 ], "skills": [0, 1]},
+		{"ability": ["FEAR", 0, null, 0 ], "skills": [0, 2]},
+		{"ability": ["ALWAYS_MAXIMUM_DAMAGE", 0, null, 0 ], "skills": [0, 3]},
+		{"ability": ["SHOOTER", 0, null, 0 ], "skills": [0, 4]},
+		{"ability": ["BLOCKS_RETALIATION", 0, null, 0 ], "skills": [0,5]},
+		{"ability": ["UNLIMITED_RETALIATIONS", 0, null, 0 ], "skills": [1, 2]},
+		{"ability": ["ATTACKS_ALL_ADJACENT", 0, null, 0 ], "skills": [1, 3]},
+		{"ability": ["NONE", 30, null, 0 ], "skills": [1, 4]}, // TODO: Implement bonus that gives chance to completely block one enemy attack per turn
+		{"ability": ["FIRE_SHIELD", 1, null, 0 ], "skills": [1, 5]},
+		{"ability": ["ADDITIONAL_ATTACK", 1, null, 0 ], "skills": [2, 3]},
+		{"ability": ["HP_REGENERATION", 50, null, 0 ], "skills": [2, 4]},
 		{"ability": ["SPELL_AFTER_ATTACK", 30, "spell.paralyze", 0 ], "skills": [2, 5]},
-		{"ability": ["JOUSTING", 5, 0, 0 ], "skills": [3, 4]},
-		{"ability": ["DEATH_STARE", 1, 1, 0 ], "skills": [3,5]},
-		{"ability": ["FLYING", 0, 0, 0 ], "skills": [4,5]}
+		{"ability": ["JOUSTING", 5, null, 0 ], "skills": [3, 4]},
+		{"ability": ["DEATH_STARE", 1, "deathStareCommander", 0 ], "skills": [3,5]},
+		{"ability": ["FLYING", 0, "movementFlying", 0 ], "skills": [4,5]}
 	]
 }

+ 1 - 1
config/creatures/fortress.json

@@ -114,7 +114,7 @@
 			"deathStare" : 
 			{
 				"type" : "DEATH_STARE",
-				"subtype" : 0,
+				"subtype" : "deathStareGorgon",
 				"val" : 10
 			}
 		},

+ 2 - 2
config/creatures/inferno.json

@@ -361,7 +361,7 @@
 			"FLYING_ARMY" :
 			{
 				// type loaded from crtraits
-				"subtype" : 1 // teleports
+				"subtype" : "movementTeleporting"
 			},
 			"descreaseLuck" :
 			{
@@ -415,7 +415,7 @@
 			"FLYING_ARMY" :
 			{
 				// type loaded from crtraits
-				"subtype" : 1 // teleports
+				"subtype" : "movementTeleporting"
 			},
 			"descreaseLuck" :
 			{

+ 2 - 4
config/creatures/necropolis.json

@@ -151,8 +151,7 @@
 		{
 			"noRetalitation" : 
 			{
-				"type" : "BLOCKS_RETALIATION",
-				"subtype" : 1
+				"type" : "BLOCKS_RETALIATION"
 			}
 		},
 		"upgrades": ["vampireLord"],
@@ -180,8 +179,7 @@
 		{
 			"noRetalitation" : 
 			{
-				"type" : "BLOCKS_RETALIATION",
-				"subtype" : 1
+				"type" : "BLOCKS_RETALIATION"
 			},
 			"drainsLife" :
 			{

+ 3 - 3
config/creatures/neutral.json

@@ -529,7 +529,7 @@
 			"visionsMonsters" :
 			{
 				"type" : "VISIONS",
-				"subtype" : 0,
+				"subtype" : "visionsMonsters",
 				"val" : 3,
 				"valueType" : "INDEPENDENT_MAX",
 				"propagator" : "HERO"
@@ -537,7 +537,7 @@
 			"visionsHeroes" :
 			{
 				"type" : "VISIONS",
-				"subtype" : 1,
+				"subtype" : "visionsHeroes",
 				"val" : 3,
 				"valueType" : "INDEPENDENT_MAX",
 				"propagator" : "HERO"				
@@ -545,7 +545,7 @@
 			"visionsTowns" :
 			{
 				"type" : "VISIONS",
-				"subtype" : 2,
+				"subtype" : "visionsTowns",
 				"val" : 3,
 				"valueType" : "INDEPENDENT_MAX",
 				"propagator" : "HERO"				

+ 1 - 1
config/factions/dungeon.json

@@ -178,7 +178,7 @@
 				"special3":       { "type" : "portalOfSummoning" },
 				"special4":       { "type" : "experienceVisitingBonus" },
 				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 },
-					"bonuses": [ { "type": "PRIMARY_SKILL", "subtype": "primSkill.spellpower", "val": 12 } ] },
+					"bonuses": [ { "type": "PRIMARY_SKILL", "subtype": "primarySkill.spellpower", "val": 12 } ] },
 
 				"dwellingLvl1":   { "id" : 30, "requires" : [ "fort" ] },
 				"dwellingLvl2":   { "id" : 31, "requires" : [ "dwellingLvl1" ] },

+ 2 - 2
config/factions/fortress.json

@@ -177,8 +177,8 @@
 				"special3":       { "type" : "attackGarrisonBonus", "requires" : [ "special2" ] },
 				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }, 
 					"bonuses": [
-						{ "type": "PRIMARY_SKILL", "subtype": "primSkill.attack", "val": 10 },
-						{ "type": "PRIMARY_SKILL", "subtype": "primSkill.defence", "val": 10 }
+						{ "type": "PRIMARY_SKILL", "subtype": "primarySkill.attack", "val": 10 },
+						{ "type": "PRIMARY_SKILL", "subtype": "primarySkill.defence", "val": 10 }
 					]
 				},
 

+ 1 - 1
config/factions/stronghold.json

@@ -173,7 +173,7 @@
 				"special3":       { "type" : "ballistaYard", "requires" : [ "blacksmith" ] },
 				"special4":       { "type" : "attackVisitingBonus", "requires" : [ "fort" ] },
 				"grail":          { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 },
-					"bonuses": [ { "type": "PRIMARY_SKILL", "subtype": "primSkill.attack", "val": 20 } ] },
+					"bonuses": [ { "type": "PRIMARY_SKILL", "subtype": "primarySkill.attack", "val": 20 } ] },
 
 				"dwellingLvl1":   { "id" : 30, "requires" : [ "fort" ] },
 				"dwellingLvl2":   { "id" : 31, "requires" : [ "dwellingLvl1" ] },

+ 1 - 1
config/factions/tower.json

@@ -175,7 +175,7 @@
 				"special2":       { "type" : "lookoutTower", "height" : "high", "requires" : [ "fort" ] },
 				"special3":       { "type" : "library", "requires" : [ "mageGuild1" ] },
 				"special4":       { "type" : "knowledgeVisitingBonus", "requires" : [ "mageGuild1" ] },
-				"grail":          { "height" : "skyship",  "produce" : { "gold": 5000 }, "bonuses": [ { "type": "PRIMARY_SKILL", "subtype": "primSkill.knowledge", "val": 15 } ] },
+				"grail":          { "height" : "skyship",  "produce" : { "gold": 5000 }, "bonuses": [ { "type": "PRIMARY_SKILL", "subtype": "primarySkill.knowledge", "val": 15 } ] },
 
 				"dwellingLvl1":   { "id" : 30, "requires" : [ "fort" ] },
 				"dwellingLvl2":   { "id" : 31, "requires" : [ "dwellingLvl1" ] },

+ 2 - 2
config/gameConfig.json

@@ -430,7 +430,7 @@
 				"landMovement" :
 				{
 					"type" : "MOVEMENT", //Basic land movement
-					"subtype" : 1,
+					"subtype" : "heroMovementLand",
 					"val" : 1300,
 					"valueType" : "BASE_NUMBER",
 					"updater" : {
@@ -446,7 +446,7 @@
 				"seaMovement" :
 				{
 					"type" : "MOVEMENT", //Basic sea movement
-					"subtype" : 0,
+					"subtype" : "heroMovementSea",
 					"val" : 1500,
 					"valueType" : "BASE_NUMBER"
 				}

+ 3 - 4
config/heroes/castle.json

@@ -13,7 +13,7 @@
 			"bonuses" : {
 				"archery" : {
 					"type" : "PERCENTAGE_DAMAGE_BOOST",
-					"subtype" : 1,
+					"subtype" : "damageTypeRanged",
 					"updater" : "TIMES_HERO_LEVEL",
 					"val" : 5,
 					"valueType" : "PERCENT_TO_TARGET_TYPE",
@@ -64,7 +64,7 @@
 			"bonuses" : {
 				"navigation" : {
 					"targetSourceType" : "SECONDARY_SKILL",
-					"subtype" : 0,
+					"subtype" : "heroMovementSea",
 					"type" : "MOVEMENT",
 					"updater" : "TIMES_HERO_LEVEL",
 					"val" : 5,
@@ -183,7 +183,7 @@
 							"type" : "HAS_ANOTHER_BONUS_LIMITER",
 							"parameters" : [
 								"GENERAL_DAMAGE_PREMY",
-								1,
+								null,
 								{
 									"type" : "SPELL_EFFECT",
 									"id" : "spell.bless"
@@ -268,7 +268,6 @@
 		"specialty" : {
 			"bonuses" : {
 				"eagleEye" : {
-					"subtype" : 0,
 					"type" : "LEARN_BATTLE_SPELL_CHANCE",
 					"updater" : "TIMES_HERO_LEVEL",
 					"val" : 5,

+ 18 - 18
config/heroes/conflux.json

@@ -21,8 +21,8 @@
 				"val" : 3
 			},
 			"bonuses" : {
-				"attack" : { "subtype" : "primSkill.attack" },
-				"defence" : { "subtype" : "primSkill.defence" }
+				"attack" : { "subtype" : "primarySkill.attack" },
+				"defence" : { "subtype" : "primarySkill.defence" }
 			}
 		}
 	},
@@ -48,16 +48,16 @@
 			"bonuses" : {
 				"damage" : {
 					"type" : "CREATURE_DAMAGE",
-					"subtype" : 0,
+					"subtype" : "creatureDamageBoth",
 					"val" : 5
 				},
 				"attack" : {
-					"subtype" : "primSkill.attack",
+					"subtype" : "primarySkill.attack",
 					"type" : "PRIMARY_SKILL",
 					"val" : 2
 				},
 				"defence" : {
-					"subtype" : "primSkill.defence",
+					"subtype" : "primarySkill.defence",
 					"type" : "PRIMARY_SKILL",
 					"val" : 1
 				}
@@ -85,17 +85,17 @@
 			},
 			"bonuses" : {
 				"damage" : {
-					"subtype" : 0,
+					"subtype" : "creatureDamageBoth",
 					"type" : "CREATURE_DAMAGE",
 					"val" : 2
 				},
 				"attack" : {
-					"subtype" : "primSkill.attack",
+					"subtype" : "primarySkill.attack",
 					"type" : "PRIMARY_SKILL",
 					"val" : 1
 				},
 				"defence" : {
-					"subtype" : "primSkill.defence",
+					"subtype" : "primarySkill.defence",
 					"type" : "PRIMARY_SKILL",
 					"val" : 2
 				}
@@ -120,7 +120,7 @@
 							"type" : "CREATURE_TYPE_LIMITER"
 						}
 					],
-					"subtype" : "primSkill.attack",
+					"subtype" : "primarySkill.attack",
 					"type" : "PRIMARY_SKILL",
 					"val" : 2
 				}
@@ -149,8 +149,8 @@
 				"val" : 3
 			},
 			"bonuses" : {
-				"attack" : { "subtype" : "primSkill.attack" },
-				"defence" : { "subtype" : "primSkill.defence" }
+				"attack" : { "subtype" : "primarySkill.attack" },
+				"defence" : { "subtype" : "primarySkill.defence" }
 			}
 		}
 	},
@@ -176,16 +176,16 @@
 			"bonuses" : {
 				"damage" : {
 					"type" : "CREATURE_DAMAGE",
-					"subtype" : 0,
+					"subtype" : "creatureDamageMin",
 					"val" : 5
 				},
 				"attack" : {
-					"subtype" : "primSkill.attack",
+					"subtype" : "primarySkill.attack",
 					"type" : "PRIMARY_SKILL",
 					"val" : 2
 				},
 				"defence" : {
-					"subtype" : "primSkill.defence",
+					"subtype" : "primarySkill.defence",
 					"type" : "PRIMARY_SKILL",
 					"val" : 1
 				}
@@ -212,17 +212,17 @@
 			},
 			"bonuses" : {
 				"damage" : {
-					"subtype" : 0,
+					"subtype" : "creatureDamageBoth",
 					"type" : "CREATURE_DAMAGE",
 					"val" : 2
 				},
 				"attack" : {
-					"subtype" : "primSkill.attack",
+					"subtype" : "primarySkill.attack",
 					"type" : "PRIMARY_SKILL",
 					"val" : 1
 				},
 				"defence" : {
-					"subtype" : "primSkill.defence",
+					"subtype" : "primarySkill.defence",
 					"type" : "PRIMARY_SKILL",
 					"val" : 2
 				}
@@ -248,7 +248,7 @@
 							"type" : "CREATURE_TYPE_LIMITER"
 						}
 					],
-					"subtype" : "primSkill.attack",
+					"subtype" : "primarySkill.attack",
 					"type" : "PRIMARY_SKILL",
 					"val" : 2
 				}

+ 1 - 2
config/heroes/dungeon.json

@@ -88,7 +88,7 @@
 			"bonuses" : {
 				"logistics" : {
 					"targetSourceType" : "SECONDARY_SKILL",
-					"subtype" : 1,
+					"subtype" : "heroMovementLand",
 					"type" : "MOVEMENT",
 					"updater" : "TIMES_HERO_LEVEL",
 					"val" : 5,
@@ -229,7 +229,6 @@
 		"specialty" : {
 			"bonuses" : {
 				"eagleEye" : {
-					"subtype" : 0,
 					"type" : "LEARN_BATTLE_SPELL_CHANCE",
 					"updater" : "TIMES_HERO_LEVEL",
 					"val" : 5,

+ 2 - 3
config/heroes/fortress.json

@@ -69,7 +69,7 @@
 			"bonuses" : {
 				"armorer" : {
 					"type" : "GENERAL_DAMAGE_REDUCTION",
-					"subtype" : -1,
+					"subtype" : "damageTypeAll",
 					"targetSourceType" : "SECONDARY_SKILL",
 					"updater" : "TIMES_HERO_LEVEL",
 					"val" : 5,
@@ -192,7 +192,7 @@
 			"bonuses" : {
 				"navigation" : {
 					"targetSourceType" : "SECONDARY_SKILL",
-					"subtype" : 0,
+					"subtype" : "heroMovementSea",
 					"type" : "MOVEMENT",
 					"updater" : "TIMES_HERO_LEVEL",
 					"val" : 5,
@@ -307,7 +307,6 @@
 		"specialty" : {
 			"bonuses" : {
 				"eagleEye" : {
-					"subtype" : 0,
 					"type" : "LEARN_BATTLE_SPELL_CHANCE",
 					"updater" : "TIMES_HERO_LEVEL",
 					"val" : 5,

+ 0 - 1
config/heroes/necropolis.json

@@ -214,7 +214,6 @@
 		"specialty" : {
 			"bonuses" : {
 				"eagleEye" : {
-					"subtype" : 0,
 					"type" : "LEARN_BATTLE_SPELL_CHANCE",
 					"updater" : "TIMES_HERO_LEVEL",
 					"val" : 5,

+ 2 - 3
config/heroes/rampart.json

@@ -13,7 +13,7 @@
 			"bonuses" : {
 				"armorer" : {
 					"type" : "GENERAL_DAMAGE_REDUCTION",
-					"subtype" : -1,
+					"subtype" : "damageTypeAll",
 					"targetSourceType" : "SECONDARY_SKILL",
 					"updater" : "TIMES_HERO_LEVEL",
 					"val" : 5,
@@ -132,7 +132,7 @@
 			"bonuses" : {
 				"logistics" : {
 					"targetSourceType" : "SECONDARY_SKILL",
-					"subtype" : 1,
+					"subtype" : "heroMovementLand",
 					"type" : "MOVEMENT",
 					"updater" : "TIMES_HERO_LEVEL",
 					"val" : 5,
@@ -245,7 +245,6 @@
 		"specialty" : {
 			"bonuses" : {
 				"eagleEye" : {
-					"subtype" : 0,
 					"type" : "LEARN_BATTLE_SPELL_CHANCE",
 					"updater" : "TIMES_HERO_LEVEL",
 					"val" : 5,

+ 12 - 12
config/heroes/special.json

@@ -117,17 +117,17 @@
 			},
 			"bonuses" : {
 				"damage" : {
-					"subtype" : 0,
+					"subtype" : "creatureDamageBoth",
 					"type" : "CREATURE_DAMAGE",
 					"val" : 10
 				},
 				"attack" : {
-					"subtype" : "primSkill.attack",
+					"subtype" : "primarySkill.attack",
 					"type" : "PRIMARY_SKILL",
 					"val" : 5
 				},
 				"defence" : {
-					"subtype" : "primSkill.defence",
+					"subtype" : "primarySkill.defence",
 					"type" : "PRIMARY_SKILL",
 					"val" : 5
 				}
@@ -156,17 +156,17 @@
 			},
 			"bonuses" : {
 				"damage" : {
-					"subtype" : 0,
+					"subtype" : "creatureDamageBoth",
 					"type" : "CREATURE_DAMAGE",
 					"val" : 10
 				},
 				"attack" : {
-					"subtype" : "primSkill.attack",
+					"subtype" : "primarySkill.attack",
 					"type" : "PRIMARY_SKILL",
 					"val" : 5
 				},
 				"defence" : {
-					"subtype" : "primSkill.defence",
+					"subtype" : "primarySkill.defence",
 					"type" : "PRIMARY_SKILL",
 					"val" : 5
 				}
@@ -197,8 +197,8 @@
 				"val" : 5
 			},
 			"bonuses" : {
-				"attack" : { "subtype" : "primSkill.attack" },
-				"defence" : { "subtype" : "primSkill.defence" }
+				"attack" : { "subtype" : "primarySkill.attack" },
+				"defence" : { "subtype" : "primarySkill.defence" }
 			}
 		}
 	},
@@ -241,8 +241,8 @@
 				"val" : 5
 			},
 			"bonuses" : {
-				"attack" : { "subtype" : "primSkill.attack" },
-				"defence" : { "subtype" : "primSkill.defence" }
+				"attack" : { "subtype" : "primarySkill.attack" },
+				"defence" : { "subtype" : "primarySkill.defence" }
 			}
 		},
 		"army" :
@@ -313,12 +313,12 @@
 			},
 			"bonuses" : {
 				"attack" : {
-					"subtype" : "primSkill.attack",
+					"subtype" : "primarySkill.attack",
 					"type" : "PRIMARY_SKILL",
 					"val" : 4
 				},
 				"defence" : {
-					"subtype" : "primSkill.defence",
+					"subtype" : "primarySkill.defence",
 					"type" : "PRIMARY_SKILL",
 					"val" : 2
 				},

+ 2 - 3
config/heroes/stronghold.json

@@ -95,7 +95,7 @@
 		"specialty" : {
 			"bonuses" : {
 				"offence" : {
-					"subtype" : 0,
+					"subtype" : "damageTypeMelee",
 					"type" : "PERCENTAGE_DAMAGE_BOOST",
 					"updater" : "TIMES_HERO_LEVEL",
 					"val" : 5,
@@ -173,7 +173,7 @@
 			"bonuses" : {
 				"logistics" : {
 					"targetSourceType" : "SECONDARY_SKILL",
-					"subtype" : 1,
+					"subtype" : "heroMovementLand",
 					"type" : "MOVEMENT",
 					"updater" : "TIMES_HERO_LEVEL",
 					"val" : 5,
@@ -262,7 +262,6 @@
 		"specialty" : {
 			"bonuses" : {
 				"eagleEye" : {
-					"subtype" : 0,
 					"type" : "LEARN_BATTLE_SPELL_CHANCE",
 					"updater" : "TIMES_HERO_LEVEL",
 					"val" : 5,

+ 1 - 2
config/heroes/tower.json

@@ -58,7 +58,7 @@
 			"bonuses" : {
 				"armorer" : {
 					"type" : "GENERAL_DAMAGE_REDUCTION",
-					"subtype" : -1,
+					"subtype" : "damageTypeAll",
 					"targetSourceType" : "SECONDARY_SKILL",
 					"updater" : "TIMES_HERO_LEVEL",
 					"val" : 5,
@@ -191,7 +191,6 @@
 		"specialty" : {
 			"bonuses" : {
 				"eagleEye" : {
-					"subtype" : 0,
 					"type" : "LEARN_BATTLE_SPELL_CHANCE",
 					"updater" : "TIMES_HERO_LEVEL",
 					"val" : 5,

+ 2 - 2
config/objects/rewardableBonusing.json

@@ -356,7 +356,7 @@
 						},
 						"message" : 138,
 						"movePoints" : 400,
-						"bonuses" : [ { "type" : "MOVEMENT", "subtype" : 1,  "val" : 400, "valueType" : "ADDITIVE_VALUE", "duration" : "ONE_WEEK"} ],
+						"bonuses" : [ { "type" : "MOVEMENT", "subtype" : "heroMovementLand",  "val" : 400, "valueType" : "ADDITIVE_VALUE", "duration" : "ONE_WEEK"} ],
 						"changeCreatures" : {
 							"cavalier" : "champion"
 						}
@@ -364,7 +364,7 @@
 					{
 						"message" : 137,
 						"movePoints" : 400,
-						"bonuses" : [ { "type" : "MOVEMENT", "subtype" : 1,  "val" : 400, "valueType" : "ADDITIVE_VALUE", "duration" : "ONE_WEEK"} ]
+						"bonuses" : [ { "type" : "MOVEMENT", "subtype" : "heroMovementLand",  "val" : 400, "valueType" : "ADDITIVE_VALUE", "duration" : "ONE_WEEK"} ]
 					}
 				]
 			}

+ 1 - 4
config/schemas/bonus.json

@@ -44,10 +44,7 @@
 			"description" : "type"
 		},
 		"subtype" : {
-			"anyOf" : [
-				{ "type" : "string" },
-				{ "type" : "number" }
-			],
+			"type" : "string",
 			"description" : "subtype"
 		},
 		"sourceID" : {

+ 5 - 8
config/skills.json

@@ -31,7 +31,7 @@
 			"effects" : {
 				"main" : {
 					"type" : "PERCENTAGE_DAMAGE_BOOST",
-					"subtype" : 1,
+					"subtype" : "damageTypeRanged",
 					"valueType" : "BASE_NUMBER"
 				}
 			}
@@ -57,7 +57,7 @@
 		"base" : {
 			"effects" : {
 				"main" : {
-					"subtype" : 1,
+					"subtype" : "heroMovementLand",
 					"type" : "MOVEMENT",
 					"valueType" : "PERCENT_TO_BASE"
 				}
@@ -144,7 +144,7 @@
 		"base" : {
 			"effects" : {
 				"main" : {
-					"subtype" : 0,
+					"subtype" : "heroMovementSea",
 					"type" : "MOVEMENT",
 					"valueType" : "PERCENT_TO_BASE"
 				}
@@ -310,12 +310,10 @@
 		"base" : {
 			"effects" : {
 				"main" : {
-					"subtype" : 0,
 					"type" : "LEARN_BATTLE_SPELL_CHANCE",
 					"valueType" : "BASE_NUMBER"
 				},
 				"val2" : {
-					"subtype" : -1,
 					"type" : "LEARN_BATTLE_SPELL_LEVEL_LIMIT",
 					"valueType" : "BASE_NUMBER"
 				}
@@ -518,7 +516,6 @@
 		"base" : {
 			"effects" : {
 				"main" : {
-					"subtype" : -1,
 					"type" : "LEARN_MEETING_SPELL_LIMIT",
 					"valueType" : "BASE_NUMBER"
 				}
@@ -657,7 +654,7 @@
 		"base" : {
 			"effects" : {
 				"main" : {
-					"subtype" : 0,
+					"subtype" : "damageTypeMelee",
 					"type" : "PERCENTAGE_DAMAGE_BOOST",
 					"valueType" : "BASE_NUMBER"
 				}
@@ -685,7 +682,7 @@
 			"effects" : {
 				"main" : {
 					"type" : "GENERAL_DAMAGE_REDUCTION",
-					"subtype" : -1,
+					"subtype" : "damageTypeAll",
 					"valueType" : "BASE_NUMBER"
 				}
 			}

+ 3 - 3
config/spells/ability.json

@@ -141,13 +141,13 @@
 					"attack" : {
 						"val" : -2,
 						"type" : "PRIMARY_SKILL",
-						"subtype" : "primSkill.attack",
+						"subtype" : "primarySkill.attack",
 						"duration" : "N_TURNS"
 					},
 					"defence" : {
 						"val" : -2,
 						"type" : "PRIMARY_SKILL",
-						"subtype" : "primSkill.defence",
+						"subtype" : "primarySkill.defence",
 						"duration" : "N_TURNS"
 					}
 				}
@@ -384,7 +384,7 @@
 							"primarySkill" : {
 								"val" : -3,
 								"type" : "PRIMARY_SKILL",
-								"subtype" : "primSkill.defence",
+								"subtype" : "primarySkill.defence",
 								"duration" : "PERMANENT",
 								"valueType" : "ADDITIVE_VALUE"
 							}

+ 7 - 8
config/spells/adventure.json

@@ -46,7 +46,7 @@
 				"effects" : {
 					"visionsMonsters" : {
 						"type" : "VISIONS",
-						"subtype" : 0,
+						"subtype" : "visionsMonsters",
 						"duration" : "ONE_DAY",
 						"val" : 1,
 						"valueType" : "INDEPENDENT_MAX"
@@ -60,10 +60,10 @@
 					},
 					"visionsHeroes" :{
 						"type" : "VISIONS",
-						"subtype" : 1,
+						"subtype" : "visionsHeroes",
 						"duration" : "ONE_DAY",
 						"val" : 2,
-						"valueType" : "INDEPENDENT_MAX"					
+						"valueType" : "INDEPENDENT_MAX"
 					}
 					
 				}			
@@ -75,17 +75,17 @@
 					},
 					"visionsHeroes" :{
 						"type" : "VISIONS",
-						"subtype" : 1,
+						"subtype" : "visionsHeroes",
 						"duration" : "ONE_DAY",
 						"val" : 3,
-						"valueType" : "INDEPENDENT_MAX"					
+						"valueType" : "INDEPENDENT_MAX"
 					},
 					"visionsTowns" :{
 						"type" : "VISIONS",
-						"subtype" : 2,
+						"subtype" : "visionsTowns",
 						"duration" : "ONE_DAY",
 						"val" : 3,
-						"valueType" : "INDEPENDENT_MAX"					
+						"valueType" : "INDEPENDENT_MAX"
 					}
 				}			
 			}
@@ -123,7 +123,6 @@
 				"effects" : {
 					"stealth" : {
 						"type" : "DISGUISED",
-						"subtype" : 0, //required
 						"duration" : "ONE_DAY",
 						"val" : 1,
 						"valueType" : "INDEPENDENT_MAX"

+ 7 - 7
config/spells/moats.json

@@ -72,7 +72,7 @@
                             "primarySkill" : {
                                 "val" : -3,
                                 "type" : "PRIMARY_SKILL",
-                                "subtype" : "primSkill.defence",
+                                "subtype" : "primarySkill.defence",
                                 "valueType" : "ADDITIVE_VALUE"
                             }
                         }
@@ -169,7 +169,7 @@
                             "primarySkill" : {
                                 "val" : -3,
                                 "type" : "PRIMARY_SKILL",
-                                "subtype" : "primSkill.defence",
+                                "subtype" : "primarySkill.defence",
                                 "valueType" : "ADDITIVE_VALUE"
                             }
                         }
@@ -317,7 +317,7 @@
                             "primarySkill" : {
                                 "val" : -3,
                                 "type" : "PRIMARY_SKILL",
-                                "subtype" : "primSkill.defence",
+                                "subtype" : "primarySkill.defence",
                                 "valueType" : "ADDITIVE_VALUE"
                             }
                         }
@@ -414,7 +414,7 @@
                             "primarySkill" : {
                                 "val" : -3,
                                 "type" : "PRIMARY_SKILL",
-                                "subtype" : "primSkill.defence",
+                                "subtype" : "primarySkill.defence",
                                 "valueType" : "ADDITIVE_VALUE"
                             }
                         }
@@ -511,7 +511,7 @@
                             "primarySkill" : {
                                 "val" : -3,
                                 "type" : "PRIMARY_SKILL",
-                                "subtype" : "primSkill.defence",
+                                "subtype" : "primarySkill.defence",
                                 "valueType" : "ADDITIVE_VALUE"
                             }
                         }
@@ -608,7 +608,7 @@
                             "primarySkill" : {
                                 "val" : -3,
                                 "type" : "PRIMARY_SKILL",
-                                "subtype" : "primSkill.defence",
+                                "subtype" : "primarySkill.defence",
                                 "valueType" : "ADDITIVE_VALUE"
                             }
                         }
@@ -705,7 +705,7 @@
                             "primarySkill" : {
                                 "val" : -3,
                                 "type" : "PRIMARY_SKILL",
-                                "subtype" : "primSkill.defence",
+                                "subtype" : "primarySkill.defence",
                                 "valueType" : "ADDITIVE_VALUE"
                             }
                         }

+ 10 - 12
config/spells/timed.json

@@ -17,7 +17,7 @@
 				"effects" : {
 					"generalDamageReduction" : {
 						"type" : "GENERAL_DAMAGE_REDUCTION",
-						"subtype" : 0,
+						"subtype" : "damageTypeMelee",
 						"duration" : "N_TURNS"
 					}
 				}
@@ -48,7 +48,7 @@
 				"effects" : {
 					"generalDamageReduction" : {
 						"type" : "GENERAL_DAMAGE_REDUCTION",
-						"subtype" : 1,
+						"subtype" : "damageTypeRanged",
 						"duration" : "N_TURNS"
 					}
 				}
@@ -367,7 +367,6 @@
 					"alwaysMaximumDamage" : {
 						"val" : 0,
 						"type" : "ALWAYS_MAXIMUM_DAMAGE",
-						"subtype" : -1,
 						"valueType" : "INDEPENDENT_MAX",
 						"duration" : "N_TURNS"
 					}
@@ -421,7 +420,6 @@
 						"addInfo" : 0,
 						"val" : 0,
 						"type" : "ALWAYS_MINIMUM_DAMAGE",
-						"subtype" : -1,
 						"valueType" : "INDEPENDENT_MAX",
 						"duration" : "N_TURNS"
 					}
@@ -476,7 +474,7 @@
 					"primarySkill" : {
 						"val" : 3,
 						"type" : "PRIMARY_SKILL",
-						"subtype" : "primSkill.attack",
+						"subtype" : "primarySkill.attack",
 						"effectRange" : "ONLY_MELEE_FIGHT",
 						"duration" : "N_TURNS"
 					}
@@ -527,7 +525,7 @@
 				"effects" : {
 					"primarySkill" : {
 						"type" : "PRIMARY_SKILL",
-						"subtype" : "primSkill.attack",
+						"subtype" : "primarySkill.attack",
 						"val" : 3,
 						"effectRange" : "ONLY_DISTANCE_FIGHT",
 						"duration" : "N_TURNS"
@@ -576,7 +574,7 @@
 				"effects" : {
 					"primarySkill" : {
 						"type" : "PRIMARY_SKILL",
-						"subtype" : "primSkill.attack",
+						"subtype" : "primarySkill.attack",
 						"val" : -3,
 						"duration" : "N_TURNS"
 					}
@@ -623,7 +621,7 @@
 				"effects" : {
 					"primarySkill" : {
 						"type" : "PRIMARY_SKILL",
-						"subtype" : "primSkill.defence",
+						"subtype" : "primarySkill.defence",
 						"val" : 3,
 						"duration" : "N_TURNS"
 					}
@@ -667,7 +665,7 @@
 				"cumulativeEffects" : {
 					"primarySkill" : {
 						"type" : "PRIMARY_SKILL",
-						"subtype" : "primSkill.defence",
+						"subtype" : "primarySkill.defence",
 						"val" : -3,
 						"valueType" : "ADDITIVE_VALUE",
 						"duration" : "PERMANENT"
@@ -710,13 +708,13 @@
 				"effects" : {
 					"attack" : {
 						"type" : "PRIMARY_SKILL",
-						"subtype" : "primSkill.attack",
+						"subtype" : "primarySkill.attack",
 						"val" : 2,
 						"duration" : "N_TURNS"
 					},
 					"defence" : {
 						"type" : "PRIMARY_SKILL",
-						"subtype" : "primSkill.defence",
+						"subtype" : "primarySkill.defence",
 						"val" : 2,
 						"duration" : "N_TURNS"
 					},
@@ -1401,7 +1399,7 @@
 					"notActive" : {
 						"val" : 0,
 						"type" : "NOT_ACTIVE",
-						"subtype" : 62,
+						"subtype" : "blind",
 						"duration" : [
 							"UNTIL_BEING_ATTACKED",
 							"N_TURNS"

+ 64 - 25
docs/modders/Bonus/Bonus_Types.md

@@ -39,20 +39,22 @@ On each turn, hides area in fog of war around affected town for all players othe
 
 Increases amount of movement points available to affected hero on new turn
 
-- subtype: 0 - sea, subtype 1 - land
+- subtype: 
+- - heroMovementLand: only land movement will be affected
+- - heroMovementSea: only sea movement will be affected
 - val: number of movement points (100 points for a tile)
 
 ### WATER_WALKING
 
 Allows movement over water for affected heroes
 
-- subtype: 1 - without penalty, 2 - with penalty
+- val: TODO
 
 ### FLYING_MOVEMENT
 
 Allows flying movement for affected heroes
 
-- subtype: 1 - without penalty, 2 - with penalty
+- val: TODO
 
 ### NO_TERRAIN_PENALTY
 
@@ -118,14 +120,12 @@ Determines chance for affected heroes to learn spell casted by enemy hero after
 
 Allows affected heroes to learn spell casted by enemy hero after battle
 
-- subtype: must be set to -1
 - val: maximal level of spell that can be learned
 
 ### LEARN_MEETING_SPELL_LIMIT
 
 Allows affected heroes to learn spells from each other during hero exchange
 
-- subtype: must be set to -1
 - val: maximal level of spell that can be learned
 
 ### ROUGH_TERRAIN_DISCOUNT
@@ -224,16 +224,20 @@ Gives additional bonus to effect of specific spell
 
 TODO: blesses and curses with id = val dependent on unit's level
 
-- subtype: 0 or 1 for Coronius
+- subtype: affected spell identifier
 
 ### SPECIAL_ADD_VALUE_ENCHANT
 
 TODO: specialty spell like Aenin has, increased effect of spell, additionalInfo = value to add
 
+- subtype: affected spell identifier
+
 ### SPECIAL_FIXED_VALUE_ENCHANT
 
 TODO: specialty spell like Melody has, constant spell effect (i.e. 3 luck), additionalInfo = value to fix.
 
+- subtype: affected spell identifier
+
 ### SPECIAL_UPGRADE
 
 Allows creature upgrade for affected armies
@@ -260,7 +264,7 @@ Allows affected heroes to cast specified spell, even if this spell is banned in
 
 Allows affected heroes to cast any spell of specified level. Does not grant spells banned in map options.
 
-- subtype: spell level
+- subtype: spell level, in form "spellLevelX" where X is desired level (1-5)
 
 ### SPELLS_OF_SCHOOL
 
@@ -280,7 +284,7 @@ Affected heroes will add specified resources amounts to player treasure on new d
 Increases weekly growth of creatures in affected towns (Legion artifacts)
 
 - value: number of additional weekly creatures
-- subtype: level of affected dwellings
+- subtype: dwelling level, in form "creatureLevelX" where X is desired level (1-7)
 
 ### CREATURE_GROWTH_PERCENT
 
@@ -294,8 +298,11 @@ Heroes affected by this bonus can not retreat or surrender in battle
 
 ### NEGATE_ALL_NATURAL_IMMUNITIES
 
-- subtype: TODO
-Orb of Vulnerability
+Negates all natural immunities for affected stacks. (Orb of Vulnerability)
+
+- subtype:
+- - immunityBattleWide: Entire battle will be affected by bonus
+- - immunityEnemyHero: Only enemy hero will be affected by bonus
 
 ### OPENING_BATTLE_SPELL
 
@@ -330,7 +337,10 @@ Increases movement speed of units in battle
 
 Increases base damage of creature in battle
 
-- subtype: 0 = both min and max, 1 = min, 2 = max
+- subtype:
+- - creatureDamageMin: increases only minimal damage 
+- - creatureDamageMax: increases only maximal damage
+- - creatureDamageBoth: increases both minimal and maximal damage
 - val: additional damage points
 
 ### SHOTS
@@ -387,7 +397,9 @@ Affected units can not receive good or bad morale
 
 Affected unit can fly on the battlefield
 
-- subtype: 0 - flying unit, 1 - teleporting unit
+- subtype:
+- - movementFlying: creature will fly (slowly move across battlefield)
+- - movementTeleporting: creature will instantly teleport to destination
 
 ### SHOOTER
 
@@ -423,7 +435,7 @@ Affected unit will deal more damage based on movement distance (Champions)
 
 Affected unit will deal more damage when attacking specific creature
 
-- subtype - identifier of hated creature,
+- subtype - identifier of hated creature
 - val - additional damage, percentage
 
 ### SPELL_LIKE_ATTACK
@@ -460,14 +472,19 @@ Affected unit will ignore specified percentage of attacked unit defence (Behemot
 Affected units will receive reduced damage from attacks by other units
 
 - val: damage reduction, percentage
-- subtype: 0 - melee damage (Shield spell), 1 - ranged damage (Air Shield), -1 - all damage (Armorer skill)
+- subtype: 
+- - damageTypeMelee: only melee damage will be reduced
+- - damageTypeRanged: only ranged damage will be reduced
+- - damageTypeAll: all damage will be reduced
 
 ### PERCENTAGE_DAMAGE_BOOST
 
 Affected units will deal increased damage when attacking other units
 
 - val: damage increase, percentage
-- subtype: 0 - melee damage (Offense skill), 1 - ranged damage (Archery skill)
+- subtype: 
+- - damageTypeMelee: only melee damage will increased
+- - damageTypeRanged: only ranged damage will increased
 
 ### GENERAL_ATTACK_REDUCTION
 
@@ -506,14 +523,18 @@ Affected unit will never receive retaliations when attacking
 Affected unit will gain new creatures for each enemy killed by this unit
 
 - val: number of units gained per enemy killed
-- subtype: 0 - gained units disappear after battle, 1 - gained units are permanent
+- subtype: 
+- - soulStealPermanent: creature will stay after the battle
+- - soulStealBattle: creature will be lost after the battle
 
 ### TRANSMUTATION
 
 Affected units have chance to transform attacked unit to other creature type
 
 - val: chance for ability to trigger, percentage
-- subtype: 0 - transformed unit will have same HP pool as original stack, 1 - transformed unit will have same number of units as original stack
+- subtype: 
+- - transmutationPerHealth: transformed unit will have same HP pool as original stack, 
+- - transmutationPerUnit: transformed unit will have same number of units as original stack
 - addInfo: creature to transform to. If not set, creature will transform to same unit as attacker
 
 ### SUMMON_GUARDIANS
@@ -548,7 +569,9 @@ Affected unit will attack units on all hexes that surround attacked hex in range
 Affected unit will kills additional units after attack
 
 - val: chance to trigger, percentage
-- subtype: 0 - kill percentage of units, 1 - kill amount
+- subtype: 
+- - destructionKillPercentage: kill percentage of units, 
+- - destructionKillAmount: kill amount
 - addInfo: amount or percentage to kill
 
 ### LIMITED_SHOOTING_RANGE
@@ -576,7 +599,7 @@ Defines spell mastery level for spell used by CATAPULT bonus
 
 Hero can control war machine affected by this bonus
 
-- id: creature identifier of affected war machine
+- subtype: creature identifier of affected war machine
 - val: chance to control unit, percentage
 
 ### CHANGES_SPELL_COST_FOR_ALLY
@@ -660,10 +683,12 @@ Affected unit will deal additional damage after attack
 
 Affected unit will kill additional units after attack
 
-- subtype: 0 - random amount (Gorgons), 1 - fixed amount (Commanders)
+- subtype: 
+- - deathStareGorgon: random amount
+- - deathStareCommander: fixed amount
 - val: 
-- - for subtype 0: chance to kill, counted separately for each unit in attacking stack, percentage. At most (stack size \* chance) units can be killed at once. TODO: recheck formula
-- - for subtype 1: number of creatures to kill, total amount of killed creatures is (attacker level / defender level) \* val
+- - for deathStareGorgon: chance to kill, counted separately for each unit in attacking stack, percentage. At most (stack size \* chance) units can be killed at once. TODO: recheck formula
+- - for deathStareCommander: number of creatures to kill, total amount of killed creatures is (attacker level / defender level) \* val
 
 ### SPECIAL_CRYSTAL_GENERATION
 
@@ -749,7 +774,9 @@ Affected stack will resurrect after death
 TODO: recheck math
 
 - val - percent of total stack HP restored
-- subtype = 0 - regular, 1 - at least one unit (sacred Phoenix)
+- subtype:
+- - rebirthRegular: (Phoenix)
+- - rebirthSpecial: at least one unit will always resurrect (sacred Phoenix)
 
 ### ENCHANTED
 
@@ -796,7 +823,13 @@ Affected creature is immune to all mind spells and receives reduced damage from
 
 Affected unit is completely immune to effects of specific spell
 
-- subid: identifier of spell to which unit is immune
+- subtype: identifier of spell to which unit is immune
+
+### SPELL_SCHOOL_IMMUNITY
+
+Affected unit is immune to all spells of a specified spell school
+
+- subtype: spell school to which this unit is immune to
 
 ### RECEPTIVE
 
@@ -826,6 +859,8 @@ Affected unit has its ranged attack power reduced (Forgetfulness)
 
 Affected unit can not act and is excluded from turn order (Blind, Stone Gaze, Paralyze)
 
+- subtype: spell that caused this effect, optional
+
 ### ALWAYS_MINIMUM_DAMAGE
 
 Affected creature always deals its minimum damage
@@ -878,13 +913,17 @@ Affected unit will deal more damage in all attacks (Adela specialty)
 
 Affected heroes will be under effect of Disguise spell, hiding some of their information from opposing players
 
-- subtype: spell mastery level
+- val: spell mastery level
 
 ### VISIONS
 
 Affected heroes will be under effect of Visions spell, revealing information of enemy objects in specific range
 
 - val: multiplier to effect range. Information is revealed within (val \* hero spell power) range
+- subtype:
+- - visionsMonsters: reveal information on monsters, 
+- - visionsHeroes: reveal information on heroes, 
+- - visionsTowns: reveal information on towns
 
 ### BLOCK_MAGIC_BELOW
 

+ 2 - 2
docs/modders/Game_Identifiers.md

@@ -548,7 +548,7 @@ This is a list of all game identifiers available to modders. Note that only iden
 
 ### primSkill
 
-Deprected, please use primarySkill instead
+Deprecated, please use primarySkill instead
 
 - primSkill.attack
 - primSkill.defence
@@ -619,7 +619,7 @@ Deprected, please use primarySkill instead
 
 ### skill
 
-Deprected, please use secondarySkill instead
+Deprecated, please use secondarySkill instead
 
 - skill.airMagic
 - skill.archery

+ 1 - 1
include/vcmi/FactionMember.h

@@ -15,7 +15,7 @@
 VCMI_LIB_NAMESPACE_BEGIN
 
 class BonusList;
-enum class PrimarySkill : int8_t;
+class PrimarySkill;
 
 class DLL_LINKAGE AFactionMember: public IConstBonusProvider, public INativeTerrainProvider
 {

+ 1 - 1
lib/ArtifactUtils.cpp

@@ -145,7 +145,7 @@ DLL_LINKAGE CArtifactInstance * ArtifactUtils::createScroll(const SpellID & sid)
 {
 	auto ret = new CArtifactInstance(VLC->arth->objects[ArtifactID::SPELL_SCROLL]);
 	auto bonus = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::SPELL,
-		BonusSource::ARTIFACT_INSTANCE, -1, ArtifactID::SPELL_SCROLL, sid);
+		BonusSource::ARTIFACT_INSTANCE, -1, BonusSourceID(ArtifactID(ArtifactID::SPELL_SCROLL)), BonusSubtypeID(sid));
 	ret->addNewBonus(bonus);
 	return ret;
 }

+ 6 - 6
lib/BasicTypes.cpp

@@ -35,7 +35,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(BonusType::NO_TERRAIN_PENALTY, any);
+	static const auto selectorNoTerrainPenalty = Selector::typeSubtype(BonusType::NO_TERRAIN_PENALTY, BonusSubtypeID(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.
@@ -54,7 +54,7 @@ int AFactionMember::getAttack(bool ranged) const
 {
 	const std::string cachingStr = "type_PRIMARY_SKILLs_ATTACK";
 
-	static const auto selector = Selector::typeSubtype(BonusType::PRIMARY_SKILL, static_cast<int>(PrimarySkill::ATTACK));
+	static const auto selector = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK));
 
 	return getBonusBearer()->valOfBonuses(selector, cachingStr);
 }
@@ -63,7 +63,7 @@ int AFactionMember::getDefense(bool ranged) const
 {
 	const std::string cachingStr = "type_PRIMARY_SKILLs_DEFENSE";
 
-	static const auto selector = Selector::typeSubtype(BonusType::PRIMARY_SKILL, static_cast<int>(PrimarySkill::DEFENSE));
+	static const auto selector = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::DEFENSE));
 
 	return getBonusBearer()->valOfBonuses(selector, cachingStr);
 }
@@ -71,14 +71,14 @@ 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(BonusType::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, 1));
+	static const auto selector = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageBoth).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMin));
 	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(BonusType::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, 2));
+	static const auto selector = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageBoth).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMax));
 	return getBonusBearer()->valOfBonuses(selector, cachingStr);
 }
 
@@ -87,7 +87,7 @@ int AFactionMember::getPrimSkillLevel(PrimarySkill id) const
 	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()(static_cast<int>(id)));
+	auto ret = allSkills->valOfBonuses(Selector::subtype()(BonusSubtypeID(id)));
 	auto minSkillValue = (id == PrimarySkill::SPELL_POWER || id == PrimarySkill::KNOWLEDGE) ? 1 : 0;
 	return std::max(ret, minSkillValue); //otherwise, some artifacts may cause negative skill value effect, sp=0 works in old saves
 }

+ 1 - 1
lib/BattleFieldHandler.cpp

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

+ 1 - 1
lib/CArtHandler.cpp

@@ -751,7 +751,7 @@ void CArtHandler::afterLoadFinalization()
 		{
 			assert(art == objects[art->id]);
 			assert(bonus->source == BonusSource::ARTIFACT);
-			bonus->sid = art->id;
+			bonus->sid = BonusSourceID(art->id);
 		}
 	}
 	CBonusSystemNode::treeHasChanged();

+ 1 - 1
lib/CArtifactInstance.cpp

@@ -68,7 +68,7 @@ SpellID CScrollArtifactInstance::getScrollSpellID() const
 		logMod->warn("Warning: %s doesn't bear any spell!", artInst->nodeName());
 		return SpellID::NONE;
 	}
-	return SpellID(bonus->subtype);
+	return bonus->subtype.as<SpellID>();
 }
 
 void CGrowingArtifactInstance::growingUp()

+ 20 - 23
lib/CBonusTypeHandler.cpp

@@ -77,10 +77,10 @@ std::string CBonusTypeHandler::bonusToString(const std::shared_ptr<Bonus> & bonu
 		boost::algorithm::replace_all(text, "${val}", std::to_string(bearer->valOfBonuses(Selector::typeSubtype(bonus->type, bonus->subtype))));
 
 	if (text.find("${subtype.creature}") != std::string::npos)
-		boost::algorithm::replace_all(text, "${subtype.creature}", CreatureID(bonus->subtype).toCreature()->getNamePluralTranslated());
+		boost::algorithm::replace_all(text, "${subtype.creature}", bonus->subtype.as<CreatureID>().toCreature()->getNamePluralTranslated());
 
 	if (text.find("${subtype.spell}") != std::string::npos)
-		boost::algorithm::replace_all(text, "${subtype.spell}", SpellID(bonus->subtype).toSpell()->getNameTranslated());
+		boost::algorithm::replace_all(text, "${subtype.spell}", bonus->subtype.as<SpellID>().toSpell()->getNameTranslated());
 
 	return text;
 }
@@ -95,57 +95,57 @@ ImagePath CBonusTypeHandler::bonusToGraphics(const std::shared_ptr<Bonus> & bonu
 	case BonusType::SPELL_IMMUNITY:
 	{
 		fullPath = true;
-		const CSpell * sp = SpellID(bonus->subtype).toSpell();
+		const CSpell * sp = bonus->subtype.as<SpellID>().toSpell();
 		fileName = sp->getIconImmune();
 		break;
 	}
 	case BonusType::SPELL_DAMAGE_REDUCTION: //Spell damage reduction for all schools
 	{
-		if (bonus->subtype == SpellSchool::ANY.getNum())
+		if (bonus->subtype.as<SpellSchool>() == SpellSchool::ANY)
 			fileName = "E_GOLEM.bmp";
 
-		if (bonus->subtype == SpellSchool::AIR.getNum())
+		if (bonus->subtype.as<SpellSchool>() == SpellSchool::AIR)
 			fileName = "E_LIGHT.bmp";
 
-		if (bonus->subtype == SpellSchool::FIRE.getNum())
+		if (bonus->subtype.as<SpellSchool>() == SpellSchool::FIRE)
 			fileName = "E_FIRE.bmp";
 
-		if (bonus->subtype == SpellSchool::WATER.getNum())
+		if (bonus->subtype.as<SpellSchool>() == SpellSchool::WATER)
 			fileName = "E_COLD.bmp";
 
-		if (bonus->subtype == SpellSchool::EARTH.getNum())
+		if (bonus->subtype.as<SpellSchool>() == SpellSchool::EARTH)
 			fileName = "E_SPEATH1.bmp"; //No separate icon for earth damage
 
 		break;
 	}
 	case BonusType::SPELL_SCHOOL_IMMUNITY: //for all school
 	{
-		if (bonus->subtype == SpellSchool::AIR.getNum())
+		if (bonus->subtype.as<SpellSchool>() == SpellSchool::AIR)
 			fileName = "E_SPAIR.bmp";
 
-		if (bonus->subtype == SpellSchool::FIRE.getNum())
+		if (bonus->subtype.as<SpellSchool>() == SpellSchool::FIRE)
 			fileName = "E_SPFIRE.bmp";
 
-		if (bonus->subtype == SpellSchool::WATER.getNum())
+		if (bonus->subtype.as<SpellSchool>() == SpellSchool::WATER)
 			fileName = "E_SPWATER.bmp";
 
-		if (bonus->subtype == SpellSchool::EARTH.getNum())
+		if (bonus->subtype.as<SpellSchool>() == SpellSchool::EARTH)
 			fileName = "E_SPEATH.bmp";
 
 		break;
 	}
 	case BonusType::NEGATIVE_EFFECTS_IMMUNITY:
 	{
-		if (bonus->subtype == SpellSchool::AIR.getNum())
+		if (bonus->subtype.as<SpellSchool>() == SpellSchool::AIR)
 			fileName = "E_SPAIR1.bmp";
 
-		if (bonus->subtype == SpellSchool::FIRE.getNum())
+		if (bonus->subtype.as<SpellSchool>() == SpellSchool::FIRE)
 			fileName = "E_SPFIRE1.bmp";
 
-		if (bonus->subtype == SpellSchool::WATER.getNum())
+		if (bonus->subtype.as<SpellSchool>() == SpellSchool::WATER)
 			fileName = "E_SPWATER1.bmp";
 
-		if (bonus->subtype == SpellSchool::EARTH.getNum())
+		if (bonus->subtype.as<SpellSchool>() == SpellSchool::EARTH)
 			fileName = "E_SPEATH1.bmp";
 
 		break;
@@ -168,15 +168,12 @@ ImagePath CBonusTypeHandler::bonusToGraphics(const std::shared_ptr<Bonus> & bonu
 	}
 	case BonusType::GENERAL_DAMAGE_REDUCTION:
 	{
-		switch(bonus->subtype)
-		{
-		case 0:
+		if (bonus->subtype == BonusCustomSubtype::damageTypeMelee)
 			fileName = "DamageReductionMelee.bmp";
-			break;
-		case 1:
+
+		if (bonus->subtype == BonusCustomSubtype::damageTypeRanged)
 			fileName = "DamageReductionRanged.bmp";
-			break;
-		}
+
 		break;
 	}
 

+ 54 - 51
lib/CCreatureHandler.cpp

@@ -113,25 +113,25 @@ FactionID CCreature::getFaction() const
 
 int32_t CCreature::getBaseAttack() const
 {
-	static const auto SELECTOR = Selector::typeSubtype(BonusType::PRIMARY_SKILL, static_cast<int>(PrimarySkill::ATTACK)).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));
+	static const auto SELECTOR = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK)).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));
 	return getExportedBonusList().valOfBonuses(SELECTOR);
 }
 
 int32_t CCreature::getBaseDefense() const
 {
-	static const auto SELECTOR = Selector::typeSubtype(BonusType::PRIMARY_SKILL, static_cast<int>(PrimarySkill::DEFENSE)).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));
+	static const auto SELECTOR = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::DEFENSE)).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));
 	return getExportedBonusList().valOfBonuses(SELECTOR);
 }
 
 int32_t CCreature::getBaseDamageMin() const
 {
-	static const auto SELECTOR = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, 1).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));
+	static const auto SELECTOR = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMin).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));
 	return getExportedBonusList().valOfBonuses(SELECTOR);
 }
 
 int32_t CCreature::getBaseDamageMax() const
 {
-	static const auto SELECTOR = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, 2).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));
+	static const auto SELECTOR = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMax).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));
 	return getExportedBonusList().valOfBonuses(SELECTOR);
 }
 
@@ -291,9 +291,14 @@ CCreature::CCreature()
 	fightValue = AIValue = growth = hordeGrowth = ammMin = ammMax = 0;
 }
 
-void CCreature::addBonus(int val, BonusType type, int subtype)
+void CCreature::addBonus(int val, BonusType type)
 {
-	auto selector = Selector::typeSubtype(type, subtype).And(Selector::source(BonusSource::CREATURE_ABILITY, getIndex()));
+	addBonus(val, type, BonusSubtypeID());
+}
+
+void CCreature::addBonus(int val, BonusType type, BonusSubtypeID subtype)
+{
+	auto selector = Selector::typeSubtype(type, subtype).And(Selector::source(BonusSource::CREATURE_ABILITY, BonusSourceID(getId())));
 	BonusList & exported = getExportedBonusList();
 
 	BonusList existing;
@@ -301,7 +306,7 @@ void CCreature::addBonus(int val, BonusType type, int subtype)
 
 	if(existing.empty())
 	{
-		auto added = std::make_shared<Bonus>(BonusDuration::PERMANENT, type, BonusSource::CREATURE_ABILITY, val, getIndex(), subtype, BonusValueType::BASE_NUMBER);
+		auto added = std::make_shared<Bonus>(BonusDuration::PERMANENT, type, BonusSource::CREATURE_ABILITY, val, BonusSourceID(getId()), subtype, BonusValueType::BASE_NUMBER);
 		addNewBonus(added);
 	}
 	else
@@ -345,16 +350,16 @@ void CCreature::updateFrom(const JsonNode & data)
 			addBonus(configNode["speed"].Integer(), BonusType::STACKS_SPEED);
 
 		if(!configNode["attack"].isNull())
-			addBonus(configNode["attack"].Integer(), BonusType::PRIMARY_SKILL, static_cast<int>(PrimarySkill::ATTACK));
+			addBonus(configNode["attack"].Integer(), BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK));
 
 		if(!configNode["defense"].isNull())
-			addBonus(configNode["defense"].Integer(), BonusType::PRIMARY_SKILL, static_cast<int>(PrimarySkill::DEFENSE));
+			addBonus(configNode["defense"].Integer(), BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::DEFENSE));
 
 		if(!configNode["damage"]["min"].isNull())
-			addBonus(configNode["damage"]["min"].Integer(), BonusType::CREATURE_DAMAGE, 1);
+			addBonus(configNode["damage"]["min"].Integer(), BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMin);
 
 		if(!configNode["damage"]["max"].isNull())
-			addBonus(configNode["damage"]["max"].Integer(), BonusType::CREATURE_DAMAGE, 2);
+			addBonus(configNode["damage"]["max"].Integer(), BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMax);
 
 		if(!configNode["shots"].isNull())
 			addBonus(configNode["shots"].Integer(), BonusType::SHOTS);
@@ -604,11 +609,11 @@ CCreature * CCreatureHandler::loadFromJson(const std::string & scope, const Json
 
 	cre->addBonus(node["hitPoints"].Integer(), BonusType::STACK_HEALTH);
 	cre->addBonus(node["speed"].Integer(), BonusType::STACKS_SPEED);
-	cre->addBonus(node["attack"].Integer(), BonusType::PRIMARY_SKILL, static_cast<int>(PrimarySkill::ATTACK));
-	cre->addBonus(node["defense"].Integer(), BonusType::PRIMARY_SKILL, static_cast<int>(PrimarySkill::DEFENSE));
+	cre->addBonus(node["attack"].Integer(), BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK));
+	cre->addBonus(node["defense"].Integer(), BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::DEFENSE));
 
-	cre->addBonus(node["damage"]["min"].Integer(), BonusType::CREATURE_DAMAGE, 1);
-	cre->addBonus(node["damage"]["max"].Integer(), BonusType::CREATURE_DAMAGE, 2);
+	cre->addBonus(node["damage"]["min"].Integer(), BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMin);
+	cre->addBonus(node["damage"]["max"].Integer(), BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMax);
 
 	assert(node["damage"]["min"].Integer() <= node["damage"]["max"].Integer());
 
@@ -785,9 +790,9 @@ void CCreatureHandler::loadCrExpBon(CBonusSystemNode & globalEffects)
 		}
 		do //parse everything that's left
 		{
-			auto sid = static_cast<ui32>(parser.readNumber()); //id = this particular creature ID
+			CreatureID sid = static_cast<ui32>(parser.readNumber()); //id = this particular creature ID
 
-			b.sid = sid;
+			b.sid = BonusSourceID(sid);
 			bl.clear();
 			loadStackExp(b, bl, parser);
 			for(const auto & b : bl)
@@ -893,7 +898,7 @@ void CCreatureHandler::loadCreatureJson(CCreature * creature, const JsonNode & c
 			{
 				auto b = JsonUtils::parseBonus(ability.second);
 				b->source = BonusSource::CREATURE_ABILITY;
-				b->sid = creature->getIndex();
+				b->sid = BonusSourceID(creature->getId());
 				b->duration = BonusDuration::PERMANENT;
 				creature->addNewBonus(b);
 			}
@@ -911,7 +916,7 @@ void CCreatureHandler::loadCreatureJson(CCreature * creature, const JsonNode & c
 			{
 				auto b = JsonUtils::parseBonus(ability);
 				b->source = BonusSource::CREATURE_ABILITY;
-				b->sid = creature->getIndex();
+				b->sid = BonusSourceID(creature->getId());
 				b->duration = BonusDuration::PERMANENT;
 				creature->addNewBonus(b);
 			}
@@ -1025,19 +1030,19 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars
 		break;
 	case 'A':
 		b.type = BonusType::PRIMARY_SKILL;
-		b.subtype = static_cast<int>(PrimarySkill::ATTACK);
+		b.subtype = BonusSubtypeID(PrimarySkill::ATTACK);
 		break;
 	case 'D':
 		b.type = BonusType::PRIMARY_SKILL;
-		b.subtype = static_cast<int>(PrimarySkill::DEFENSE);
+		b.subtype = BonusSubtypeID(PrimarySkill::DEFENSE);
 		break;
 	case 'M': //Max damage
 		b.type = BonusType::CREATURE_DAMAGE;
-		b.subtype = 2;
+		b.subtype = BonusCustomSubtype::creatureDamageMax;
 		break;
 	case 'm': //Min damage
 		b.type = BonusType::CREATURE_DAMAGE;
-		b.subtype = 1;
+		b.subtype = BonusCustomSubtype::creatureDamageMin;
 		break;
 	case 'S':
 		b.type = BonusType::STACKS_SPEED; break;
@@ -1051,17 +1056,16 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars
 		b.type = BonusType::DEFENSIVE_STANCE; break;
 	case 'e':
 		b.type = BonusType::DOUBLE_DAMAGE_CHANCE;
-		b.subtype = 0;
 		break;
 	case 'E':
 		b.type = BonusType::DEATH_STARE;
-		b.subtype = 0; //Gorgon
+		b.subtype = BonusCustomSubtype::deathStareGorgon;
 		break;
 	case 'F':
 		b.type = BonusType::FEAR; break;
 	case 'g':
 		b.type = BonusType::SPELL_DAMAGE_REDUCTION;
-		b.subtype = SpellSchool(ESpellSchool::ANY);
+		b.subtype = BonusSubtypeID(SpellSchool::ANY);
 		break;
 	case 'P':
 		b.type = BonusType::CASTS; break;
@@ -1069,7 +1073,6 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars
 		b.type = BonusType::ADDITIONAL_RETALIATION; break;
 	case 'W':
 		b.type = BonusType::MAGIC_RESISTANCE;
-		b.subtype = 0; //otherwise creature window goes crazy
 		break;
 	case 'f': //on-off skill
 		enable = true; //sometimes format is: 2 -> 0, 1 -> 1
@@ -1103,7 +1106,7 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars
 				b.type = BonusType::MIND_IMMUNITY; break;
 			case 'r':
 				b.type = BonusType::REBIRTH; //on/off? makes sense?
-				b.subtype = 0;
+				b.subtype = BonusCustomSubtype::rebirthRegular;
 				b.val = 20; //arbitrary value
 				break;
 			case 'R':
@@ -1126,42 +1129,42 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars
 		{
 			case 'B': //Blind
 				b.type = BonusType::SPELL_IMMUNITY;
-				b.subtype = SpellID::BLIND;
+				b.subtype = BonusSubtypeID(SpellID(SpellID::BLIND));
 				b.additionalInfo = 0;//normal immunity
 				break;
 			case 'H': //Hypnotize
 				b.type = BonusType::SPELL_IMMUNITY;
-				b.subtype = SpellID::HYPNOTIZE;
+				b.subtype = BonusSubtypeID(SpellID(SpellID::HYPNOTIZE));
 				b.additionalInfo = 0;//normal immunity
 				break;
 			case 'I': //Implosion
 				b.type = BonusType::SPELL_IMMUNITY;
-				b.subtype = SpellID::IMPLOSION;
+				b.subtype = BonusSubtypeID(SpellID(SpellID::IMPLOSION));
 				b.additionalInfo = 0;//normal immunity
 				break;
 			case 'K': //Berserk
 				b.type = BonusType::SPELL_IMMUNITY;
-				b.subtype = SpellID::BERSERK;
+				b.subtype = BonusSubtypeID(SpellID(SpellID::BERSERK));
 				b.additionalInfo = 0;//normal immunity
 				break;
 			case 'M': //Meteor Shower
 				b.type = BonusType::SPELL_IMMUNITY;
-				b.subtype = SpellID::METEOR_SHOWER;
+				b.subtype = BonusSubtypeID(SpellID(SpellID::METEOR_SHOWER));
 				b.additionalInfo = 0;//normal immunity
 				break;
 			case 'N': //dispell beneficial spells
 				b.type = BonusType::SPELL_IMMUNITY;
-				b.subtype = SpellID::DISPEL_HELPFUL_SPELLS;
+				b.subtype = BonusSubtypeID(SpellID(SpellID::DISPEL_HELPFUL_SPELLS));
 				b.additionalInfo = 0;//normal immunity
 				break;
 			case 'R': //Armageddon
 				b.type = BonusType::SPELL_IMMUNITY;
-				b.subtype = SpellID::ARMAGEDDON;
+				b.subtype = BonusSubtypeID(SpellID(SpellID::ARMAGEDDON));
 				b.additionalInfo = 0;//normal immunity
 				break;
 			case 'S': //Slow
 				b.type = BonusType::SPELL_IMMUNITY;
-				b.subtype = SpellID::SLOW;
+				b.subtype = BonusSubtypeID(SpellID(SpellID::SLOW));
 				b.additionalInfo = 0;//normal immunity
 				break;
 			case '6':
@@ -1177,51 +1180,51 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars
 				break;
 			case 'F':
 				b.type = BonusType::NEGATIVE_EFFECTS_IMMUNITY;
-				b.subtype = SpellSchool(ESpellSchool::FIRE); 
+				b.subtype = BonusSubtypeID(SpellSchool::FIRE);
 				break;
 			case 'O':
 				b.type = BonusType::SPELL_DAMAGE_REDUCTION;
-				b.subtype = SpellSchool(ESpellSchool::FIRE);
+				b.subtype = BonusSubtypeID(SpellSchool::FIRE);
 				b.val = 100; //Full damage immunity
 				break;
 			case 'f':
 				b.type = BonusType::SPELL_SCHOOL_IMMUNITY;
-				b.subtype = SpellSchool(ESpellSchool::FIRE); 
+				b.subtype = BonusSubtypeID(SpellSchool::FIRE);
 				break;
 			case 'C':
 				b.type = BonusType::NEGATIVE_EFFECTS_IMMUNITY;
-				b.subtype = SpellSchool(ESpellSchool::WATER);
+				b.subtype = BonusSubtypeID(SpellSchool::WATER);
 				break;
 			case 'W':
 				b.type = BonusType::SPELL_DAMAGE_REDUCTION;
-				b.subtype = SpellSchool(ESpellSchool::WATER);
+				b.subtype = BonusSubtypeID(SpellSchool::WATER);
 				b.val = 100; //Full damage immunity
 				break;
 			case 'w':
 				b.type = BonusType::SPELL_SCHOOL_IMMUNITY;
-				b.subtype = SpellSchool(ESpellSchool::WATER);
+				b.subtype = BonusSubtypeID(SpellSchool::WATER);
 				break;
 			case 'E':
 				b.type = BonusType::SPELL_DAMAGE_REDUCTION;
-				b.subtype = SpellSchool(ESpellSchool::EARTH);
+				b.subtype = BonusSubtypeID(SpellSchool::EARTH);
 				b.val = 100; //Full damage immunity
 				break;
 			case 'e':
 				b.type = BonusType::SPELL_SCHOOL_IMMUNITY;
-				b.subtype = SpellSchool(ESpellSchool::EARTH);
+				b.subtype = BonusSubtypeID(SpellSchool::EARTH);
 				break;
 			case 'A':
 				b.type = BonusType::SPELL_DAMAGE_REDUCTION;
-				b.subtype = SpellSchool(ESpellSchool::AIR);
+				b.subtype = BonusSubtypeID(SpellSchool::AIR);
 				b.val = 100; //Full damage immunity
 				break;
 			case 'a':
 				b.type = BonusType::SPELL_SCHOOL_IMMUNITY;
-				b.subtype = SpellSchool(ESpellSchool::AIR);
+				b.subtype = BonusSubtypeID(SpellSchool::AIR);
 				break;
 			case 'D':
 				b.type = BonusType::SPELL_DAMAGE_REDUCTION;
-				b.subtype = SpellSchool(ESpellSchool::ANY);
+				b.subtype = BonusSubtypeID(SpellSchool::ANY);
 				b.val = 100; //Full damage immunity
 				break;
 			case '0':
@@ -1250,16 +1253,16 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars
 	case 'K':
 	case 'k':
 		b.type = BonusType::SPELL_AFTER_ATTACK;
-		b.subtype = stringToNumber(mod);
+		b.subtype = BonusSubtypeID(SpellID(stringToNumber(mod)));
 		break;
 	case 'h':
 		b.type = BonusType::HATE;
-		b.subtype = stringToNumber(mod);
+		b.subtype = BonusSubtypeID(CreatureID(stringToNumber(mod)));
 		break;
 	case 'p':
 	case 'J':
 		b.type = BonusType::SPELL_BEFORE_ATTACK;
-		b.subtype = stringToNumber(mod);
+		b.subtype = BonusSubtypeID(SpellID(stringToNumber(mod)));
 		b.additionalInfo = 3; //always expert?
 		break;
 	case 'r':
@@ -1268,7 +1271,7 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars
 		break;
 	case 's':
 		b.type = BonusType::ENCHANTED;
-		b.subtype = stringToNumber(mod);
+		b.subtype = BonusSubtypeID(SpellID(stringToNumber(mod)));
 		b.valType = BonusValueType::INDEPENDENT_MAX;
 		break;
 	default:

+ 2 - 1
lib/CCreatureHandler.h

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

+ 3 - 3
lib/CGameInfoCallback.cpp

@@ -268,7 +268,7 @@ bool CGameInfoCallback::getTownInfo(const CGObjectInstance * town, InfoAboutTown
 		{
 			const auto * selectedHero = dynamic_cast<const CGHeroInstance *>(selectedObject);
 			if(nullptr != selectedHero)
-				detailed = selectedHero->hasVisions(town, 1);
+				detailed = selectedHero->hasVisions(town, BonusCustomSubtype::visionsTowns);
 		}
 
 		dest.initFromTown(dynamic_cast<const CGTownInstance *>(town), detailed);
@@ -322,7 +322,7 @@ bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero
 	{
 		const auto * selectedHero = dynamic_cast<const CGHeroInstance *>(selectedObject);
 		if(nullptr != selectedHero)
-			if(selectedHero->hasVisions(hero, 1))
+			if(selectedHero->hasVisions(hero, BonusCustomSubtype::visionsHeroes))
 				infoLevel = InfoAboutHero::EInfoLevel::DETAILED;
 	}
 
@@ -332,7 +332,7 @@ bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero
 	if(getPlayerRelations(*getPlayerID(), hero->tempOwner) == PlayerRelations::ENEMIES)
 	{
 		//todo: bonus cashing
-		int disguiseLevel = h->valOfBonuses(Selector::typeSubtype(BonusType::DISGUISED, 0));
+		int disguiseLevel = h->valOfBonuses(BonusType::DISGUISED);
 
 		auto doBasicDisguise = [](InfoAboutHero & info)
 		{

+ 4 - 4
lib/CHeroHandler.cpp

@@ -480,7 +480,7 @@ void CHeroHandler::loadHeroSkills(CHero * hero, const JsonNode & node) const
 	for(const JsonNode &set : node["skills"].Vector())
 	{
 		int skillLevel = static_cast<int>(boost::range::find(NSecondarySkill::levels, set["level"].String()) - std::begin(NSecondarySkill::levels));
-		if (skillLevel < SecSkillLevel::LEVELS_SIZE)
+		if (skillLevel < MasteryLevel::LEVELS_SIZE)
 		{
 			size_t currentIndex = hero->secSkillsInit.size();
 			hero->secSkillsInit.emplace_back(SecondarySkill(-1), skillLevel);
@@ -547,7 +547,7 @@ static std::vector<std::shared_ptr<Bonus>> createCreatureSpecialty(CreatureID ba
 		{
 			std::shared_ptr<Bonus> bonus = std::make_shared<Bonus>();
 			bonus->type = BonusType::PRIMARY_SKILL;
-			bonus->subtype = static_cast<int>(PrimarySkill::ATTACK);
+			bonus->subtype = BonusSubtypeID(PrimarySkill::ATTACK);
 			bonus->val = 0;
 			bonus->limiter.reset(new CCreatureTypeLimiter(specCreature, false));
 			bonus->updater.reset(new GrowsWithLevelUpdater(specCreature.getAttack(false), stepSize));
@@ -557,7 +557,7 @@ static std::vector<std::shared_ptr<Bonus>> createCreatureSpecialty(CreatureID ba
 		{
 			std::shared_ptr<Bonus> bonus = std::make_shared<Bonus>();
 			bonus->type = BonusType::PRIMARY_SKILL;
-			bonus->subtype = static_cast<int>(PrimarySkill::DEFENSE);
+			bonus->subtype = BonusSubtypeID(PrimarySkill::DEFENSE);
 			bonus->val = 0;
 			bonus->limiter.reset(new CCreatureTypeLimiter(specCreature, false));
 			bonus->updater.reset(new GrowsWithLevelUpdater(specCreature.getDefense(false), stepSize));
@@ -605,7 +605,7 @@ void CHeroHandler::loadHeroSpecialty(CHero * hero, const JsonNode & node)
 	{
 		bonus->duration = BonusDuration::PERMANENT;
 		bonus->source = BonusSource::HERO_SPECIAL;
-		bonus->sid = hero->getIndex();
+		bonus->sid = BonusSourceID(hero->getId());
 		return bonus;
 	};
 

+ 1 - 1
lib/CSkillHandler.cpp

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

+ 6 - 6
lib/CStack.cpp

@@ -120,23 +120,23 @@ BattleHex::EDir CStack::destShiftDir() const
 	}
 }
 
-std::vector<si32> CStack::activeSpells() const
+std::vector<SpellID> CStack::activeSpells() const
 {
-	std::vector<si32> ret;
+	std::vector<SpellID> ret;
 
 	std::stringstream cachingStr;
 	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 != BonusType::NONE && SpellID(b->sid).toSpell() && !SpellID(b->sid).toSpell()->isAdventure();
+		return b->type != BonusType::NONE && b->sid.as<SpellID>().toSpell() && !b->sid.as<SpellID>().toSpell()->isAdventure();
 	}));
 
 	TConstBonusListPtr spellEffects = getBonuses(selector, Selector::all, cachingStr.str());
 	for(const auto & it : *spellEffects)
 	{
-		if(!vstd::contains(ret, it->sid))  //do not duplicate spells with multiple effects
-			ret.push_back(it->sid);
+		if(!vstd::contains(ret, it->sid.as<SpellID>()))  //do not duplicate spells with multiple effects
+			ret.push_back(it->sid.as<SpellID>());
 	}
 
 	return ret;
@@ -220,7 +220,7 @@ void CStack::prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand, const
 					resurrectedCount += 1;
 			}
 
-			if(customState->hasBonusOfType(BonusType::REBIRTH, 1))
+			if(customState->hasBonusOfType(BonusType::REBIRTH, BonusCustomSubtype::rebirthSpecial))
 			{
 				// resurrect at least one Sacred Phoenix
 				vstd::amax(resurrectedCount, 1);

+ 1 - 1
lib/CStack.h

@@ -58,7 +58,7 @@ public:
 
 	ui32 level() const;
 	si32 magicResistance() const override; //include aura of resistance
-	std::vector<si32> activeSpells() const; //returns vector of active spell IDs sorted by time of cast
+	std::vector<SpellID> activeSpells() const; //returns vector of active spell IDs sorted by time of cast
 	const CGHeroInstance * getMyHero() const; //if stack belongs to hero (directly or was by him summoned) returns hero, nullptr otherwise
 
 	static std::vector<BattleHex> meleeAttackHexes(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos = BattleHex::INVALID, BattleHex defenderPos = BattleHex::INVALID);

+ 43 - 13
lib/CTownHandler.cpp

@@ -49,6 +49,30 @@ const std::map<std::string, CBuilding::ETowerHeight> CBuilding::TOWER_TYPES =
 	{ "skyship", CBuilding::HEIGHT_SKYSHIP }
 };
 
+BuildingTypeUniqueID::BuildingTypeUniqueID(FactionID factionID, BuildingID buildingID ):
+	BuildingTypeUniqueID(factionID.getNum() * 0x10000 + buildingID.getNum())
+{
+	assert(factionID.getNum() >= 0);
+	assert(factionID.getNum() < 0x10000);
+	assert(buildingID.getNum() >= 0);
+	assert(buildingID.getNum() < 0x10000);
+}
+
+BuildingID BuildingTypeUniqueID::getBuilding() const
+{
+	return BuildingID(getNum() % 0x10000);
+}
+
+FactionID BuildingTypeUniqueID::getFaction() const
+{
+	return FactionID(getNum() / 0x10000);
+}
+
+const BuildingTypeUniqueID CBuilding::getUniqueTypeID() const
+{
+	return BuildingTypeUniqueID(town->faction->getId(), bid);
+}
+
 std::string CBuilding::getJsonKey() const
 {
 	return modScope + ':' + identifier;;
@@ -527,16 +551,16 @@ void CTownHandler::addBonusesForVanilaBuilding(CBuilding * building) const
 		b = createBonus(building, BonusType::LUCK, +2);
 		break;
 	case BuildingSubID::SPELL_POWER_GARRISON_BONUS:
-		b = createBonus(building, BonusType::PRIMARY_SKILL, +2, static_cast<int>(PrimarySkill::SPELL_POWER));
+		b = createBonus(building, BonusType::PRIMARY_SKILL, +2, BonusSubtypeID(PrimarySkill::SPELL_POWER));
 		break;
 	case BuildingSubID::ATTACK_GARRISON_BONUS:
-		b = createBonus(building, BonusType::PRIMARY_SKILL, +2, static_cast<int>(PrimarySkill::ATTACK));
+		b = createBonus(building, BonusType::PRIMARY_SKILL, +2, BonusSubtypeID(PrimarySkill::ATTACK));
 		break;
 	case BuildingSubID::DEFENSE_GARRISON_BONUS:
-		b = createBonus(building, BonusType::PRIMARY_SKILL, +2, static_cast<int>(PrimarySkill::DEFENSE));
+		b = createBonus(building, BonusType::PRIMARY_SKILL, +2, BonusSubtypeID(PrimarySkill::DEFENSE));
 		break;
 	case BuildingSubID::LIGHTHOUSE:
-		b = createBonus(building, BonusType::MOVEMENT, +500, playerPropagator, 0);
+		b = createBonus(building, BonusType::MOVEMENT, +500, BonusCustomSubtype::heroMovementSea, playerPropagator);
 		break;
 	}
 
@@ -544,26 +568,32 @@ void CTownHandler::addBonusesForVanilaBuilding(CBuilding * building) const
 		building->addNewBonus(b, building->buildingBonuses);
 }
 
-std::shared_ptr<Bonus> CTownHandler::createBonus(CBuilding * build, BonusType type, int val, int subtype) const
+std::shared_ptr<Bonus> CTownHandler::createBonus(CBuilding * build, BonusType type, int val) const
+{
+	return createBonus(build, type, val, BonusSubtypeID(), emptyPropagator());
+}
+
+std::shared_ptr<Bonus> CTownHandler::createBonus(CBuilding * build, BonusType type, int val, BonusSubtypeID subtype) const
 {
-	return createBonus(build, type, val, emptyPropagator(), subtype);
+	return createBonus(build, type, val, subtype, emptyPropagator());
 }
 
-std::shared_ptr<Bonus> CTownHandler::createBonus(CBuilding * build, BonusType type, int val, TPropagatorPtr & prop, int subtype) const
+std::shared_ptr<Bonus> CTownHandler::createBonus(CBuilding * build, BonusType type, int val, BonusSubtypeID subtype, TPropagatorPtr & prop) const
 {
 	std::ostringstream descr;
 	descr << build->getNameTranslated();
-	return createBonusImpl(build->bid, type, val, prop, descr.str(), subtype);
+	return createBonusImpl(build->bid, build->town->faction->getId(), type, val, prop, descr.str(), subtype);
 }
 
 std::shared_ptr<Bonus> CTownHandler::createBonusImpl(const BuildingID & building,
+													 const FactionID & faction,
 													 BonusType type,
 													 int val,
 													 TPropagatorPtr & prop,
 													 const std::string & description,
-													 int subtype) const
+													 BonusSubtypeID subtype) const
 {
-	auto b = std::make_shared<Bonus>(BonusDuration::PERMANENT, type, BonusSource::TOWN_STRUCTURE, val, building, description, subtype);
+	auto b = std::make_shared<Bonus>(BonusDuration::PERMANENT, type, BonusSource::TOWN_STRUCTURE, val, BuildingTypeUniqueID(faction, building), subtype, description);
 
 	if(prop)
 		b->addPropagator(prop);
@@ -575,12 +605,12 @@ void CTownHandler::loadSpecialBuildingBonuses(const JsonNode & source, BonusList
 {
 	for(const auto & b : source.Vector())
 	{
-		auto bonus = JsonUtils::parseBuildingBonus(b, building->bid, building->getNameTranslated());
+		auto bonus = JsonUtils::parseBuildingBonus(b, building->town->faction->getId(), building->bid, building->getNameTranslated());
 
 		if(bonus == nullptr)
 			continue;
 
-		bonus->sid = Bonus::getSid32(building->town->faction->getIndex(), building->bid);
+		bonus->sid = BonusSourceID(building->getUniqueTypeID());
 		//JsonUtils::parseBuildingBonus produces UNKNOWN type propagator instead of empty.
 		if(bonus->propagator != nullptr
 			&& bonus->propagator->getPropagatorType() == CBonusSystemNode::ENodeTypes::UNKNOWN)
@@ -644,7 +674,7 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
 				ret->subId = BuildingSubID::CUSTOM_VISITING_BONUS;
 
 			for(auto & bonus : ret->onVisitBonuses)
-				bonus->sid = Bonus::getSid32(ret->town->faction->getIndex(), ret->bid);
+				bonus->sid = BonusSourceID(ret->getUniqueTypeID());
 		}
 		
 		if(source["type"].String() == "configurable" && ret->subId == BuildingSubID::NONE)

+ 7 - 6
lib/CTownHandler.h

@@ -37,9 +37,6 @@ class JsonSerializeFormat;
 /// a typical building encountered in every castle ;]
 /// this is structure available to both client and server
 /// contains all mechanics-related data about town structures
-
-
-
 class DLL_LINKAGE CBuilding
 {
 	std::string modScope;
@@ -84,6 +81,8 @@ public:
 
 	CBuilding() : town(nullptr), mode(BUILD_NORMAL) {};
 
+	const BuildingTypeUniqueID getUniqueTypeID() const;
+
 	std::string getJsonKey() const;
 
 	std::string getNameTranslated() const;
@@ -392,14 +391,16 @@ 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, 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> createBonus(CBuilding * build, BonusType type, int val) const;
+	std::shared_ptr<Bonus> createBonus(CBuilding * build, BonusType type, int val, BonusSubtypeID subtype) const;
+	std::shared_ptr<Bonus> createBonus(CBuilding * build, BonusType type, int val, BonusSubtypeID subtype, TPropagatorPtr & prop) const;
 	std::shared_ptr<Bonus> createBonusImpl(const BuildingID & building,
+										   const FactionID & faction,
 												  BonusType type,
 												  int val,
 												  TPropagatorPtr & prop,
 												  const std::string & description,
-												  int subtype = -1) const;
+												  BonusSubtypeID subtype) const;
 
 	/// loads CStructure's into town
 	void loadStructure(CTown & town, const std::string & stringID, const JsonNode & source) const;

+ 258 - 67
lib/JsonNode.cpp

@@ -417,13 +417,241 @@ std::string JsonNode::toJson(bool compact) const
 
 ///JsonUtils
 
-void JsonUtils::parseTypedBonusShort(const JsonVector & source, const std::shared_ptr<Bonus> & dest)
+static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const JsonNode & node)
 {
-	dest->val = static_cast<si32>(source[1].Float());
-	resolveIdentifier(source[2],dest->subtype);
-	dest->additionalInfo = static_cast<si32>(source[3].Float());
-	dest->duration = BonusDuration::PERMANENT; //TODO: handle flags (as integer)
-	dest->turnsRemain = 0;
+	if (node.isNull())
+	{
+		subtype = BonusSubtypeID();
+		return;
+	}
+
+	if (node.isNumber()) // Compatibility code for 1.3 or older
+	{
+		logMod->warn("Bonus subtype must be string!");
+		subtype = BonusCustomSubtype(node.Integer());
+		return;
+	}
+
+	if (!node.isString())
+	{
+		logMod->warn("Bonus subtype must be string!");
+		subtype = BonusSubtypeID();
+		return;
+	}
+
+	switch (type)
+	{
+		case BonusType::MAGIC_SCHOOL_SKILL:
+		case BonusType::SPELL_DAMAGE:
+		case BonusType::SPELLS_OF_SCHOOL:
+		case BonusType::SPELL_DAMAGE_REDUCTION:
+		case BonusType::SPELL_SCHOOL_IMMUNITY:
+		{
+			VLC->identifiers()->requestIdentifier( "spellSchool", node, [&subtype](int32_t identifier)
+			{
+				subtype = SpellSchool(identifier);
+			});
+			break;
+		}
+		case BonusType::NO_TERRAIN_PENALTY:
+		{
+			VLC->identifiers()->requestIdentifier( "terrain", node, [&subtype](int32_t identifier)
+			{
+				subtype = TerrainId(identifier);
+			});
+			break;
+		}
+		case BonusType::PRIMARY_SKILL:
+		{
+			VLC->identifiers()->requestIdentifier( "primarySkill", node, [&subtype](int32_t identifier)
+			{
+				subtype = PrimarySkill(identifier);
+			});
+			break;
+		}
+		case BonusType::IMPROVED_NECROMANCY:
+		case BonusType::HERO_GRANTS_ATTACKS:
+		case BonusType::BONUS_DAMAGE_CHANCE:
+		case BonusType::BONUS_DAMAGE_PERCENTAGE:
+		case BonusType::SPECIAL_UPGRADE:
+		case BonusType::HATE:
+		case BonusType::SUMMON_GUARDIANS:
+		case BonusType::MANUAL_CONTROL:
+		{
+			VLC->identifiers()->requestIdentifier( "creature", node, [&subtype](int32_t identifier)
+			{
+				subtype = CreatureID(identifier);
+			});
+			break;
+		}
+		case BonusType::SPELL_IMMUNITY:
+		case BonusType::SPECIAL_ADD_VALUE_ENCHANT:
+		case BonusType::SPECIAL_FIXED_VALUE_ENCHANT:
+		case BonusType::SPECIAL_PECULIAR_ENCHANT:
+		case BonusType::SPECIAL_SPELL_LEV:
+		case BonusType::SPECIFIC_SPELL_DAMAGE:
+		case BonusType::SPELL:
+		case BonusType::OPENING_BATTLE_SPELL:
+		case BonusType::SPELL_LIKE_ATTACK:
+		case BonusType::CATAPULT:
+		case BonusType::CATAPULT_EXTRA_SHOTS:
+		case BonusType::HEALER:
+		case BonusType::SPELLCASTER:
+		case BonusType::ENCHANTER:
+		case BonusType::SPELL_AFTER_ATTACK:
+		case BonusType::SPELL_BEFORE_ATTACK:
+		case BonusType::SPECIFIC_SPELL_POWER:
+		case BonusType::ENCHANTED:
+		case BonusType::MORE_DAMAGE_FROM_SPELL:
+		case BonusType::NOT_ACTIVE:
+		{
+			VLC->identifiers()->requestIdentifier( "spell", node, [&subtype](int32_t identifier)
+			{
+				subtype = SpellID(identifier);
+			});
+			break;
+		}
+		case BonusType::GENERATE_RESOURCE:
+		{
+			VLC->identifiers()->requestIdentifier( "resource", node, [&subtype](int32_t identifier)
+			{
+				subtype = GameResID(identifier);
+			});
+			break;
+		}
+		case BonusType::MOVEMENT:
+		case BonusType::WATER_WALKING:
+		case BonusType::FLYING_MOVEMENT:
+		case BonusType::NEGATE_ALL_NATURAL_IMMUNITIES:
+		case BonusType::CREATURE_DAMAGE:
+		case BonusType::FLYING:
+		case BonusType::GENERAL_DAMAGE_REDUCTION:
+		case BonusType::PERCENTAGE_DAMAGE_BOOST:
+		case BonusType::SOUL_STEAL:
+		case BonusType::TRANSMUTATION:
+		case BonusType::DESTRUCTION:
+		case BonusType::DEATH_STARE:
+		case BonusType::REBIRTH:
+		case BonusType::VISIONS:
+		case BonusType::SPELLS_OF_LEVEL: // spell level
+		case BonusType::CREATURE_GROWTH: // creature level
+		{
+			VLC->identifiers()->requestIdentifier( "bonusSubtype", node, [&subtype](int32_t identifier)
+			{
+				subtype = BonusCustomSubtype(identifier);
+			});
+			break;
+		}
+		default:
+			for(const auto & i : bonusNameMap)
+				if(i.second == type)
+					logMod->warn("Bonus type %s does not supports subtypes!", i.first );
+			subtype =  BonusSubtypeID();
+	}
+}
+
+static void loadBonusSourceInstance(BonusSourceID & sourceInstance, BonusSource sourceType, const JsonNode & node)
+{
+	if (node.isNull())
+	{
+		sourceInstance = BonusCustomSource();
+		return;
+	}
+
+	if (node.isNumber()) // Compatibility code for 1.3 or older
+	{
+		logMod->warn("Bonus source must be string!");
+		sourceInstance = BonusCustomSource(node.Integer());
+		return;
+	}
+
+	if (!node.isString())
+	{
+		logMod->warn("Bonus source must be string!");
+		sourceInstance = BonusCustomSource();
+		return;
+	}
+
+	switch (sourceType)
+	{
+		case BonusSource::ARTIFACT:
+		case BonusSource::ARTIFACT_INSTANCE:
+		{
+			VLC->identifiers()->requestIdentifier( "artifact", node, [&sourceInstance](int32_t identifier)
+			{
+				sourceInstance = ArtifactID(identifier);
+			});
+			break;
+		}
+		case BonusSource::OBJECT_TYPE:
+		{
+			VLC->identifiers()->requestIdentifier( "object", node, [&sourceInstance](int32_t identifier)
+			{
+				sourceInstance = Obj(identifier);
+			});
+			break;
+		}
+		case BonusSource::OBJECT_INSTANCE:
+		case BonusSource::HERO_BASE_SKILL:
+			sourceInstance = ObjectInstanceID(ObjectInstanceID::decode(node.String()));
+			break;
+		case BonusSource::CREATURE_ABILITY:
+		{
+			VLC->identifiers()->requestIdentifier( "creature", node, [&sourceInstance](int32_t identifier)
+			{
+				sourceInstance = CreatureID(identifier);
+			});
+			break;
+		}
+		case BonusSource::TERRAIN_OVERLAY:
+		{
+			VLC->identifiers()->requestIdentifier( "spell", node, [&sourceInstance](int32_t identifier)
+			{
+				sourceInstance = BattleField(identifier);
+			});
+			break;
+		}
+		case BonusSource::SPELL_EFFECT:
+		{
+			VLC->identifiers()->requestIdentifier( "spell", node, [&sourceInstance](int32_t identifier)
+			{
+				sourceInstance = SpellID(identifier);
+			});
+			break;
+		}
+		case BonusSource::TOWN_STRUCTURE:
+			assert(0); // TODO
+			sourceInstance = BuildingTypeUniqueID();
+			break;
+		case BonusSource::SECONDARY_SKILL:
+		{
+			VLC->identifiers()->requestIdentifier( "secondarySkill", node, [&sourceInstance](int32_t identifier)
+			{
+				sourceInstance = SecondarySkill(identifier);
+			});
+			break;
+		}
+		case BonusSource::HERO_SPECIAL:
+		{
+			VLC->identifiers()->requestIdentifier( "hero", node, [&sourceInstance](int32_t identifier)
+			{
+				sourceInstance = HeroTypeID(identifier);
+			});
+			break;
+		}
+		case BonusSource::CAMPAIGN_BONUS:
+			sourceInstance = CampaignScenarioID(CampaignScenarioID::decode(node.String()));
+			break;
+		case BonusSource::ARMY:
+		case BonusSource::STACK_EXPERIENCE:
+		case BonusSource::COMMANDER:
+		case BonusSource::GLOBAL:
+		case BonusSource::TERRAIN_NATIVE:
+		case BonusSource::OTHER:
+		default:
+			sourceInstance = BonusSourceID();
+			break;
+	}
 }
 
 std::shared_ptr<Bonus> JsonUtils::parseBonus(const JsonVector & ability_vec)
@@ -438,7 +666,11 @@ std::shared_ptr<Bonus> JsonUtils::parseBonus(const JsonVector & ability_vec)
 	}
 	b->type = it->second;
 
-	parseTypedBonusShort(ability_vec, b);
+	b->val = static_cast<si32>(ability_vec[1].Float());
+	loadBonusSubtype(b->subtype, b->type, ability_vec[2]);
+	b->additionalInfo = static_cast<si32>(ability_vec[3].Float());
+	b->duration = BonusDuration::PERMANENT; //TODO: handle flags (as integer)
+	b->turnsRemain = 0;
 	return b;
 }
 
@@ -473,31 +705,6 @@ const T parseByMapN(const std::map<std::string, T> & map, const JsonNode * val,
 		return parseByMap<T>(map, val, err);
 }
 
-void JsonUtils::resolveIdentifier(si32 & var, const JsonNode & node, const std::string & name)
-{
-	const JsonNode &value = node[name];
-	if (!value.isNull())
-	{
-		switch (value.getType())
-		{
-			case JsonNode::JsonType::DATA_INTEGER:
-				var = static_cast<si32>(value.Integer());
-				break;
-			case JsonNode::JsonType::DATA_FLOAT:
-				var = static_cast<si32>(value.Float());
-				break;
-			case JsonNode::JsonType::DATA_STRING:
-				VLC->identifiers()->requestIdentifier(value, [&](si32 identifier)
-				{
-					var = identifier;
-				});
-				break;
-			default:
-				logMod->error("Error! Wrong identifier used for value of %s.", name);
-		}
-	}
-}
-
 void JsonUtils::resolveAddInfo(CAddInfo & var, const JsonNode & node)
 {
 	const JsonNode & value = node["addInfo"];
@@ -549,27 +756,6 @@ void JsonUtils::resolveAddInfo(CAddInfo & var, const JsonNode & node)
 	}
 }
 
-void JsonUtils::resolveIdentifier(const JsonNode &node, si32 &var)
-{
-	switch (node.getType())
-	{
-		case JsonNode::JsonType::DATA_INTEGER:
-			var = static_cast<si32>(node.Integer());
-			break;
-		case JsonNode::JsonType::DATA_FLOAT:
-			var = static_cast<si32>(node.Float());
-			break;
-		case JsonNode::JsonType::DATA_STRING:
-			VLC->identifiers()->requestIdentifier(node, [&](si32 identifier)
-			{
-				var = identifier;
-			});
-			break;
-		default:
-			logMod->error("Error! Wrong identifier used for identifier!");
-	}
-}
-
 std::shared_ptr<ILimiter> JsonUtils::parseLimiter(const JsonNode & limiter)
 {
 	switch(limiter.getType())
@@ -660,7 +846,7 @@ std::shared_ptr<ILimiter> JsonUtils::parseLimiter(const JsonNode & limiter)
 								bonusLimiter->source = sourceIt->second;
 								bonusLimiter->isSourceRelevant = true;
 								if(!parameter["id"].isNull()) {
-									resolveIdentifier(parameter["id"], bonusLimiter->sid);
+									loadBonusSourceInstance(bonusLimiter->sid, bonusLimiter->source, parameter["id"]);
 									bonusLimiter->isSourceIDRelevant = true;
 								}
 							}
@@ -673,7 +859,7 @@ std::shared_ptr<ILimiter> JsonUtils::parseLimiter(const JsonNode & limiter)
 							return bonusLimiter;
 						else
 						{
-							resolveIdentifier(parameters[1], bonusLimiter->subtype);
+							loadBonusSubtype(bonusLimiter->subtype, bonusLimiter->type, parameters[1]);
 							bonusLimiter->isSubtypeRelevant = true;
 							if(parameters.size() > 2)
 								findSource(parameters[2]);
@@ -759,13 +945,13 @@ std::shared_ptr<Bonus> JsonUtils::parseBonus(const JsonNode &ability)
 	return b;
 }
 
-std::shared_ptr<Bonus> JsonUtils::parseBuildingBonus(const JsonNode & ability, const BuildingID & building, const std::string & description)
+std::shared_ptr<Bonus> JsonUtils::parseBuildingBonus(const JsonNode & ability, const FactionID & faction, const BuildingID & building, const std::string & description)
 {
 	/*	duration = BonusDuration::PERMANENT
 		source = BonusSource::TOWN_STRUCTURE
 		bonusType, val, subtype - get from json
 	*/
-	auto b = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::NONE, BonusSource::TOWN_STRUCTURE, 0, building, description, -1);
+	auto b = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::NONE, BonusSource::TOWN_STRUCTURE, 0, BuildingTypeUniqueID(faction, building), description);
 
 	if(!parseBonus(ability, b.get()))
 		return nullptr;
@@ -865,7 +1051,7 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
 	else
 		b->type = it->second;
 
-	resolveIdentifier(b->subtype, params->isConverted ? params->toJson() : ability, "subtype");
+	loadBonusSubtype(b->subtype, b->type, params->isConverted ? params->toJson()["subtype"] : ability["subtype"]);
 
 	if(!params->isConverted)
 	{
@@ -882,8 +1068,6 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
 
 	b->turnsRemain = static_cast<si32>(ability["turns"].Float());
 
-	b->sid = static_cast<si32>(ability["sourceID"].Float());
-
 	if(!ability["description"].isNull())
 	{
 		if (ability["description"].isString())
@@ -921,6 +1105,9 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
 	if (!value->isNull())
 		b->source = static_cast<BonusSource>(parseByMap(bonusSourceMap, value, "source type "));
 
+	if (!ability["sourceID"].isNull())
+		loadBonusSourceInstance(b->sid, b->source, ability["sourceID"]);
+
 	value = &ability["targetSourceType"];
 	if (!value->isNull())
 		b->targetSourceType = static_cast<BonusSource>(parseByMap(bonusSourceMap, value, "target type "));
@@ -984,24 +1171,29 @@ CSelector JsonUtils::parseSelector(const JsonNode & ability)
 		ret = ret.And(base.Not());
 	}
 
+	BonusType type = BonusType::NONE;
+
 	// Actual selector parser
 	value = &ability["type"];
 	if(value->isString())
 	{
 		auto it = bonusNameMap.find(value->String());
 		if(it != bonusNameMap.end())
+		{
+			type = it->second;
 			ret = ret.And(Selector::type()(it->second));
+		}
 	}
 	value = &ability["subtype"];
-	if(!value->isNull())
+	if(!value->isNull() && type != BonusType::NONE)
 	{
-		TBonusSubtype subtype;
-		resolveIdentifier(subtype, ability, "subtype");
+		BonusSubtypeID subtype;
+		loadBonusSubtype(subtype, type, ability);
 		ret = ret.And(Selector::subtype()(subtype));
 	}
 	value = &ability["sourceType"];
 	std::optional<BonusSource> src = std::nullopt; //Fixes for GCC false maybe-uninitialized
-	std::optional<si32> id = std::nullopt;
+	std::optional<BonusSourceID> id = std::nullopt;
 	if(value->isString())
 	{
 		auto it = bonusSourceMap.find(value->String());
@@ -1010,10 +1202,9 @@ CSelector JsonUtils::parseSelector(const JsonNode & ability)
 	}
 
 	value = &ability["sourceID"];
-	if(!value->isNull())
+	if(!value->isNull() && src.has_value())
 	{
-		id = -1;
-		resolveIdentifier(*id, ability, "sourceID");
+		loadBonusSourceInstance(*id, *src, ability);
 	}
 
 	if(src && id)

+ 1 - 10
lib/JsonNode.h

@@ -127,21 +127,12 @@ public:
 
 namespace JsonUtils
 {
-	/**
-	 * @brief parse short bonus format, excluding type
-	 * @note sets duration to Permament
-	 */
-	DLL_LINKAGE void parseTypedBonusShort(const JsonVector & source, const std::shared_ptr<Bonus> & dest);
-
-	///
 	DLL_LINKAGE std::shared_ptr<Bonus> parseBonus(const JsonVector & ability_vec);
 	DLL_LINKAGE std::shared_ptr<Bonus> parseBonus(const JsonNode & ability);
-	DLL_LINKAGE std::shared_ptr<Bonus> parseBuildingBonus(const JsonNode & ability, const BuildingID & building, const std::string & description);
+	DLL_LINKAGE std::shared_ptr<Bonus> parseBuildingBonus(const JsonNode & ability, const FactionID & faction, const BuildingID & building, const std::string & description);
 	DLL_LINKAGE bool parseBonus(const JsonNode & ability, Bonus * placement);
 	DLL_LINKAGE std::shared_ptr<ILimiter> parseLimiter(const JsonNode & limiter);
 	DLL_LINKAGE CSelector parseSelector(const JsonNode &ability);
-	DLL_LINKAGE void resolveIdentifier(si32 & var, const JsonNode & node, const std::string & name);
-	DLL_LINKAGE void resolveIdentifier(const JsonNode & node, si32 & var);
 	DLL_LINKAGE void resolveAddInfo(CAddInfo & var, const JsonNode & node);
 
 	/**

+ 2 - 2
lib/NetPacks.h

@@ -496,8 +496,8 @@ struct DLL_LINKAGE RemoveBonus : public CPackForClient
 	ui32 whoID = 0; //hero, town or player id - whoever loses bonus
 
 	//vars to identify bonus: its source
-	ui8 source = 0;
-	ui32 id = 0; //source id
+	BonusSource source;
+	BonusSourceID id; //source id
 
 	//used locally: copy of removed bonus
 	Bonus bonus;

+ 4 - 4
lib/NetPacksLib.cpp

@@ -993,7 +993,7 @@ void GiveBonus::applyGs(CGameState *gs)
 
 	if(bdescr.empty() && (bonus.type == BonusType::LUCK || bonus.type == BonusType::MORALE))
 	{
-		if (bonus.source == BonusSource::OBJECT)
+		if (bonus.source == BonusSource::OBJECT_TYPE || bonus.source == BonusSource::OBJECT_INSTANCE)
 		{
 			descr = VLC->generaltexth->arraytxt[bonus.val > 0 ? 110 : 109]; //+/-%d Temporary until next battle"
 		}
@@ -1119,7 +1119,7 @@ void RemoveBonus::applyGs(CGameState *gs)
 
 	for(const auto & b : bonuses)
 	{
-		if(vstd::to_underlying(b->source) == source && b->sid == id)
+		if(b->source == source && b->sid == id)
 		{
 			bonus = *b; //backup bonus (to show to interfaces later)
 			node->removeBonus(b);
@@ -2201,7 +2201,7 @@ void BattleTriggerEffect::applyGs(CGameState * gs) const
 	}
 	case BonusType::POISON:
 	{
-		auto b = st->getBonusLocalFirst(Selector::source(BonusSource::SPELL_EFFECT, SpellID::POISON)
+		auto b = st->getBonusLocalFirst(Selector::source(BonusSource::SPELL_EFFECT, SpellID(SpellID::POISON))
 				.And(Selector::type()(BonusType::STACK_HEALTH)));
 		if (b)
 			b->val = val;
@@ -2553,7 +2553,7 @@ void PlayerCheated::applyGs(CGameState * gs) const
 
 void PlayerStartsTurn::applyGs(CGameState * gs) const
 {
-	assert(gs->actingPlayers.count(player) == 0);
+	//assert(gs->actingPlayers.count(player) == 0);//Legal - may happen after loading of deserialized map state
 	gs->actingPlayers.insert(player);
 }
 

+ 4 - 4
lib/battle/BattleInfo.cpp

@@ -442,9 +442,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>(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, static_cast<int>(PrimarySkill::ATTACK))->addLimiter(nativeTerrain));
-	curB->addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::PRIMARY_SKILL, BonusSource::TERRAIN_NATIVE, 1, 0, static_cast<int>(PrimarySkill::DEFENSE))->addLimiter(nativeTerrain));
+	curB->addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::STACKS_SPEED, BonusSource::TERRAIN_NATIVE, 1,  BonusSourceID())->addLimiter(nativeTerrain));
+	curB->addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::PRIMARY_SKILL, BonusSource::TERRAIN_NATIVE, 1, BonusSourceID(), BonusSubtypeID(PrimarySkill::ATTACK))->addLimiter(nativeTerrain));
+	curB->addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::PRIMARY_SKILL, BonusSource::TERRAIN_NATIVE, 1, BonusSourceID(), BonusSubtypeID(PrimarySkill::DEFENSE))->addLimiter(nativeTerrain));
 	//////////////////////////////////////////////////////////////////////////
 
 	//tactics
@@ -802,7 +802,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 == BonusSource::SPELL_EFFECT && b->sid != SpellID::DISRUPTING_RAY;
+			return b->source == BonusSource::SPELL_EFFECT && b->sid.as<SpellID>() != SpellID::DISRUPTING_RAY;
 		};
 		changedStack->removeBonusesRecursive(selector);
 	}

+ 3 - 3
lib/battle/CBattleInfoCallback.cpp

@@ -1658,7 +1658,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, c
 		std::stringstream cachingStr;
 		cachingStr << "source_" << vstd::to_underlying(BonusSource::SPELL_EFFECT) << "id_" << spellID.num;
 
-		if(subject->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, spellID), Selector::all, cachingStr.str())
+		if(subject->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(spellID)), Selector::all, cachingStr.str())
 		 //TODO: this ability has special limitations
 		|| !(spellID.toSpell()->canBeCast(this, spells::Mode::CREATURE_ACTIVE, subject)))
 			continue;
@@ -1755,7 +1755,7 @@ SpellID CBattleInfoCallback::getRandomCastedSpell(CRandomGenerator & rand,const
 		return SpellID::NONE;
 
 	if(bl->size() == 1)
-		return SpellID(bl->front()->subtype);
+		return bl->front()->subtype.as<SpellID>();
 
 	int totalWeight = 0;
 	for(const auto & b : *bl)
@@ -1772,7 +1772,7 @@ SpellID CBattleInfoCallback::getRandomCastedSpell(CRandomGenerator & rand,const
 		randomPos -= std::max(b->additionalInfo[0], 0);
 		if(randomPos < 0)
 		{
-			return SpellID(b->subtype);
+			return b->subtype.as<SpellID>();
 		}
 	}
 

+ 8 - 8
lib/battle/CUnitState.cpp

@@ -340,12 +340,12 @@ CUnitState::CUnitState():
 	health(this),
 	shots(this),
 	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, static_cast<int>(PrimarySkill::ATTACK)), 0),
-	defence(this, Selector::typeSubtype(BonusType::PRIMARY_SKILL, static_cast<int>(PrimarySkill::DEFENSE)), 0),
+	minDamage(this, Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageBoth).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMin)), 0),
+	maxDamage(this, Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageBoth).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMax)), 0),
+	attack(this, Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK)), 0),
+	defence(this, Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::DEFENSE)), 0),
 	inFrenzy(this, Selector::type()(BonusType::IN_FRENZY)),
-	cloneLifetimeMarker(this, Selector::type()(BonusType::NONE).And(Selector::source(BonusSource::SPELL_EFFECT, SpellID::CLONE))),
+	cloneLifetimeMarker(this, Selector::type()(BonusType::NONE).And(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(SpellID(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(BonusType::SPELLCASTER, spell->getIndex()));
+	int32_t skill = valOfBonuses(Selector::typeSubtype(BonusType::SPELLCASTER, BonusSubtypeID(spell->getId())));
 	vstd::abetween(skill, 0, 3);
 	return skill;
 }
@@ -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(BonusType::SPECIFIC_SPELL_POWER, spell->getIndex());
+	return static_cast<int64_t>(getCount()) * valOfBonuses(BonusType::SPECIFIC_SPELL_POWER, BonusSubtypeID(spell->getId()));
 }
 
 PlayerColor CUnitState::getCasterOwner() const
@@ -511,7 +511,7 @@ bool CUnitState::isGhost() const
 
 bool CUnitState::isFrozen() const
 {
-	return hasBonus(Selector::source(BonusSource::SPELL_EFFECT, SpellID::STONE_GAZE), Selector::all);
+	return hasBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(SpellID(SpellID::STONE_GAZE))), Selector::all);
 }
 
 bool CUnitState::isValidTarget(bool allowDead) const

+ 14 - 13
lib/battle/DamageCalculator.cpp

@@ -52,7 +52,7 @@ DamageRange DamageCalculator::getBaseDamageSingle() const
 	{
 		auto retrieveHeroPrimSkill = [&](PrimarySkill skill) -> int
 		{
-			std::shared_ptr<const Bonus> b = info.attacker->getBonus(Selector::sourceTypeSel(BonusSource::HERO_BASE_SKILL).And(Selector::typeSubtype(BonusType::PRIMARY_SKILL, static_cast<int>(skill))));
+			std::shared_ptr<const Bonus> b = info.attacker->getBonus(Selector::sourceTypeSel(BonusSource::HERO_BASE_SKILL).And(Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(skill))));
 			return b ? b->val : 0;
 		};
 
@@ -142,8 +142,9 @@ int DamageCalculator::getActorAttackSlayer() const
 
 		if(isAffected)
 		{
-			int attackBonus = SpellID(SpellID::SLAYER).toSpell()->getLevelPower(spLevel);
-			if(info.attacker->hasBonusOfType(BonusType::SPECIAL_PECULIAR_ENCHANT, SpellID::SLAYER))
+			SpellID spell(SpellID::SLAYER);
+			int attackBonus = spell.toSpell()->getLevelPower(spLevel);
+			if(info.attacker->hasBonusOfType(BonusType::SPECIAL_PECULIAR_ENCHANT, BonusSubtypeID(spell)))
 			{
 				ui8 attackerTier = info.attacker->unitType()->getLevel();
 				ui8 specialtyBonus = std::max(5 - attackerTier, 0);
@@ -205,11 +206,11 @@ double DamageCalculator::getAttackOffenseArcheryFactor() const
 	if(info.shooting)
 	{
 		const std::string cachingStrArchery = "type_PERCENTAGE_DAMAGE_BOOSTs_1";
-		static const auto selectorArchery = Selector::typeSubtype(BonusType::PERCENTAGE_DAMAGE_BOOST, 1);
+		static const auto selectorArchery = Selector::typeSubtype(BonusType::PERCENTAGE_DAMAGE_BOOST, BonusCustomSubtype::damageTypeRanged);
 		return info.attacker->valOfBonuses(selectorArchery, cachingStrArchery) / 100.0;
 	}
 	const std::string cachingStrOffence = "type_PERCENTAGE_DAMAGE_BOOSTs_0";
-	static const auto selectorOffence = Selector::typeSubtype(BonusType::PERCENTAGE_DAMAGE_BOOST, 0);
+	static const auto selectorOffence = Selector::typeSubtype(BonusType::PERCENTAGE_DAMAGE_BOOST, BonusCustomSubtype::damageTypeMelee);
 	return info.attacker->valOfBonuses(selectorOffence, cachingStrOffence) / 100.0;
 }
 
@@ -231,7 +232,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(BonusType::BONUS_DAMAGE_PERCENTAGE, info.attacker->creatureIndex());
+		const auto selector = Selector::typeSubtype(BonusType::BONUS_DAMAGE_PERCENTAGE, BonusSubtypeID(info.attacker->creatureId()));
 		return info.attacker->valOfBonuses(selector, cachingStr) / 100.0;
 	}
 	return 0.0;
@@ -259,7 +260,7 @@ double DamageCalculator::getAttackHateFactor() const
 
 	auto allHateEffects = info.attacker->getBonuses(selectorHate, cachingStrHate);
 
-	return allHateEffects->valOfBonuses(Selector::subtype()(info.defender->creatureIndex())) / 100.0;
+	return allHateEffects->valOfBonuses(Selector::subtype()(BonusSubtypeID(info.defender->creatureId()))) / 100.0;
 }
 
 double DamageCalculator::getDefenseSkillFactor() const
@@ -281,7 +282,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(BonusType::GENERAL_DAMAGE_REDUCTION, -1).And(Selector::sourceTypeSel(BonusSource::SPELL_EFFECT).Not());
+	static const auto selectorArmorer = Selector::typeSubtype(BonusType::GENERAL_DAMAGE_REDUCTION, BonusCustomSubtype::damageTypeAll).And(Selector::sourceTypeSel(BonusSource::SPELL_EFFECT).Not());
 	return info.defender->valOfBonuses(selectorArmorer, cachingStrArmorer) / 100.0;
 
 }
@@ -289,10 +290,10 @@ double DamageCalculator::getDefenseArmorerFactor() const
 double DamageCalculator::getDefenseMagicShieldFactor() const
 {
 	const std::string cachingStrMeleeReduction = "type_GENERAL_DAMAGE_REDUCTIONs_0";
-	static const auto selectorMeleeReduction = Selector::typeSubtype(BonusType::GENERAL_DAMAGE_REDUCTION, 0);
+	static const auto selectorMeleeReduction = Selector::typeSubtype(BonusType::GENERAL_DAMAGE_REDUCTION, BonusCustomSubtype::damageTypeMelee);
 
 	const std::string cachingStrRangedReduction = "type_GENERAL_DAMAGE_REDUCTIONs_1";
-	static const auto selectorRangedReduction = Selector::typeSubtype(BonusType::GENERAL_DAMAGE_REDUCTION, 1);
+	static const auto selectorRangedReduction = Selector::typeSubtype(BonusType::GENERAL_DAMAGE_REDUCTION, BonusCustomSubtype::damageTypeRanged);
 
 	//handling spell effects - shield and air shield
 	if(info.shooting)
@@ -312,8 +313,8 @@ double DamageCalculator::getDefenseRangePenaltiesFactor() const
 		auto isAdvancedAirShield = [](const Bonus* bonus)
 		{
 			return bonus->source == BonusSource::SPELL_EFFECT
-					&& bonus->sid == SpellID::AIR_SHIELD
-					&& bonus->val >= SecSkillLevel::ADVANCED;
+					&& bonus->sid == BonusSourceID(SpellID(SpellID::AIR_SHIELD))
+					&& bonus->val >= MasteryLevel::ADVANCED;
 		};
 
 		const bool distPenalty = callback.battleHasDistancePenalty(info.attacker, attackerPos, defenderPos);
@@ -386,7 +387,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(BonusType::GENERAL_DAMAGE_REDUCTION, -1).And(Selector::sourceTypeSel(BonusSource::SPELL_EFFECT));
+	static const auto selectorAllReduction = Selector::typeSubtype(BonusType::GENERAL_DAMAGE_REDUCTION, BonusCustomSubtype::damageTypeAll).And(Selector::sourceTypeSel(BonusSource::SPELL_EFFECT));
 
 	return info.defender->valOfBonuses(selectorAllReduction, cachingStrAllReduction) / 100.0;
 }

+ 25 - 36
lib/bonuses/Bonus.cpp

@@ -100,19 +100,19 @@ std::string Bonus::Description(std::optional<si32> customValue) const
 			switch(source)
 			{
 			case BonusSource::ARTIFACT:
-				str << ArtifactID(sid).toArtifact(VLC->artifacts())->getNameTranslated();
+				str << sid.as<ArtifactID>().toArtifact(VLC->artifacts())->getNameTranslated();
 				break;
 			case BonusSource::SPELL_EFFECT:
-				str << SpellID(sid).toSpell(VLC->spells())->getNameTranslated();
+				str << sid.as<SpellID>().toSpell(VLC->spells())->getNameTranslated();
 				break;
 			case BonusSource::CREATURE_ABILITY:
-				str << CreatureID(sid).toCreature(VLC->creatures())->getNamePluralTranslated();
+				str << sid.as<CreatureID>().toCreature(VLC->creatures())->getNamePluralTranslated();
 				break;
 			case BonusSource::SECONDARY_SKILL:
-				str << VLC->skills()->getByIndex(sid)->getNameTranslated();
+				str << VLC->skills()->getById(sid.as<SecondarySkill>())->getNameTranslated();
 				break;
 			case BonusSource::HERO_SPECIAL:
-				str << VLC->heroTypes()->getByIndex(sid)->getNameTranslated();
+				str << VLC->heroTypes()->getById(sid.as<HeroTypeID>())->getNameTranslated();
 				break;
 			default:
 				//todo: handle all possible sources
@@ -134,29 +134,6 @@ std::string Bonus::Description(std::optional<si32> customValue) const
 	return str.str();
 }
 
-static JsonNode subtypeToJson(BonusType type, int subtype)
-{
-	switch(type)
-	{
-	case BonusType::PRIMARY_SKILL:
-		return JsonUtils::stringNode("primSkill." + NPrimarySkill::names[subtype]);
-	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(ModUtility::makeFullIdentifier("", "spell", SpellID::encode(subtype)));
-	case BonusType::IMPROVED_NECROMANCY:
-	case BonusType::SPECIAL_UPGRADE:
-		return JsonUtils::stringNode(ModUtility::makeFullIdentifier("", "creature", CreatureID::encode(subtype)));
-	case BonusType::GENERATE_RESOURCE:
-		return JsonUtils::stringNode("resource." + GameConstants::RESOURCE_NAMES[subtype]);
-	default:
-		return JsonUtils::intNode(subtype);
-	}
-}
-
 static JsonNode additionalInfoToJson(BonusType type, CAddInfo addInfo)
 {
 	switch(type)
@@ -173,16 +150,16 @@ JsonNode Bonus::toJsonNode() const
 	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
 	// only add values that might reasonably be found in config files
 	root["type"].String() = vstd::findKey(bonusNameMap, type);
-	if(subtype != -1)
-		root["subtype"] = subtypeToJson(type, subtype);
+	if(subtype != BonusSubtypeID())
+		root["subtype"].String() = subtype.toString();
 	if(additionalInfo != CAddInfo::NONE)
 		root["addInfo"] = additionalInfoToJson(type, additionalInfo);
 	if(source != BonusSource::OTHER)
 		root["sourceType"].String() = vstd::findKey(bonusSourceMap, source);
 	if(targetSourceType != BonusSource::OTHER)
 		root["targetSourceType"].String() = vstd::findKey(bonusSourceMap, targetSourceType);
-	if(sid != 0)
-		root["sourceID"].Integer() = sid;
+	if(sid != BonusSourceID())
+		root["sourceID"].String() = sid.toString();
 	if(val != 0)
 		root["val"].Integer() = val;
 	if(valType != BonusValueType::ADDITIVE_VALUE)
@@ -206,7 +183,19 @@ JsonNode Bonus::toJsonNode() const
 	return root;
 }
 
-Bonus::Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::string Desc, si32 Subtype):
+Bonus::Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID ID)
+	: Bonus(Duration, Type, Src, Val, ID, BonusSubtypeID(), std::string())
+{}
+
+Bonus::Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID ID, std::string Desc)
+	: Bonus(Duration, Type, Src, Val, ID, BonusSubtypeID(), Desc)
+{}
+
+Bonus::Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID ID, BonusSubtypeID Subtype)
+	: Bonus(Duration, Type, Src, Val, ID, Subtype, std::string())
+{}
+
+Bonus::Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID ID, BonusSubtypeID Subtype, std::string Desc):
 	duration(Duration),
 	type(Type),
 	subtype(Subtype),
@@ -219,7 +208,7 @@ Bonus::Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32
 	targetSourceType = BonusSource::OTHER;
 }
 
-Bonus::Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, si32 Subtype, BonusValueType ValType):
+Bonus::Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID ID, BonusSubtypeID Subtype, BonusValueType ValType):
 	duration(Duration),
 	type(Type),
 	subtype(Subtype),
@@ -247,10 +236,10 @@ DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const Bonus &bonus)
 
 #define printField(field) out << "\t" #field ": " << (int)bonus.field << "\n"
 	printField(val);
-	printField(subtype);
+	out << "\tSubtype: " << bonus.subtype.toString() << "\n";
 	printField(duration.to_ulong());
 	printField(source);
-	printField(sid);
+	out << "\tSource ID: " << bonus.sid.toString() << "\n";
 	if(bonus.additionalInfo != CAddInfo::NONE)
 		out << "\taddInfo: " << bonus.additionalInfo.toString() << "\n";
 	printField(turnsRemain);

+ 12 - 19
lib/bonuses/Bonus.h

@@ -10,6 +10,9 @@
 #pragma once
 
 #include "BonusEnum.h"
+#include "BonusCustomTypes.h"
+#include "../constants/VariantIdentifier.h"
+#include "../constants/EntityIdentifiers.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -22,7 +25,8 @@ class IUpdater;
 class BonusList;
 class CSelector;
 
-using TBonusSubtype = int32_t;
+using BonusSubtypeID = VariantIdentifier<BonusCustomSubtype, SpellID, CreatureID, PrimarySkill, TerrainId, GameResID, SpellSchool>;
+using BonusSourceID = VariantIdentifier<BonusCustomSource, SpellID, CreatureID, ArtifactID, CampaignScenarioID, SecondarySkill, HeroTypeID, Obj, ObjectInstanceID, BuildingTypeUniqueID, BattleField>;
 using TBonusListPtr = std::shared_ptr<BonusList>;
 using TConstBonusListPtr = std::shared_ptr<const BonusList>;
 using TLimiterPtr = std::shared_ptr<ILimiter>;
@@ -56,12 +60,12 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
 	si16 turnsRemain = 0; //used if duration is N_TURNS, N_DAYS or ONE_WEEK
 
 	BonusType type = BonusType::NONE; //uses BonusType values - says to what is this bonus - 1 byte
-	TBonusSubtype subtype = -1; //-1 if not applicable - 4 bytes
+	BonusSubtypeID subtype;
 
 	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
+	BonusSourceID sid; //source id: id of object/artifact/spell
 	BonusValueType valType = BonusValueType::ADDITIVE_VALUE;
 	std::string stacking; // bonuses with the same stacking value don't stack (e.g. Angel/Archangel morale bonus)
 
@@ -75,8 +79,11 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
 
 	std::string description;
 
-	Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::string Desc, si32 Subtype=-1);
-	Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, si32 Subtype=-1, BonusValueType ValType = BonusValueType::ADDITIVE_VALUE);
+	Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID sourceID);
+	Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID sourceID, std::string Desc);
+	Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID sourceID, BonusSubtypeID subtype);
+	Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID sourceID, BonusSubtypeID subtype, std::string Desc);
+	Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID sourceID, BonusSubtypeID subtype, BonusValueType ValType);
 	Bonus() = default;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
@@ -163,20 +170,6 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
 	{
 		val += Val;
 	}
-	STRONG_INLINE static ui32 getSid32(ui32 high, ui32 low)
-	{
-		return (high << 16) + low;
-	}
-
-	STRONG_INLINE static ui32 getHighFromSid32(ui32 sid)
-	{
-		return sid >> 16;
-	}
-
-	STRONG_INLINE static ui32 getLowFromSid32(ui32 sid)
-	{
-		return sid & 0x0000FFFF;
-	}
 
 	std::string Description(std::optional<si32> customValue = {}) const;
 	JsonNode toJsonNode() const;

+ 74 - 0
lib/bonuses/BonusCustomTypes.cpp

@@ -0,0 +1,74 @@
+/*
+ * BonusCustomTypes.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 "BonusCustomTypes.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+const BonusCustomSubtype BonusCustomSubtype::creatureDamageBoth(0);
+const BonusCustomSubtype BonusCustomSubtype::creatureDamageMin(1);
+const BonusCustomSubtype BonusCustomSubtype::creatureDamageMax(2);
+const BonusCustomSubtype BonusCustomSubtype::damageTypeAll(-1);
+const BonusCustomSubtype BonusCustomSubtype::damageTypeMelee(0);
+const BonusCustomSubtype BonusCustomSubtype::damageTypeRanged(1);
+const BonusCustomSubtype BonusCustomSubtype::heroMovementLand(1);
+const BonusCustomSubtype BonusCustomSubtype::heroMovementSea(0);
+const BonusCustomSubtype BonusCustomSubtype::deathStareGorgon(0);
+const BonusCustomSubtype BonusCustomSubtype::deathStareCommander(1);
+const BonusCustomSubtype BonusCustomSubtype::rebirthRegular(0);
+const BonusCustomSubtype BonusCustomSubtype::rebirthSpecial(1);
+const BonusCustomSubtype BonusCustomSubtype::visionsMonsters(0);
+const BonusCustomSubtype BonusCustomSubtype::visionsHeroes(1);
+const BonusCustomSubtype BonusCustomSubtype::visionsTowns(2);
+const BonusCustomSubtype BonusCustomSubtype::immunityBattleWide(0);
+const BonusCustomSubtype BonusCustomSubtype::immunityEnemyHero(1);
+const BonusCustomSubtype BonusCustomSubtype::transmutationPerHealth(0);
+const BonusCustomSubtype BonusCustomSubtype::transmutationPerUnit(1);
+const BonusCustomSubtype BonusCustomSubtype::destructionKillPercentage(0);
+const BonusCustomSubtype BonusCustomSubtype::destructionKillAmount(1);
+const BonusCustomSubtype BonusCustomSubtype::soulStealPermanent(0);
+const BonusCustomSubtype BonusCustomSubtype::soulStealBattle(1);
+const BonusCustomSubtype BonusCustomSubtype::movementFlying(0);
+const BonusCustomSubtype BonusCustomSubtype::movementTeleporting(1);
+
+const BonusCustomSource BonusCustomSource::undeadMoraleDebuff(-2);
+
+BonusCustomSubtype BonusCustomSubtype::spellLevel(int level)
+{
+	return BonusCustomSubtype(level);
+}
+
+BonusCustomSubtype BonusCustomSubtype::creatureLevel(int level)
+{
+	return BonusCustomSubtype(level);
+}
+
+si32 BonusCustomSubtype::decode(const std::string & identifier)
+{
+	return std::stoi(identifier);
+}
+
+std::string BonusCustomSubtype::encode(const si32 index)
+{
+	return std::to_string(index);
+}
+
+si32 BonusCustomSource::decode(const std::string & identifier)
+{
+	return std::stoi(identifier);
+}
+
+std::string BonusCustomSource::encode(const si32 index)
+{
+	return std::to_string(index);
+}
+
+VCMI_LIB_NAMESPACE_END

+ 75 - 0
lib/bonuses/BonusCustomTypes.h

@@ -0,0 +1,75 @@
+/*
+ * BonusCustomTypes.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../constants/EntityIdentifiers.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class DLL_LINKAGE BonusCustomSource : public Identifier<BonusCustomSource>
+{
+public:
+	using Identifier<BonusCustomSource>::Identifier;
+
+	static std::string encode(int32_t index);
+	static si32 decode(const std::string & identifier);
+
+	static const BonusCustomSource undeadMoraleDebuff; // -2
+};
+
+class DLL_LINKAGE BonusCustomSubtype : public Identifier<BonusCustomSubtype>
+{
+public:
+	using Identifier<BonusCustomSubtype>::Identifier;
+
+	static std::string encode(int32_t index);
+	static si32 decode(const std::string & identifier);
+
+	static const BonusCustomSubtype creatureDamageBoth; // 0
+	static const BonusCustomSubtype creatureDamageMin; // 1
+	static const BonusCustomSubtype creatureDamageMax; // 2
+
+	static const BonusCustomSubtype damageTypeAll; // -1
+	static const BonusCustomSubtype damageTypeMelee; // 0
+	static const BonusCustomSubtype damageTypeRanged; // 1
+
+	static const BonusCustomSubtype heroMovementLand; // 1
+	static const BonusCustomSubtype heroMovementSea; // 0
+
+	static const BonusCustomSubtype deathStareGorgon; // 0
+	static const BonusCustomSubtype deathStareCommander;
+
+	static const BonusCustomSubtype rebirthRegular; // 0
+	static const BonusCustomSubtype rebirthSpecial; // 1
+
+	static const BonusCustomSubtype visionsMonsters; // 0
+	static const BonusCustomSubtype visionsHeroes; // 1
+	static const BonusCustomSubtype visionsTowns; // 2
+
+	static const BonusCustomSubtype immunityBattleWide; // 0
+	static const BonusCustomSubtype immunityEnemyHero; // 1
+
+	static const BonusCustomSubtype transmutationPerHealth; // 0
+	static const BonusCustomSubtype transmutationPerUnit; // 1
+
+	static const BonusCustomSubtype destructionKillPercentage; // 0
+	static const BonusCustomSubtype destructionKillAmount; // 1
+
+	static const BonusCustomSubtype soulStealPermanent; // 0
+	static const BonusCustomSubtype soulStealBattle; // 1
+
+	static const BonusCustomSubtype movementFlying; // 0
+	static const BonusCustomSubtype movementTeleporting; // 1
+
+	static BonusCustomSubtype spellLevel(int level);
+	static BonusCustomSubtype creatureLevel(int level);
+};
+
+VCMI_LIB_NAMESPACE_END

+ 2 - 2
lib/bonuses/BonusEnum.h

@@ -175,7 +175,8 @@ class JsonNode;
 #define BONUS_SOURCE_LIST \
 	BONUS_SOURCE(ARTIFACT)\
 	BONUS_SOURCE(ARTIFACT_INSTANCE)\
-	BONUS_SOURCE(OBJECT)\
+	BONUS_SOURCE(OBJECT_TYPE)\
+	BONUS_SOURCE(OBJECT_INSTANCE)\
 	BONUS_SOURCE(CREATURE_ABILITY)\
 	BONUS_SOURCE(TERRAIN_NATIVE)\
 	BONUS_SOURCE(TERRAIN_OVERLAY)\
@@ -186,7 +187,6 @@ class JsonNode;
 	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*/\

+ 1 - 1
lib/bonuses/BonusList.cpp

@@ -275,4 +275,4 @@ DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const BonusList &bonusL
 	return out;
 }
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

+ 1 - 1
lib/bonuses/BonusList.h

@@ -111,4 +111,4 @@ public:
 DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const BonusList &bonusList);
 
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

+ 38 - 41
lib/bonuses/BonusParams.cpp

@@ -15,6 +15,9 @@
 #include "BonusSelector.h"
 
 #include "../ResourceSet.h"
+#include "../VCMI_Lib.h"
+#include "../modding/IdentifierStorage.h"
+#include "../modding/ModScope.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -81,76 +84,76 @@ BonusParams::BonusParams(std::string deprecatedTypeStr, std::string deprecatedSu
 		else if(deprecatedSubtype == SecondarySkill::SORCERY || deprecatedSubtypeStr == "skill.sorcery")
 		{
 			type = BonusType::SPELL_DAMAGE;
-			subtype = SpellSchool(ESpellSchool::ANY);
+			subtype = BonusSubtypeID(SpellSchool::ANY);
 		}
 		else if(deprecatedSubtype == SecondarySkill::SCHOLAR || deprecatedSubtypeStr == "skill.scholar")
 			type = BonusType::LEARN_MEETING_SPELL_LIMIT;
 		else if(deprecatedSubtype == SecondarySkill::ARCHERY|| deprecatedSubtypeStr == "skill.archery")
 		{
-			subtype = 1;
+			subtype = BonusCustomSubtype::damageTypeRanged;
 			type = BonusType::PERCENTAGE_DAMAGE_BOOST;
 		}
 		else if(deprecatedSubtype == SecondarySkill::OFFENCE || deprecatedSubtypeStr == "skill.offence")
 		{
-			subtype = 0;
+			subtype = BonusCustomSubtype::damageTypeMelee;
 			type = BonusType::PERCENTAGE_DAMAGE_BOOST;
 		}
 		else if(deprecatedSubtype == SecondarySkill::ARMORER || deprecatedSubtypeStr == "skill.armorer")
 		{
-			subtype = -1;
+			subtype = BonusCustomSubtype::damageTypeAll;
 			type = BonusType::GENERAL_DAMAGE_REDUCTION;
 		}
 		else if(deprecatedSubtype == SecondarySkill::NAVIGATION || deprecatedSubtypeStr == "skill.navigation")
 		{
-			subtype = 0;
+			subtype = BonusCustomSubtype::heroMovementSea;
 			valueType = BonusValueType::PERCENT_TO_BASE;
 			type = BonusType::MOVEMENT;
 		}
 		else if(deprecatedSubtype == SecondarySkill::LOGISTICS || deprecatedSubtypeStr == "skill.logistics")
 		{
-			subtype = 1;
+			subtype = BonusCustomSubtype::heroMovementLand;
 			valueType = BonusValueType::PERCENT_TO_BASE;
 			type = BonusType::MOVEMENT;
 		}
 		else if(deprecatedSubtype == SecondarySkill::ESTATES || deprecatedSubtypeStr == "skill.estates")
 		{
 			type = BonusType::GENERATE_RESOURCE;
-			subtype = GameResID(EGameResID::GOLD);
+			subtype = BonusSubtypeID(GameResID(EGameResID::GOLD));
 		}
 		else if(deprecatedSubtype == SecondarySkill::AIR_MAGIC || deprecatedSubtypeStr == "skill.airMagic")
 		{
 			type = BonusType::MAGIC_SCHOOL_SKILL;
-			subtype = SpellSchool(ESpellSchool::AIR);
+			subtype = BonusSubtypeID(SpellSchool::AIR);
 		}
 		else if(deprecatedSubtype == SecondarySkill::WATER_MAGIC || deprecatedSubtypeStr == "skill.waterMagic")
 		{
 			type = BonusType::MAGIC_SCHOOL_SKILL;
-			subtype = SpellSchool(ESpellSchool::WATER);
+			subtype = BonusSubtypeID(SpellSchool::WATER);
 		}
 		else if(deprecatedSubtype == SecondarySkill::FIRE_MAGIC || deprecatedSubtypeStr == "skill.fireMagic")
 		{
 			type = BonusType::MAGIC_SCHOOL_SKILL;
-			subtype = SpellSchool(ESpellSchool::FIRE);
+			subtype = BonusSubtypeID(SpellSchool::FIRE);
 		}
 		else if(deprecatedSubtype == SecondarySkill::EARTH_MAGIC || deprecatedSubtypeStr == "skill.earthMagic")
 		{
 			type = BonusType::MAGIC_SCHOOL_SKILL;
-			subtype = SpellSchool(ESpellSchool::EARTH);
+			subtype = BonusSubtypeID(SpellSchool::EARTH);
 		}
 		else if (deprecatedSubtype == SecondarySkill::ARTILLERY || deprecatedSubtypeStr == "skill.artillery")
 		{
 			type = BonusType::BONUS_DAMAGE_CHANCE;
-			subtypeStr = "core:creature.ballista";
+			subtype = BonusSubtypeID(CreatureID(CreatureID::BALLISTA));
 		}
 		else if (deprecatedSubtype == SecondarySkill::FIRST_AID || deprecatedSubtypeStr == "skill.firstAid")
 		{
 			type = BonusType::SPECIFIC_SPELL_POWER;
-			subtypeStr = "core:spell.firstAid";
+			subtype = SpellID(*VLC->identifiers()->getIdentifier( ModScope::scopeGame(), "spell", "firstAid"));
 		}
 		else if (deprecatedSubtype == SecondarySkill::BALLISTICS || deprecatedSubtypeStr == "skill.ballistics")
 		{
 			type = BonusType::CATAPULT_EXTRA_SHOTS;
-			subtypeStr = "core:spell.catapultShot";
+			subtype = SpellID(*VLC->identifiers()->getIdentifier( ModScope::scopeGame(), "spell", "catapultShot"));
 		}
 		else
 			isConverted = false;
@@ -162,27 +165,27 @@ BonusParams::BonusParams(std::string deprecatedTypeStr, std::string deprecatedSu
 		else if (deprecatedSubtype == SecondarySkill::ARTILLERY || deprecatedSubtypeStr == "skill.artillery")
 		{
 			type = BonusType::HERO_GRANTS_ATTACKS;
-			subtypeStr = "core:creature.ballista";
+			subtype = BonusSubtypeID(CreatureID(CreatureID::BALLISTA));
 		}
 		else
 			isConverted = false;
 	}
 	else if (deprecatedTypeStr == "SEA_MOVEMENT")
 	{
-		subtype = 0;
+		subtype = BonusCustomSubtype::heroMovementSea;
 		valueType = BonusValueType::ADDITIVE_VALUE;
 		type = BonusType::MOVEMENT;
 	}
 	else if (deprecatedTypeStr == "LAND_MOVEMENT")
 	{
-		subtype = 1;
+		subtype = BonusCustomSubtype::heroMovementLand;
 		valueType = BonusValueType::ADDITIVE_VALUE;
 		type = BonusType::MOVEMENT;
 	}
 	else if (deprecatedTypeStr == "MAXED_SPELL")
 	{
 		type = BonusType::SPELL;
-		subtypeStr = deprecatedSubtypeStr;
+		subtype = SpellID(*VLC->identifiers()->getIdentifier( ModScope::scopeGame(), "spell", deprecatedSubtypeStr));
 		valueType = BonusValueType::INDEPENDENT_MAX;
 		val = 3;
 	}
@@ -223,52 +226,52 @@ BonusParams::BonusParams(std::string deprecatedTypeStr, std::string deprecatedSu
 	else if (deprecatedTypeStr == "DIRECT_DAMAGE_IMMUNITY")
 	{
 		type = BonusType::SPELL_DAMAGE_REDUCTION;
-		subtype = SpellSchool(ESpellSchool::ANY);
+		subtype = BonusSubtypeID(SpellSchool::ANY);
 		val = 100;
 	}
 	else if (deprecatedTypeStr == "AIR_SPELL_DMG_PREMY")
 	{
 		type = BonusType::SPELL_DAMAGE;
-		subtype = SpellSchool(ESpellSchool::AIR);
+		subtype = BonusSubtypeID(SpellSchool::AIR);
 	}
 	else if (deprecatedTypeStr == "FIRE_SPELL_DMG_PREMY")
 	{
 		type = BonusType::SPELL_DAMAGE;
-		subtype = SpellSchool(ESpellSchool::FIRE);
+		subtype = BonusSubtypeID(SpellSchool::FIRE);
 	}
 	else if (deprecatedTypeStr == "WATER_SPELL_DMG_PREMY")
 	{
 		type = BonusType::SPELL_DAMAGE;
-		subtype = SpellSchool(ESpellSchool::WATER);
+		subtype = BonusSubtypeID(SpellSchool::WATER);
 	}
 	else if (deprecatedTypeStr == "EARTH_SPELL_DMG_PREMY")
 	{
 		type = BonusType::SPELL_DAMAGE;
-		subtype = SpellSchool(ESpellSchool::EARTH);
+		subtype = BonusSubtypeID(SpellSchool::EARTH);
 	}
 	else if (deprecatedTypeStr == "AIR_SPELLS")
 	{
 		type = BonusType::SPELLS_OF_SCHOOL;
-		subtype = SpellSchool(ESpellSchool::AIR);
+		subtype = BonusSubtypeID(SpellSchool::AIR);
 	}
 	else if (deprecatedTypeStr == "FIRE_SPELLS")
 	{
 		type = BonusType::SPELLS_OF_SCHOOL;
-		subtype = SpellSchool(ESpellSchool::FIRE);
+		subtype = BonusSubtypeID(SpellSchool::FIRE);
 	}
 	else if (deprecatedTypeStr == "WATER_SPELLS")
 	{
 		type = BonusType::SPELLS_OF_SCHOOL;
-		subtype = SpellSchool(ESpellSchool::WATER);
+		subtype = BonusSubtypeID(SpellSchool::WATER);
 	}
 	else if (deprecatedTypeStr == "EARTH_SPELLS")
 	{
 		type = BonusType::SPELLS_OF_SCHOOL;
-		subtype = SpellSchool(ESpellSchool::EARTH);
+		subtype = BonusSubtypeID(SpellSchool::EARTH);
 	}
 	else if (deprecatedTypeStr == "AIR_IMMUNITY")
 	{
-		subtype = SpellSchool(ESpellSchool::AIR);
+		subtype = BonusSubtypeID(SpellSchool::AIR);
 		switch(deprecatedSubtype)
 		{
 			case 0:
@@ -284,7 +287,7 @@ BonusParams::BonusParams(std::string deprecatedTypeStr, std::string deprecatedSu
 	}
 	else if (deprecatedTypeStr == "FIRE_IMMUNITY")
 	{
-		subtype = SpellSchool(ESpellSchool::FIRE);
+		subtype = BonusSubtypeID(SpellSchool::FIRE);
 		switch(deprecatedSubtype)
 		{
 			case 0:
@@ -300,7 +303,7 @@ BonusParams::BonusParams(std::string deprecatedTypeStr, std::string deprecatedSu
 	}
 	else if (deprecatedTypeStr == "WATER_IMMUNITY")
 	{
-		subtype = SpellSchool(ESpellSchool::WATER);
+		subtype = BonusSubtypeID(SpellSchool::WATER);
 		switch(deprecatedSubtype)
 		{
 			case 0:
@@ -316,7 +319,7 @@ BonusParams::BonusParams(std::string deprecatedTypeStr, std::string deprecatedSu
 	}
 	else if (deprecatedTypeStr == "EARTH_IMMUNITY")
 	{
-		subtype = SpellSchool(ESpellSchool::EARTH);
+		subtype = BonusSubtypeID(SpellSchool::EARTH);
 		switch(deprecatedSubtype)
 		{
 			case 0:
@@ -340,10 +343,8 @@ const JsonNode & BonusParams::toJson()
 	if(ret.isNull())
 	{
 		ret["type"].String() = vstd::findKey(bonusNameMap, type);
-		if(subtypeStr)
-			ret["subtype"].String() = *subtypeStr;
-		else if(subtype)
-			ret["subtype"].Integer() = *subtype;
+		if(subtype)
+			ret["subtype"].String() = subtype->toString();
 		if(valueType)
 			ret["valueType"].String() = vstd::findKey(bonusValueMap, *valueType);
 		if(val)
@@ -352,17 +353,13 @@ const JsonNode & BonusParams::toJson()
 			ret["targetSourceType"].String() = vstd::findKey(bonusSourceMap, *targetType);
 		jsonCreated = true;
 	}
+	ret.setMeta(ModScope::scopeGame());
 	return ret;
 };
 
 CSelector BonusParams::toSelector()
 {
 	assert(isConverted);
-	if(subtypeStr)
-	{
-		subtype = -1;
-		JsonUtils::resolveIdentifier(*subtype, toJson(), "subtype");
-	}
 
 	auto ret = Selector::type()(type);
 	if(subtype)
@@ -374,4 +371,4 @@ CSelector BonusParams::toSelector()
 	return ret;
 }
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

+ 2 - 3
lib/bonuses/BonusParams.h

@@ -19,8 +19,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 struct DLL_LINKAGE BonusParams {
 	bool isConverted;
 	BonusType type = BonusType::NONE;
-	std::optional<TBonusSubtype> subtype = std::nullopt;
-	std::optional<std::string> subtypeStr = std::nullopt;
+	std::optional<BonusSubtypeID> subtype = std::nullopt;
 	std::optional<BonusValueType> valueType = std::nullopt;
 	std::optional<si32> val = std::nullopt;
 	std::optional<BonusSource> targetType = std::nullopt;
@@ -36,4 +35,4 @@ private:
 
 extern DLL_LINKAGE const std::set<std::string> deprecatedBonusSet;
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

+ 8 - 8
lib/bonuses/BonusSelector.cpp

@@ -21,9 +21,9 @@ namespace Selector
 		return stype;
 	}
 
-	DLL_LINKAGE CSelectFieldEqual<TBonusSubtype> & subtype()
+	DLL_LINKAGE CSelectFieldEqual<BonusSubtypeID> & subtype()
 	{
-		static CSelectFieldEqual<TBonusSubtype> ssubtype(&Bonus::subtype);
+		static CSelectFieldEqual<BonusSubtypeID> ssubtype(&Bonus::subtype);
 		return ssubtype;
 	}
 
@@ -54,22 +54,22 @@ namespace Selector
 	DLL_LINKAGE CWillLastTurns turns;
 	DLL_LINKAGE CWillLastDays days;
 
-	CSelector DLL_LINKAGE typeSubtype(BonusType Type, TBonusSubtype Subtype)
+	CSelector DLL_LINKAGE typeSubtype(BonusType Type, BonusSubtypeID Subtype)
 	{
 		return type()(Type).And(subtype()(Subtype));
 	}
 
-	CSelector DLL_LINKAGE typeSubtypeInfo(BonusType type, TBonusSubtype subtype, const CAddInfo & info)
+	CSelector DLL_LINKAGE typeSubtypeInfo(BonusType type, BonusSubtypeID subtype, const CAddInfo & info)
 	{
 		return CSelectFieldEqual<BonusType>(&Bonus::type)(type)
-			.And(CSelectFieldEqual<TBonusSubtype>(&Bonus::subtype)(subtype))
+			.And(CSelectFieldEqual<BonusSubtypeID>(&Bonus::subtype)(subtype))
 			.And(CSelectFieldEqual<CAddInfo>(&Bonus::additionalInfo)(info));
 	}
 
-	CSelector DLL_LINKAGE source(BonusSource source, ui32 sourceID)
+	CSelector DLL_LINKAGE source(BonusSource source, BonusSourceID sourceID)
 	{
 		return CSelectFieldEqual<BonusSource>(&Bonus::source)(source)
-			.And(CSelectFieldEqual<ui32>(&Bonus::sid)(sourceID));
+			.And(CSelectFieldEqual<BonusSourceID>(&Bonus::sid)(sourceID));
 	}
 
 	CSelector DLL_LINKAGE sourceTypeSel(BonusSource source)
@@ -86,4 +86,4 @@ namespace Selector
 	DLL_LINKAGE CSelector none([](const Bonus * b){return false;});
 }
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

+ 5 - 5
lib/bonuses/BonusSelector.h

@@ -126,7 +126,7 @@ public:
 namespace Selector
 {
 	extern DLL_LINKAGE CSelectFieldEqual<BonusType> & type();
-	extern DLL_LINKAGE CSelectFieldEqual<TBonusSubtype> & subtype();
+	extern DLL_LINKAGE CSelectFieldEqual<BonusSubtypeID> & subtype();
 	extern DLL_LINKAGE CSelectFieldEqual<CAddInfo> & info();
 	extern DLL_LINKAGE CSelectFieldEqual<BonusSource> & sourceType();
 	extern DLL_LINKAGE CSelectFieldEqual<BonusSource> & targetSourceType();
@@ -134,9 +134,9 @@ namespace Selector
 	extern DLL_LINKAGE CWillLastTurns turns;
 	extern DLL_LINKAGE CWillLastDays days;
 
-	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 typeSubtype(BonusType Type, BonusSubtypeID Subtype);
+	CSelector DLL_LINKAGE typeSubtypeInfo(BonusType type, BonusSubtypeID subtype, const CAddInfo & info);
+	CSelector DLL_LINKAGE source(BonusSource source, BonusSourceID sourceID);
 	CSelector DLL_LINKAGE sourceTypeSel(BonusSource source);
 	CSelector DLL_LINKAGE valueType(BonusValueType valType);
 
@@ -153,4 +153,4 @@ namespace Selector
 	extern DLL_LINKAGE CSelector none;
 }
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

+ 8 - 8
lib/bonuses/IBonusBearer.cpp

@@ -62,30 +62,30 @@ bool IBonusBearer::hasBonusOfType(BonusType type) const
 	return hasBonus(s, cachingStr);
 }
 
-int IBonusBearer::valOfBonuses(BonusType type, int subtype) const
+int IBonusBearer::valOfBonuses(BonusType type, BonusSubtypeID subtype) const
 {
 	//This part is performance-critical
-	std::string cachingStr = "type_" + std::to_string(static_cast<int>(type)) + "_" + std::to_string(subtype);
+	std::string cachingStr = "type_" + std::to_string(static_cast<int>(type)) + "_" + subtype.toString();
 
 	CSelector s = Selector::typeSubtype(type, subtype);
 
 	return valOfBonuses(s, cachingStr);
 }
 
-bool IBonusBearer::hasBonusOfType(BonusType type, int subtype) const
+bool IBonusBearer::hasBonusOfType(BonusType type, BonusSubtypeID subtype) const
 {
 	//This part is performance-critical
-	std::string cachingStr = "type_" + std::to_string(static_cast<int>(type)) + "_" + std::to_string(subtype);
+	std::string cachingStr = "type_" + std::to_string(static_cast<int>(type)) + "_" + subtype.toString();
 
 	CSelector s = Selector::typeSubtype(type, subtype);
 
 	return hasBonus(s, cachingStr);
 }
 
-bool IBonusBearer::hasBonusFrom(BonusSource source, ui32 sourceID) const
+bool IBonusBearer::hasBonusFrom(BonusSource source, BonusSourceID sourceID) const
 {
-	boost::format fmt("source_%did_%d");
-	fmt % static_cast<int>(source) % sourceID;
+	boost::format fmt("source_%did_%s");
+	fmt % static_cast<int>(source) % sourceID.toString();
 
 	return hasBonus(Selector::source(source,sourceID), fmt.str());
 }
@@ -95,4 +95,4 @@ std::shared_ptr<const Bonus> IBonusBearer::getBonus(const CSelector &selector) c
 	return bonuses->getFirst(Selector::all);
 }
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

+ 4 - 4
lib/bonuses/IBonusBearer.h

@@ -35,11 +35,11 @@ public:
 	//Optimized interface (with auto-caching)
 	int valOfBonuses(BonusType type) const; //subtype -> subtype of bonus;
 	bool hasBonusOfType(BonusType type) const;//determines if hero has a bonus of given type (and optionally subtype)
-	int valOfBonuses(BonusType type, int subtype) const; //subtype -> subtype of bonus;
-	bool hasBonusOfType(BonusType type, int subtype) const;//determines if hero has a bonus of given type (and optionally subtype)
-	bool hasBonusFrom(BonusSource source, ui32 sourceID) const;
+	int valOfBonuses(BonusType type, BonusSubtypeID subtype) const; //subtype -> subtype of bonus;
+	bool hasBonusOfType(BonusType type, BonusSubtypeID subtype) const;//determines if hero has a bonus of given type (and optionally subtype)
+	bool hasBonusFrom(BonusSource source, BonusSourceID sourceID) const;
 
 	virtual int64_t getTreeVersion() const = 0;
 };
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

+ 8 - 8
lib/bonuses/Limiters.cpp

@@ -136,11 +136,11 @@ JsonNode CCreatureTypeLimiter::toJsonNode() const
 }
 
 HasAnotherBonusLimiter::HasAnotherBonusLimiter( BonusType bonus )
-	: type(bonus), subtype(0), isSubtypeRelevant(false), isSourceRelevant(false), isSourceIDRelevant(false)
+	: type(bonus), isSubtypeRelevant(false), isSourceRelevant(false), isSourceIDRelevant(false)
 {
 }
 
-HasAnotherBonusLimiter::HasAnotherBonusLimiter( BonusType bonus, TBonusSubtype _subtype )
+HasAnotherBonusLimiter::HasAnotherBonusLimiter( BonusType bonus, BonusSubtypeID _subtype )
 	: type(bonus), subtype(_subtype), isSubtypeRelevant(true), isSourceRelevant(false), isSourceIDRelevant(false)
 {
 }
@@ -150,7 +150,7 @@ HasAnotherBonusLimiter::HasAnotherBonusLimiter(BonusType bonus, BonusSource src)
 {
 }
 
-HasAnotherBonusLimiter::HasAnotherBonusLimiter(BonusType bonus, TBonusSubtype _subtype, BonusSource src)
+HasAnotherBonusLimiter::HasAnotherBonusLimiter(BonusType bonus, BonusSubtypeID _subtype, BonusSource src)
 	: type(bonus), subtype(_subtype), isSubtypeRelevant(true), source(src), isSourceRelevant(true), isSourceIDRelevant(false)
 {
 }
@@ -184,8 +184,8 @@ std::string HasAnotherBonusLimiter::toString() const
 	std::string typeName = vstd::findKey(bonusNameMap, type);
 	if(isSubtypeRelevant)
 	{
-		boost::format fmt("HasAnotherBonusLimiter(type=%s, subtype=%d)");
-		fmt % typeName % subtype;
+		boost::format fmt("HasAnotherBonusLimiter(type=%s, subtype=%s)");
+		fmt % typeName % subtype.toString();
 		return fmt.str();
 	}
 	else
@@ -205,7 +205,7 @@ JsonNode HasAnotherBonusLimiter::toJsonNode() const
 	root["type"].String() = "HAS_ANOTHER_BONUS_LIMITER";
 	root["parameters"].Vector().push_back(JsonUtils::stringNode(typeName));
 	if(isSubtypeRelevant)
-		root["parameters"].Vector().push_back(JsonUtils::intNode(subtype));
+		root["parameters"].Vector().push_back(JsonUtils::stringNode(subtype.toString()));
 	if(isSourceRelevant)
 		root["parameters"].Vector().push_back(JsonUtils::stringNode(sourceTypeName));
 
@@ -303,10 +303,10 @@ ILimiter::EDecision FactionLimiter::limit(const BonusLimitationContext &context)
 		switch(context.b.source)
 		{
 			case BonusSource::CREATURE_ABILITY:
-				return bearer->getFaction() == CreatureID(context.b.sid).toCreature()->getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
+				return bearer->getFaction() == context.b.sid.as<CreatureID>().toCreature()->getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
 			
 			case BonusSource::TOWN_STRUCTURE:
-				return bearer->getFaction() == FactionID(Bonus::getHighFromSid32(context.b.sid)) ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
+				return bearer->getFaction() == context.b.sid.as<BuildingTypeUniqueID>().getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
 
 			//TODO: other sources of bonuses
 		}

+ 4 - 4
lib/bonuses/Limiters.h

@@ -116,17 +116,17 @@ class DLL_LINKAGE HasAnotherBonusLimiter : public ILimiter //applies only to nod
 {
 public:
 	BonusType type;
-	TBonusSubtype subtype;
+	BonusSubtypeID subtype;
 	BonusSource source;
-	si32 sid;
+	BonusSourceID 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(BonusType bonus = BonusType::NONE);
-	HasAnotherBonusLimiter(BonusType bonus, TBonusSubtype _subtype);
+	HasAnotherBonusLimiter(BonusType bonus, BonusSubtypeID _subtype);
 	HasAnotherBonusLimiter(BonusType bonus, BonusSource src);
-	HasAnotherBonusLimiter(BonusType bonus, TBonusSubtype _subtype, BonusSource src);
+	HasAnotherBonusLimiter(BonusType bonus, BonusSubtypeID _subtype, BonusSource src);
 
 	EDecision limit(const BonusLimitationContext &context) const override;
 	std::string toString() const override;

+ 0 - 6
lib/campaign/CampaignConstants.h

@@ -48,10 +48,4 @@ enum class CampaignBonusType : int8_t
 	HERO
 };
 
-enum class CampaignScenarioID : int8_t
-{
-	NONE = -1,
-	// no members - fake enum to create integer type that is not implicitly convertible to int
-};
-
 VCMI_LIB_NAMESPACE_END

+ 129 - 2
lib/constants/EntityIdentifiers.cpp

@@ -37,9 +37,12 @@
 #include "TerrainHandler.h" //TODO: remove
 #include "BattleFieldHandler.h"
 #include "ObstacleHandler.h"
+#include "CTownHandler.h"
+#include "mapObjectConstructors/CObjectClassesHandler.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
+const CampaignScenarioID CampaignScenarioID::NONE(-1);
 const BattleID BattleID::NONE(-1);
 const QueryID QueryID::NONE(-1);
 const QueryID QueryID::CLIENT(-2);
@@ -80,6 +83,15 @@ const FactionID FactionID::FORTRESS(7);
 const FactionID FactionID::CONFLUX(8);
 const FactionID FactionID::NEUTRAL(9);
 
+const PrimarySkill PrimarySkill::NONE(-1);
+const PrimarySkill PrimarySkill::ATTACK(0);
+const PrimarySkill PrimarySkill::DEFENSE(1);
+const PrimarySkill PrimarySkill::SPELL_POWER(2);
+const PrimarySkill PrimarySkill::KNOWLEDGE(3);
+const PrimarySkill PrimarySkill::BEGIN(0);
+const PrimarySkill PrimarySkill::END(4);
+const PrimarySkill PrimarySkill::EXPERIENCE(4);
+
 const BoatId BoatId::NONE(-1);
 const BoatId BoatId::NECROPOLIS(0);
 const BoatId BoatId::CASTLE(1);
@@ -124,6 +136,40 @@ std::string HeroClassID::entityType()
 	return "heroClass";
 }
 
+si32 ObjectInstanceID::decode(const std::string & identifier)
+{
+	return std::stoi(identifier);
+}
+
+std::string ObjectInstanceID::encode(const si32 index)
+{
+	return std::to_string(index);
+}
+
+si32 CampaignScenarioID::decode(const std::string & identifier)
+{
+	return std::stoi(identifier);
+}
+
+std::string CampaignScenarioID::encode(const si32 index)
+{
+	return std::to_string(index);
+}
+
+std::string Obj::encode(int32_t index)
+{
+	return VLC->objtypeh->getObjectHandlerName(index);
+}
+
+si32 Obj::decode(const std::string & identifier)
+{
+	auto rawId = VLC->identifiers()->getIdentifier(ModScope::scopeGame(), "objects", identifier);
+	if(rawId)
+		return rawId.value();
+	else
+		return -1;
+}
+
 si32 HeroTypeID::decode(const std::string & identifier)
 {
 	auto rawId = VLC->identifiers()->getIdentifier(ModScope::scopeMap(), "hero", identifier);
@@ -172,6 +218,20 @@ std::string ArtifactID::entityType()
 	return "artifact";
 }
 
+si32 SecondarySkill::decode(const std::string& identifier)
+{
+	auto rawId = VLC->identifiers()->getIdentifier(ModScope::scopeGame(), "secondarySkill", identifier);
+	if(rawId)
+		return rawId.value();
+	else
+		return -1;
+}
+
+std::string SecondarySkill::encode(const si32 index)
+{
+	return VLC->skills()->getById(SecondarySkill(index))->getJsonKey();
+}
+
 const CCreature * CreatureIDBase::toCreature() const
 {
 	return VLC->creh->objects.at(num);
@@ -230,6 +290,20 @@ std::string SpellID::encode(const si32 index)
 	return VLC->spells()->getByIndex(index)->getJsonKey();
 }
 
+si32 BattleField::decode(const std::string & identifier)
+{
+	auto rawId = VLC->identifiers()->getIdentifier(ModScope::scopeGame(), "spell", identifier);
+	if(rawId)
+		return rawId.value();
+	else
+		return -1;
+}
+
+std::string BattleField::encode(const si32 index)
+{
+	return VLC->spells()->getByIndex(index)->getJsonKey();
+}
+
 std::string SpellID::entityType()
 {
 	return "spell";
@@ -247,8 +321,6 @@ bool PlayerColor::isSpectator() const
 
 std::string PlayerColor::toString() const
 {
-	if (num == -1)
-		return "neutral";
 	return encode(num);
 }
 
@@ -259,6 +331,9 @@ si32 PlayerColor::decode(const std::string & identifier)
 
 std::string PlayerColor::encode(const si32 index)
 {
+	if (index == -1)
+		return "neutral";
+
 	if (index < 0 || index >= std::size(GameConstants::PLAYER_COLOR_NAMES))
 	{
 		assert(0);
@@ -273,6 +348,21 @@ std::string PlayerColor::entityType()
 	return "playerColor";
 }
 
+si32 PrimarySkill::decode(const std::string& identifier)
+{
+	return *VLC->identifiers()->getIdentifier(ModScope::scopeGame(), entityType(), identifier);
+}
+
+std::string PrimarySkill::encode(const si32 index)
+{
+	return NPrimarySkill::names[index];
+}
+
+std::string PrimarySkill::entityType()
+{
+	return "primarySkill";
+}
+
 si32 FactionID::decode(const std::string & identifier)
 {
 	auto rawId = VLC->identifiers()->getIdentifier(ModScope::scopeGame(), entityType(), identifier);
@@ -323,6 +413,43 @@ const ObstacleInfo * Obstacle::getInfo() const
 	return VLC->obstacles()->getById(*this);
 }
 
+si32 SpellSchool::decode(const std::string & identifier)
+{
+	return *VLC->identifiers()->getIdentifier(ModScope::scopeGame(), entityType(), identifier);
+}
+
+std::string SpellSchool::encode(const si32 index)
+{
+	return SpellConfig::SCHOOL[index].jsonName;
+}
+
+std::string SpellSchool::entityType()
+{
+	return "spellSchool";
+}
+
+si32 GameResID::decode(const std::string & identifier)
+{
+	return *VLC->identifiers()->getIdentifier(ModScope::scopeGame(), entityType(), identifier);
+}
+
+std::string GameResID::encode(const si32 index)
+{
+	return GameConstants::RESOURCE_NAMES[index];
+}
+
+si32 BuildingTypeUniqueID::decode(const std::string & identifier)
+{
+	assert(0); //TODO
+	return -1;
+}
+
+std::string BuildingTypeUniqueID::encode(const si32 index)
+{
+	assert(0); // TODO
+	return "";
+}
+
 std::string GameResID::entityType()
 {
 	return "resource";

+ 84 - 73
lib/constants/EntityIdentifiers.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "NumericConstants.h"
+#include "IdentifierBase.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -35,62 +36,6 @@ class CNonConstInfoCallback;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
-class IdentifierBase
-{
-protected:
-	constexpr IdentifierBase():
-		num(-1)
-	{}
-
-	explicit constexpr IdentifierBase(int32_t value):
-		num(value)
-	{}
-
-	~IdentifierBase() = default;
-public:
-	int32_t num;
-
-	constexpr int32_t getNum() const
-	{
-		return num;
-	}
-
-	constexpr void setNum(int32_t value)
-	{
-		num = value;
-	}
-
-	struct hash
-	{
-		size_t operator()(const IdentifierBase & id) const
-		{
-			return std::hash<int>()(id.num);
-		}
-	};
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & num;
-	}
-
-	constexpr void advance(int change)
-	{
-		num += change;
-	}
-
-	constexpr operator int32_t () const
-	{
-		return num;
-	}
-
-	friend std::ostream& operator<<(std::ostream& os, const IdentifierBase& dt)
-	{
-		return os << dt.num;
-	}
-};
-
-///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
 // Note: use template to force different type, blocking any Identifier<A> <=> Identifier<B> comparisons
 template<typename FinalClass>
 class Identifier : public IdentifierBase
@@ -212,11 +157,14 @@ public:
 	using Identifier<BattleID>::Identifier;
 	DLL_LINKAGE static const BattleID NONE;
 };
-class ObjectInstanceID : public Identifier<ObjectInstanceID>
+class DLL_LINKAGE ObjectInstanceID : public Identifier<ObjectInstanceID>
 {
 public:
 	using Identifier<ObjectInstanceID>::Identifier;
-	DLL_LINKAGE static const ObjectInstanceID NONE;
+	static const ObjectInstanceID NONE;
+
+	static si32 decode(const std::string & identifier);
+	static std::string encode(const si32 index);
 };
 
 class HeroClassID : public Identifier<HeroClassID>
@@ -347,6 +295,29 @@ class SecondarySkill : public IdentifierWithEnum<SecondarySkill, SecondarySkillB
 public:
 	using IdentifierWithEnum<SecondarySkill, SecondarySkillBase>::IdentifierWithEnum;
 	static std::string entityType();
+	static si32 decode(const std::string& identifier);
+	static std::string encode(const si32 index);
+};
+
+class DLL_LINKAGE PrimarySkill : public Identifier<PrimarySkill>
+{
+public:
+	using Identifier<PrimarySkill>::Identifier;
+
+	static const PrimarySkill NONE;
+	static const PrimarySkill ATTACK;
+	static const PrimarySkill DEFENSE;
+	static const PrimarySkill SPELL_POWER;
+	static const PrimarySkill KNOWLEDGE;
+
+	static const PrimarySkill BEGIN;
+	static const PrimarySkill END;
+
+	static const PrimarySkill EXPERIENCE;
+
+	static si32 decode(const std::string& identifier);
+	static std::string encode(const si32 index);
+	static std::string entityType();
 };
 
 class DLL_LINKAGE FactionID : public Identifier<FactionID>
@@ -430,6 +401,10 @@ class BuildingID : public IdentifierWithEnum<BuildingID, BuildingIDBase>
 {
 public:
 	using IdentifierWithEnum<BuildingID, BuildingIDBase>::IdentifierWithEnum;
+
+	DLL_LINKAGE static si32 decode(const std::string & identifier);
+	DLL_LINKAGE static std::string encode(const si32 index);
+	static std::string entityType();
 };
 
 class ObjBase : public IdentifierBase
@@ -577,10 +552,13 @@ public:
 	};
 };
 
-class Obj : public IdentifierWithEnum<Obj, ObjBase>
+class DLL_LINKAGE Obj : public IdentifierWithEnum<Obj, ObjBase>
 {
 public:
 	using IdentifierWithEnum<Obj, ObjBase>::IdentifierWithEnum;
+
+	static std::string encode(int32_t index);
+	static si32 decode(const std::string & identifier);
 };
 
 class DLL_LINKAGE RoadId : public Identifier<RoadId>
@@ -718,18 +696,18 @@ public:
 	DLL_LINKAGE const Creature * toCreature(const CreatureService * creatures) const;
 };
 
-class CreatureID : public IdentifierWithEnum<CreatureID, CreatureIDBase>
+class DLL_LINKAGE CreatureID : public IdentifierWithEnum<CreatureID, CreatureIDBase>
 {
 public:
 	using IdentifierWithEnum<CreatureID, CreatureIDBase>::IdentifierWithEnum;
 
 	///json serialization helpers
-	DLL_LINKAGE static si32 decode(const std::string & identifier);
-	DLL_LINKAGE static std::string encode(const si32 index);
+	static si32 decode(const std::string & identifier);
+	static std::string encode(const si32 index);
 	static std::string entityType();
 };
 
-class SpellIDBase : public IdentifierBase
+class DLL_LINKAGE SpellIDBase : public IdentifierBase
 {
 public:
 	enum Type
@@ -832,29 +810,32 @@ public:
 		AFTER_LAST = 82
 	};
 
-	DLL_LINKAGE const CSpell * toSpell() const; //deprecated
-	DLL_LINKAGE const spells::Spell * toSpell(const spells::Service * service) const;
+	const CSpell * toSpell() const; //deprecated
+	const spells::Spell * toSpell(const spells::Service * service) const;
 };
 
-class SpellID : public IdentifierWithEnum<SpellID, SpellIDBase>
+class DLL_LINKAGE SpellID : public IdentifierWithEnum<SpellID, SpellIDBase>
 {
 public:
 	using IdentifierWithEnum<SpellID, SpellIDBase>::IdentifierWithEnum;
 
 	///json serialization helpers
-	DLL_LINKAGE static si32 decode(const std::string & identifier);
-	DLL_LINKAGE static std::string encode(const si32 index);
+	static si32 decode(const std::string & identifier);
+	static std::string encode(const si32 index);
 	static std::string entityType();
 };
 
 class BattleFieldInfo;
-class BattleField : public Identifier<BattleField>
+class DLL_LINKAGE BattleField : public Identifier<BattleField>
 {
 public:
 	using Identifier<BattleField>::Identifier;
 
-	DLL_LINKAGE static const BattleField NONE;
-	DLL_LINKAGE const BattleFieldInfo * getInfo() const;
+	static const BattleField NONE;
+	const BattleFieldInfo * getInfo() const;
+
+	static si32 decode(const std::string & identifier);
+	static std::string encode(const si32 index);
 };
 
 class DLL_LINKAGE BoatId : public Identifier<BoatId>
@@ -919,6 +900,10 @@ public:
 	static const SpellSchool FIRE;
 	static const SpellSchool WATER;
 	static const SpellSchool EARTH;
+
+	static si32 decode(const std::string & identifier);
+	static std::string encode(const si32 index);
+	static std::string entityType();
 };
 
 class GameResIDBase : public IdentifierBase
@@ -941,17 +926,43 @@ public:
 	};
 };
 
-class GameResID : public IdentifierWithEnum<GameResID, GameResIDBase>
+class DLL_LINKAGE GameResID : public IdentifierWithEnum<GameResID, GameResIDBase>
 {
 public:
 	using IdentifierWithEnum<GameResID, GameResIDBase>::IdentifierWithEnum;
 
+	static si32 decode(const std::string & identifier);
+	static std::string encode(const si32 index);
 	static std::string entityType();
 };
 
+class BuildingTypeUniqueID : public Identifier<BuildingTypeUniqueID>
+{
+public:
+	BuildingTypeUniqueID(FactionID faction, BuildingID building );
+
+	static si32 decode(const std::string & identifier);
+	static std::string encode(const si32 index);
+
+	BuildingID getBuilding() const;
+	FactionID getFaction() const;
+
+	using Identifier<BuildingTypeUniqueID>::Identifier;
+};
+
+class DLL_LINKAGE CampaignScenarioID : public Identifier<CampaignScenarioID>
+{
+public:
+	using Identifier<CampaignScenarioID>::Identifier;
+
+	static si32 decode(const std::string & identifier);
+	static std::string encode(int32_t index);
+
+	static const CampaignScenarioID NONE;
+};
+
 // Deprecated
 // TODO: remove
-using ESpellSchool = SpellSchool;
 using ETownType = FactionID;
 using EGameResID = GameResID;
 using River = RiverId;

+ 2 - 12
lib/constants/Enumerations.h

@@ -11,16 +11,6 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-enum class PrimarySkill : int8_t
-{
-	NONE = -1,
-	ATTACK,
-	DEFENSE,
-	SPELL_POWER,
-	KNOWLEDGE,
-	EXPERIENCE = 4 //for some reason changePrimSkill uses it
-};
-
 enum class EAlignment : int8_t
 {
 	GOOD,
@@ -143,9 +133,9 @@ enum class ETeleportChannelType : int8_t
 	MIXED
 };
 
-namespace SecSkillLevel
+namespace MasteryLevel
 {
-	enum SecSkillLevel
+	enum Type
 	{
 		NONE,
 		BASIC,

+ 64 - 0
lib/constants/IdentifierBase.h

@@ -0,0 +1,64 @@
+/*
+ * IdentifierBase.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
+
+class IdentifierBase
+{
+protected:
+	constexpr IdentifierBase():
+		num(-1)
+	{}
+
+	explicit constexpr IdentifierBase(int32_t value):
+		num(value)
+	{}
+
+	~IdentifierBase() = default;
+public:
+	int32_t num;
+
+	constexpr int32_t getNum() const
+	{
+		return num;
+	}
+
+	constexpr void setNum(int32_t value)
+	{
+		num = value;
+	}
+
+	struct hash
+	{
+		size_t operator()(const IdentifierBase & id) const
+		{
+			return std::hash<int>()(id.num);
+		}
+	};
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & num;
+	}
+
+	constexpr void advance(int change)
+	{
+		num += change;
+	}
+
+	constexpr operator int32_t () const
+	{
+		return num;
+	}
+
+	friend std::ostream& operator<<(std::ostream& os, const IdentifierBase& dt)
+	{
+		return os << dt.num;
+	}
+};

+ 80 - 0
lib/constants/VariantIdentifier.h

@@ -0,0 +1,80 @@
+/*
+ * VariantIdentifier.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "IdentifierBase.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+/// This class represents field that may contain value of multiple different identifer types
+template<typename... Types>
+class DLL_LINKAGE VariantIdentifier
+{
+	std::variant<Types...> value;
+public:
+
+	VariantIdentifier()
+	{}
+
+	template<typename IdentifierType>
+	VariantIdentifier(const IdentifierType & identifier)
+		: value(identifier)
+	{}
+
+	int32_t getNum() const
+	{
+		int32_t result;
+
+		std::visit([&result] (const auto& v) { result = v.getNum(); }, value);
+
+		return result;
+	}
+
+	std::string toString() const
+	{
+		std::string result;
+
+		std::visit([&result] (const auto& v) { result = v.encode(v.getNum()); }, value);
+
+		return result;
+	}
+
+	template<typename IdentifierType>
+	IdentifierType as() const
+	{
+		auto * result = std::get_if<IdentifierType>(&value);
+		assert(result);
+
+		if (result)
+			return *result;
+		else
+			return IdentifierType();
+	}
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & value;
+	}
+
+	bool operator == (const VariantIdentifier & other) const
+	{
+		return value == other.value;
+	}
+	bool operator != (const VariantIdentifier & other) const
+	{
+		return value != other.value;
+	}
+	bool operator < (const VariantIdentifier & other) const
+	{
+		return value < other.value;
+	}
+};
+
+VCMI_LIB_NAMESPACE_END

+ 2 - 2
lib/gameState/CGameState.cpp

@@ -655,7 +655,7 @@ void CGameState::initGlobalBonuses()
 	{
 		auto bonus = JsonUtils::parseBonus(b.second);
 		bonus->source = BonusSource::GLOBAL;//for all
-		bonus->sid = -1; //there is one global object
+		bonus->sid = BonusSourceID(); //there is one global object
 		globalEffects.addNewBonus(bonus);
 	}
 	VLC->creh->loadCrExpBon(globalEffects);
@@ -1871,7 +1871,7 @@ struct statsHLP
 		//Heroes can produce gold as well - skill, specialty or arts
 		for(const auto & h : ps->heroes)
 		{
-			totalIncome += h->valOfBonuses(Selector::typeSubtype(BonusType::GENERATE_RESOURCE, GameResID(EGameResID::GOLD)));
+			totalIncome += h->valOfBonuses(Selector::typeSubtype(BonusType::GENERATE_RESOURCE, BonusSubtypeID(GameResID(GameResID::GOLD))));
 
 			if(!heroOrTown)
 				heroOrTown = h;

+ 13 - 15
lib/gameState/CGameStateCampaign.cpp

@@ -84,10 +84,10 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(std::vector<CampaignHeroR
 		//trimming prim skills
 		for(CGHeroInstance * cgh : crossoverHeroes)
 		{
-			for(int g=0; g<GameConstants::PRIMARY_SKILLS; ++g)
+			for(auto g = PrimarySkill::BEGIN; g < PrimarySkill::END; ++g)
 			{
 				auto sel = Selector::type()(BonusType::PRIMARY_SKILL)
-					.And(Selector::subtype()(g))
+					.And(Selector::subtype()(BonusSubtypeID(g)))
 					.And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL));
 
 				cgh->getBonusLocalFirst(sel)->val = cgh->type->heroClass->primarySkillInitial[g];
@@ -118,7 +118,7 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(std::vector<CampaignHeroR
 		//trimming artifacts
 		for(CGHeroInstance * hero : crossoverHeroes)
 		{
-			auto const & checkAndRemoveArtifact = [&](const ArtifactPosition & artifactPosition )
+			const auto & checkAndRemoveArtifact = [&](const ArtifactPosition & artifactPosition)
 			{
 				if(artifactPosition == ArtifactPosition::SPELLBOOK)
 					return; // do not handle spellbook this way
@@ -141,7 +141,7 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(std::vector<CampaignHeroR
 
 			// process on copy - removal of artifact will invalidate container
 			auto artifactsWorn = hero->artifactsWorn;
-			for (auto const & art : artifactsWorn)
+			for(const auto & art : artifactsWorn)
 				checkAndRemoveArtifact(art.first);
 
 			// process in reverse - removal of artifact will shift all artifacts after this one
@@ -308,16 +308,14 @@ void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero)
 		case CampaignBonusType::PRIMARY_SKILL:
 		{
 			const ui8 * ptr = reinterpret_cast<const ui8 *>(&curBonus->info2);
-			for(int g = 0; g < GameConstants::PRIMARY_SKILLS; ++g)
+			for(auto g = PrimarySkill::BEGIN; g < PrimarySkill::END; ++g)
 			{
-				int val = ptr[g];
+				int val = ptr[g.getNum()];
 				if(val == 0)
-				{
 					continue;
-				}
-				auto bb = std::make_shared<Bonus>(
-					BonusDuration::PERMANENT, BonusType::PRIMARY_SKILL, BonusSource::CAMPAIGN_BONUS, val, static_cast<int>(*gameState->scenarioOps->campState->currentScenario()), g
-				);
+
+				auto currentScenario = *gameState->scenarioOps->campState->currentScenario();
+				auto bb = std::make_shared<Bonus>( BonusDuration::PERMANENT, BonusType::PRIMARY_SKILL, BonusSource::CAMPAIGN_BONUS, val, BonusSourceID(currentScenario), BonusSubtypeID(g) );
 				hero->addNewBonus(bb);
 			}
 			break;
@@ -386,9 +384,9 @@ std::vector<CampaignHeroReplacement> CGameStateCampaign::generateCampaignHeroesT
 	}
 
 	//selecting heroes by type
-	for (auto const * placeholder : placeholdersByType)
+	for(const auto * placeholder : placeholdersByType)
 	{
-		auto const & node = campaignState->getHeroByType(*placeholder->heroType);
+		const auto & node = campaignState->getHeroByType(*placeholder->heroType);
 		if (node.isNull())
 		{
 			logGlobal->info("Hero crossover: Unable to replace placeholder for %d (%s)!", placeholder->heroType->getNum(), VLC->heroTypes()->getById(*placeholder->heroType)->getNameTranslated());
@@ -412,10 +410,10 @@ std::vector<CampaignHeroReplacement> CGameStateCampaign::generateCampaignHeroesT
 			return *a->powerRank > *b->powerRank;
 		});
 
-		auto const & nodeList = campaignState->getHeroesByPower(lastScenario.value());
+		const auto & nodeList = campaignState->getHeroesByPower(lastScenario.value());
 		auto nodeListIter = nodeList.begin();
 
-		for (auto const * placeholder : placeholdersByPower)
+		for(const auto * placeholder : placeholdersByPower)
 		{
 			if (nodeListIter == nodeList.end())
 				break;

+ 5 - 0
lib/mapObjectConstructors/CObjectClassesHandler.cpp

@@ -488,4 +488,9 @@ std::string CObjectClassesHandler::getObjectHandlerName(si32 type) const
 	return objects.at(type)->handlerName;
 }
 
+std::string CObjectClassesHandler::getJsonKey(si32 type) const
+{
+	return objects.at(type)->getJsonKey();
+}
+
 VCMI_LIB_NAMESPACE_END

+ 2 - 0
lib/mapObjectConstructors/CObjectClassesHandler.h

@@ -129,6 +129,8 @@ public:
 	/// Returns handler string describing the handler (for use in client)
 	std::string getObjectHandlerName(si32 type) const;
 
+	std::string getJsonKey(si32 type) const;
+
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & objects;

+ 2 - 2
lib/mapObjectConstructors/CRewardableConstructor.cpp

@@ -49,8 +49,8 @@ void CRewardableConstructor::configureObject(CGObjectInstance * object, CRandomG
 		{
 			for (auto & bonus : rewardInfo.reward.bonuses)
 			{
-				bonus.source = BonusSource::OBJECT;
-				bonus.sid = rewardableObject->ID;
+				bonus.source = BonusSource::OBJECT_TYPE;
+				bonus.sid = BonusSourceID(rewardableObject->ID);
 			}
 		}
 		assert(!rewardableObject->configuration.info.empty());

+ 3 - 4
lib/mapObjects/CArmedInstance.cpp

@@ -59,7 +59,7 @@ void CArmedInstance::updateMoraleBonusFromArmy()
 	auto b = getExportedBonusList().getFirst(Selector::sourceType()(BonusSource::ARMY).And(Selector::type()(BonusType::MORALE)));
  	if(!b)
 	{
-		b = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::MORALE, BonusSource::ARMY, 0, -1);
+		b = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::MORALE, BonusSource::ARMY, 0, BonusSourceID());
 		addNewBonus(b);
 	}
 
@@ -120,13 +120,12 @@ void CArmedInstance::updateMoraleBonusFromArmy()
 	CBonusSystemNode::treeHasChanged();
 
 	//-1 modifier for any Undead unit in army
-	const ui8 UNDEAD_MODIFIER_ID = -2;
-	auto undeadModifier = getExportedBonusList().getFirst(Selector::source(BonusSource::ARMY, UNDEAD_MODIFIER_ID));
+	auto undeadModifier = getExportedBonusList().getFirst(Selector::source(BonusSource::ARMY, BonusCustomSource::undeadMoraleDebuff));
  	if(hasUndead)
 	{
 		if(!undeadModifier)
 		{
-			undeadModifier = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::MORALE, BonusSource::ARMY, -1, UNDEAD_MODIFIER_ID, VLC->generaltexth->arraytxt[116]);
+			undeadModifier = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::MORALE, BonusSource::ARMY, -1, BonusCustomSource::undeadMoraleDebuff, VLC->generaltexth->arraytxt[116]);
 			undeadModifier->description = undeadModifier->description.substr(0, undeadModifier->description.size()-2);//trim value
 			addNewBonus(undeadModifier);
 		}

+ 3 - 3
lib/mapObjects/CBank.cpp

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

+ 1 - 1
lib/mapObjects/CGCreature.cpp

@@ -46,7 +46,7 @@ std::string CGCreature::getHoverText(PlayerColor player) const
 std::string CGCreature::getHoverText(const CGHeroInstance * hero) const
 {
 	std::string hoverName;
-	if(hero->hasVisions(this, 0))
+	if(hero->hasVisions(this, BonusCustomSubtype::visionsMonsters))
 	{
 		MetaString ms;
 		ms.appendNumber(stacks.begin()->second->count);

+ 29 - 39
lib/mapObjects/CGHeroInstance.cpp

@@ -95,7 +95,7 @@ ui32 CGHeroInstance::getTileMovementCost(const TerrainTile & dest, const Terrain
 	}
 	else if(ti->nativeTerrain != from.terType->getId() &&//the terrain is not native
 			ti->nativeTerrain != ETerrainId::ANY_TERRAIN && //no special creature bonus
-			!ti->hasBonusOfType(BonusType::NO_TERRAIN_PENALTY, from.terType->getIndex())) //no special movement bonus
+			!ti->hasBonusOfType(BonusType::NO_TERRAIN_PENALTY, BonusSubtypeID(from.terType->getId()))) //no special movement bonus
 	{
 
 		ret = VLC->terrainTypeHandler->getById(from.terType->getId())->moveCost;
@@ -249,14 +249,14 @@ void CGHeroInstance::updateArmyMovementBonus(bool onLand, const TurnInfo * ti) c
 		lowestCreatureSpeed = realLowestSpeed;
 		//Let updaters run again
 		treeHasChanged();
-		ti->updateHeroBonuses(BonusType::MOVEMENT, Selector::subtype()(!!onLand));
+		ti->updateHeroBonuses(BonusType::MOVEMENT, Selector::subtype()(onLand ? BonusCustomSubtype::heroMovementLand : BonusCustomSubtype::heroMovementSea));
 	}
 }
 
 int CGHeroInstance::movementPointsLimitCached(bool onLand, const TurnInfo * ti) const
 {
 	updateArmyMovementBonus(onLand, ti);
-	return ti->valOfBonuses(BonusType::MOVEMENT, !!onLand);
+	return ti->valOfBonuses(BonusType::MOVEMENT, onLand ? BonusCustomSubtype::heroMovementLand : BonusCustomSubtype::heroMovementSea);
 }
 
 CGHeroInstance::CGHeroInstance():
@@ -369,7 +369,7 @@ void CGHeroInstance::initHero(CRandomGenerator & rand)
 	{
 		auto bonus = JsonUtils::parseBonus(b.second);
 		bonus->source = BonusSource::HERO_BASE_SKILL;
-		bonus->sid = id.getNum();
+		bonus->sid = BonusSourceID(id);
 		bonus->duration = BonusDuration::PERMANENT;
 		addNewBonus(bonus);
 	}
@@ -589,7 +589,7 @@ void CGHeroInstance::recreateSecondarySkillsBonuses()
 
 void CGHeroInstance::updateSkillBonus(const SecondarySkill & which, int val)
 {
-	removeBonuses(Selector::source(BonusSource::SECONDARY_SKILL, which));
+	removeBonuses(Selector::source(BonusSource::SECONDARY_SKILL, BonusSourceID(which)));
 	auto skillBonus = (*VLC->skillh)[which]->at(val).effects;
 	for(const auto & b : skillBonus)
 		addNewBonus(std::make_shared<Bonus>(*b));
@@ -638,7 +638,7 @@ int32_t CGHeroInstance::getSpellSchoolLevel(const spells::Spell * spell, int32_t
 
 	spell->forEachSchool([&, this](const SpellSchool & cnf, bool & stop)
 	{
-		int32_t thisSchool = valOfBonuses(BonusType::MAGIC_SCHOOL_SKILL, cnf); //FIXME: Bonus shouldn't be additive (Witchking Artifacts : Crown of Skies)
+		int32_t thisSchool = valOfBonuses(BonusType::MAGIC_SCHOOL_SKILL, BonusSubtypeID(cnf)); //FIXME: Bonus shouldn't be additive (Witchking Artifacts : Crown of Skies)
 		if(thisSchool > skill)
 		{
 			skill = thisSchool;
@@ -647,8 +647,8 @@ int32_t CGHeroInstance::getSpellSchoolLevel(const spells::Spell * spell, int32_t
 		}
 	});
 
-	vstd::amax(skill, valOfBonuses(BonusType::MAGIC_SCHOOL_SKILL, SpellSchool(ESpellSchool::ANY))); //any school bonus
-	vstd::amax(skill, valOfBonuses(BonusType::SPELL, spell->getIndex())); //given by artifact or other effect
+	vstd::amax(skill, valOfBonuses(BonusType::MAGIC_SCHOOL_SKILL, BonusSubtypeID(SpellSchool::ANY))); //any school bonus
+	vstd::amax(skill, valOfBonuses(BonusType::SPELL, BonusSubtypeID(spell->getId()))); //given by artifact or other effect
 
 	vstd::amax(skill, 0); //in case we don't know any school
 	vstd::amin(skill, 3);
@@ -660,28 +660,28 @@ 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(BonusType::SPELL_DAMAGE, SpellSchool(ESpellSchool::ANY))) / 100.0);
+		base = static_cast<int64_t>(base * (valOfBonuses(BonusType::SPELL_DAMAGE, BonusSubtypeID(SpellSchool::ANY))) / 100.0);
 
-	base = static_cast<int64_t>(base * (100 + valOfBonuses(BonusType::SPECIFIC_SPELL_DAMAGE, spell->getIndex())) / 100.0);
+	base = static_cast<int64_t>(base * (100 + valOfBonuses(BonusType::SPECIFIC_SPELL_DAMAGE, BonusSubtypeID(spell->getId()))) / 100.0);
 
 	int maxSchoolBonus = 0;
 
 	spell->forEachSchool([&maxSchoolBonus, this](const SpellSchool & cnf, bool & stop)
 	{
-		vstd::amax(maxSchoolBonus, valOfBonuses(BonusType::SPELL_DAMAGE, cnf));
+		vstd::amax(maxSchoolBonus, valOfBonuses(BonusType::SPELL_DAMAGE, BonusSubtypeID(cnf)));
 	});
 
 	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(BonusType::SPECIAL_SPELL_LEV, spell->getIndex()) / affectedStack->creatureLevel()) / 100.0);
+		base = static_cast<int64_t>(base * static_cast<double>(100 + valOfBonuses(BonusType::SPECIAL_SPELL_LEV, BonusSubtypeID(spell->getId())) / 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(BonusType::SPECIFIC_SPELL_DAMAGE, spell->getIndex())) / 100.0);
+	base = static_cast<int64_t>(base * (100 + valOfBonuses(BonusType::SPECIFIC_SPELL_DAMAGE, BonusSubtypeID(spell->getId()))) / 100.0);
 	return base;
 }
 
@@ -751,19 +751,19 @@ 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(BonusType::SPELL, spell->getIndex());
+	const bool specificBonus = hasBonusOfType(BonusType::SPELL, BonusSubtypeID(spell->getId()));
 
 	bool schoolBonus = false;
 
 	spell->forEachSchool([this, &schoolBonus](const SpellSchool & cnf, bool & stop)
 	{
-		if(hasBonusOfType(BonusType::SPELLS_OF_SCHOOL, cnf))
+		if(hasBonusOfType(BonusType::SPELLS_OF_SCHOOL, BonusSubtypeID(cnf)))
 		{
 			schoolBonus = stop = true;
 		}
 	});
 
-	const bool levelBonus = hasBonusOfType(BonusType::SPELLS_OF_LEVEL, spell->getLevel());
+	const bool levelBonus = hasBonusOfType(BonusType::SPELLS_OF_LEVEL, BonusCustomSubtype::spellLevel(spell->getLevel()));
 
 	if(spell->isSpecial())
 	{
@@ -845,13 +845,6 @@ CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &b
 		TConstBonusListPtr improvedNecromancy = getBonuses(Selector::type()(BonusType::IMPROVED_NECROMANCY));
 		if(!improvedNecromancy->empty())
 		{
-			auto getCreatureID = [](const std::shared_ptr<Bonus> & bonus) -> CreatureID
-			{
-				assert(bonus->subtype >=0);
-				if(bonus->subtype >= 0)
-					return CreatureID(bonus->subtype);
-				return CreatureID::NONE;
-			};
 			int maxCasualtyLevel = 1;
 			for(const auto & casualty : casualties)
 				vstd::amax(maxCasualtyLevel, VLC->creatures()->getByIndex(casualty.first)->getLevel());
@@ -868,9 +861,9 @@ CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &b
 				}
 				else
 				{
-					auto quality = [getCreatureID](const std::shared_ptr<Bonus> & pick) -> std::tuple<int, int, int>
+					auto quality = [](const std::shared_ptr<Bonus> & pick) -> std::tuple<int, int, int>
 					{
-						const auto * c = getCreatureID(pick).toCreature();
+						const auto * c = pick->subtype.as<CreatureID>().toCreature();
 						return std::tuple<int, int, int> {c->getLevel(), static_cast<int>(c->getFullRecruitCost().marketValue()), -pick->additionalInfo[1]};
 					};
 					if(quality(topPick) < quality(newPick))
@@ -879,7 +872,7 @@ CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &b
 			}
 			if(topPick)
 			{
-				creatureTypeRaised = getCreatureID(topPick);
+				creatureTypeRaised = topPick->subtype.as<CreatureID>();
 				requiredCasualtyLevel = std::max(topPick->additionalInfo[1], 1);
 			}
 		}
@@ -1015,12 +1008,12 @@ int32_t CGHeroInstance::getSpellCost(const spells::Spell * sp) const
 
 void CGHeroInstance::pushPrimSkill( PrimarySkill which, int val )
 {
-	auto sel = Selector::typeSubtype(BonusType::PRIMARY_SKILL, static_cast<int>(which))
+	auto sel = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(which))
 		.And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL));
 	if(hasBonus(sel))
 		removeBonuses(sel);
 		
-	addNewBonus(std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::PRIMARY_SKILL, BonusSource::HERO_BASE_SKILL, val, id.getNum(), static_cast<int>(which)));
+	addNewBonus(std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::PRIMARY_SKILL, BonusSource::HERO_BASE_SKILL, val, BonusSourceID(id), BonusSubtypeID(which)));
 }
 
 EAlignment CGHeroInstance::getAlignment() const
@@ -1283,7 +1276,7 @@ std::vector<SecondarySkill> CGHeroInstance::getLevelUpProposedSecondarySkills()
 
 	for(const auto & elem : secSkills)
 	{
-		if(elem.second < SecSkillLevel::EXPERT)
+		if(elem.second < MasteryLevel::EXPERT)
 			basicAndAdv.insert(elem.first);
 		else
 			expert.insert(elem.first);
@@ -1403,7 +1396,7 @@ void CGHeroInstance::setPrimarySkill(PrimarySkill primarySkill, si64 value, ui8
 	if(primarySkill < PrimarySkill::EXPERIENCE)
 	{
 		auto skill = getBonusLocalFirst(Selector::type()(BonusType::PRIMARY_SKILL)
-			.And(Selector::subtype()(static_cast<int>(primarySkill)))
+			.And(Selector::subtype()(BonusSubtypeID(primarySkill)))
 			.And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)));
 		assert(skill);
 
@@ -1474,13 +1467,10 @@ void CGHeroInstance::levelUpAutomatically(CRandomGenerator & rand)
 	}
 }
 
-bool CGHeroInstance::hasVisions(const CGObjectInstance * target, const int subtype) const
+bool CGHeroInstance::hasVisions(const CGObjectInstance * target, BonusSubtypeID subtype) const
 {
 	//VISIONS spell support
-
-	const auto cached = "type_" + std::to_string(vstd::to_underlying(BonusType::VISIONS)) + "__subtype_" + std::to_string(subtype);
-
-	const int visionsMultiplier = valOfBonuses(Selector::typeSubtype(BonusType::VISIONS,subtype), cached);
+	const int visionsMultiplier = valOfBonuses(BonusType::VISIONS, subtype);
 
 	int visionsRange =  visionsMultiplier * getPrimSkillLevel(PrimarySkill::SPELL_POWER);
 
@@ -1567,11 +1557,11 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler)
 		{
 			auto primarySkills = handler.enterStruct("primarySkills");
 
-			for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
+			for(auto i = PrimarySkill::BEGIN; i < PrimarySkill::END; ++i)
 			{
-				int value = valOfBonuses(Selector::typeSubtype(BonusType::PRIMARY_SKILL, i).And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)));
+				int value = valOfBonuses(Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(i)).And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)));
 
-				handler.serializeInt(NPrimarySkill::names[i], value, 0);
+				handler.serializeInt(NPrimarySkill::names[i.getNum()], value, 0);
 			}
 		}
 	}
@@ -1762,7 +1752,7 @@ bool CGHeroInstance::isMissionCritical() const
 
 void CGHeroInstance::fillUpgradeInfo(UpgradeInfo & info, const CStackInstance &stack) const
 {
-	TConstBonusListPtr lista = getBonuses(Selector::typeSubtype(BonusType::SPECIAL_UPGRADE, stack.type->getId()));
+	TConstBonusListPtr lista = getBonuses(Selector::typeSubtype(BonusType::SPECIAL_UPGRADE, BonusSubtypeID(stack.type->getId())));
 	for(const auto & it : *lista)
 	{
 		auto nid = CreatureID(it->additionalInfo[0]);

+ 1 - 1
lib/mapObjects/CGHeroInstance.h

@@ -248,7 +248,7 @@ public:
 
 	void fillUpgradeInfo(UpgradeInfo & info, const CStackInstance &stack) const override;
 
-	bool hasVisions(const CGObjectInstance * target, const int subtype) const;
+	bool hasVisions(const CGObjectInstance * target, BonusSubtypeID masteryLevel) const;
 	/// If this hero perishes, the scenario is failed
 	bool isMissionCritical() const;
 

+ 2 - 2
lib/mapObjects/CGObjectInstance.cpp

@@ -221,8 +221,8 @@ void CGObjectInstance::giveDummyBonus(const ObjectInstanceID & heroID, BonusDura
 	gbonus.bonus.type = BonusType::NONE;
 	gbonus.id = heroID.getNum();
 	gbonus.bonus.duration = duration;
-	gbonus.bonus.source = BonusSource::OBJECT;
-	gbonus.bonus.sid = ID;
+	gbonus.bonus.source = BonusSource::OBJECT_TYPE;
+	gbonus.bonus.sid = BonusSourceID(ID);
 	cb->giveHeroBonus(&gbonus);
 }
 

Some files were not shown because too many files changed in this diff