1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-06-27 00:41:08 +02:00

Implement shortcut for hero skill specialties

Should make changes to secondary skill bonuses less mod-breaking, and
reduce chances of incorrectly defined specialties in mods
This commit is contained in:
Ivan Savenko
2025-06-03 17:32:15 +03:00
parent 4fda657296
commit eb9a5bb71b
17 changed files with 170 additions and 382 deletions

View File

@ -10,16 +10,7 @@
{ "skill" : "archery", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"archery" : {
"type" : "PERCENTAGE_DAMAGE_BOOST",
"subtype" : "damageTypeRanged",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE",
"targetSourceType" : "SECONDARY_SKILL"
}
}
"secondary" : "archery"
}
},
"valeska":
@ -61,16 +52,7 @@
{ "skill" : "navigation", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"navigation" : {
"targetSourceType" : "SECONDARY_SKILL",
"subtype" : "heroMovementSea",
"type" : "MOVEMENT",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE"
}
}
"secondary" : "navigation"
}
},
"lordHaart":
@ -84,16 +66,7 @@
{ "skill" : "estates", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"estates" : {
"subtype" : "resource.gold",
"type" : "GENERATE_RESOURCE",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE",
"targetSourceType" : "SECONDARY_SKILL"
}
}
"secondary" : "estates"
}
},
"sorsha":
@ -150,16 +123,7 @@
{ "skill" : "firstAid", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"firstAid" : {
"subtype" : "spell.firstAid",
"type" : "SPECIFIC_SPELL_POWER",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE",
"targetSourceType" : "SECONDARY_SKILL"
}
}
"secondary" : "firstAid"
}
},
"adela":
@ -265,15 +229,7 @@
{ "skill" : "eagleEye", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"eagleEye" : {
"type" : "LEARN_BATTLE_SPELL_CHANCE",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE",
"targetSourceType" : "SECONDARY_SKILL"
}
}
"secondary" : "eagleEye"
}
},
"loynis":

View File

@ -85,16 +85,7 @@
{ "skill" : "tactics", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"logistics" : {
"targetSourceType" : "SECONDARY_SKILL",
"subtype" : "heroMovementLand",
"type" : "MOVEMENT",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE"
}
}
"secondary" : "logistics"
}
},
"synca":
@ -159,15 +150,7 @@
{ "skill" : "mysticism", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"mysticism" : {
"targetSourceType" : "SECONDARY_SKILL",
"type" : "MANA_REGENERATION",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE"
}
}
"secondary" : "mysticism"
}
},
"malekith":
@ -182,16 +165,7 @@
{ "skill" : "sorcery", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"sorcery" : {
"targetSourceType" : "SECONDARY_SKILL",
"type" : "SPELL_DAMAGE",
"subtype" : "spellSchool.any",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE"
}
}
"secondary" : "sorcery"
}
},
"jeddite":
@ -227,15 +201,7 @@
{ "skill" : "eagleEye", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"eagleEye" : {
"type" : "LEARN_BATTLE_SPELL_CHANCE",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE",
"targetSourceType" : "SECONDARY_SKILL"
}
}
"secondary" : "eagleEye"
}
},
"deemer":

View File

@ -66,16 +66,7 @@
{ "skill" : "armorer", "level": "advanced" }
],
"specialty" : {
"bonuses" : {
"armorer" : {
"type" : "GENERAL_DAMAGE_REDUCTION",
"subtype" : "damageTypeAll",
"targetSourceType" : "SECONDARY_SKILL",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE"
}
}
"secondary" : "armorer"
}
},
"alkin":
@ -166,15 +157,7 @@
{ "skill" : "mysticism", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"mysticism" : {
"targetSourceType" : "SECONDARY_SKILL",
"type" : "MANA_REGENERATION",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE"
}
}
"secondary" : "mysticism"
}
},
"voy":
@ -189,16 +172,7 @@
{ "skill" : "navigation", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"navigation" : {
"targetSourceType" : "SECONDARY_SKILL",
"subtype" : "heroMovementSea",
"type" : "MOVEMENT",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE"
}
}
"secondary" : "navigation"
}
},
"verdish":
@ -213,16 +187,7 @@
{ "skill" : "firstAid", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"firstAid" : {
"subtype" : "spell.firstAid",
"type" : "SPECIFIC_SPELL_POWER",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE",
"targetSourceType" : "SECONDARY_SKILL"
}
}
"secondary" : "firstAid"
}
},
"merist":
@ -258,16 +223,7 @@
{ "skill" : "sorcery", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"sorcery" : {
"targetSourceType" : "SECONDARY_SKILL",
"type" : "SPELL_DAMAGE",
"subtype" : "spellSchool.any",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE"
}
}
"secondary" : "sorcery"
}
},
"andra":
@ -282,15 +238,7 @@
{ "skill" : "intelligence", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"intelligence" : {
"targetSourceType" : "SECONDARY_SKILL",
"type" : "MANA_PER_KNOWLEDGE_PERCENTAGE",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE"
}
}
"secondary" : "intelligence"
}
},
"tiva":
@ -305,15 +253,7 @@
{ "skill" : "eagleEye", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"eagleEye" : {
"type" : "LEARN_BATTLE_SPELL_CHANCE",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE",
"targetSourceType" : "SECONDARY_SKILL"
}
}
"secondary" : "eagleEye"
}
}
}

View File

@ -126,15 +126,7 @@
{ "skill" : "intelligence", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"intelligence" : {
"targetSourceType" : "SECONDARY_SKILL",
"type" : "MANA_PER_KNOWLEDGE_PERCENTAGE",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE"
}
}
"secondary" : "intelligence"
}
},
"xyron":
@ -171,15 +163,7 @@
{ "skill" : "mysticism", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"mysticism" : {
"targetSourceType" : "SECONDARY_SKILL",
"type" : "MANA_REGENERATION",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE"
}
}
"secondary" : "mysticism"
}
},
"olema":
@ -257,16 +241,7 @@
{ "skill" : "sorcery", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"sorcery" : {
"targetSourceType" : "SECONDARY_SKILL",
"type" : "SPELL_DAMAGE",
"subtype" : "spellSchool.any",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE"
}
}
"secondary" : "sorcery"
}
},
"xarfax":

View File

@ -85,15 +85,7 @@
{ "skill" : "necromancy", "level": "advanced" }
],
"specialty" : {
"bonuses" : {
"necromancy" : {
"type" : "UNDEAD_RAISE_PERCENTAGE",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE",
"targetSourceType" : "SECONDARY_SKILL"
}
}
"secondary" : "necromancy"
}
},
"clavius":
@ -188,16 +180,7 @@
{ "skill" : "sorcery", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"sorcery" : {
"targetSourceType" : "SECONDARY_SKILL",
"type" : "SPELL_DAMAGE",
"subtype" : "spellSchool.any",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE"
}
}
"secondary" : "sorcery"
}
},
"nimbus":
@ -212,15 +195,7 @@
{ "skill" : "eagleEye", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"eagleEye" : {
"type" : "LEARN_BATTLE_SPELL_CHANCE",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE",
"targetSourceType" : "SECONDARY_SKILL"
}
}
"secondary" : "eagleEye"
}
},
"thant":
@ -277,15 +252,7 @@
{ "skill" : "necromancy", "level": "advanced" }
],
"specialty" : {
"bonuses" : {
"necromancy" : {
"type" : "UNDEAD_RAISE_PERCENTAGE",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE",
"targetSourceType" : "SECONDARY_SKILL"
}
}
"secondary" : "necromancy"
}
},
"nagash":

View File

@ -10,16 +10,7 @@
{ "skill" : "armorer", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"armorer" : {
"type" : "GENERAL_DAMAGE_REDUCTION",
"subtype" : "damageTypeAll",
"targetSourceType" : "SECONDARY_SKILL",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE"
}
}
"secondary" : "armorer"
}
},
"ufretin":
@ -79,15 +70,7 @@
{ "skill" : "resistance", "level": "advanced" }
],
"specialty" : {
"bonuses" : {
"resistance" : {
"type" : "MAGIC_RESISTANCE",
"targetSourceType" : "SECONDARY_SKILL",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE"
}
}
"secondary" : "resistance"
}
},
"ivor":
@ -129,16 +112,7 @@
{ "skill" : "logistics", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"logistics" : {
"targetSourceType" : "SECONDARY_SKILL",
"subtype" : "heroMovementLand",
"type" : "MOVEMENT",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE"
}
}
"secondary" : "logistics"
}
},
"coronius":
@ -196,15 +170,7 @@
{ "skill" : "intelligence", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"intelligence" : {
"targetSourceType" : "SECONDARY_SKILL",
"type" : "MANA_PER_KNOWLEDGE_PERCENTAGE",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE"
}
}
"secondary" : "intelligence"
}
},
"gem":
@ -219,16 +185,7 @@
{ "skill" : "firstAid", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"firstAid" : {
"subtype" : "spell.firstAid",
"type" : "SPECIFIC_SPELL_POWER",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE",
"targetSourceType" : "SECONDARY_SKILL"
}
}
"secondary" : "firstAid"
}
},
"malcom":
@ -243,15 +200,7 @@
{ "skill" : "eagleEye", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"eagleEye" : {
"type" : "LEARN_BATTLE_SPELL_CHANCE",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE",
"targetSourceType" : "SECONDARY_SKILL"
}
}
"secondary" : "eagleEye"
}
},
"melodia":

View File

@ -93,16 +93,7 @@
{ "skill" : "offence", "level": "advanced" }
],
"specialty" : {
"bonuses" : {
"offence" : {
"subtype" : "damageTypeMelee",
"type" : "PERCENTAGE_DAMAGE_BOOST",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE",
"targetSourceType" : "SECONDARY_SKILL"
}
}
"secondary" : "offence"
}
},
"tyraxor":
@ -131,16 +122,7 @@
{ "skill" : "sorcery", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"sorcery" : {
"targetSourceType" : "SECONDARY_SKILL",
"type" : "SPELL_DAMAGE",
"subtype" : "spellSchool.any",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE"
}
}
"secondary" : "sorcery"
}
},
"vey":
@ -170,16 +152,7 @@
{ "skill" : "logistics", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"logistics" : {
"targetSourceType" : "SECONDARY_SKILL",
"subtype" : "heroMovementLand",
"type" : "MOVEMENT",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE"
}
}
"secondary" : "logistics"
}
},
"terek":
@ -236,16 +209,7 @@
{ "skill" : "offence", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"offence" : {
"subtype" : "damageTypeMelee",
"type" : "PERCENTAGE_DAMAGE_BOOST",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE",
"targetSourceType" : "SECONDARY_SKILL"
}
}
"secondary" : "offence"
}
},
"oris":
@ -260,15 +224,7 @@
{ "skill" : "eagleEye", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"eagleEye" : {
"type" : "LEARN_BATTLE_SPELL_CHANCE",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE",
"targetSourceType" : "SECONDARY_SKILL"
}
}
"secondary" : "eagleEye"
}
},
"saurug":

View File

@ -55,16 +55,7 @@
{ "skill" : "armorer", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"armorer" : {
"type" : "GENERAL_DAMAGE_REDUCTION",
"subtype" : "damageTypeAll",
"targetSourceType" : "SECONDARY_SKILL",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE"
}
}
"secondary" : "armorer"
}
},
"torosar":
@ -167,15 +158,7 @@
{ "skill" : "mysticism", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"mysticism" : {
"targetSourceType" : "SECONDARY_SKILL",
"type" : "MANA_REGENERATION",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE"
}
}
"secondary" : "mysticism"
}
},
"serena":
@ -190,15 +173,7 @@
{ "skill" : "eagleEye", "level": "basic" }
],
"specialty" : {
"bonuses" : {
"eagleEye" : {
"type" : "LEARN_BATTLE_SPELL_CHANCE",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_TARGET_TYPE",
"targetSourceType" : "SECONDARY_SKILL"
}
}
"secondary" : "eagleEye"
}
},
"daremyth":

View File

@ -132,6 +132,10 @@
"creature" : {
"type" : "string",
"description" : "Shortcut for defining creature specialty, using standard H3 rules."
},
"secondary" : {
"type" : "string",
"description" : "Shortcut for defining secondary skill specialty, using standard H3 rules."
}
}
},

View File

@ -43,7 +43,7 @@
}
}
},
"required" : ["name", "basic", "advanced", "expert"],
"required" : ["name", "basic", "advanced", "expert", "specialty", "gainChance" ],
"properties" : {
"name" : {
"type" : "string",
@ -61,6 +61,11 @@
"type" : "boolean",
"description" : "This skill is minor obligatory (like H3 Magic school)"
},
"specialty" : {
"type" : "array",
"description" : "List of bonuses that are affected by hero specialty",
"items" : { "type" : "string" }
},
"gainChance" : {
"description" : "Chance for the skill to be offered on level-up (heroClass may override)",
"type" : "object",

View File

@ -1,6 +1,7 @@
{
"pathfinding" : {
"index" : 0,
"specialty" : [], // no generic specialty
"base" : {
"effects" : {
"main" : {
@ -27,6 +28,9 @@
},
"archery" : {
"index" : 1,
"specialty" : [
"main"
],
"base" : {
"effects" : {
"main" : {
@ -54,6 +58,9 @@
},
"logistics" : {
"index" : 2,
"specialty" : [
"main"
],
"base" : {
"effects" : {
"main" : {
@ -81,6 +88,9 @@
},
"scouting" : {
"index" : 3,
"specialty" : [ // NOTE: no such specialists in H3, hota uses different logic
"main"
],
"base" : {
"effects" : {
"main" : {
@ -107,6 +117,9 @@
},
"diplomacy" : {
"index" : 4,
"specialty" : [ // NOTE: no such specialists in H3
"surr"
],
"base" : {
"effects" : {
"main" : {
@ -141,6 +154,9 @@
},
"navigation" : {
"index" : 5,
"specialty" : [
"main"
],
"base" : {
"effects" : {
"main" : {
@ -169,6 +185,7 @@
},
"leadership" : {
"index" : 6,
"specialty" : [], // generic specialty not applicable
"base" : {
"effects" : {
"main" : {
@ -195,6 +212,7 @@
},
"wisdom" : {
"index" : 7,
"specialty" : [], // generic specialty not applicable
"obligatoryMajor" : true,
"base" : {
"effects" : {
@ -222,6 +240,9 @@
},
"mysticism" : {
"index" : 8,
"specialty" : [
"main"
],
"base" : {
"effects" : {
"main" : {
@ -248,6 +269,7 @@
},
"luck" : {
"index" : 9,
"specialty" : [], // generic specialty not applicable
"base" : {
"effects" : {
"main" : {
@ -274,6 +296,7 @@
},
"ballistics" : {
"index" : 10,
"specialty" : [], // generic specialty not applicable
"base" : {
"effects" : {
"main" : {
@ -307,6 +330,9 @@
},
"eagleEye" : {
"index" : 11,
"specialty" : [
"main"
],
"base" : {
"effects" : {
"main" : {
@ -340,6 +366,9 @@
},
"necromancy" : {
"index" : 12,
"specialty" : [
"main"
],
"base" : {
"effects" : {
"main" : {
@ -374,6 +403,9 @@
},
"estates" : {
"index" : 13,
"specialty" : [
"main"
],
"base" : {
"effects" : {
"main" : {
@ -401,6 +433,7 @@
},
"fireMagic" : {
"index" : 14,
"specialty" : [], // generic specialty not applicable
"obligatoryMinor" : true,
"base" : {
"effects" : {
@ -429,6 +462,7 @@
},
"airMagic" : {
"index" : 15,
"specialty" : [], // generic specialty not applicable
"obligatoryMinor" : true,
"base" : {
"effects" : {
@ -457,6 +491,7 @@
},
"waterMagic" : {
"index" : 16,
"specialty" : [], // generic specialty not applicable
"obligatoryMinor" : true,
"base" : {
"effects" : {
@ -485,6 +520,7 @@
},
"earthMagic" : {
"index" : 17,
"specialty" : [], // generic specialty not applicable
"obligatoryMinor" : true,
"base" : {
"effects" : {
@ -513,6 +549,7 @@
},
"scholar" : {
"index" : 18,
"specialty" : [], // generic specialty not applicable
"base" : {
"effects" : {
"main" : {
@ -539,6 +576,7 @@
},
"tactics" : {
"index" : 19,
"specialty" : [], // generic specialty not applicable
"base" : {
"effects" : {
"main" : {
@ -572,6 +610,7 @@
},
"artillery" : {
"index" : 20,
"specialty" : [], // generic specialty not applicable, H3 heroes specialize in ballista creature instead
"base" : {
"effects" : {
"main" : {
@ -625,6 +664,9 @@
},
"learning" : {
"index" : 21,
"specialty" : [
"main"
],
"base" : {
"effects" : {
"main" : {
@ -651,6 +693,9 @@
},
"offence" : {
"index" : 22,
"specialty" : [
"main"
],
"base" : {
"effects" : {
"main" : {
@ -678,6 +723,9 @@
},
"armorer" : {
"index" : 23,
"specialty" : [
"main"
],
"base" : {
"effects" : {
"main" : {
@ -705,6 +753,9 @@
},
"intelligence" : {
"index" : 24,
"specialty" : [
"main"
],
"base" : {
"effects" : {
"main" : {
@ -731,6 +782,9 @@
},
"sorcery" : {
"index" : 25,
"specialty" : [
"main"
],
"base" : {
"effects" : {
"main" : {
@ -758,6 +812,9 @@
},
"resistance" : {
"index" : 26,
"specialty" : [
"main"
],
"base" : {
"effects" : {
"main" : {
@ -784,6 +841,9 @@
},
"firstAid" : {
"index" : 27,
"specialty" : [
"main"
],
"base" : {
"effects" : {
"main" : {

View File

@ -129,8 +129,11 @@ In order to make functional hero you also need:
"someBonus" : {Bonus Format},
"anotherOne" : {Bonus Format}
},
// Optional. Shortcut for defining creature specialty, using standard H3 rules
"creature" : "griffin"
// Shortcut for defining creature specialty, using standard H3 rules
"creature" : "griffin",
// Shortcut for defining specialty in secondary skill, using standard H3 rules
"secondary" : "offence"
}
}
```

View File

@ -26,6 +26,11 @@
"advanced": {Skill level format},
"expert": {Skill level format},
// Names of bonuses of the skill that are affected by default secondary skill specialty of a hero
"specialty" : [
"main"
],
// Chance for the skill to be offered on level-up (heroClass may override)
"gainChance" : {
// Chance for hero classes with might affinity

View File

@ -14,16 +14,14 @@
#include "CSkillHandler.h"
#include "bonuses/Updaters.h"
#include "constants/StringConstants.h"
#include "filesystem/Filesystem.h"
#include "json/JsonBonus.h"
#include "json/JsonUtils.h"
#include "modding/IdentifierStorage.h"
#include "modding/ModUtility.h"
#include "modding/ModScope.h"
#include "texts/CGeneralTextHandler.h"
#include "texts/CLegacyConfigParser.h"
#include "texts/TextOperations.h"
#include "GameLibrary.h"
VCMI_LIB_NAMESPACE_BEGIN
@ -253,6 +251,23 @@ std::shared_ptr<CSkill> CSkillHandler::loadFromJson(const std::string & scope, c
skillAtLevel.iconMedium = levelNode["images"]["medium"].String();
skillAtLevel.iconLarge = levelNode["images"]["large"].String();
}
for(const auto & b : json["specialty"].Vector())
{
const auto & bonusNode = json["basic"]["effects"][b.String()];
if (bonusNode.isStruct())
{
auto bonus = JsonUtils::parseBonus(bonusNode);
bonus->val = 5; // default H3 value, hardcoded for now
bonus->updater = std::make_shared<TimesHeroLevelUpdater>();
bonus->valType = BonusValueType::PERCENT_TO_TARGET_TYPE;
bonus->targetSourceType = BonusSource::SECONDARY_SKILL;
skill->specialtyTargetBonuses.push_back(bonus);
}
else
logMod->warn("Failed to load speciality bonus '%s' for skill '%s'", b.String(), identifier);
}
logMod->debug("loaded secondary skill %s(%d)", identifier, skill->id.getNum());
return skill;

View File

@ -71,6 +71,9 @@ public:
std::array<si32, 2> gainChance; // gainChance[0/1] = default gain chance on level-up for might/magic heroes
/// Bonuses that should be given to hero that specializes in this skill
std::vector<std::shared_ptr<const Bonus>> specialtyTargetBonuses;
void updateFrom(const JsonNode & data);
void serializeJson(JsonSerializeFormat & handler);

View File

@ -21,6 +21,7 @@
#include "../../json/JsonBonus.h"
#include "../../json/JsonUtils.h"
#include "../../modding/IdentifierStorage.h"
#include "../../CSkillHandler.h"
#include "../../texts/CGeneralTextHandler.h"
#include "../../texts/CLegacyConfigParser.h"
@ -140,7 +141,7 @@ void CHeroHandler::loadHeroSkills(CHero * hero, const JsonNode & node) const
}
/// creates standard H3 hero specialty for creatures
static std::vector<std::shared_ptr<Bonus>> createCreatureSpecialty(CreatureID cid)
std::vector<std::shared_ptr<Bonus>> CHeroHandler::createCreatureSpecialty(CreatureID cid) const
{
std::vector<std::shared_ptr<Bonus>> result;
@ -174,6 +175,19 @@ static std::vector<std::shared_ptr<Bonus>> createCreatureSpecialty(CreatureID ci
bonus->updater.reset(new GrowsWithLevelUpdater(specCreature.getDefense(false), stepSize));
result.push_back(bonus);
}
return result;
}
std::vector<std::shared_ptr<Bonus>> CHeroHandler::createSecondarySkillSpecialty(SecondarySkill skillID) const
{
std::vector<std::shared_ptr<Bonus>> result;
const auto & skillPtr = LIBRARY->skillh->objects[skillID.getNum()];
if (skillPtr->specialtyTargetBonuses.empty())
logMod->warn("Skill '%s' does not supports generic specialties!", skillPtr->getJsonKey());
for (const auto & bonus : skillPtr->specialtyTargetBonuses)
result.push_back(std::make_shared<Bonus>(*bonus));
return result;
}
@ -201,15 +215,7 @@ void CHeroHandler::beforeValidate(JsonNode & object)
}
}
void CHeroHandler::afterLoadFinalization()
{
for(const auto & functor : callAfterLoadFinalization)
functor();
callAfterLoadFinalization.clear();
}
void CHeroHandler::loadHeroSpecialty(CHero * hero, const JsonNode & node)
void CHeroHandler::loadHeroSpecialty(CHero * hero, const JsonNode & node) const
{
auto prepSpec = [=](std::shared_ptr<Bonus> bonus)
{
@ -230,18 +236,21 @@ void CHeroHandler::loadHeroSpecialty(CHero * hero, const JsonNode & node)
//creature specialty - alias for simplicity
if(!specialtyNode["creature"].isNull())
{
JsonNode creatureNode = specialtyNode["creature"];
std::function<void()> specialtyLoader = [creatureNode, hero, prepSpec]
LIBRARY->identifiers()->requestIdentifier("creature", specialtyNode["creature"], [this, hero, prepSpec](si32 creature)
{
LIBRARY->identifiers()->requestIdentifier("creature", creatureNode, [hero, prepSpec](si32 creature)
{
for (const auto & bonus : createCreatureSpecialty(CreatureID(creature)))
hero->specialty.push_back(prepSpec(bonus));
});
};
for (const auto & bonus : createCreatureSpecialty(CreatureID(creature)))
hero->specialty.push_back(prepSpec(bonus));
});
}
callAfterLoadFinalization.push_back(specialtyLoader);
//secondary skill specialty - alias for simplicity
if(!specialtyNode["secondary"].isNull())
{
LIBRARY->identifiers()->requestIdentifier("secondarySkill", specialtyNode["secondary"], [this, hero, prepSpec](si32 creature)
{
for (const auto & bonus : createSecondarySkillSpecialty(SecondarySkill(creature)))
hero->specialty.push_back(prepSpec(bonus));
});
}
for(const auto & keyValue : specialtyNode["bonuses"].Struct())

View File

@ -28,11 +28,12 @@ class DLL_LINKAGE CHeroHandler : public CHandlerBase<HeroTypeID, HeroType, CHero
/// helpers for loading to avoid huge load functions
void loadHeroArmy(CHero * hero, const JsonNode & node) const;
void loadHeroSkills(CHero * hero, const JsonNode & node) const;
void loadHeroSpecialty(CHero * hero, const JsonNode & node);
void loadHeroSpecialty(CHero * hero, const JsonNode & node) const;
void loadExperience();
std::vector<std::function<void()>> callAfterLoadFinalization;
std::vector<std::shared_ptr<Bonus>> createCreatureSpecialty(CreatureID cid) const;
std::vector<std::shared_ptr<Bonus>> createSecondarySkillSpecialty(SecondarySkill skillID) const;
public:
ui32 level(TExpType experience) const; //calculates level corresponding to given experience amount
@ -44,7 +45,6 @@ public:
void beforeValidate(JsonNode & object) override;
void loadObject(std::string scope, std::string name, const JsonNode & data) override;
void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override;
void afterLoadFinalization() override;
CHeroHandler();
~CHeroHandler();