Browse Source

added support for new specialty json format; old format is converted to bonuses with updaters

Henning Koehler 8 years ago
parent
commit
3e0022be27

+ 21 - 12
config/schemas/hero.json

@@ -118,20 +118,29 @@
 			"type":"array",
 			"description": "Description of hero specialty using bonus system",	
 			"items": {
-				"type":"object",
-				"additionalProperties" : false,
-				"required" : [ "bonuses" ],
-				"properties":{
-					"growsWithLevel" : {
-						"type" : "boolean",
-						"description" : "Specialty growth with level, so far only SECONDARY_SKILL_PREMY and PRIMATY SKILL with creature limiter can grow"
+				"oneOf" : [
+					{
+						"type" : "object",
+						"additionalProperties" : false,
+						"required" : [ "bonuses" ],
+						"properties" : {
+							"growsWithLevel" : {
+								"type" : "boolean",
+								"description" : "Specialty growth with level. Deprecated, use bonuses with updaters instead."
+							},
+							"bonuses" : {
+								"type" : "array",
+								"description" : "List of bonuses",
+								"items" : { "$ref" : "vcmi:bonus" }
+							}
+						}
 					},
-					"bonuses": {
-						"type":"array",
-						"description": "List of bonuses",
-						"items": { "$ref" : "vcmi:bonus" }
+					{
+						"type" : "object",
+						"description" : "List of bonuses",
+						"items" : { "$ref" : "vcmi:bonus" }
 					}
-				}
+				]
 			}
 		},
 		"spellbook": {

+ 172 - 8
lib/CHeroHandler.cpp

@@ -379,8 +379,169 @@ void CHeroHandler::loadHeroSkills(CHero * hero, const JsonNode & node)
 	}
 }
 
+// convert deprecated format
+std::vector<std::shared_ptr<Bonus>> SpecialtyInfoToBonuses(const SSpecialtyInfo & spec, int sid)
+{
+	std::vector<std::shared_ptr<Bonus>> result;
+
+	std::shared_ptr<Bonus> bonus = std::make_shared<Bonus>();
+	bonus->duration = Bonus::PERMANENT;
+	bonus->source = Bonus::HERO_SPECIAL;
+	bonus->sid = sid;
+	bonus->val = spec.val;
+
+	switch (spec.type)
+	{
+		case 1: //creature specialty
+			{
+				const CCreature &specCreature = *VLC->creh->creatures[spec.additionalinfo]; //creature in which we have specialty
+				bonus->limiter.reset(new CCreatureTypeLimiter(specCreature, true));
+
+				bonus->type = Bonus::STACKS_SPEED;
+				bonus->valType = Bonus::ADDITIVE_VALUE;
+				bonus->val = 1;
+				result.push_back(bonus);
+
+				bonus = std::make_shared<Bonus>(*bonus);
+				bonus->type = Bonus::PRIMARY_SKILL;
+				int stepSize = specCreature.level ? specCreature.level : 5;
+
+				bonus->subtype = PrimarySkill::ATTACK;
+				bonus->updater.reset(new ScalingUpdater(specCreature.getAttack(false), stepSize));
+				result.push_back(bonus);
+
+				bonus = std::make_shared<Bonus>(*bonus);
+				bonus->subtype = PrimarySkill::DEFENSE;
+				bonus->updater.reset(new ScalingUpdater(specCreature.getDefence(false), stepSize));
+				result.push_back(bonus);
+			}
+			break;
+		case 2: //secondary skill
+			bonus->type = Bonus::SECONDARY_SKILL_PREMY;
+			bonus->valType = Bonus::PERCENT_TO_BASE;
+			bonus->subtype = spec.subtype;
+			bonus->updater.reset(new ScalingUpdater(spec.val * 20));
+			result.push_back(bonus);
+			break;
+		case 3: //spell damage bonus, level dependent but calculated elsewhere
+			bonus->type = Bonus::SPECIAL_SPELL_LEV;
+			bonus->subtype = spec.subtype;
+			result.push_back(bonus);
+			break;
+		case 4: //creature stat boost
+			switch (spec.subtype)
+			{
+				case 1:
+					bonus->type = Bonus::PRIMARY_SKILL;
+					bonus->subtype = PrimarySkill::ATTACK;
+					break;
+				case 2:
+					bonus->type = Bonus::PRIMARY_SKILL;
+					bonus->subtype = PrimarySkill::DEFENSE;
+					break;
+				case 3:
+					bonus->type = Bonus::CREATURE_DAMAGE;
+					bonus->subtype = 0; //both min and max
+					break;
+				case 4:
+					bonus->type = Bonus::STACK_HEALTH;
+					break;
+				case 5:
+					bonus->type = Bonus::STACKS_SPEED;
+					break;
+				default:
+					logMod->warn("Unknown subtype for specialty 4");
+					return result;
+			}
+			bonus->valType = Bonus::ADDITIVE_VALUE;
+			bonus->limiter.reset(new CCreatureTypeLimiter(*VLC->creh->creatures[spec.additionalinfo], true));
+			result.push_back(bonus);
+			break;
+		case 5: //spell damage bonus in percent
+			bonus->type = Bonus::SPECIFIC_SPELL_DAMAGE;
+			bonus->valType = Bonus::BASE_NUMBER; //current spell system is screwed
+			bonus->subtype = spec.subtype; //spell id
+			result.push_back(bonus);
+			break;
+		case 6: //damage bonus for bless (Adela)
+			bonus->type = Bonus::SPECIAL_BLESS_DAMAGE;
+			bonus->subtype = spec.subtype; //spell id if you ever wanted to use it otherwise
+			bonus->additionalInfo = spec.additionalinfo; //damage factor
+			result.push_back(bonus);
+			break;
+		case 7: //maxed mastery for spell
+			bonus->type = Bonus::MAXED_SPELL;
+			bonus->subtype = spec.subtype; //spell id
+			result.push_back(bonus);
+			break;
+		case 8: //peculiar spells - enchantments
+			bonus->type = Bonus::SPECIAL_PECULIAR_ENCHANT;
+			bonus->subtype = spec.subtype; //spell id
+			bonus->additionalInfo = spec.additionalinfo; //0, 1 for Coronius
+			result.push_back(bonus);
+			break;
+		case 9: //upgrade creatures
+			{
+				const auto &creatures = VLC->creh->creatures;
+				bonus->type = Bonus::SPECIAL_UPGRADE;
+				bonus->subtype = spec.subtype; //base id
+				bonus->additionalInfo = spec.additionalinfo; //target id
+				result.push_back(bonus);
+				//propagate for regular upgrades of base creature
+				for(auto cre_id : creatures[spec.subtype]->upgrades)
+				{
+					bonus = std::make_shared<Bonus>(*bonus);
+					bonus->subtype = cre_id;
+					result.push_back(bonus);
+				}
+			}
+			break;
+		case 10: //resource generation
+			bonus->type = Bonus::GENERATE_RESOURCE;
+			bonus->subtype = spec.subtype;
+			result.push_back(bonus);
+			break;
+		case 11: //starting skill with mastery (Adrienne)
+			logMod->warn("Secondary skill mastery is no longer supported as specialty.");
+			break;
+		case 12: //army speed
+			bonus->type = Bonus::STACKS_SPEED;
+			result.push_back(bonus);
+			break;
+		case 13: //Dragon bonuses (Mutare)
+			bonus->type = Bonus::PRIMARY_SKILL;
+			bonus->valType = Bonus::ADDITIVE_VALUE;
+			switch (spec.subtype)
+			{
+				case 1:
+					bonus->subtype = PrimarySkill::ATTACK;
+					break;
+				case 2:
+					bonus->subtype = PrimarySkill::DEFENSE;
+					break;
+			}
+			bonus->limiter.reset(new HasAnotherBonusLimiter(Bonus::DRAGON_NATURE));
+			result.push_back(bonus);
+			break;
+		default:
+			logMod->warn("Unknown hero specialty %d", spec.type);
+			break;
+	}
+
+	return result;
+}
+
 void CHeroHandler::loadHeroSpecialty(CHero * hero, const JsonNode & node)
 {
+	int sid = hero->ID.getNum();
+	auto prepSpec = [=](std::shared_ptr<Bonus> bonus)
+	{
+		bonus->duration = Bonus::PERMANENT;
+		bonus->source = Bonus::HERO_SPECIAL;
+		bonus->sid = sid;
+		return bonus;
+	};
+
 	//deprecated, used only for original spciealties
 	for(const JsonNode &specialty : node["specialties"].Vector())
 	{
@@ -391,19 +552,22 @@ void CHeroHandler::loadHeroSpecialty(CHero * hero, const JsonNode & node)
 		spec.subtype = specialty["subtype"].Float();
 		spec.additionalinfo = specialty["info"].Float();
 
-		hero->spec.push_back(spec); //put a copy of dummy
+		for(std::shared_ptr<Bonus> bonus : SpecialtyInfoToBonuses(spec, sid))
+			hero->specialty.push_back(bonus);
 	}
 	//new format, using bonus system
-	for(const JsonNode &specialty : node["specialty"].Vector())
+	for(const JsonNode & specialty : node["specialty"].Vector())
 	{
-		SSpecialtyBonus hs;
-		hs.growsWithLevel = specialty["growsWithLevel"].Bool();
-		for (const JsonNode & bonus : specialty["bonuses"].Vector())
+		//deprecated new format
+		if(!specialty["bonuses"].isNull())
+		{
+			for (const JsonNode & bonus : specialty["bonuses"].Vector())
+				hero->specialty.push_back(prepSpec(JsonUtils::parseBonus(bonus)));
+		}
+		else //proper new format
 		{
-			auto b = JsonUtils::parseBonus(bonus);
-			hs.bonuses.push_back (b);
+			hero->specialty.push_back(prepSpec(JsonUtils::parseBonus(specialty)));
 		}
-		hero->specialty.push_back (hs); //now, how to get CGHeroInstance from it?
 	}
 }
 

+ 8 - 3
lib/CHeroHandler.h

@@ -71,8 +71,9 @@ public:
 
 	CHeroClass * heroClass;
 	std::vector<std::pair<SecondarySkill, ui8> > secSkillsInit; //initial secondary skills; first - ID of skill, second - level of skill (1 - basic, 2 - adv., 3 - expert)
-	std::vector<SSpecialtyInfo> spec;
-	std::vector<SSpecialtyBonus> specialty;
+	std::vector<SSpecialtyInfo> specDeprecated;
+	std::vector<SSpecialtyBonus> specialtyDeprecated;
+	BonusList specialty;
 	std::set<SpellID> spells;
 	bool haveSpellBook;
 	bool special; // hero is special and won't be placed in game (unless preset on map), e.g. campaign heroes
@@ -98,7 +99,8 @@ public:
 		h & initialArmy;
 		h & heroClass;
 		h & secSkillsInit;
-		h & spec;
+		//h & specDeprecated;
+		//h & specialtyDeprecated;
 		h & specialty;
 		h & spells;
 		h & haveSpellBook;
@@ -120,6 +122,9 @@ public:
 	}
 };
 
+// convert deprecated format
+std::vector<std::shared_ptr<Bonus>> SpecialtyInfoToBonuses(const SSpecialtyInfo & spec, int sid);
+
 class DLL_LINKAGE CHeroClass
 {
 public:

+ 7 - 1
lib/HeroBonus.cpp

@@ -827,6 +827,8 @@ void CBonusSystemNode::addNewBonus(const std::shared_ptr<Bonus>& b)
 	assert(!vstd::contains(exportedBonuses, b));
 	exportedBonuses.push_back(b);
 	exportBonus(b);
+	if(b->updater)
+		b->updater->update(*b, *this);
 	CBonusSystemNode::treeHasChanged();
 }
 
@@ -1563,7 +1565,11 @@ void LimiterList::add( TLimiterPtr limiter )
 	limiters.push_back(limiter);
 }
 
-bool ScalingUpdater::update(Bonus & b, const CBonusSystemNode & context)
+ScalingUpdater::ScalingUpdater(int valPer20, int stepSize) : valPer20(valPer20), stepSize(stepSize)
+{
+}
+
+bool ScalingUpdater::update(Bonus & b, const CBonusSystemNode & context) const
 {
 	if(context.getNodeType() == CBonusSystemNode::HERO)
 	{

+ 8 - 5
lib/HeroBonus.h

@@ -365,6 +365,7 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
 		h & effectRange;
 		h & limiter;
 		h & propagator;
+		h & updater;
 	}
 
 	template <typename Ptr>
@@ -709,7 +710,7 @@ public:
 	void popBonuses(const CSelector &s);
 	///updates count of remaining turns and removes outdated bonuses by selector
 	void reduceBonusDurations(const CSelector &s);
-	//run update decorators
+	//run updaters attached to bonuses
 	void updateBonuses();
 	virtual std::string bonusToString(const std::shared_ptr<Bonus>& bonus, bool description) const {return "";}; //description or bonus name
 	virtual std::string nodeName() const;
@@ -1014,7 +1015,7 @@ void BonusList::insert(const int position, InputIterator first, InputIterator la
 	changed();
 }
 
-// bonus decorators for updating bonuses based on events (e.g. hero gaining level)
+// observers for updating bonuses based on certain events (e.g. hero gaining level)
 
 class DLL_LINKAGE IUpdater
 {
@@ -1028,8 +1029,10 @@ public:
 
 struct DLL_LINKAGE ScalingUpdater : public IUpdater
 {
-	int valPer20 = 0;
-	int stepSize = 1;
+	int valPer20;
+	int stepSize;
+
+	ScalingUpdater(int valPer20, int stepSize = 1);
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
@@ -1038,5 +1041,5 @@ struct DLL_LINKAGE ScalingUpdater : public IUpdater
 		h & stepSize;
 	}
 
-	bool update(Bonus & b, const CBonusSystemNode & context);
+	bool update(Bonus & b, const CBonusSystemNode & context) const override;
 };

+ 16 - 243
lib/mapObjects/CGHeroInstance.cpp

@@ -496,9 +496,8 @@ void CGHeroInstance::SecondarySkillsInfo::resetWisdomCounter()
 void CGHeroInstance::initObj(CRandomGenerator & rand)
 {
 	blockVisit = true;
-	auto  hs = new HeroSpecial();
-	hs->setNodeType(CBonusSystemNode::SPECIALTY);
-	attachTo(hs); //do we ever need to detach it?
+	specialty.setNodeType(CBonusSystemNode::SPECIALTY);
+	attachTo(&specialty); //do we ever need to detach it?
 
 	if(!type)
 		initHero(rand); //TODO: set up everything for prison before specialties are configured
@@ -514,246 +513,24 @@ void CGHeroInstance::initObj(CRandomGenerator & rand)
 			appearance = customApp.get();
 	}
 
-	for(const auto &spec : type->spec) //TODO: unfity with bonus system
-	{
-		auto bonus = std::make_shared<Bonus>();
-		bonus->val = spec.val;
-		bonus->sid = id.getNum(); //from the hero, specialty has no unique id
-		bonus->duration = Bonus::PERMANENT;
-		bonus->source = Bonus::HERO_SPECIAL;
-		switch (spec.type)
-		{
-			case 1:// creature specialty
-				{
-					hs->growsWithLevel = true;
-
-					const CCreature &specCreature = *VLC->creh->creatures[spec.additionalinfo]; //creature in which we have specialty
-
-					//bonus->additionalInfo = spec.additionalinfo; //creature id, should not be used again - this works only with limiter
-					bonus->limiter.reset(new CCreatureTypeLimiter (specCreature, true)); //with upgrades
-					bonus->type = Bonus::PRIMARY_SKILL;
-					bonus->valType = Bonus::ADDITIVE_VALUE;
-
-					bonus->subtype = PrimarySkill::ATTACK;
-					hs->addNewBonus(bonus);
-
-					bonus = std::make_shared<Bonus>(*bonus);
-					bonus->subtype = PrimarySkill::DEFENSE;
-					hs->addNewBonus(bonus);
-					//values will be calculated later
-
-					bonus = std::make_shared<Bonus>(*bonus);
-					bonus->type = Bonus::STACKS_SPEED;
-					bonus->val = 1; //+1 speed
-					hs->addNewBonus(bonus);
-				}
-				break;
-			case 2://secondary skill
-				hs->growsWithLevel = true;
-				bonus->type = Bonus::SPECIAL_SECONDARY_SKILL; //needs to be recalculated with level, based on this value
-				bonus->valType = Bonus::BASE_NUMBER; // to receive nonzero value
-				bonus->subtype = spec.subtype; //skill id
-				bonus->val = spec.val; //value per level, in percent
-				hs->addNewBonus(bonus);
-				bonus = std::make_shared<Bonus>(*bonus);
-
-				switch (spec.additionalinfo)
-				{
-					case 0: //normal
-						bonus->valType = Bonus::PERCENT_TO_BASE;
-						break;
-					case 1: //when it's navigation or there's no 'base' at all
-						bonus->valType = Bonus::PERCENT_TO_ALL;
-						break;
-				}
-				bonus->type = Bonus::SECONDARY_SKILL_PREMY; //value will be calculated later
-				hs->addNewBonus(bonus);
-				break;
-			case 3://spell damage bonus, level dependent but calculated elsewhere
-				bonus->type = Bonus::SPECIAL_SPELL_LEV;
-				bonus->subtype = spec.subtype;
-				hs->addNewBonus(bonus);
-				break;
-			case 4://creature stat boost
-				switch (spec.subtype)
-				{
-					case 1://attack
-						bonus->type = Bonus::PRIMARY_SKILL;
-						bonus->subtype = PrimarySkill::ATTACK;
-						break;
-					case 2://defense
-						bonus->type = Bonus::PRIMARY_SKILL;
-						bonus->subtype = PrimarySkill::DEFENSE;
-						break;
-					case 3:
-						bonus->type = Bonus::CREATURE_DAMAGE;
-						bonus->subtype = 0; //both min and max
-						break;
-					case 4://hp
-						bonus->type = Bonus::STACK_HEALTH;
-						break;
-					case 5:
-						bonus->type = Bonus::STACKS_SPEED;
-						break;
-					default:
-						continue;
-				}
-				bonus->additionalInfo = spec.additionalinfo; //creature id
-				bonus->valType = Bonus::ADDITIVE_VALUE;
-				bonus->limiter.reset(new CCreatureTypeLimiter (*VLC->creh->creatures[spec.additionalinfo], true));
-				hs->addNewBonus(bonus);
-				break;
-			case 5://spell damage bonus in percent
-				bonus->type = Bonus::SPECIFIC_SPELL_DAMAGE;
-				bonus->valType = Bonus::BASE_NUMBER; // current spell system is screwed
-				bonus->subtype = spec.subtype; //spell id
-				hs->addNewBonus(bonus);
-				break;
-			case 6://damage bonus for bless (Adela)
-				bonus->type = Bonus::SPECIAL_BLESS_DAMAGE;
-				bonus->subtype = spec.subtype; //spell id if you ever wanted to use it otherwise
-				bonus->additionalInfo = spec.additionalinfo; //damage factor
-				hs->addNewBonus(bonus);
-				break;
-			case 7://maxed mastery for spell
-				bonus->type = Bonus::MAXED_SPELL;
-				bonus->subtype = spec.subtype; //spell i
-				hs->addNewBonus(bonus);
-				break;
-			case 8://peculiar spells - enchantments
-				bonus->type = Bonus::SPECIAL_PECULIAR_ENCHANT;
-				bonus->subtype = spec.subtype; //spell id
-				bonus->additionalInfo = spec.additionalinfo;//0, 1 for Coronius
-				hs->addNewBonus(bonus);
-				break;
-			case 9://upgrade creatures
-			{
-				const auto &creatures = VLC->creh->creatures;
-				bonus->type = Bonus::SPECIAL_UPGRADE;
-				bonus->subtype = spec.subtype; //base id
-				bonus->additionalInfo = spec.additionalinfo; //target id
-				hs->addNewBonus(bonus);
-				bonus = std::make_shared<Bonus>(*bonus);
-
-				for(auto cre_id : creatures[spec.subtype]->upgrades)
-				{
-					bonus->subtype = cre_id; //propagate for regular upgrades of base creature
-					hs->addNewBonus(bonus);
-					bonus = std::make_shared<Bonus>(*bonus);
-				}
-				break;
-			}
-			case 10://resource generation
-				bonus->type = Bonus::GENERATE_RESOURCE;
-				bonus->subtype = spec.subtype;
-				hs->addNewBonus(bonus);
-				break;
-			case 11://starting skill with mastery (Adrienne)
-				setSecSkillLevel(SecondarySkill(spec.val), spec.additionalinfo, true);
-				break;
-			case 12://army speed
-				bonus->type = Bonus::STACKS_SPEED;
-				hs->addNewBonus(bonus);
-				break;
-			case 13://Dragon bonuses (Mutare)
-				bonus->type = Bonus::PRIMARY_SKILL;
-				bonus->valType = Bonus::ADDITIVE_VALUE;
-				switch (spec.subtype)
-				{
-					case 1:
-						bonus->subtype = PrimarySkill::ATTACK;
-						break;
-					case 2:
-						bonus->subtype = PrimarySkill::DEFENSE;
-						break;
-				}
-				bonus->limiter.reset(new HasAnotherBonusLimiter(Bonus::DRAGON_NATURE));
-				hs->addNewBonus(bonus);
-				break;
-			default:
-				logGlobal->warn("Unexpected hero %s specialty %d", type->name, spec.type);
-				break;
-		}
-	}
-	specialty.push_back(hs); //will it work?
-
-	for (auto hs2 : type->specialty) //copy active (probably growing) bonuses from hero prootype to hero object
-	{
-		auto  hs = new HeroSpecial();
-		attachTo(hs); //do we ever need to detach it?
-
-		hs->setNodeType(CBonusSystemNode::SPECIALTY);
-		for (auto bonus : hs2.bonuses)
-		{
-			hs->addNewBonus (bonus);
-		}
-		hs->growsWithLevel = hs2.growsWithLevel;
-
-		specialty.push_back(hs); //will it work?
-	}
+	//copy active (probably growing) bonuses from hero prototype to hero object
+	for(std::shared_ptr<Bonus> b : type->specialty)
+		specialty.addNewBonus(b);
+	//dito for old-style bonuses -> compatibility for old savegames
+	for(SSpecialtyBonus & sb : type->specialtyDeprecated)
+		for(std::shared_ptr<Bonus> b : sb.bonuses)
+			specialty.addNewBonus(b);
+	for(SSpecialtyInfo & spec : type->specDeprecated)
+		for(std::shared_ptr<Bonus> b : SpecialtyInfoToBonuses(spec, type->ID.getNum()))
+			specialty.addNewBonus(b);
 
 	//initialize bonuses
 	recreateSecondarySkillsBonuses();
-	Updatespecialty();
+	updateBonuses();
 
 	mana = manaLimit(); //after all bonuses are taken into account, make sure this line is the last one
 	type->name = name;
 }
-void CGHeroInstance::Updatespecialty() //TODO: calculate special value of bonuses on-the-fly?
-{
-	for (auto hs : specialty)
-	{
-		if (hs->growsWithLevel)
-		{
-			//const auto &creatures = VLC->creh->creatures;
-
-			for(auto& b : hs->getBonusList())
-			{
-				switch (b->type)
-				{
-					case Bonus::SECONDARY_SKILL_PREMY:
-						b->val = (hs->valOfBonuses(Bonus::SPECIAL_SECONDARY_SKILL, b->subtype) * level);
-						break; //use only hero skills as bonuses to avoid feedback loop
-					case Bonus::PRIMARY_SKILL: //for creatures, that is
-					{
-						const CCreature * cre = nullptr;
-						int creLevel = 0;
-						if (auto creatureLimiter = std::dynamic_pointer_cast<CCreatureTypeLimiter>(b->limiter)) //TODO: more general eveluation of bonuses?
-						{
-							cre = creatureLimiter->creature;
-							creLevel = cre->level;
-							if (!creLevel)
-							{
-								creLevel = 5; //treat ballista as tier 5
-							}
-						}
-						else //no creature found, can't calculate value
-						{
-							logGlobal->warn("Primary skill specialty growth supported only with creature type limiters");
-							break;
-						}
-
-						double primSkillModifier = (int)(level / creLevel) / 20.0;
-						int param;
-						switch (b->subtype)
-						{
-							case PrimarySkill::ATTACK:
-								param = cre->getPrimSkillLevel(PrimarySkill::ATTACK);
-								break;
-							case PrimarySkill::DEFENSE:
-								param = cre->getPrimSkillLevel(PrimarySkill::DEFENSE);
-								break;
-							default:
-								continue;
-						}
-						b->val = ceil(param * (1 + primSkillModifier)) - param; //yep, overcomplicated but matches original
-						break;
-					}
-				}
-			}
-		}
-	}
-}
 
 void CGHeroInstance::recreateSecondarySkillsBonuses()
 {
@@ -1211,11 +988,7 @@ int CGHeroInstance::maxSpellLevel() const
 void CGHeroInstance::deserializationFix()
 {
 	artDeserializationFix(this);
-
-	for (auto hs : specialty)
-	{
-		attachTo (hs);
-	}
+	attachTo(&specialty);
 }
 
 CBonusSystemNode * CGHeroInstance::whereShouldBeAttached(CGameState *gs)
@@ -1468,8 +1241,8 @@ void CGHeroInstance::levelUp(std::vector<SecondarySkill> skills)
 		}
 	}
 
-	//specialty
-	Updatespecialty();
+	//specialty and other bonuses that scale with level
+	updateBonuses();
 }
 
 void CGHeroInstance::levelUpAutomatically(CRandomGenerator & rand)

+ 2 - 2
lib/mapObjects/CGHeroInstance.h

@@ -108,7 +108,8 @@ public:
 		}
 	};
 
-	std::vector<HeroSpecial*> specialty;
+	std::vector<HeroSpecial*> specialtyDeprecated;
+	CBonusSystemNode specialty;
 
 	struct DLL_LINKAGE SecondarySkillsInfo
 	{
@@ -215,7 +216,6 @@ public:
 	void pushPrimSkill(PrimarySkill::PrimarySkill which, int val);
 	ui8 maxlevelsToMagicSchool() const;
 	ui8 maxlevelsToWisdom() const;
-	void Updatespecialty();
 	void recreateSecondarySkillsBonuses();
 	void updateSkill(SecondarySkill which, int val);