Explorar el Código

Basic Configuration for bonus types

* introduced new handler BonusTypeHandler
* config\bonusnames.json converted to common format and splitted info main and localizable parts
* hanlders initialization refactored
alexvins hace 12 años
padre
commit
2eb8263e51

+ 12 - 12
client/CCreatureWindow.cpp

@@ -22,13 +22,13 @@
 #include "../lib/CArtHandler.h"
 #include "../lib/NetPacks.h" //ArtifactLocation
 #include "../lib/CModHandler.h"
+#include "../lib/IBonusTypeHandler.h"
 
 #include "UIFramework/CGuiHandler.h"
 #include "UIFramework/CIntObjectClasses.h"
 
 using namespace CSDL_Ext;
 
-class CBonusItem;
 class CCreatureArtifactInstance;
 class CSelectableSkill;
 
@@ -269,24 +269,24 @@ void CCreatureWindow::init(const CStackInstance *Stack, const CBonusSystemNode *
 		}
 	}
 
-	int magicResistance = 0; //handle it separately :/
+
+	//handle Magic resistance separately :/
+	const IBonusBearer *temp = stack;
+	
 	if (battleStack)
 	{
-		magicResistance = battleStack->magicResistance(); //include Aura of Resistance
-	}
-	else
-	{
-		magicResistance = stack->magicResistance(); //include Resiatance hero skill
+		temp = battleStack;
 	}
+
+	int magicResistance = temp->magicResistance(); 
+	
 	if (magicResistance)
 	{
-		std::map<Bonus::BonusType, std::pair<std::string, std::string> >::const_iterator it = CGI->creh->stackBonuses.find(Bonus::MAGIC_RESISTANCE);
-		std::string description;
-		text = it->second.first;
-		description = it->second.second;
-		boost::algorithm::replace_first(description, "%d", boost::lexical_cast<std::string>(magicResistance));
 		Bonus b;
 		b.type = Bonus::MAGIC_RESISTANCE;
+		
+		text = VLC->getBth()->bonusToString(&b,temp,false);
+		const std::string description = VLC->getBth()->bonusToString(&b,temp,true);
 		bonusItems.push_back (new CBonusItem(genRect(0, 0, 251, 57), text, description, stack->bonusToGraphics(&b)));
 	}
 

+ 0 - 1
client/CCreatureWindow.h

@@ -117,7 +117,6 @@ public:
 	CBonusItem(const Rect &Pos, const std::string &Name, const std::string &Description, const std::string &graphicsName);
 	~CBonusItem();
 
-	void setBonus (const Bonus &bonus);
 	void showAll (SDL_Surface * to);
 };
 

+ 526 - 0
config/bonuses.json

@@ -0,0 +1,526 @@
+//TODO: selector-based config
+// SECONDARY_SKILL_PREMY
+// school immunities
+// LEVEL_SPELL_IMMUNITY
+
+{
+	"ADDITIONAL_ATTACK":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_DOUBLE"
+		}
+	},
+	
+	"ADDITIONAL_RETALIATION":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_RETAIL1"
+		}
+	},
+	
+	"AIR_IMMUNITY":
+	{
+		"graphics":
+		{
+			"icon":  ""
+		}
+	},
+			
+	"ATTACKS_ALL_ADJACENT":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_ROUND"
+		}
+	},
+
+	"BLOCKS_RETALIATION":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_RETAIL"
+		}
+	},
+	
+	"CATAPULT":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/Catapult"
+		}
+	},	
+	
+	"CHANGES_SPELL_COST_FOR_ALLY":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_MANA"
+		}
+	},
+	
+	"CHANGES_SPELL_COST_FOR_ENEMY":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/MagicDamper"
+		}
+	},
+
+	"CHARGE_IMMUNITY":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/ChargeImmune"
+		}
+	},
+	
+	"DAEMON_SUMMONING":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/RiseDemons"
+		}
+	},
+	
+	"DARKNESS":
+	{
+	},				
+	
+	"DEATH_STARE":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_DEATH"
+		}
+	},	
+		
+	"DEFENSIVE_STANCE":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_DEFBON"
+		}
+	},
+		
+	"DOUBLE_DAMAGE_CHANCE":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_DBLOW"
+		}
+	},
+
+	"DRAGON_NATURE":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_DRAGON"
+		}
+	},
+	
+	"DIRECT_DAMAGE_IMMUNITY":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_SPDIR"
+		}
+	},
+
+	"EARTH_IMMUNITY":
+	{
+		"graphics":
+		{
+			"icon":  ""
+		}
+	},
+					
+	"ENCHANTER":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_CAST1"
+		}
+	},
+	
+	"ENCHANTED":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_BLESS"
+		}
+	},		
+
+
+
+	"ENEMY_DEFENCE_REDUCTION":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_RDEF"
+		}
+	},
+	
+	"FIRE_IMMUNITY":
+	{
+		"graphics":
+		{
+			"icon":  ""
+		}
+	},
+	
+	"FIRE_SHIELD":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/FireShield"
+		}		
+	},
+	"FEAR":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_FEAR"
+		}
+	},
+	
+	"FEARLESS":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_FEARL"
+		}
+	},
+	
+	"FLYING":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_FLY"
+		}
+
+	},		
+
+	"FREE_SHOOTING":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_SHOOTA"
+		}
+
+	},
+
+	"FULL_HP_REGENERATION":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_TROLL"
+		}
+	},
+			
+	"HATE":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_HATE"
+		}
+	},
+	
+	"HEALER":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/Healer"
+		}
+	},
+	
+	"HP_REGENERATION":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_TROLL"
+		}
+	},
+	"JOUSTING":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_CHAMP"
+		}
+	},
+	
+	"KING1":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_KING1"
+		}
+	},
+	
+	"KING2":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_KING2"
+		}
+	},
+	
+	"KING3":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_KING3"
+		}
+	},
+	
+	"LEVEL_SPELL_IMMUNITY":
+	{
+		"graphics":
+		{
+			"icon":  ""
+		}
+	},
+	
+	"LIFE_DRAIN":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/DrainLife"
+		}
+	},
+
+	"MANA_CHANNELING":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/ManaChannel"
+		}
+	},
+	"MANA_DRAIN":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/ManaDrain"
+		}
+	},
+
+	"MAGIC_MIRROR":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/MagicMirror"
+		}
+	},
+
+	"MAGIC_RESISTANCE":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_DWARF"
+		}
+	},
+
+	"MIND_IMMUNITY":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_MIND"
+		}
+	},
+
+	"NO_DISTANCE_PENALTY":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_DIST"
+		}
+	},
+	"NO_MELEE_PENALTY":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_MELEE"
+		}
+	},
+	
+	"NO_MORALE":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_MORAL"
+		}		
+	},		
+	"NO_WALL_PENALTY":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_OBST"
+		}
+	},
+
+	"NON_LIVING":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/NonLiving"
+		}
+	},	
+
+	"RANDOM_SPELLCASTER":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/RandomBoost"
+		}
+	},
+
+	"RECEPTIVE":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_NOFRIM"
+		}
+	},
+	"REBIRTH":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_REBIRTH"
+		}
+	},
+								
+	"RETURN_AFTER_STRIKE":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_HARPY"
+		}
+	},
+	
+	"SECONDARY_SKILL_PREMY":
+	{
+		"hidden": true
+		//todo: selector based config
+	},
+	
+	"SELF_LUCK":
+	{
+		"graphics":
+		{
+			"icon":  ""
+		}
+	},
+	
+	"SELF_MORALE":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_MINOT"
+		}
+	},
+	
+	"SHOOTER":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_SHOOT"
+		}
+	},	
+	"SPELLCASTER":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_CASTER"
+		}
+	},
+					
+	"SPELL_AFTER_ATTACK":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_CAST"
+		}
+	},
+	
+	"SPELL_BEFORE_ATTACK":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_CAST2"
+		}
+	},
+	
+	"SPELL_DAMAGE_REDUCTION":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_GOLEM"
+		}
+	},
+
+	"SPELL_IMMUNITY":
+	{
+		"graphics":
+		{
+			"icon":  "" //todo: configurable use from spell handler
+		}
+	},
+
+	
+	"SPELL_LIKE_ATTACK":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/SpellLikeAttack"
+		}
+	},
+			
+	"SPELL_RESISTANCE_AURA":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_UNIC"
+		}
+	},
+
+	"TWO_HEX_ATTACK_BREATH":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_BREATH"
+		}
+	},
+
+
+	"THREE_HEADED_ATTACK":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/ThreeHeaded"
+		}
+	},
+
+	"UNDEAD":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_UNDEAD"
+		}
+	},
+	"UNLIMITED_RETALIATIONS":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_RETAIL1"
+		}
+	},
+	"WATER_IMMUNITY":
+	{
+		"graphics":
+		{
+			"icon":  ""
+		}
+	}
+}
+

+ 387 - 0
config/bonuses_texts.json

@@ -0,0 +1,387 @@
+// macros:
+// ${val} - value of bonuses; Selector: type,subtype
+// ${subtype.creature} - creature name 
+// ${subtype.spell} - spell name
+// ${MR} - magic resistance of bearer
+
+
+{
+	"ADDITIONAL_ATTACK":
+	{
+		"name": "Double Strike",
+		"description": "Attacks twice"
+	},
+	
+	"ADDITIONAL_RETALIATION":
+	{
+		"name": "Additional retaliations",
+		"description": "May Retaliate ${val} extra times"
+	},
+	
+	"AIR_IMMUNITY":
+	{
+		"name": "Immune to Air",
+		"description": ""
+	},
+			
+	"ATTACKS_ALL_ADJACENT":
+	{
+		"name": "Attack all around",
+		"description": "Attacks all adjacent enemies"
+	},
+
+	"BLOCKS_RETALIATION":
+	{
+		"name": "No retaliation",
+		"description": "Enemy cannot Retaliate"
+	},
+	
+	"CATAPULT":
+	{
+		"name": "Catapult",
+		"description": "Attacks siege walls"
+	},	
+	
+	"CHANGES_SPELL_COST_FOR_ALLY":
+	{
+		"name": "Reduce Casting Cost (${val})",
+		"description": "Reduce Casting Cost for hero"
+	},
+	
+	"CHANGES_SPELL_COST_FOR_ENEMY":
+	{
+		"name": "Magic Damper (${val})",
+		"description": "Increase Cost of enemy spells"
+	},
+
+	"CHARGE_IMMUNITY":
+	{
+		"name": "Immune to Charge",
+		"description": "Immune to Champion charge"
+	},
+	
+	"DAEMON_SUMMONING":
+	{
+		"name": "Summoner (${subtype.creature})",
+		"description": "Can rise creatures from corpses" 
+	},
+	
+	"DARKNESS":		
+	{
+		"name": "Darness cover",
+		"description": "Adds ${val} darkness radius"
+	},
+			
+	"DEATH_STARE":
+	{
+		"name": "Death Stare (${val}%)",
+		"description": "Chance to kill single creature"
+	},	
+		
+	"DEFENSIVE_STANCE":
+	{
+		"name": "Defense Bonus",
+		"description": "+${val} Defense when defending"
+	},
+		
+	"DOUBLE_DAMAGE_CHANCE":
+	{
+		"name": "Death Blow",
+		"description": "${val}% chance for double damage"
+	},
+
+	"DRAGON_NATURE":
+	{
+		"name": "Dragon",
+		"description": "Creature has a Dragon Nature"
+	},
+	
+	"DIRECT_DAMAGE_IMMUNITY":
+	{
+		"name": "Direct Damage Immunity",
+		"description": "Immune to direct damage spells"
+	},
+
+	"EARTH_IMMUNITY":
+	{
+		"name": "Immune to Earth",
+		"description": ""
+	},
+					
+	"ENCHANTER":
+	{
+		"name": "Enchanter (${subtype.spell})",
+		"description": "Casts mass spell every turn"
+	},
+	
+	"ENCHANTED":
+	{
+		"name": "Enchanted (${subtype.spell})",
+		"description": "Affected by permanent spell"
+	},		
+
+	"ENEMY_DEFENCE_REDUCTION":
+	{
+		"name": "Reduce Enemy Defense (${val}%)",
+		"description": "Reduces Defense for one attack"
+	},
+	
+	"FIRE_IMMUNITY":
+	{
+		"name": "Immune to Fire",
+		"description": ""
+	},
+
+	"FIRE_SHIELD":
+	{
+		"name": "Fire Shield (${val}%)",
+		"description": "Reflects melee damage"		
+	},
+	"FEAR":
+	{
+		"name": "Fear",
+		"description": "Causes Fear on an enemy stack"
+	},
+	
+	"FEARLESS":
+	{
+		"name": "Fearless",
+		"description": "Immune to Fear ability"
+	},
+	
+	"FLYING":
+	{
+		"name": "Fly",
+		"description": "Can Fly (ignores obstacles)"
+	},		
+
+	"FREE_SHOOTING":
+	{
+		"name": "Shoot Close",
+		"description": "Can shoot in Close Combat"
+	},
+
+	"FULL_HP_REGENERATION":
+	{
+		"name": "Regeneration",
+		"description": "May Regenerate full Health"
+	},
+			
+	"HATE":
+	{
+		"name": "Hates ${subtype.creature}",
+		"description": "Does ${val}% more damage"
+	},
+	
+	"HEALER":
+	{
+		"name": "Healer",
+		"description": "Heals allied units"
+	},
+	
+	"HP_REGENERATION":
+	{
+		"name": "Regeneration",
+		"description": "Heals ${val} hit points every round"
+	},
+	"JOUSTING":
+	{
+		"name": "Champion Charge",
+		"description": "+5% damage per hex travelled"
+	},
+	
+	"KING1":
+	{
+	},
+	
+	"KING2":
+	{
+	},
+	
+	"KING3":
+	{
+	},
+	
+	"LEVEL_SPELL_IMMUNITY":
+	{
+		"name": "Spell immunity 1-${val}",
+		"description": "Immune to spells levels 1-${val}"
+	},
+	
+	"LIFE_DRAIN":
+	{
+		"name": "Drain life (${val}%)",
+		"description": "Drains ${val}% of damage dealt"
+	},
+
+	"MANA_CHANNELING":
+	{
+		"name": "Magic Channel ${val}%",
+		"description": "Gives mana spent by enemy"
+	},
+	"MANA_DRAIN":
+	{
+		"name": "Mana Drain",
+		"description": "Drains ${val} mana every turn"
+	},
+
+	"MAGIC_MIRROR":
+	{
+		"name": "Magic Mirror (${val}%)",
+		"description": "${val}% chance to redirects offensive spell to enemy"
+	},
+
+	"MAGIC_RESISTANCE":
+	{
+		"name": "Magic Resistance(${MR}%)",
+		"description": "${MR}% chance to resist enemy spell"
+	},
+
+	"MIND_IMMUNITY":
+	{
+		"name": "Immune to mind spells",
+		"description": ""
+	},
+
+	"NO_DISTANCE_PENALTY":
+	{
+		"name": "No distance penalty",
+		"description": "Full damage from any distance"
+	},
+	"NO_MELEE_PENALTY":
+	{
+		"name": "No melee penalty",
+		"description": "Creature has no Melee Penalty"
+	},
+	
+	
+	"NO_MORALE":
+	{
+	
+	},	
+		
+	"NO_WALL_PENALTY":
+	{
+		"name": "No wall penalty",
+		"description": "Full damage during siege"
+	},
+
+	"NON_LIVING":
+	{
+		"name": "Non living",
+		"description": "Immunity to many effects"
+	},	
+
+	"RANDOM_SPELLCASTER":
+	{
+		"name": "Random spellcaster",
+		"description": "Can cast random spell"
+	},
+
+	"RECEPTIVE":
+	{
+		"name": "Receptive",
+		"description": "No Immunity to Friendly Spells"
+	},
+	"REBIRTH":
+	{
+		"name": "Rebirth (${val}%)",
+		"description": "${val}% of stack will rise after death"
+	},
+								
+	"RETURN_AFTER_STRIKE":
+	{
+		"name": "Attack and Return",
+		"description": "Returns after melee attack"
+	},
+	
+	"SELF_LUCK":
+	{
+		"name": "Positive luck",
+		"description": "Always has Positive Luck"
+	},
+	
+	"SELF_MORALE":
+	{
+		"name": "Positive morale",
+		"description": "Always has Positive Morale"
+	},
+	
+	"SHOOTER":
+	{
+		"name": "Ranged",
+		"description": "Creature can shoot"
+	},	
+	"SPELLCASTER":
+	{
+		"name": "Spellcaster (${subtype.spell})",
+		"description": "Can cast spells"
+	},
+					
+	"SPELL_AFTER_ATTACK":
+	{
+		"name": "Caster - ${subtype.spell}",
+		"description": "${val}% chance to cast after attack"
+	},
+	
+	"SPELL_BEFORE_ATTACK":
+	{
+		"name": "Caster - ${subtype.spell}",
+		"description": "${val}% chance to cast before attack"
+	},
+	
+	"SPELL_DAMAGE_REDUCTION":
+	{
+		"name": "Spell Resistance",
+		"description": "Damage from spells reduced ${val}%."
+	},
+
+	"SPELL_IMMUNITY":
+	{
+		"name": "Immune to ${subtype.spell}",
+		"description": ""
+	},
+
+	
+	"SPELL_LIKE_ATTACK":
+	{
+		"name": "Spell-like attack",
+		"description": "Attacks with ${subtype.spell}"
+	},
+			
+	"SPELL_RESISTANCE_AURA":
+	{
+		"name": "Aura of Resistance",
+		"description": "Nearby stacks get ${val}% resistance"
+	},
+
+	"TWO_HEX_ATTACK_BREATH":
+	{
+		"name": "Breath",
+		"description": "Breath Attack (2-hex range)"
+	},
+
+
+
+	"THREE_HEADED_ATTACK":
+	{
+		"name": "Three-headed attack",
+		"description": "Attacks three adjacent units"
+	},
+
+	"UNDEAD":
+	{
+		"name": "Undead",
+		"description": "Creature is Undead"
+	},
+	"UNLIMITED_RETALIATIONS":
+	{
+		"name": "Unlimited retaliations",
+		"description": "Retaliate any number of attacks"
+	},
+	"WATER_IMMUNITY":
+	{
+		"name": "Immune to Water",
+		"description": ""
+	}
+}

+ 0 - 66
config/bonusnames.json

@@ -1,66 +0,0 @@
-{
-	"bonuses":
-		[
-			{ "id": "ADDITIONAL_RETALIATION", "name": "Additional retaliations", "description": "May Retaliate %d extra times" },
-			{ "id": "ATTACKS_ALL_ADJACENT", "name": "Attack all around", "description": "Attacks all adjacent enemies" },
-			{ "id": "RETURN_AFTER_STRIKE", "name": "Attack and Return", "description": "Returns after melee attack" },
-			{ "id": "SPELL_RESISTANCE_AURA", "name": "Aura of Resistance", "description": "Nearby stacks get %d% resistance" },
-			{ "id": "TWO_HEX_ATTACK_BREATH", "name": "Breath", "description": "Breath Attack (2-hex range)" },
-			{ "id": "SPELL_AFTER_ATTACK", "name": "Caster - %s", "description": "%d% chance to cast after attack" },
-			{ "id": "SPELL_BEFORE_ATTACK", "name": "Caster - %s", "description": "%d% chance to cast before attack" },
-			{ "id": "SPELL_LIKE_ATTACK", "name": "Spell-like attack", "description": "Attacks with %s" },
-			{ "id": "CATAPULT", "name": "Catapult", "description": "Attacks siege walls" },
-			{ "id": "JOUSTING", "name": "Champion Charge", "description": "+5% damage per hex travelled" },
-			{ "id": "DOUBLE_DAMAGE_CHANCE", "name": "Death Blow", "description": "%d% chance for double damage" },
-			{ "id": "DEFENSIVE_STANCE", "name": "Defense Bonus", "description": "+%d Defense when defending" },
-			{ "id": "ADDITIONAL_ATTACK", "name": "Double Strike", "description": "Attacks twice" },
-			{ "id": "DRAGON_NATURE", "name": "Dragon", "description": "Creature has a Dragon Nature" },
-			{ "id": "LIFE_DRAIN", "name": "Drain life (%d%)", "description": "Drains life equal to damage dealt" },
-			{ "id": "FEAR", "name": "Fear", "description": "Causes Fear on an enemy stack" },
-			{ "id": "FEARLESS", "name": "Fearless", "description": "Immune to Fear ability" },
-			{ "id": "FLYING", "name": "Fly", "description": "Can Fly (ignores obstacles)" },
-			{ "id": "HATE", "name": "Hates %s", "description": "Does %d% more damage" },
-			{ "id": "HEALER", "name": "Healer", "description": "Heals allied units" },
-			{ "id": "SPELL_IMMUNITY", "name": "Immune to %s", "description": "" },
-			{ "id": "CHARGE_IMMUNITY", "name": "Immune to Charge", "description": "Immune to Champion charge" },
-			{ "id": "MANA_CHANNELING", "name": "Magic Channel %d%", "description": "Gives mana spent by enemy" },
-			{ "id": "MANA_DRAIN", "name": "Mana Drain", "description": "Drains %d mana every turn" },
-			{ "id": "CHANGES_SPELL_COST_FOR_ENEMY", "name": "Magic Damper (%d)", "description": "Increase Cost of enemy spells" },
-			{ "id": "MAGIC_MIRROR", "name": "Magic Mirror (%d%)", "description": "Redirects offensive spell to enemy" },
-			{ "id": "MAGIC_RESISTANCE", "name": "Magic Resistance", "description": "%d% chance to resist enemy spell" },
-			{ "id": "NO_DISTANCE_PENALTY", "name": "No distance penalty", "description": "Full damage from any distance" },
-			{ "id": "NO_MELEE_PENALTY", "name": "No melee penalty", "description": "Creature has no Melee Penalty" },
-			{ "id": "NO_WALL_PENALTY", "name": "No wall penalty", "description": "Full damage during siege" },
-			{ "id": "BLOCKS_RETALIATION", "name": "No retaliation", "description": "Enemy cannot Retaliate" },
-			{ "id": "NON_LIVING", "name": "Non living", "description": "Immunity to many effects" },
-			{ "id": "SELF_LUCK", "name": "Positive luck", "description": "Always has Positive Luck" },
-			{ "id": "SELF_MORALE", "name": "Positive morale", "description": "Always has Positive Morale" },
-			{ "id": "SHOOTER", "name": "Ranged", "description": "Creature can shoot" },
-			{ "id": "CHANGES_SPELL_COST_FOR_ALLY", "name": "Reduce Casting Cost (%d)", "description": "Reduce Casting Cost for hero" },
-			{ "id": "ENEMY_DEFENCE_REDUCTION", "name": "Reduce Enemy Defense (%d%)", "description": "Reduces Defense for one attack" },
-			{ "id": "FULL_HP_REGENERATION", "name": "Regeneration", "description": "May Regenerate full Health" },
-			{ "id": "HP_REGENERATION", "name": "Regeneration", "description": "Heals %d hit points every round" },
-			{ "id": "FREE_SHOOTING", "name": "Shoot Close", "description": "Can shoot in Close Combat" },
-			{ "id": "LEVEL_SPELL_IMMUNITY", "name": "Spell immunity 1-%d", "description": "Immune to spells levels 1-%d" },
-			{ "id": "SPELL_DAMAGE_REDUCTION", "name": "Spell Resistance", "description": "Damage from spells reduced %d%." },
-			{ "id": "THREE_HEADED_ATTACK", "name": "Three-headed attack", "description": "Attacks three adjacent units" },
-			{ "id": "UNDEAD", "name": "Undead", "description": "Creature is Undead" },
-			{ "id": "UNLIMITED_RETALIATIONS", "name": "Unlimited retaliations", "description": "Retaliate any number of attacks" },
-			{ "id": "DEATH_STARE", "name": "Death Stare (%d%)", "description": "Chance to kill single creature" },
-			{ "id": "FIRE_IMMUNITY", "name": "Immune to Fire", "description": "" },
-			{ "id": "WATER_IMMUNITY", "name": "Immune to Water", "description": "" },
-			{ "id": "AIR_IMMUNITY", "name": "Immune to Air", "description": "" },
-			{ "id": "EARTH_IMMUNITY", "name": "Immune to Earth", "description": "" },
-			{ "id": "MIND_IMMUNITY", "name": "Immune to mind spells", "description": "" },
-			{ "id": "DIRECT_DAMAGE_IMMUNITY", "name": "Direct Damage Immunity", "description": "Immune to direct damage spells" },
-			{ "id": "RECEPTIVE", "name": "Receptive", "description": "No Immunity to Friendly Spells" },
-			{ "id": "REBIRTH", "name": "Rebirth (%d%)", "description": "Stack will rise after death" },
-			{ "id": "SPELLCASTER", "name": "Spellcaster (%s)", "description": "Can cast spells" },
-			{ "id": "ENCHANTER", "name": "Enchanter (%s)", "description": "Casts mass spell every turn" },
-			{ "id": "ENCHANTED", "name": "Enchanted (%s)", "description": "Affected by permanent spell" },
-			{ "id": "FIRE_SHIELD", "name": "Fire Shield (%d%)", "description": "Reflects melee damage" },
-			{ "id": "MAGIC_MIRROR", "name": "Magic Mirror (%d%)", "description": "Chance to reflect hostile spell" },
-			{ "id": "RANDOM_SPELLCASTER", "name": "Random spellcaster", "description": "Can cast random spell" },
-			{ "id": "DAEMON_SUMMONING", "name": "Summoner (%s)", "description": "Can rise creatures from corpses" }
-		]
-}

+ 6 - 0
config/gameConfig.json

@@ -45,4 +45,10 @@
 		"config/heroes/conflux.json",
 		"config/heroes/special.json"
 	],
+	
+	"bonuses" :
+	[
+		"config/bonuses.json",
+		"config/bonuses_texts.json"
+	]
 }

+ 0 - 2
lib/BattleState.h

@@ -79,8 +79,6 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallb
 	BattleInfo();
 	~BattleInfo(){};
 
-	//////////////////////////////////////////////////////////////////////////
-	//void getBonuses(BonusList &out, const CSelector &selector, const CBonusSystemNode *root = NULL) const;
 	//////////////////////////////////////////////////////////////////////////
 	CStack * getStackT(BattleHex tileID, bool onlyAlive = true);
 	CStack * getStack(int stackID, bool onlyAlive = true);

+ 1 - 1
lib/CArtHandler.cpp

@@ -161,7 +161,7 @@ CArtHandler::~CArtHandler()
 {
 }
 
-void CArtHandler::loadArtifacts(bool onlyTxt)
+void CArtHandler::load(bool onlyTxt)
 {
 	if (onlyTxt)
 		return; // looks to be broken anyway...

+ 3 - 3
lib/CArtHandler.h

@@ -59,8 +59,8 @@ public:
 	bool isBig () const;
 
 	int getArtClassSerial() const; //0 - treasure, 1 - minor, 2 - major, 3 - relic, 4 - spell scroll, 5 - other
-	std::string nodeName() const override;
-	void addNewBonus(Bonus *b) override;
+	std::string nodeName() const OVERRIDE;
+	void addNewBonus(Bonus *b) OVERRIDE;
 
 	virtual void levelUpArtifact (CArtifactInstance * art){};
 
@@ -198,7 +198,7 @@ public:
 	std::set<ArtifactID> bigArtifacts; // Artifacts that cannot be moved to backpack, e.g. war machines.
 	std::set<ArtifactID> growingArtifacts;
 
-	void loadArtifacts(bool onlyTxt);
+	void load(bool onlyTxt = false);
 	/// load artifact from json structure
 	void load(std::string objectID, const JsonNode & node);
 	/// load one artifact from json config

+ 337 - 0
lib/CBonusTypeHandler.cpp

@@ -0,0 +1,337 @@
+/*
+ * CBonusTypeHandler.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 "CBonusTypeHandler.h"
+
+#include "JsonNode.h"
+#include "Filesystem/CResourceLoader.h"
+#include "Filesystem/ISimpleResourceLoader.h"
+
+
+#include "VCMI_Lib.h"
+#include "CCreatureHandler.h"
+#include "CSpellHandler.h"
+
+///Helpers
+
+static inline void jsonSetString(const JsonNode& source, const std::string& name, std::string& dest)
+{
+	const JsonNode& val = source[name];
+	if(!val.isNull())
+	{
+		dest = val.String();
+	}
+}
+
+static inline void jsonSetBool(const JsonNode& source, const std::string& name, bool& dest)
+{
+	const JsonNode& val = source[name];
+	if(!val.isNull())
+	{
+		dest = val.Bool();
+	}
+}
+
+///MacroString
+
+MacroString::MacroString(const std::string &format)
+{
+	static const std::string MACRO_START = "${";
+	static const std::string MACRO_END = "}";
+	static const size_t MACRO_START_L = 2;
+	static const size_t MACRO_END_L = 1;
+	
+	size_t end_pos = 0;	
+	size_t start_pos = std::string::npos;
+	
+	tlog5 << "Parsing format " << format << std::endl;
+	
+	do
+	{
+		start_pos = format.find(MACRO_START, end_pos);
+		
+		if (!(start_pos == std::string::npos))
+		{
+			//chunk before macro
+			items.push_back(Item(Item::STRING,format.substr(end_pos, start_pos-end_pos)));
+			
+			start_pos += MACRO_START_L;
+			end_pos = format.find(MACRO_END, start_pos);
+			
+			if (end_pos == std::string::npos)
+			{
+				tlog2 << "Format error in: " << format <<std::endl;
+				end_pos = start_pos;
+				break;
+			}
+			else
+			{
+				items.push_back(Item(Item::MACRO,format.substr(start_pos,end_pos-start_pos)));
+				end_pos += MACRO_END_L;
+			}
+		}		
+	}
+	while (!start_pos == std::string::npos);
+	
+	//no more macros
+	items.push_back(Item(Item::STRING,format.substr(end_pos)));
+
+	
+}
+
+std::string MacroString::build(const GetValue& getValue) const
+{
+	std::string result;
+	
+	BOOST_FOREACH(const Item &i, items)
+	{
+		switch (i.type)
+		{
+			case Item::MACRO:
+			{
+				result += getValue(i.value);
+				break;
+			}			
+			case Item::STRING:
+			{
+				result += i.value;
+				break;
+			}			
+		}		
+	}
+	return result;
+}
+
+///CBonusType
+
+CBonusType::CBonusType()
+{
+	hidden = true;
+	icon = nameTemplate = descriptionTemplate = ""; 
+}
+
+CBonusType::~CBonusType()
+{
+	
+}
+
+void CBonusType::buildMacros()
+{
+	name = MacroString(nameTemplate);
+	description = MacroString(descriptionTemplate);
+}
+
+
+///CBonusTypeHandler
+
+CBonusTypeHandler::CBonusTypeHandler()
+{
+	//register predefined bonus types
+
+	#define BONUS_NAME(x) \
+		do{\
+			bonusTypes.push_back(CBonusType());\
+		}while (0);
+		
+	
+	BONUS_LIST;
+	#undef BONUS_NAME
+}
+
+CBonusTypeHandler::~CBonusTypeHandler()
+{
+	//dtor
+}
+
+std::string CBonusTypeHandler::bonusToString(Bonus *bonus, const IBonusBearer *bearer, bool description) const
+{
+	auto getValue = [=](const std::string &name) -> std::string
+	{
+		if (name == "val")
+		{
+			return boost::lexical_cast<std::string>(bearer->valOfBonuses(Selector::typeSubtype(bonus->type, bonus->subtype)));
+		}
+		else if (name == "subtype.creature")
+		{
+			return VLC->creh->creatures[bonus->subtype]->namePl;
+		}
+		else if (name == "subtype.spell")
+		{
+			 return VLC->spellh->spells[bonus->subtype]->name;
+		}		
+		else if (name == "MR")
+		{
+			 return boost::lexical_cast<std::string>(bearer->magicResistance());
+		}			
+		else
+		{
+			tlog2 << "Unknown macro in bonus config: " << name << std::endl;
+			return "[error]";
+		}
+	};
+	
+	const CBonusType& bt = bonusTypes[bonus->type];
+	
+	std::string text;
+	
+	if (description)
+	{
+		text = bt.description.build(getValue);
+	}
+	else
+	{
+		text = bt.name.build(getValue);
+	}
+	
+	return text;	
+}
+
+std::string CBonusTypeHandler::bonusToGraphics(Bonus* bonus) const
+{
+	std::string fileName;
+	bool fullPath = false;
+
+	switch (bonus->type)
+	{
+		case Bonus::SECONDARY_SKILL_PREMY:
+			if (bonus->subtype == SecondarySkill::RESISTANCE)
+			{
+				fileName = "E_DWARF.bmp";
+			}
+			break;
+		case Bonus::SPELL_IMMUNITY:
+		{
+			fullPath = true;
+			const CSpell * sp = SpellID(bonus->subtype).toSpell();
+			fileName = sp->getIconImmune();
+			break;
+		}
+		case Bonus::FIRE_IMMUNITY:
+			switch (bonus->subtype)
+			{
+				case 0:
+					fileName =  "E_SPFIRE.bmp"; break; //all
+				case 1:
+					fileName =  "E_SPFIRE1.bmp"; break; //not positive
+				case 2:
+					fileName =  "E_FIRE.bmp"; break; //direct damage
+			}
+			break;
+		case Bonus::WATER_IMMUNITY:
+			switch (bonus->subtype)
+			{
+				case 0:
+					fileName =  "E_SPWATER.bmp"; break; //all
+				case 1:
+					fileName =  "E_SPWATER1.bmp"; break; //not positive
+				case 2:
+					fileName =  "E_SPCOLD.bmp"; break; //direct damage
+			}
+			break;
+		case Bonus::AIR_IMMUNITY:
+			switch (bonus->subtype)
+			{
+				case 0:
+					fileName =  "E_SPAIR.bmp"; break; //all
+				case 1:
+					fileName =  "E_SPAIR1.bmp"; break; //not positive
+				case 2:
+					fileName = "E_LIGHT.bmp"; break;//direct damage
+			}
+			break;
+		case Bonus::EARTH_IMMUNITY:
+			switch (bonus->subtype)
+			{
+				case 0:
+					fileName =  "E_SPEATH.bmp"; break; //all
+				case 1:
+				case 2: //no specific icon for direct damage immunity
+					fileName =  "E_SPEATH1.bmp"; break; //not positive
+			}
+			break;
+		case Bonus::LEVEL_SPELL_IMMUNITY:
+		{
+			if (vstd::iswithin(bonus->val, 1 , 5))
+			{
+				fileName = "E_SPLVL" + boost::lexical_cast<std::string>(bonus->val) + ".bmp";
+			}
+			break;
+		}
+		
+		default: 
+		{
+
+			const CBonusType& bt = bonusTypes[bonus->type];
+			
+			fileName = bt.icon;
+			fullPath = true;
+			break;
+		}		
+	}
+	
+	if(!fileName.empty() && !fullPath)
+		fileName = "zvs/Lib1.res/" + fileName;
+	return fileName;	
+}
+
+
+void CBonusTypeHandler::load()
+{
+	const JsonNode gameConf(ResourceID("config/gameConfig.json"));
+	const JsonNode config(JsonUtils::assembleFromFiles(gameConf["bonuses"].convertTo<std::vector<std::string> >()));
+	
+	load(config);
+}
+
+void CBonusTypeHandler::load(const JsonNode& config)
+{
+	BOOST_FOREACH(auto & node, config.Struct())
+	{
+		auto it = bonusNameMap.find(node.first);
+		
+		if(it == bonusNameMap.end())
+		{
+			//TODO: new bonus
+			CBonusType bt;
+			loadItem(node.second, bt);
+			
+			auto new_id = bonusTypes.size();
+			
+			bonusTypes.push_back(bt);
+			
+			tlog2 << "Adding new bonuses not implemented (" << node.first << ")" << std::endl;
+		}
+		else
+		{
+			CBonusType& bt = bonusTypes[it->second];
+			
+			loadItem(node.second, bt);
+			tlog5 << "Loaded bonus type " << node.first << std::endl;
+		}	
+	}		
+}
+
+void CBonusTypeHandler::loadItem(const JsonNode& source, CBonusType& dest)
+{
+	dest.hidden = false;
+	jsonSetString(source,"name", dest.nameTemplate);
+	jsonSetString(source,"description", dest.descriptionTemplate);
+	
+	jsonSetBool(source,"hidden", dest.hidden);
+	
+	const JsonNode& graphics = source["graphics"];
+	
+	if(!graphics.isNull())
+	{
+		jsonSetString(graphics,"icon", dest.icon);
+	}	
+	dest.buildMacros();
+}
+

+ 93 - 0
lib/CBonusTypeHandler.h

@@ -0,0 +1,93 @@
+/*
+ * CBonusTypeHandler.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 "IBonusTypeHandler.h"
+#include "HeroBonus.h"
+
+
+class JsonNode;
+
+typedef Bonus::BonusType BonusTypeID;
+
+class MacroString
+{
+	struct Item
+	{
+		enum ItemType
+		{
+			STRING, MACRO
+		};
+		Item(ItemType _type, std::string _value): type(_type), value(_value){};
+		ItemType type;
+		std::string value; //consant string or macro name
+	};
+	std::vector<Item> items;
+public:
+	typedef std::function <std::string(const std::string&)> GetValue;
+	
+	MacroString(){};
+	MacroString(const std::string &format);
+	
+	std::string build(const GetValue& getValue) const;
+};
+
+class DLL_LINKAGE CBonusType
+{
+public:
+	CBonusType();
+	~CBonusType();
+	
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & icon & nameTemplate & descriptionTemplate & hidden;
+		
+		if (!h.saving)
+			buildMacros();
+	}
+protected:
+	
+private:
+	void buildMacros();
+	MacroString name, description;
+	
+	friend class CBonusTypeHandler;
+	std::string icon;
+	std::string nameTemplate, descriptionTemplate;
+	
+	bool hidden;
+};
+
+ 
+class DLL_LINKAGE CBonusTypeHandler : public IBonusTypeHandler
+{
+public:
+	CBonusTypeHandler();
+	virtual ~CBonusTypeHandler();
+	
+	std::string bonusToString(Bonus *bonus, const IBonusBearer *bearer, bool description) const override;
+	std::string bonusToGraphics(Bonus *bonus) const override;
+	
+	void load();
+	void load(const JsonNode& config);
+	
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & bonusTypes;
+	}
+private:
+	
+	void loadItem(const JsonNode &source, CBonusType &dest);
+	
+	std::vector<CBonusType> bonusTypes; //index = BonusTypeID 
+
+};

+ 3 - 23
lib/CCreatureHandler.cpp

@@ -127,10 +127,7 @@ void CCreature::addBonus(int val, Bonus::BonusType type, int subtype /*= -1*/)
 	Bonus *added = new Bonus(Bonus::PERMANENT, type, Bonus::CREATURE_ABILITY, val, idNumber, subtype, Bonus::BASE_NUMBER);
 	addNewBonus(added);
 }
-// void CCreature::getParents(TCNodes &out, const CBonusSystemNode *root /*= NULL*/) const
-// {
-// 	out.insert (VLC->creh->globalEffects);
-// }
+
 bool CCreature::isMyUpgrade(const CCreature *anotherCre) const
 {
 	//TODO upgrade of upgrade?
@@ -251,7 +248,8 @@ void CCreatureHandler::loadBonuses(CCreature & ncre, std::string bonuses)
 	}
 }
 
-void CCreatureHandler::loadCreatures()
+
+void CCreatureHandler::load()
 {
 	tlog5 << "\t\tReading ZCRTRAIT.TXT" << std::endl;
 
@@ -373,24 +371,6 @@ void CCreatureHandler::loadCreatures()
 
 	loadAnimationInfo();
 
-	//reading creature ability names
-	const JsonNode config2(ResourceID("config/bonusnames.json"));
-
-	BOOST_FOREACH(const JsonNode &bonus, config2["bonuses"].Vector())
-	{
-		const std::string bonusID = bonus["id"].String();
-
-		auto it_map = bonusNameMap.find(bonusID);
-		if (it_map != bonusNameMap.end())
-			stackBonuses[it_map->second] = std::pair<std::string, std::string>(bonus["name"].String(), bonus["description"].String());
-		else
-			tlog2 << "Bonus " << bonusID << " not recognized, ignoring\n";
-	}
-
-	//handle magic resistance secondary skill premy, potentialy may be buggy
-	//std::map<Bonus::BonusType, std::pair<std::string, std::string> >::iterator it = stackBonuses.find(Bonus::MAGIC_RESISTANCE);
-	//stackBonuses[Bonus::SECONDARY_SKILL_PREMY] = std::pair<std::string, std::string>(it->second.first, it->second.second);
-
 	if (VLC->modh->modules.STACK_EXP) 	//reading default stack experience bonuses
 	{
 		CLegacyConfigParser parser("DATA/CREXPBON.TXT");

+ 2 - 4
lib/CCreatureHandler.h

@@ -106,7 +106,6 @@ public:
 
 	void addBonus(int val, Bonus::BonusType type, int subtype = -1);
 	std::string nodeName() const override;
-	//void getParents(TCNodes &out, const CBonusSystemNode *root /*= NULL*/) const;
 
 	template<typename RanGen>
 	int getRandomAmount(RanGen ranGen) const
@@ -148,7 +147,6 @@ public:
 	std::vector<ConstTransitivePtr<CCreature> > creatures; //creature ID -> creature info.
 
 	//stack exp
-	std::map<Bonus::BonusType, std::pair<std::string, std::string> > stackBonuses; // bonus => name, description
 	std::vector<std::vector<ui32> > expRanks; // stack experience needed for certain rank, index 0 for other tiers (?)
 	std::vector<ui32> maxExpPerBattle; //%, tiers same as above
 	si8 expAfterUpgrade;//multiplier in %
@@ -163,7 +161,7 @@ public:
 	/// adding abilities from ZCRTRAIT.TXT
 	void loadBonuses(CCreature & creature, std::string bonuses);
 	/// load all creatures from H3 files
-	void loadCreatures();
+	void load();
 	/// load creature from json structure
 	void load(std::string creatureID, const JsonNode & node);
 	/// load one creature from json config
@@ -191,7 +189,7 @@ public:
 	{
 		//TODO: should be optimized, not all these informations needs to be serialized (same for ccreature)
 		h & doubledCreatures & creatures;
-		h & stackBonuses & expRanks & maxExpPerBattle & expAfterUpgrade;
+		h & expRanks & maxExpPerBattle & expAfterUpgrade;
 		h & skillLevels & skillRequirements & commanderLevelPremy;
 		h & allCreatures;
 		h & creaturesOfLevel;

+ 8 - 328
lib/CCreatureSet.cpp

@@ -10,6 +10,7 @@
 #include "CGeneralTextHandler.h"
 #include "CSpellHandler.h"
 #include "CHeroHandler.h"
+#include "IBonusTypeHandler.h"
 
 /*
  * CCreatureSet.cpp, part of VCMI engine
@@ -561,341 +562,20 @@ void CStackInstance::setType(const CCreature *c)
 }
 std::string CStackInstance::bonusToString(Bonus *bonus, bool description) const
 {
-	std::map<Bonus::BonusType, std::pair<std::string, std::string> >::iterator it = VLC->creh->stackBonuses.find(bonus->type);
-	if (it != VLC->creh->stackBonuses.end())
+	if(Bonus::MAGIC_RESISTANCE == bonus->type)
 	{
-		std::string text;
-		if (description) //long ability description
-		{
-			text = it->second.second;
-			switch (bonus->type)
-			{
-				//no additional modifiers needed
-				case Bonus::FLYING:
-				case Bonus::UNLIMITED_RETALIATIONS:
-				case Bonus::SHOOTER:
-				case Bonus::FREE_SHOOTING:
-				case Bonus::NO_MELEE_PENALTY:
-				case Bonus::NO_DISTANCE_PENALTY:
-				case Bonus::NO_WALL_PENALTY:
-				case Bonus::JOUSTING: //TODO: percent bonus?
-				case Bonus::RETURN_AFTER_STRIKE:
-				case Bonus::BLOCKS_RETALIATION:
-				case Bonus::TWO_HEX_ATTACK_BREATH:
-				case Bonus::THREE_HEADED_ATTACK:
-				case Bonus::ATTACKS_ALL_ADJACENT:
-				case Bonus::ADDITIONAL_ATTACK: //TODO: what with more than one attack? Axe of Ferocity for example
-				case Bonus::FULL_HP_REGENERATION:
-				case Bonus::MANA_DRAIN:
-				case Bonus::LIFE_DRAIN:
-				case Bonus::REBIRTH:
-				case Bonus::SELF_MORALE:
-				case Bonus::SELF_LUCK:
-				case Bonus::FEAR:
-				case Bonus::FEARLESS:
-				case Bonus::CHARGE_IMMUNITY:
-				case Bonus::HEALER:
-				case Bonus::CATAPULT:
-				case Bonus::DRAGON_NATURE:
-				case Bonus::NON_LIVING:
-				case Bonus::UNDEAD:
-				case Bonus::FIRE_IMMUNITY:
-				case Bonus::WATER_IMMUNITY:
-				case Bonus::AIR_IMMUNITY:
-				case Bonus::EARTH_IMMUNITY:
-				case Bonus::RECEPTIVE:
-				case Bonus::DIRECT_DAMAGE_IMMUNITY:
-				break;
-				//One numeric value. magic resistance handled separately
-				case Bonus::SPELL_RESISTANCE_AURA:
-				case Bonus::SPELL_DAMAGE_REDUCTION:
-				case Bonus::LEVEL_SPELL_IMMUNITY:
-				case Bonus::HP_REGENERATION:
-				case Bonus::ADDITIONAL_RETALIATION:
-				case Bonus::DEFENSIVE_STANCE:
-				case Bonus::DOUBLE_DAMAGE_CHANCE:
-				case Bonus::DARKNESS: //Darkness Dragons any1?
-					boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(valOfBonuses(Selector::typeSubtype(bonus->type, bonus->subtype))));
-					break;
-				//Complex descriptions
-				//case Bonus::SECONDARY_SKILL_PREMY: //only if there's no simple MR
-				//	if (bonus->subtype == CGHeroInstance::RESISTANCE)
-				//	{
-				//		if (!hasBonusOfType(Bonus::MAGIC_RESISTANCE))
-				//			boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>( magicResistance() ));
-				//	}
-				//	break;
-				//case Bonus::MAGIC_RESISTANCE:
-				//		boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>( magicResistance() ));
-				//	break;
-				case Bonus::HATE:
-					boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(valOfBonuses(Selector::typeSubtype(bonus->type, bonus->subtype))));
-					boost::algorithm::replace_first(text, "%s", VLC->creh->creatures[bonus->subtype]->namePl);
-					break;
-				case Bonus::SPELL_AFTER_ATTACK:
-				case Bonus::SPELL_BEFORE_ATTACK:
-				{
-					boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(valOfBonuses(Selector::typeSubtype(bonus->type, bonus->subtype))));
-					boost::algorithm::replace_first(text, "%s", VLC->spellh->spells[bonus->subtype]->name);
-					break;
-				}
-				case Bonus::SPELL_LIKE_ATTACK:
-				{
-					boost::algorithm::replace_first(text, "%s", VLC->spellh->spells[bonus->subtype]->name);
-					break;
-				}
-				default:
-					{}//TODO: allow custom bonus types... someday, somehow
-			}
-		}
-		else //short name
-		{
-			text = it->second.first;
-			switch (bonus->type)
-			{
-				case Bonus::MANA_CHANNELING:
-				case Bonus::MAGIC_MIRROR:
-				case Bonus::CHANGES_SPELL_COST_FOR_ALLY:
-				case Bonus::CHANGES_SPELL_COST_FOR_ENEMY:
-				case Bonus::ENEMY_DEFENCE_REDUCTION:
-				case Bonus::REBIRTH:
-				case Bonus::DEATH_STARE:
-				case Bonus::LIFE_DRAIN:
-				case Bonus::FIRE_SHIELD:
-					boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(valOfBonuses(Selector::typeSubtype(bonus->type, bonus->subtype))));
-					break;
-				case Bonus::HATE:
-				case Bonus::DAEMON_SUMMONING:
-					boost::algorithm::replace_first(text, "%s", VLC->creh->creatures[bonus->subtype]->namePl);
-					break;
-				case Bonus::LEVEL_SPELL_IMMUNITY:
-					boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(bonus->val));
-					break;
-				case Bonus::SPELL_AFTER_ATTACK:
-				case Bonus::SPELL_BEFORE_ATTACK:
-				case Bonus::SPELL_IMMUNITY:
-				case Bonus::SPELLCASTER:
-				case Bonus::ENCHANTER:
-				case Bonus::ENCHANTED:
-					boost::algorithm::replace_first(text, "%s", VLC->spellh->spells[bonus->subtype]->name);
-					break;
-				case Bonus::MAGIC_RESISTANCE:
-					text = ""; //handled separately
-					break;
-				//case Bonus::SECONDARY_SKILL_PREMY:
-				//	if (bonus->subtype != CGHeroInstance::RESISTANCE || hasBonusOfType(Bonus::MAGIC_RESISTANCE)) //handle it there
-				//	text = "";
-				//	break;
-			}
-		}
-		return text;
+		return "";
 	}
 	else
-		return "";
+	{
+		return VLC->getBth()->bonusToString(bonus, this, description);
+	}
+	
 }
 
 std::string CStackInstance::bonusToGraphics(Bonus *bonus) const
 {
-	std::string fileName;
-	bool fullPath = false;
-	switch (bonus->type)
-	{
-			//"E_ALIVE.bmp"
-			//"E_ART.bmp"
-		case Bonus::ENCHANTED:
-			fileName = "E_BLESS.bmp"; break;
-			//"E_BLOCK.bmp"
-			//"E_BLOCK1.bmp"
-			//"E_BLOCK2.bmp"
-		case Bonus::TWO_HEX_ATTACK_BREATH:
-			fileName = "E_BREATH.bmp"; break;
-		case Bonus::SPELL_AFTER_ATTACK:
-			fileName = "E_CAST.bmp"; break;
-		case Bonus::ENCHANTER:
-			fileName = "E_CAST1.bmp"; break;
-		case Bonus::RANDOM_SPELLCASTER:
-			fileName = "RandomBoost.bmp"; break;
-		case Bonus::SPELL_BEFORE_ATTACK:
-			fileName ="E_CAST2.bmp"; break;
-		case Bonus::SPELLCASTER:
-			fileName = "E_CASTER.bmp"; break;
-		case Bonus::JOUSTING:
-			fileName = "E_CHAMP.bmp"; break;
-		case Bonus::DOUBLE_DAMAGE_CHANCE:
-			fileName = "E_DBLOW.bmp"; break;
-		case Bonus::DEATH_STARE:
-			fileName = "E_DEATH.bmp"; break;
-		case Bonus::DEFENSIVE_STANCE:
-			fileName = "E_DEFBON.bmp"; break;
-		case Bonus::NO_DISTANCE_PENALTY:
-			fileName = "E_DIST.bmp"; break;
-		case Bonus::ADDITIONAL_ATTACK:
-			fileName = "E_DOUBLE.bmp"; break;
-		case Bonus::DRAGON_NATURE:
-			fileName = "E_DRAGON.bmp"; break;
-		case Bonus::MAGIC_RESISTANCE:
-			fileName = "E_DWARF.bmp"; break;
-		case Bonus::SECONDARY_SKILL_PREMY:
-			if (bonus->subtype == SecondarySkill::RESISTANCE)
-			{
-				fileName = "E_DWARF.bmp";
-			}
-			break;
-		case Bonus::FEAR:
-			fileName = "E_FEAR.bmp"; break;
-		case Bonus::FEARLESS:
-			fileName = "E_FEARL.bmp"; break;
-		case Bonus::FLYING:
-			fileName = "E_FLY.bmp"; break;
-		case Bonus::SPELL_DAMAGE_REDUCTION:
-			fileName = "E_GOLEM.bmp"; break;
-		case Bonus::RETURN_AFTER_STRIKE:
-			fileName = "E_HARPY.bmp"; break;
-		case Bonus::HATE:
-			fileName = "E_HATE.bmp"; break;
-		case Bonus::KING1:
-			fileName = "E_KING1.bmp"; break;
-		case Bonus::KING2:
-			fileName = "E_KING2.bmp"; break;
-		case Bonus::KING3:
-			fileName = "E_KING3.bmp"; break;
-		case Bonus::CHANGES_SPELL_COST_FOR_ALLY:
-			fileName = "E_MANA.bmp"; break;
-		case Bonus::CHANGES_SPELL_COST_FOR_ENEMY:
-			fileName = "MagicDamper.bpm"; break;
-		case Bonus::NO_MELEE_PENALTY:
-			fileName = "E_MELEE.bmp"; break;
-		case Bonus::MIND_IMMUNITY:
-			fileName = "E_MIND.bmp"; break;
-		case Bonus::SELF_MORALE:
-			fileName = "E_MINOT.bmp"; break;
-		case Bonus::NO_MORALE:
-			fileName = "E_MORAL.bmp"; break;
-		case Bonus::RECEPTIVE:
-			fileName = "E_NOFRIM.bmp"; break;
-		case Bonus::NO_WALL_PENALTY:
-			fileName = "E_OBST.bmp"; break;
-		case Bonus::ENEMY_DEFENCE_REDUCTION:
-			fileName = "E_RDEF.bmp"; break;
-		case Bonus::REBIRTH:
-			fileName = "E_REBIRTH.bmp"; break;
-		case Bonus::BLOCKS_RETALIATION:
-			fileName = "E_RETAIL.bmp"; break;
-		case Bonus::UNLIMITED_RETALIATIONS:
-		case Bonus::ADDITIONAL_RETALIATION:
-			fileName = "E_RETAIL1.bmp"; break;
-		case Bonus::ATTACKS_ALL_ADJACENT:
-			fileName = "E_ROUND.bmp"; break;
-			//"E_SGNUM.bmp"
-			//"E_SGTYPE.bmp"
-		case Bonus::SHOOTER:
-			fileName = "E_SHOOT.bmp"; break;
-		case Bonus::FREE_SHOOTING: //shooter is not blocked by enemy
-			fileName = "E_SHOOTA.bmp"; break;
-			//"E_SHOOTN.bmp"
-		case Bonus::SPELL_IMMUNITY:
-		{
-			fullPath = true;
-			const CSpell * sp = SpellID(bonus->subtype).toSpell();
-			fileName = sp->getIconImmune();
-			break;
-		}
-			//"E_SPAWILL.bmp"
-		case Bonus::DIRECT_DAMAGE_IMMUNITY:
-			fileName = "E_SPDIR.bmp"; break;
-			//"E_SPDISB.bmp"
-			//"E_SPDISP.bmp"
-			//"E_SPEATH.bmp"
-			//"E_SPEATH1.bmp"
-		case Bonus::FIRE_IMMUNITY:
-			switch (bonus->subtype)
-			{
-				case 0:
-					fileName =  "E_SPFIRE.bmp"; break; //all
-				case 1:
-					fileName =  "E_SPFIRE1.bmp"; break; //not positive
-				case 2:
-					fileName =  "E_FIRE.bmp"; break; //direct damage
-			}
-			break;
-		case Bonus::WATER_IMMUNITY:
-			switch (bonus->subtype)
-			{
-				case 0:
-					fileName =  "E_SPWATER.bmp"; break; //all
-				case 1:
-					fileName =  "E_SPWATER1.bmp"; break; //not positive
-				case 2:
-					fileName =  "E_SPCOLD.bmp"; break; //direct damage
-			}
-			break;
-		case Bonus::AIR_IMMUNITY:
-			switch (bonus->subtype)
-			{
-				case 0:
-					fileName =  "E_SPAIR.bmp"; break; //all
-				case 1:
-					fileName =  "E_SPAIR1.bmp"; break; //not positive
-				case 2:
-					fileName = "E_LIGHT.bmp"; break;//direct damage
-			}
-			break;
-		case Bonus::EARTH_IMMUNITY:
-			switch (bonus->subtype)
-			{
-				case 0:
-					fileName =  "E_SPEATH.bmp"; break; //all
-				case 1:
-				case 2: //no specific icon for direct damage immunity
-					fileName =  "E_SPEATH1.bmp"; break; //not positive
-			}
-			break;
-		case Bonus::LEVEL_SPELL_IMMUNITY:
-		{
-			if (vstd::iswithin(bonus->val, 1 , 5))
-			{
-				fileName = "E_SPLVL" + boost::lexical_cast<std::string>(bonus->val) + ".bmp";
-			}
-			break;
-		}
-			//"E_SUMMON.bmp"
-			//"E_SUMMON1.bmp"
-			//"E_SUMMON2.bmp"
-		case Bonus::FULL_HP_REGENERATION:
-		case Bonus::HP_REGENERATION:
-			fileName = "E_TROLL.bmp"; break;
-		case Bonus::UNDEAD:
-			fileName = "E_UNDEAD.bmp"; break;
-		case Bonus::SPELL_RESISTANCE_AURA:
-			fileName = "E_UNIC.bmp"; break;
-		case Bonus::THREE_HEADED_ATTACK:
-			fileName = "ThreeHeaded.bmp"; break;
-		case Bonus::DAEMON_SUMMONING:
-			fileName = "RiseDemons.bmp"; break;
-		case Bonus::CHARGE_IMMUNITY:
-			fileName = "ChargeImmune.bmp"; break;
-		case Bonus::HEALER:
-			fileName = "Healer.bmp"; break;
-		case Bonus::CATAPULT:
-			fileName = "Catapult.bmp"; break;
-		case Bonus::MANA_CHANNELING:
-			fileName = "ManaChannel.bmp"; break;
-		case Bonus::MANA_DRAIN:
-			fileName = "ManaDrain.bmp"; break;
-		case Bonus::LIFE_DRAIN:
-			fileName = "DrainLife.bmp"; break;
-		case Bonus::FIRE_SHIELD:
-			fileName = "FireShield.bmp"; break;
-		case Bonus::MAGIC_MIRROR:
-			fileName = "MagicMirror.bmp"; break;
-		case Bonus::NON_LIVING:
-			fileName = "NonLiving.bmp"; break;
-		case Bonus::SPELL_LIKE_ATTACK:
-			fileName = "SpellLikeAttack.bmp"; break;
-	}
-	if(!fileName.empty() && !fullPath)
-		fileName = "zvs/Lib1.res/" + fileName;
-	return fileName;
+	return VLC->getBth()->bonusToGraphics(bonus);
 }
 
 void CStackInstance::setArmyObj(const CArmedInstance *ArmyObj)

+ 1 - 2
lib/CCreatureSet.h

@@ -56,8 +56,7 @@ public:
 	}
 
 	//overrides CBonusSystemNode
-	//void getParents(TCNodes &out, const CBonusSystemNode *source = NULL) const;  //retrieves list of parent nodes (nodes to inherit bonuses from), source is the prinary asker
-	std::string bonusToString(Bonus *bonus, bool description) const; // how would bonus description look for this particular type of node
+	std::string bonusToString(Bonus *bonus, bool description) const override; // how would bonus description look for this particular type of node
 	std::string bonusToGraphics(Bonus *bonus) const; //file name of graphics from StackSkills , in future possibly others
 
 	virtual ui64 getPower() const;

+ 0 - 14
lib/CGameState.cpp

@@ -2736,20 +2736,6 @@ std::string PlayerState::nodeName() const
 	return "Player " + (color.getNum() < VLC->generaltexth->capColors.size() ? VLC->generaltexth->capColors[color.getNum()] : boost::lexical_cast<std::string>(color));
 }
 
-// void PlayerState::getParents(TCNodes &out, const CBonusSystemNode *root /*= NULL*/) const
-// {
-// 	return; //no loops possible
-// }
-//
-// void PlayerState::getBonuses(BonusList &out, const CSelector &selector, const CBonusSystemNode *root /*= NULL*/) const
-// {
-// 	for (std::vector<CGHeroInstance *>::const_iterator it = heroes.begin(); it != heroes.end(); it++)
-// 	{
-// 		if (*it != root)
-// 			(*it)->getBonuses(out, selector, this);
-// 	}
-// }
-
 InfoAboutArmy::InfoAboutArmy():
     owner(PlayerColor::NEUTRAL)
 {}

+ 0 - 4
lib/CGameState.h

@@ -181,10 +181,6 @@ public:
 	PlayerState();
 	std::string nodeName() const OVERRIDE;
 
-	//override
-	//void getParents(TCNodes &out, const CBonusSystemNode *root = NULL) const;
-	//void getBonuses(BonusList &out, const CSelector &selector, const CBonusSystemNode *root = NULL) const;
-
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & color & human & currentSelection & team & resources & status;

+ 2 - 0
lib/CMakeLists.txt

@@ -26,6 +26,7 @@ set(lib_SRCS
 		BattleState.cpp
 		CArtHandler.cpp
 		CBattleCallback.cpp
+		CBonusTypeHandler.cpp
 		CBuildingHandler.cpp
 		CConfigHandler.cpp
 		CConsoleHandler.cpp
@@ -67,6 +68,7 @@ set(lib_HEADERS
 		AI_Base.h
 		CondSh.h
 		ConstTransitivePtr.h
+		CBonusTypeHandler.h
 		CRandomGenerator.h
 		CScriptingModule.h
 		CStopWatch.h

+ 1 - 1
lib/CModHandler.cpp

@@ -13,7 +13,7 @@
 #include "StringConstants.h"
 
 /*
- * CModHandler.h, part of VCMI engine
+ * CModHandler.cpp, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *

+ 1 - 1
lib/CObjectHandler.cpp

@@ -198,7 +198,7 @@ static void readBankLevel(const JsonNode &level, BankConfig &bc)
 	bc.easiest = level["easiest"].Float();
 }
 
-void CObjectHandler::loadObjects()
+void CObjectHandler::load()
 {
 	tlog5 << "\t\tReading cregens \n";
 

+ 2 - 6
lib/CObjectHandler.h

@@ -353,9 +353,6 @@ public:
 		//visitied town pointer will be restored by map serialization method
 	}
 	//////////////////////////////////////////////////////////////////////////
-// 	void getParents(TCNodes &out, const CBonusSystemNode *root = NULL) const;
-// 	void getBonuses(BonusList &out, const CSelector &selector, const CBonusSystemNode *root = NULL) const;
-	//////////////////////////////////////////////////////////////////////////
 	int3 getSightCenter() const; //"center" tile from which the sight distance is calculated
 	int getSightRadious() const; //sight distance (should be used if player-owned structure)
 	//////////////////////////////////////////////////////////////////////////
@@ -622,8 +619,7 @@ public:
 	void setVisitingHero(CGHeroInstance *h);
 	void setGarrisonedHero(CGHeroInstance *h);
 	const CArmedInstance *getUpperArmy() const; //garrisoned hero if present or the town itself
-// 	void getParents(TCNodes &out, const CBonusSystemNode *root = NULL) const;
-// 	void getBonuses(BonusList &out, const CSelector &selector, const CBonusSystemNode *root = NULL) const;
+
 	//////////////////////////////////////////////////////////////////////////
 
 	ui8 getPassableness() const; //bitmap - if the bit is set the corresponding player can pass through the visitable tiles of object, even if it's blockvis; if not set - default properties from definfo are used
@@ -1398,7 +1394,7 @@ public:
 	std::map <ui32, std::string> creBanksNames; //[crebank index] -> name of this creature bank
 	std::vector<ui32> resVals; //default values of resources in gold
 
-	void loadObjects();
+	void load();
 	int bankObjToIndex (const CGObjectInstance * obj);
 
 	template <typename Handler> void serialize(Handler &h, const int version)

+ 1 - 1
lib/CSpellHandler.cpp

@@ -397,7 +397,7 @@ CSpell * CSpellHandler::loadSpell(CLegacyConfigParser & parser, const SpellID id
 	return spell;
 }
 
-void CSpellHandler::loadSpells()
+void CSpellHandler::load()
 {
 	CLegacyConfigParser parser("DATA/SPTRAITS.TXT");
 

+ 1 - 1
lib/CSpellHandler.h

@@ -168,7 +168,7 @@ public:
 	CSpellHandler();
 	std::vector< ConstTransitivePtr<CSpell> > spells;
 
-	void loadSpells();
+	void load();
 
 	/**
 	 * Gets a list of default allowed spells. OH3 spells are all allowed by default.

+ 25 - 0
lib/IBonusTypeHandler.h

@@ -0,0 +1,25 @@
+/*
+ * IBonusTypeHandler.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 IBonusBearer;
+struct Bonus;
+
+///High level interface for BonusTypeHandler
+
+class IBonusTypeHandler
+{
+public:
+	virtual ~IBonusTypeHandler(){};
+
+	virtual std::string bonusToString(Bonus *bonus, const IBonusBearer *bearer, bool description) const = 0;
+	virtual std::string bonusToGraphics(Bonus *bonus) const = 0;
+};

+ 1 - 0
lib/IGameCallback.cpp

@@ -18,6 +18,7 @@
 #include "GameConstants.h"
 #include "CModHandler.h"
 #include "CDefObjInfoHandler.h"
+#include "CBonusTypeHandler.h"
 
 #include "Connection.h"
 

+ 53 - 46
lib/VCMI_Lib.cpp

@@ -1,8 +1,18 @@
+/*
+ * VCMI_Lib.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 "VCMI_Lib.h"
 
-
 #include "CArtHandler.h"
+#include "CBonusTypeHandler.h"
 #include "CCreatureHandler.h"
 #include "CDefObjInfoHandler.h"
 #include "CHeroHandler.h"
@@ -17,15 +27,6 @@
 #include "VCMIDirs.h"
 #include "Filesystem/CResourceLoader.h"
 
-/*
- * VCMI_Lib.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
- *
- */
 
 LibClasses * VLC = NULL;
 
@@ -50,6 +51,11 @@ DLL_LINKAGE void loadDLLClasses()
 	HANDLE_EXCEPTION;
 }
 
+const IBonusTypeHandler * LibClasses::getBth() const
+{
+	return bth;
+}
+
 void LibClasses::loadFilesystem()
 {
 	CStopWatch totalTime;
@@ -71,41 +77,40 @@ void LibClasses::loadFilesystem()
 	tlog0<<"Basic initialization: "<<totalTime.getDiff()<<std::endl;
 }
 
-void LibClasses::init()
+static void logHandlerLoaded(const std::string& name, CStopWatch &timer)
 {
-	CStopWatch pomtime;
+   tlog0<<"\t" << name << " handler: "<<timer.getDiff()<<std::endl;
+};
 
-	generaltexth = new CGeneralTextHandler;
-	generaltexth->load();
-	tlog0<<"\tGeneral text handler: "<<pomtime.getDiff()<<std::endl;
+template <class Handler> void createHandler(Handler *&handler, const std::string &name, CStopWatch &timer)
+{
+	handler = new Handler();
+	handler->load();
+	logHandlerLoaded(name, timer);
+} 
 
-	heroh = new CHeroHandler;
-	heroh->load();
-	tlog0 <<"\tHero handler: "<<pomtime.getDiff()<<std::endl;
+void LibClasses::init()
+{
+	CStopWatch pomtime;
+	
+	createHandler(bth, "Bonus type", pomtime);
+	
+	createHandler(generaltexth, "General text", pomtime);
 
-	arth = new CArtHandler;
-	arth->loadArtifacts(false);
-	tlog0<<"\tArtifact handler: "<<pomtime.getDiff()<<std::endl;
+	createHandler(heroh, "Hero", pomtime);
 
-	creh = new CCreatureHandler();
-	creh->loadCreatures();
-	tlog0<<"\tCreature handler: "<<pomtime.getDiff()<<std::endl;
+	createHandler(arth, "Artifact", pomtime);
 
-	townh = new CTownHandler;
-	townh->load();
-	tlog0<<"\tTown handler: "<<pomtime.getDiff()<<std::endl;
+	createHandler(creh, "Creature", pomtime);
 
-	objh = new CObjectHandler;
-	objh->loadObjects();
-	tlog0<<"\tObject handler: "<<pomtime.getDiff()<<std::endl;
+	createHandler(townh, "Town", pomtime);
+	
+	createHandler(objh, "Object", pomtime);
+	
+	createHandler(dobjinfo, "Def information", pomtime);
 
-	dobjinfo = new CDefObjInfoHandler;
-	dobjinfo->load();
-	tlog0<<"\tDef information handler: "<<pomtime.getDiff()<<std::endl;
+	createHandler(spellh, "Spell", pomtime);
 
-	spellh = new CSpellHandler;
-	spellh->loadSpells();
-	tlog0<<"\tSpell handler: "<<pomtime.getDiff()<<std::endl;
 
 	modh->loadActiveMods();
 	modh->reload();
@@ -126,20 +131,22 @@ void LibClasses::clear()
 	delete dobjinfo;
 	delete spellh;
 	delete modh;
+	delete bth;
 	makeNull();
 }
 
 void LibClasses::makeNull()
 {
-	generaltexth = NULL;
-	heroh = NULL;
-	arth = NULL;
-	creh = NULL;
-	townh = NULL;
-	objh = NULL;
-	dobjinfo = NULL;
-	spellh = NULL;
-	modh = NULL;
+	generaltexth = nullptr;
+	heroh = nullptr;
+	arth = nullptr;
+	creh = nullptr;
+	townh = nullptr;
+	objh = nullptr;
+	dobjinfo = nullptr;
+	spellh = nullptr;
+	modh = nullptr;
+	bth = nullptr;
 }
 
 LibClasses::LibClasses()
@@ -152,7 +159,7 @@ void LibClasses::callWhenDeserializing()
 {
 	generaltexth = new CGeneralTextHandler;
 	generaltexth->load();
-	arth->loadArtifacts(true);
+	arth->load(true);
 	//modh->recreateHandlers();
 	//modh->loadConfigFromFile ("defaultMods"); //TODO: remember last saved config
 }

+ 9 - 1
lib/VCMI_Lib.h

@@ -20,14 +20,21 @@ class CDefObjInfoHandler;
 class CTownHandler;
 class CGeneralTextHandler;
 class CModHandler;
+class IBonusTypeHandler;
+class CBonusTypeHandler;
 
 /// Loads and constructs several handlers
 class DLL_LINKAGE LibClasses
 {
+	CBonusTypeHandler * bth;
+	
 	void callWhenDeserializing(); //should be called only by serialize !!!
 	void makeNull(); //sets all handler pointers to null
 public:
 	bool IS_AI_ENABLED; //VLC is the only object visible from both CMT and GeniusAI
+	
+	const IBonusTypeHandler * getBth() const;
+	
 	CArtHandler * arth;
 	CHeroHandler * heroh;
 	CCreatureHandler * creh;
@@ -49,7 +56,8 @@ public:
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & heroh & arth & creh & townh & objh & dobjinfo & spellh & modh & IS_AI_ENABLED;;
+		h & heroh & arth & creh & townh & objh & dobjinfo & spellh & modh & IS_AI_ENABLED;
+		h & bth;
 		if(!h.saving)
 		{
 			callWhenDeserializing();

+ 6 - 0
lib/VCMI_lib.cbp

@@ -13,6 +13,8 @@
 				<Option object_output="../obj/Debug/Lib" />
 				<Option type="3" />
 				<Option compiler="gcc" />
+				<Option host_application="D:/projects/vcmi/engine/VCMI_client.exe" />
+				<Option run_host_application_in_terminal="1" />
 				<Option createStaticLib="1" />
 				<Compiler>
 					<Add option="-O1" />
@@ -73,6 +75,8 @@
 		<Unit filename="CArtHandler.h" />
 		<Unit filename="CBattleCallback.cpp" />
 		<Unit filename="CBattleCallback.h" />
+		<Unit filename="CBonusTypeHandler.cpp" />
+		<Unit filename="CBonusTypeHandler.h" />
 		<Unit filename="CBuildingHandler.cpp" />
 		<Unit filename="CBuildingHandler.h" />
 		<Unit filename="CConfigHandler.cpp" />
@@ -136,6 +140,7 @@
 		<Unit filename="GameConstants.h" />
 		<Unit filename="HeroBonus.cpp" />
 		<Unit filename="HeroBonus.h" />
+		<Unit filename="IBonusTypeHandler.h" />
 		<Unit filename="IGameCallback.cpp" />
 		<Unit filename="IGameCallback.h" />
 		<Unit filename="IGameEventsReceiver.h" />
@@ -171,6 +176,7 @@
 		</Unit>
 		<Unit filename="StringConstants.h" />
 		<Unit filename="UnlockGuard.h" />
+		<Unit filename="VCMIDirs.cpp" />
 		<Unit filename="VCMIDirs.h" />
 		<Unit filename="VCMI_Lib.cpp" />
 		<Unit filename="VCMI_Lib.h" />