1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Merge pull request #379 from henningkoehlernz/hero_specialty_scaling

Hero specialty scaling
This commit is contained in:
ArseniyShestakov 2018-02-28 15:03:24 +08:00 committed by GitHub
commit 7fd090786c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 2475 additions and 973 deletions

View File

@ -68,4 +68,4 @@ Piotr Wójcik aka Chocimier, <chocimier@tlen.pl>
* Various bug fixes
Henning Koehler, <henning.koehler.nz@gmail.com>
* skill modding
* skill modding, bonus updaters

View File

@ -27,6 +27,7 @@ MODS:
* Improve support for WoG commander artifacts and skill descriptions
* Added basic support for secondary skill modding
* Map object sounds can now be configured via json
* Added bonus updaters for hero specialties
SOUND:
* Fixed many mising or wrong pickup and visit sounds for map objects

View File

@ -369,6 +369,24 @@ namespace vstd
return std::find(c.begin(),c.end(),i);
}
//returns first key that maps to given value if present, returns success via found if provided
template <typename Key, typename T>
Key findKey(const std::map<Key, T> & map, const T & value, bool * found = nullptr)
{
for(auto iter = map.cbegin(); iter != map.cend(); iter++)
{
if(iter->second == value)
{
if(found)
*found = true;
return iter->first;
}
}
if(found)
*found = false;
return Key();
}
//removes element i from container c, returns false if c does not contain i
template <typename Container, typename Item>
typename Container::size_type operator-=(Container &c, const Item &i)

View File

@ -9,10 +9,17 @@
{ "skill" : "leadership", "level": "basic" },
{ "skill" : "archery", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 1, "info": 0 }
]
"specialty" : {
"bonuses" : {
"archery" : {
"subtype" : "skill.archery",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"valeska":
{
@ -24,10 +31,9 @@
{ "skill" : "leadership", "level": "basic" },
{ "skill" : "archery", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 2 }
]
"specialty" : {
"creature" : "archer"
}
},
"edric":
{
@ -39,10 +45,9 @@
{ "skill" : "leadership", "level": "basic" },
{ "skill" : "armorer", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 4 }
]
"specialty" : {
"creature" : "griffin"
}
},
"sylvia":
{
@ -54,10 +59,17 @@
{ "skill" : "leadership", "level": "basic" },
{ "skill" : "navigation", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 2, "subtype": 5, "info": 1 }
]
"specialty" : {
"bonuses" : {
"navigation" : {
"subtype" : "skill.navigation",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"lordHaart":
{
@ -70,10 +82,17 @@
{ "skill" : "leadership", "level": "basic" },
{ "skill" : "estates", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 13, "info": 0 }
]
"specialty" : {
"bonuses" : {
"estates" : {
"subtype" : "skill.estates",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"sorsha":
{
@ -85,10 +104,9 @@
{ "skill" : "leadership", "level": "basic" },
{ "skill" : "offence", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 6 }
]
"specialty" : {
"creature" : "swordsman"
}
},
"christian":
{
@ -100,10 +118,9 @@
{ "skill" : "leadership", "level": "basic" },
{ "skill" : "artillery", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 146 }
]
"specialty" : {
"creature" : "ballista"
}
},
"tyris":
{
@ -115,10 +132,9 @@
{ "skill" : "leadership", "level": "basic" },
{ "skill" : "tactics", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 10 }
]
"specialty" : {
"creature" : "cavalier"
}
},
"rion":
{
@ -131,10 +147,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "firstAid", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 27, "info": 0 }
]
"specialty" : {
"bonuses" : {
"firstAid" : {
"subtype" : "skill.firstAid",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"adela":
{
@ -147,10 +170,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "diplomacy", "level": "basic" }
],
"specialties":
[
{ "type":6, "val": 3, "subtype": 41, "info": 0 }
]
"specialty" : {
"bonuses" : {
"bless" : {
"addInfo" : 0,
"subtype" : "spell.bless",
"type" : "SPECIAL_BLESS_DAMAGE",
"updater" : "TIMES_HERO_LEVEL",
"val" : 3
}
}
}
},
"cuthbert":
{
@ -163,10 +193,15 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "estates", "level": "basic" }
],
"specialties":
[
{ "type":8, "val": 0, "subtype": 45, "info": 0 }
]
"specialty" : {
"bonuses" : {
"weakness" : {
"addInfo" : 0,
"subtype" : "spell.weakness",
"type" : "SPECIAL_PECULIAR_ENCHANT"
}
}
}
},
"adelaide":
{
@ -178,10 +213,16 @@
[
{ "skill" : "wisdom", "level": "advanced" }
],
"specialties":
[
{ "type":3, "val": 3, "subtype": 20, "info": 0 }
]
"specialty" : {
"bonuses" : {
"frostRing" : {
"subtype" : "spell.frostRing",
"type" : "SPECIAL_SPELL_LEV",
"updater" : "TIMES_HERO_LEVEL",
"val" : 3
}
}
}
},
"ingham":
{
@ -194,10 +235,9 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "mysticism", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 8 }
]
"specialty" : {
"creature" : "monk"
}
},
"sanya":
{
@ -210,10 +250,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "eagleEye", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 11, "info": 0 }
]
"specialty" : {
"bonuses" : {
"eagleEye" : {
"subtype" : "skill.eagleEye",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"loynis":
{
@ -226,10 +273,15 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "learning", "level": "basic" }
],
"specialties":
[
{ "type":8, "val": 0, "subtype": 48, "info": 0 }
]
"specialty" : {
"bonuses" : {
"prayer" : {
"addInfo" : 0,
"subtype" : "spell.prayer",
"type" : "SPECIAL_PECULIAR_ENCHANT"
}
}
}
},
"caitlin":
{
@ -242,9 +294,14 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "intelligence", "level": "basic" }
],
"specialties":
[
{ "type":10, "val": 350, "subtype": 6, "info": 0 }
]
"specialty" : {
"bonuses" : {
"gold" : {
"subtype" : "resource.gold",
"type" : "GENERATE_RESOURCE",
"val" : 350
}
}
}
}
}

View File

@ -9,11 +9,22 @@
{ "skill" : "artillery", "level": "basic" },
{ "skill" : "offence", "level": "basic" }
],
"specialties":
[
{ "type":4, "val": 3, "subtype": 1, "info": 120 },
{ "type":4, "val": 3, "subtype": 2, "info": 120 }
]
"specialty" : {
"base" : {
"limiters" : [
{
"parameters" : [ "psychicElemental", true ],
"type" : "CREATURE_TYPE_LIMITER"
}
],
"type" : "PRIMARY_SKILL",
"val" : 3
},
"bonuses" : {
"attack" : { "subtype" : "primSkill.attack" },
"defence" : { "subtype" : "primSkill.defence" }
}
}
},
"thunar":
{
@ -25,12 +36,32 @@
{ "skill" : "estates", "level": "basic" },
{ "skill" : "tactics", "level": "basic" }
],
"specialties":
[
{ "type":4, "val": 2, "subtype": 1, "info": 113 },
{ "type":4, "val": 1, "subtype": 2, "info": 113 },
{ "type":4, "val": 5, "subtype": 4, "info": 113 }
]
"specialty" : {
"base" : {
"limiters" : [
{
"parameters" : [ "earthElemental", true ],
"type" : "CREATURE_TYPE_LIMITER"
}
]
},
"bonuses" : {
"health" : {
"type" : "STACK_HEALTH",
"val" : 5
},
"attack" : {
"subtype" : "primSkill.attack",
"type" : "PRIMARY_SKILL",
"val" : 2
},
"defence" : {
"subtype" : "primSkill.defence",
"type" : "PRIMARY_SKILL",
"val" : 1
}
}
}
},
"ignissa":
{
@ -42,12 +73,33 @@
{ "skill" : "artillery", "level": "basic" },
{ "skill" : "offence", "level": "basic" }
],
"specialties":
[
{ "type":4, "val": 1, "subtype": 1, "info": 114 },
{ "type":4, "val": 2, "subtype": 1, "info": 114 },
{ "type":4, "val": 2, "subtype": 3, "info": 114 }
]
"specialty" : {
"base" : {
"limiters" : [
{
"parameters" : [ "fireElemental", true ],
"type" : "CREATURE_TYPE_LIMITER"
}
]
},
"bonuses" : {
"damage" : {
"subtype" : 0,
"type" : "CREATURE_DAMAGE",
"val" : 2
},
"attack" : {
"subtype" : "primSkill.attack",
"type" : "PRIMARY_SKILL",
"val" : 1
},
"defence" : {
"subtype" : "primSkill.defence",
"type" : "PRIMARY_SKILL",
"val" : 2
}
}
}
},
"lacus":
{
@ -58,10 +110,21 @@
[
{ "skill" : "tactics", "level": "advanced" }
],
"specialties":
[
{ "type":4, "val": 2, "subtype": 1, "info": 115 }
]
"specialty" : {
"bonuses" : {
"attack" : {
"limiters" : [
{
"parameters" : [ "waterElemental", true ],
"type" : "CREATURE_TYPE_LIMITER"
}
],
"subtype" : "primSkill.attack",
"type" : "PRIMARY_SKILL",
"val" : 2
}
}
}
},
"monere":
{
@ -73,11 +136,22 @@
{ "skill" : "logistics", "level": "basic" },
{ "skill" : "offence", "level": "basic" }
],
"specialties":
[
{ "type":4, "val": 3, "subtype": 1, "info": 120 },
{ "type":4, "val": 3, "subtype": 2, "info": 120 }
]
"specialty" : {
"base" : {
"limiters" : [
{
"parameters" : [ "psychicElemental", true ],
"type" : "CREATURE_TYPE_LIMITER"
}
],
"type" : "PRIMARY_SKILL",
"val" : 3
},
"bonuses" : {
"attack" : { "subtype" : "primSkill.attack" },
"defence" : { "subtype" : "primSkill.defence" }
}
}
},
"erdamon":
{
@ -89,12 +163,32 @@
{ "skill" : "estates", "level": "basic" },
{ "skill" : "tactics", "level": "basic" }
],
"specialties":
[
{ "type":4, "val": 2, "subtype": 1, "info": 113 },
{ "type":4, "val": 1, "subtype": 2, "info": 113 },
{ "type":4, "val": 5, "subtype": 4, "info": 113 }
]
"specialty" : {
"base" : {
"limiters" : [
{
"parameters" : [ "earthElemental", true ],
"type" : "CREATURE_TYPE_LIMITER"
}
]
},
"bonuses" : {
"health" : {
"type" : "STACK_HEALTH",
"val" : 5
},
"attack" : {
"subtype" : "primSkill.attack",
"type" : "PRIMARY_SKILL",
"val" : 2
},
"defence" : {
"subtype" : "primSkill.defence",
"type" : "PRIMARY_SKILL",
"val" : 1
}
}
}
},
"fiur":
{
@ -105,12 +199,33 @@
[
{ "skill" : "offence", "level": "advanced" }
],
"specialties":
[
{ "type":4, "val": 1, "subtype": 1, "info": 114 },
{ "type":4, "val": 2, "subtype": 1, "info": 114 },
{ "type":4, "val": 2, "subtype": 3, "info": 114 }
]
"specialty" : {
"base" : {
"limiters" : [
{
"parameters" : [ "fireElemental", true ],
"type" : "CREATURE_TYPE_LIMITER"
}
]
},
"bonuses" : {
"damage" : {
"subtype" : 0,
"type" : "CREATURE_DAMAGE",
"val" : 2
},
"attack" : {
"subtype" : "primSkill.attack",
"type" : "PRIMARY_SKILL",
"val" : 1
},
"defence" : {
"subtype" : "primSkill.defence",
"type" : "PRIMARY_SKILL",
"val" : 2
}
}
}
},
"kalt":
{
@ -122,10 +237,21 @@
{ "skill" : "tactics", "level": "basic" },
{ "skill" : "learning", "level": "basic" }
],
"specialties":
[
{ "type":4, "val": 2, "subtype": 1, "info": 115 }
]
"specialty" : {
"bonuses" : {
"attack" : {
"limiters" : [
{
"parameters" : [ "waterElemental", true ],
"type" : "CREATURE_TYPE_LIMITER"
}
],
"subtype" : "primSkill.attack",
"type" : "PRIMARY_SKILL",
"val" : 2
}
}
}
},
"luna":
{
@ -138,10 +264,16 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "fireMagic", "level": "basic" }
],
"specialties":
[
{ "type":5, "val": 100, "subtype": 13, "info": 0 }
]
"specialty" : {
"bonuses" : {
"fireWall" : {
"subtype" : "spell.fireWall",
"type" : "SPECIFIC_SPELL_DAMAGE",
"val" : 100,
"valueType" : "BASE_NUMBER"
}
}
}
},
"brissa":
{
@ -154,10 +286,15 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "airMagic", "level": "basic" }
],
"specialties":
[
{ "type":8, "val": 0, "subtype": 53, "info": 0 }
]
"specialty" : {
"bonuses" : {
"haste" : {
"addInfo" : 0,
"subtype" : "spell.haste",
"type" : "SPECIAL_PECULIAR_ENCHANT"
}
}
}
},
"ciele":
{
@ -170,10 +307,16 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "waterMagic", "level": "basic" }
],
"specialties":
[
{ "type":5, "val": 50, "subtype": 15, "info": 0 }
]
"specialty" : {
"bonuses" : {
"magicArrow" : {
"subtype" : "spell.magicArrow",
"type" : "SPECIFIC_SPELL_DAMAGE",
"val" : 50,
"valueType" : "BASE_NUMBER"
}
}
}
},
"labetha":
{
@ -186,10 +329,15 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "earthMagic", "level": "basic" }
],
"specialties":
[
{ "type":8, "val": 0, "subtype": 46, "info": 0 }
]
"specialty" : {
"bonuses" : {
"stoneSkin" : {
"addInfo" : 0,
"subtype" : "spell.stoneSkin",
"type" : "SPECIAL_PECULIAR_ENCHANT"
}
}
}
},
"inteus":
{
@ -202,10 +350,15 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "fireMagic", "level": "basic" }
],
"specialties":
[
{ "type":8, "val": 0, "subtype": 43, "info": 0 }
]
"specialty" : {
"bonuses" : {
"bloodlust" : {
"addInfo" : 0,
"subtype" : "spell.bloodlust",
"type" : "SPECIAL_PECULIAR_ENCHANT"
}
}
}
},
"aenain":
{
@ -218,10 +371,15 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "airMagic", "level": "basic" }
],
"specialties":
[
{ "type":8, "val": 0, "subtype": 47, "info": 0 }
]
"specialty" : {
"bonuses" : {
"disruptingRay" : {
"addInfo" : 0,
"subtype" : "spell.disruptingRay",
"type" : "SPECIAL_PECULIAR_ENCHANT"
}
}
}
},
"gelare":
{
@ -234,10 +392,15 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "waterMagic", "level": "basic" }
],
"specialties":
[
{ "type":10, "val": 350, "subtype": 6, "info": 0 }
]
"specialty" : {
"bonuses" : {
"gold" : {
"subtype" : "resource.gold",
"type" : "GENERATE_RESOURCE",
"val" : 350
}
}
}
},
"grindan":
{
@ -250,9 +413,14 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "earthMagic", "level": "basic" }
],
"specialties":
[
{ "type":10, "val": 350, "subtype": 6, "info": 0 }
]
"specialty" : {
"bonuses" : {
"gold" : {
"subtype" : "resource.gold",
"type" : "GENERATE_RESOURCE",
"val" : 350
}
}
}
}
}

View File

@ -9,10 +9,9 @@
{ "skill" : "scouting", "level": "basic" },
{ "skill" : "leadership", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 72 }
]
"specialty" : {
"creature" : "harpy"
}
},
"arlach":
{
@ -24,10 +23,9 @@
{ "skill" : "artillery", "level": "basic" },
{ "skill" : "offence", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 146 }
]
"specialty" : {
"creature" : "ballista"
}
},
"dace":
{
@ -39,10 +37,9 @@
{ "skill" : "tactics", "level": "basic" },
{ "skill" : "offence", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 78 }
]
"specialty" : {
"creature" : "minotaur"
}
},
"ajit":
{
@ -54,10 +51,9 @@
{ "skill" : "leadership", "level": "basic" },
{ "skill" : "resistance", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 74 }
]
"specialty" : {
"creature" : "beholder"
}
},
"damacon":
{
@ -68,10 +64,15 @@
[
{ "skill" : "offence", "level": "advanced" }
],
"specialties":
[
{ "type":10, "val": 350, "subtype": 6, "info": 0 }
]
"specialty" : {
"bonuses" : {
"gold" : {
"subtype" : "resource.gold",
"type" : "GENERATE_RESOURCE",
"val" : 350
}
}
}
},
"gunnar":
{
@ -83,10 +84,17 @@
{ "skill" : "logistics", "level": "basic" },
{ "skill" : "tactics", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 2, "info": 0 }
]
"specialty" : {
"bonuses" : {
"logistics" : {
"subtype" : "skill.logistics",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"synca":
{
@ -98,10 +106,9 @@
{ "skill" : "leadership", "level": "basic" },
{ "skill" : "scholar", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 80 }
]
"specialty" : {
"creature" : "manticore"
}
},
"shakti":
{
@ -113,10 +120,9 @@
{ "skill" : "tactics", "level": "basic" },
{ "skill" : "offence", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 70 }
]
"specialty" : {
"creature" : "troglodyte"
}
},
"alamar":
{
@ -129,10 +135,16 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "scholar", "level": "basic" }
],
"specialties":
[
{ "type":3, "val": 3, "subtype": 38, "info": 0 }
]
"specialty" : {
"bonuses" : {
"resurrection" : {
"subtype" : "spell.resurrection",
"type" : "SPECIAL_SPELL_LEV",
"updater" : "TIMES_HERO_LEVEL",
"val" : 3
}
}
}
},
"jaegar":
{
@ -145,10 +157,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "mysticism", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 8, "info": 1 }
]
"specialty" : {
"bonuses" : {
"mysticism" : {
"subtype" : "skill.mysticism",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"malekith":
{
@ -161,10 +180,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "sorcery", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 25, "info": 0 }
]
"specialty" : {
"bonuses" : {
"sorcery" : {
"subtype" : "skill.sorcery",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"jeddite":
{
@ -176,10 +202,16 @@
[
{ "skill" : "wisdom", "level": "advanced" }
],
"specialties":
[
{ "type":3, "val": 3, "subtype": 38, "info": 0 }
]
"specialty" : {
"bonuses" : {
"resurrection" : {
"subtype" : "spell.resurrection",
"type" : "SPECIAL_SPELL_LEV",
"updater" : "TIMES_HERO_LEVEL",
"val" : 3
}
}
}
},
"geon":
{
@ -192,10 +224,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "eagleEye", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 11, "info": 1 }
]
"specialty" : {
"bonuses" : {
"eagleEye" : {
"subtype" : "skill.eagleEye",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"deemer":
{
@ -208,10 +247,16 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "scouting", "level": "advanced" }
],
"specialties":
[
{ "type":3, "val": 3, "subtype": 23, "info": 0 }
]
"specialty" : {
"bonuses" : {
"meteorShower" : {
"subtype" : "spell.meteorShower",
"type" : "SPECIAL_SPELL_LEV",
"updater" : "TIMES_HERO_LEVEL",
"val" : 3
}
}
}
},
"sephinroth":
{
@ -224,10 +269,15 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "intelligence", "level": "basic" }
],
"specialties":
[
{ "type":10, "val": 1, "subtype": 4, "info": 0 }
]
"specialty" : {
"bonuses" : {
"crystal" : {
"subtype" : "resource.crystal",
"type" : "GENERATE_RESOURCE",
"val" : 1
}
}
}
},
"darkstorn":
{
@ -240,9 +290,14 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "learning", "level": "basic" }
],
"specialties":
[
{ "type":8, "val": 0, "subtype": 46, "info": 0 }
]
"specialty" : {
"bonuses" : {
"stoneSkin" : {
"addInfo" : 0,
"subtype" : "spell.stoneSkin",
"type" : "SPECIAL_PECULIAR_ENCHANT"
}
}
}
}
}

View File

@ -9,10 +9,9 @@
{ "skill" : "armorer", "level": "basic" },
{ "skill" : "resistance", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 106 }
],
"specialty" : {
"creature" : "basilisk"
},
"army" :
[
{
@ -39,10 +38,9 @@
{ "skill" : "armorer", "level": "basic" },
{ "skill" : "leadership", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 98 }
]
"specialty" : {
"creature" : "gnoll"
}
},
"wystan":
{
@ -54,10 +52,9 @@
{ "skill" : "armorer", "level": "basic" },
{ "skill" : "archery", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 100 }
]
"specialty" : {
"creature" : "lizardman"
}
},
"tazar":
{
@ -68,10 +65,17 @@
[
{ "skill" : "armorer", "level": "advanced" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 23, "info": 0 }
]
"specialty" : {
"bonuses" : {
"armorer" : {
"subtype" : "skill.armorer",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"alkin":
{
@ -83,10 +87,9 @@
{ "skill" : "armorer", "level": "basic" },
{ "skill" : "offence", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 102 }
]
"specialty" : {
"creature" : "gorgon"
}
},
"korbac":
{
@ -98,10 +101,9 @@
{ "skill" : "armorer", "level": "basic" },
{ "skill" : "pathfinding", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 104 }
]
"specialty" : {
"creature" : "serpentFly"
}
},
"gerwulf":
{
@ -113,10 +115,9 @@
{ "skill" : "armorer", "level": "basic" },
{ "skill" : "artillery", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 146 }
]
"specialty" : {
"creature" : "ballista"
}
},
"broghild":
{
@ -128,10 +129,9 @@
{ "skill" : "armorer", "level": "basic" },
{ "skill" : "scouting", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 108 }
]
"specialty" : {
"creature" : "wyvern"
}
},
"mirlanda":
{
@ -143,10 +143,15 @@
[
{ "skill" : "wisdom", "level": "advanced" }
],
"specialties":
[
{ "type":8, "val": 0, "subtype": 45, "info": 0 }
]
"specialty" : {
"bonuses" : {
"weakness" : {
"addInfo" : 0,
"subtype" : "spell.weakness",
"type" : "SPECIAL_PECULIAR_ENCHANT"
}
}
}
},
"rosic":
{
@ -159,10 +164,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "mysticism", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 8, "info": 1 }
]
"specialty" : {
"bonuses" : {
"mysticism" : {
"subtype" : "skill.mysticism",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"voy":
{
@ -175,10 +187,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "navigation", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 2, "subtype": 5, "info": 1 }
]
"specialty" : {
"bonuses" : {
"navigation" : {
"subtype" : "skill.navigation",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"verdish":
{
@ -191,10 +210,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "firstAid", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 27, "info": 1 }
]
"specialty" : {
"bonuses" : {
"firstAid" : {
"subtype" : "skill.firstAid",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"merist":
{
@ -207,10 +233,15 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "learning", "level": "basic" }
],
"specialties":
[
{ "type":8, "val": 0, "subtype": 46, "info": 0 }
]
"specialty" : {
"bonuses" : {
"stoneSkin" : {
"addInfo" : 0,
"subtype" : "spell.stoneSkin",
"type" : "SPECIAL_PECULIAR_ENCHANT"
}
}
}
},
"styg":
{
@ -223,10 +254,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "sorcery", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 25, "info": 0 }
]
"specialty" : {
"bonuses" : {
"sorcery" : {
"subtype" : "skill.sorcery",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"andra":
{
@ -239,10 +277,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "intelligence", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 24, "info": 0 }
]
"specialty" : {
"bonuses" : {
"intelligence" : {
"subtype" : "skill.intelligence",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"tiva":
{
@ -255,9 +300,16 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "eagleEye", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 11, "info": 1 }
]
"specialty" : {
"bonuses" : {
"eagleEye" : {
"subtype" : "skill.eagleEye",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
}
}

View File

@ -8,10 +8,9 @@
[
{ "skill" : "scouting", "level": "advanced" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 46 }
]
"specialty" : {
"creature" : "hellHound"
}
},
"rashka":
{
@ -23,10 +22,9 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "scholar", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 52 }
]
"specialty" : {
"creature" : "efreet"
}
},
"marius":
{
@ -37,10 +35,9 @@
[
{ "skill" : "armorer", "level": "advanced" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 48 }
]
"specialty" : {
"creature" : "demon"
}
},
"ignatius":
{
@ -52,10 +49,9 @@
{ "skill" : "tactics", "level": "basic" },
{ "skill" : "resistance", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 42 }
]
"specialty" : {
"creature" : "imp"
}
},
"octavia":
{
@ -67,10 +63,15 @@
{ "skill" : "scholar", "level": "basic" },
{ "skill" : "offence", "level": "basic" }
],
"specialties":
[
{ "type":10, "val": 350, "subtype": 6, "info": 0 }
]
"specialty" : {
"bonuses" : {
"gold" : {
"subtype" : "resource.gold",
"type" : "GENERATE_RESOURCE",
"val" : 350
}
}
}
},
"calh":
{
@ -82,10 +83,9 @@
{ "skill" : "archery", "level": "basic" },
{ "skill" : "scouting", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 42 }
]
"specialty" : {
"creature" : "gog"
}
},
"pyre":
{
@ -97,10 +97,9 @@
{ "skill" : "artillery", "level": "basic" },
{ "skill" : "logistics", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 146 }
]
"specialty" : {
"creature" : "ballista"
}
},
"nymus":
{
@ -111,10 +110,9 @@
[
{ "skill" : "offence", "level": "advanced" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 50 }
]
"specialty" : {
"creature" : "pitFiend"
}
},
"ayden":
{
@ -127,10 +125,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "intelligence", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 24, "info": 0 }
]
"specialty" : {
"bonuses" : {
"intelligence" : {
"subtype" : "skill.intelligence",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"xyron":
{
@ -143,10 +148,16 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "scholar", "level": "basic" }
],
"specialties":
[
{ "type":3, "val": 3, "subtype": 22, "info": 0 }
]
"specialty" : {
"bonuses" : {
"inferno" : {
"subtype" : "spell.inferno",
"type" : "SPECIAL_SPELL_LEV",
"updater" : "TIMES_HERO_LEVEL",
"val" : 3
}
}
}
},
"axsis":
{
@ -159,10 +170,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "mysticism", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 8, "info": 1 }
]
"specialty" : {
"bonuses" : {
"mysticism" : {
"subtype" : "skill.mysticism",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"olema":
{
@ -175,10 +193,15 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "ballistics", "level": "basic" }
],
"specialties":
[
{ "type":8, "val": 0, "subtype": 45, "info": 0 }
]
"specialty" : {
"bonuses" : {
"weakness" : {
"addInfo" : 0,
"subtype" : "spell.weakness",
"type" : "SPECIAL_PECULIAR_ENCHANT"
}
}
}
},
"calid":
{
@ -191,10 +214,15 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "learning", "level": "basic" }
],
"specialties":
[
{ "type":10, "val": 1, "subtype": 3, "info": 0 }
]
"specialty" : {
"bonuses" : {
"sulfur" : {
"subtype" : "resource.sulfur",
"type" : "GENERATE_RESOURCE",
"val" : 1
}
}
}
},
"ash":
{
@ -207,10 +235,15 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "eagleEye", "level": "basic" }
],
"specialties":
[
{ "type":8, "val": 0, "subtype": 43, "info": 0 }
]
"specialty" : {
"bonuses" : {
"bloodlust" : {
"addInfo" : 0,
"subtype" : "spell.bloodlust",
"type" : "SPECIAL_PECULIAR_ENCHANT"
}
}
}
},
"zydar":
{
@ -223,10 +256,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "sorcery", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 25, "info": 0 }
]
"specialty" : {
"bonuses" : {
"sorcery" : {
"subtype" : "skill.sorcery",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"xarfax":
{
@ -239,9 +279,15 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "leadership", "level": "basic" }
],
"specialties":
[
{ "type":3, "val": 3, "subtype": 21, "info": 0 }
]
"specialty" : {
"bonuses" : {
"fireball" : {
"subtype" : "spell.fireball",
"type" : "SPECIAL_SPELL_LEV",
"updater" : "TIMES_HERO_LEVEL",
"val" : 3
}
}
}
}
}

View File

@ -10,10 +10,9 @@
{ "skill" : "necromancy", "level": "basic" },
{ "skill" : "resistance", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 58 }
]
"specialty" : {
"creature" : "walkingDead"
}
},
"vokial":
{
@ -26,10 +25,9 @@
{ "skill" : "necromancy", "level": "basic" },
{ "skill" : "artillery", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 62 }
]
"specialty" : {
"creature" : "vampire"
}
},
"moandor":
{
@ -42,10 +40,9 @@
{ "skill" : "necromancy", "level": "basic" },
{ "skill" : "learning", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 64 }
]
"specialty" : {
"creature" : "lich"
}
},
"charna":
{
@ -58,10 +55,9 @@
{ "skill" : "necromancy", "level": "basic" },
{ "skill" : "tactics", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 60 }
]
"specialty" : {
"creature" : "wight"
}
},
"tamika":
{
@ -74,10 +70,9 @@
{ "skill" : "necromancy", "level": "basic" },
{ "skill" : "offence", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 66 }
]
"specialty" : {
"creature" : "blackKnight"
}
},
"isra":
{
@ -89,10 +84,17 @@
[
{ "skill" : "necromancy", "level": "advanced" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 12, "info": 0 }
]
"specialty" : {
"bonuses" : {
"necromancy" : {
"subtype" : "skill.necromancy",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"clavius":
{
@ -105,10 +107,15 @@
{ "skill" : "necromancy", "level": "basic" },
{ "skill" : "offence", "level": "basic" }
],
"specialties":
[
{ "type":10, "val": 350, "subtype": 6, "info": 0 }
]
"specialty" : {
"bonuses" : {
"gold" : {
"subtype" : "resource.gold",
"type" : "GENERATE_RESOURCE",
"val" : 350
}
}
}
},
"galthran":
{
@ -121,10 +128,9 @@
{ "skill" : "necromancy", "level": "basic" },
{ "skill" : "armorer", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 56 }
]
"specialty" : {
"creature" : "skeleton"
}
},
"septienna":
{
@ -137,10 +143,16 @@
{ "skill" : "necromancy", "level": "basic" },
{ "skill" : "scholar", "level": "basic" }
],
"specialties":
[
{ "type":3, "val": 3, "subtype": 24, "info": 0 }
]
"specialty" : {
"bonuses" : {
"deathRipple" : {
"subtype" : "spell.deathRipple",
"type" : "SPECIAL_SPELL_LEV",
"updater" : "TIMES_HERO_LEVEL",
"val" : 3
}
}
}
},
"aislinn":
{
@ -153,10 +165,16 @@
{ "skill" : "necromancy", "level": "basic" },
{ "skill" : "wisdom", "level": "basic" }
],
"specialties":
[
{ "type":3, "val": 3, "subtype": 23, "info": 0 }
]
"specialty" : {
"bonuses" : {
"meteorShower" : {
"subtype" : "spell.meteorShower",
"type" : "SPECIAL_SPELL_LEV",
"updater" : "TIMES_HERO_LEVEL",
"val" : 3
}
}
}
},
"sandro":
{
@ -169,10 +187,17 @@
{ "skill" : "necromancy", "level": "basic" },
{ "skill" : "sorcery", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 25, "info": 0 }
]
"specialty" : {
"bonuses" : {
"sorcery" : {
"subtype" : "skill.sorcery",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"nimbus":
{
@ -185,10 +210,17 @@
{ "skill" : "necromancy", "level": "basic" },
{ "skill" : "eagleEye", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 11, "info": 1 }
]
"specialty" : {
"bonuses" : {
"eagleEye" : {
"subtype" : "skill.eagleEye",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"thant":
{
@ -201,10 +233,16 @@
{ "skill" : "necromancy", "level": "basic" },
{ "skill" : "mysticism", "level": "basic" }
],
"specialties":
[
{ "type":3, "val": 39, "subtype": 0, "info": 3 }
]
"specialty" : {
"bonuses" : {
"animateDead" : {
"subtype" : "spell.animateDead",
"type" : "SPECIAL_SPELL_LEV",
"updater" : "TIMES_HERO_LEVEL",
"val" : 3
}
}
}
},
"xsi":
{
@ -217,10 +255,15 @@
{ "skill" : "necromancy", "level": "basic" },
{ "skill" : "learning", "level": "basic" }
],
"specialties":
[
{ "type":8, "val": 0, "subtype": 46, "info": 0 }
]
"specialty" : {
"bonuses" : {
"stoneSkin" : {
"addInfo" : 0,
"subtype" : "spell.stoneSkin",
"type" : "SPECIAL_PECULIAR_ENCHANT"
}
}
}
},
"vidomina":
{
@ -232,10 +275,17 @@
[
{ "skill" : "necromancy", "level": "advanced" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 12, "info": 0 }
]
"specialty" : {
"bonuses" : {
"necromancy" : {
"subtype" : "skill.necromancy",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"nagash":
{
@ -248,9 +298,14 @@
{ "skill" : "necromancy", "level": "basic" },
{ "skill" : "intelligence", "level": "basic" }
],
"specialties":
[
{ "type":10, "val": 350, "subtype": 6, "info": 0 }
]
"specialty" : {
"bonuses" : {
"gold" : {
"subtype" : "resource.gold",
"type" : "GENERATE_RESOURCE",
"val" : 350
}
}
}
}
}

View File

@ -9,10 +9,17 @@
{ "skill" : "leadership", "level": "basic" },
{ "skill" : "armorer", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 23, "info": 0 }
]
"specialty" : {
"bonuses" : {
"armorer" : {
"subtype" : "skill.armorer",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"ufretin":
{
@ -24,10 +31,9 @@
{ "skill" : "luck", "level": "basic" },
{ "skill" : "resistance", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 16 }
]
"specialty" : {
"creature" : "dwarf"
}
},
"jenova":
{
@ -38,10 +44,15 @@
[
{ "skill" : "archery", "level": "advanced" }
],
"specialties":
[
{ "type":10, "val": 350, "subtype": 6, "info": 0 }
]
"specialty" : {
"bonuses" : {
"gold" : {
"subtype" : "resource.gold",
"type" : "GENERATE_RESOURCE",
"val" : 350
}
}
}
},
"ryland":
{
@ -53,10 +64,9 @@
{ "skill" : "diplomacy", "level": "basic" },
{ "skill" : "leadership", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 22 }
]
"specialty" : {
"creature" : "dendroidGuard"
}
},
"thorgrim":
{
@ -67,10 +77,17 @@
[
{ "skill" : "resistance", "level": "advanced" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 26, "info": 0 }
]
"specialty" : {
"bonuses" : {
"resistance" : {
"subtype" : "skill.resistance",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"ivor":
{
@ -82,10 +99,9 @@
{ "skill" : "archery", "level": "basic" },
{ "skill" : "offence", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 18 }
]
"specialty" : {
"creature" : "woodElf"
}
},
"clancy":
{
@ -97,10 +113,9 @@
{ "skill" : "pathfinding", "level": "basic" },
{ "skill" : "resistance", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 24 }
]
"specialty" : {
"creature" : "unicorn"
}
},
"kyrre":
{
@ -112,10 +127,17 @@
{ "skill" : "archery", "level": "basic" },
{ "skill" : "logistics", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 2, "info": 0 }
]
"specialty" : {
"bonuses" : {
"logistics" : {
"subtype" : "skill.logistics",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"coronius":
{
@ -128,10 +150,15 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "scholar", "level": "basic" }
],
"specialties":
[
{ "type":8, "val": 0, "subtype": 55, "info": 1 }
]
"specialty" : {
"bonuses" : {
"slayer" : {
"addInfo" : 1,
"subtype" : "spell.slayer",
"type" : "SPECIAL_PECULIAR_ENCHANT"
}
}
}
},
"uland":
{
@ -144,10 +171,16 @@
{ "skill" : "wisdom", "level": "advanced" },
{ "skill" : "ballistics", "level": "basic" }
],
"specialties":
[
{ "type":3, "val": 3, "subtype": 37, "info": 0 }
]
"specialty" : {
"bonuses" : {
"cure" : {
"subtype" : "spell.cure",
"type" : "SPECIAL_SPELL_LEV",
"updater" : "TIMES_HERO_LEVEL",
"val" : 3
}
}
}
},
"elleshar":
{
@ -160,10 +193,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "intelligence", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 24, "info": 0 }
]
"specialty" : {
"bonuses" : {
"intelligence" : {
"subtype" : "skill.intelligence",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"gem":
{
@ -176,10 +216,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "firstAid", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 27, "info": 0 }
]
"specialty" : {
"bonuses" : {
"firstAid" : {
"subtype" : "skill.firstAid",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"malcom":
{
@ -192,10 +239,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "eagleEye", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 11, "info": 0 }
]
"specialty" : {
"bonuses" : {
"eagleEye" : {
"subtype" : "skill.eagleEye",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"melodia":
{
@ -208,10 +262,14 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "luck", "level": "basic" }
],
"specialties":
[
{ "type":7, "val": 0, "subtype": 51, "info": 0 }
]
"specialty" : {
"bonuses" : {
"fortune" : {
"subtype" : "spell.fortune",
"type" : "MAXED_SPELL"
}
}
}
},
"alagar":
{
@ -224,10 +282,16 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "sorcery", "level": "basic" }
],
"specialties":
[
{ "type":3, "val": 3, "subtype": 16, "info": 0 }
]
"specialty" : {
"bonuses" : {
"iceBolt" : {
"subtype" : "spell.iceBolt",
"type" : "SPECIAL_SPELL_LEV",
"updater" : "TIMES_HERO_LEVEL",
"val" : 3
}
}
}
},
"aeris":
{
@ -240,9 +304,8 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "scouting", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 20 }
]
"specialty" : {
"creature" : "pegasus"
}
}
}

View File

@ -10,10 +10,14 @@
[
{ "skill" : "leadership", "level": "advanced" }
],
"specialties":
[
{ "type":12, "val": 2, "subtype": 0, "info": 0 }
]
"specialty" : {
"bonuses" : {
"speed" : {
"type" : "STACKS_SPEED",
"val" : 2
}
}
}
},
"adrienne":
{
@ -25,11 +29,9 @@
"skills":
[
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "fireMagic", "level": "expert" } ],
"specialties":
[
{ "type":11, "val": 14, "subtype": 0, "info": 3 }
]
{ "skill" : "fireMagic", "level": "expert" }
],
"specialty" : { "bonuses" : { } } // has expert fire magic as "specialty"
},
"catherine":
{
@ -42,10 +44,9 @@
{ "skill" : "leadership", "level": "basic" },
{ "skill" : "offence", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 4 }
]
"specialty" : {
"creature" : "swordsman"
}
},
"dracon":
{
@ -58,11 +59,18 @@
[
{ "skill" : "wisdom", "level": "advanced" }
],
"specialties":
[
{ "type":9, "val": 0, "subtype": 8, "info": 136 },
{ "type":9, "val": 0, "subtype": 34, "info": 136 }
]
"specialty" : {
"base" : {
"addInfo" : "creature.enchanter",
"type" : "SPECIAL_UPGRADE"
},
"bonuses" : {
"archMage2enchanter" : { "subtype" : "creature.archMage" },
"mage2enchanter" : { "subtype" : "creature.mage" },
"monk2enchanter" : { "subtype" : "creature.monk" },
"zealot2enchanter" : { "subtype" : "creature.zealot" }
}
}
},
"gelu":
{
@ -75,11 +83,18 @@
{ "skill" : "archery", "level": "basic" },
{ "skill" : "leadership", "level": "basic" }
],
"specialties":
[
{ "type":9, "val": 0, "subtype": 2, "info": 137 },
{ "type":9, "val": 0, "subtype": 18, "info": 137 }
]
"specialty" : {
"base" : {
"addInfo" : "creature.sharpshooter",
"type" : "SPECIAL_UPGRADE"
},
"bonuses" : {
"archer2sharpshooter" : { "subtype" : "creature.archer" },
"grandElf2sharpshooter" : { "subtype" : "creature.grandElf" },
"marksman2sharpshooter" : { "subtype" : "creature.marksman" },
"woodElf2sharpshooter" : { "subtype" : "creature.woodElf" }
}
}
},
"kilgor":
{
@ -91,12 +106,33 @@
[
{ "skill" : "offence", "level": "advanced" }
],
"specialties":
[
{ "type":4, "val": 5, "subtype": 1, "info": 96 },
{ "type":4, "val": 5, "subtype": 2, "info": 96 },
{ "type":4, "val": 10, "subtype": 3, "info": 96 }
]
"specialty" : {
"base" : {
"limiters" : [
{
"parameters" : [ "behemoth", true ],
"type" : "CREATURE_TYPE_LIMITER"
}
]
},
"bonuses" : {
"damage" : {
"subtype" : 0,
"type" : "CREATURE_DAMAGE",
"val" : 10
},
"attack" : {
"subtype" : "primSkill.attack",
"type" : "PRIMARY_SKILL",
"val" : 5
},
"defence" : {
"subtype" : "primSkill.defence",
"type" : "PRIMARY_SKILL",
"val" : 5
}
}
}
},
"undeadHaart": // undead version of Lord Haart
{
@ -109,12 +145,33 @@
[
{ "skill" : "necromancy", "level": "advanced" }
],
"specialties":
[
{ "type":4, "val": 5, "subtype": 1, "info": 66 },
{ "type":4, "val": 5, "subtype": 2, "info": 66 },
{ "type":4, "val": 10, "subtype": 3, "info": 66 }
]
"specialty" : {
"base" : {
"limiters" : [
{
"parameters" : [ "blackKnight", true ],
"type" : "CREATURE_TYPE_LIMITER"
}
]
},
"bonuses" : {
"damage" : {
"subtype" : 0,
"type" : "CREATURE_DAMAGE",
"val" : 10
},
"attack" : {
"subtype" : "primSkill.attack",
"type" : "PRIMARY_SKILL",
"val" : 5
},
"defence" : {
"subtype" : "primSkill.defence",
"type" : "PRIMARY_SKILL",
"val" : 5
}
}
}
},
"mutare":
{
@ -128,11 +185,22 @@
{ "skill" : "estates", "level": "basic" },
{ "skill" : "tactics", "level": "basic" }
],
"specialties":
[
{ "type":13, "val": 5, "subtype": 1, "info": 0 },
{ "type":13, "val": 5, "subtype": 2, "info": 0 }
]
"specialty" : {
"base" : {
"limiters" : [
{
"parameters" : [ "DRAGON_NATURE" ],
"type" : "HAS_ANOTHER_BONUS_LIMITER"
}
],
"type" : "PRIMARY_SKILL",
"val" : 5
},
"bonuses" : {
"attack" : { "subtype" : "primSkill.attack" },
"defence" : { "subtype" : "primSkill.defence" }
}
}
},
"roland":
{
@ -145,10 +213,9 @@
{ "skill" : "leadership", "level": "basic" },
{ "skill" : "armorer", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 4 }
]
"specialty" : {
"creature" : "griffin"
}
},
"mutareDrake":
{
@ -162,11 +229,22 @@
{ "skill" : "estates", "level": "basic" },
{ "skill" : "tactics", "level": "basic" }
],
"specialties":
[
{ "type":13, "val": 1, "subtype": 1, "info": 5 },
{ "type":13, "val": 1, "subtype": 1, "info": 5 }
],
"specialty" : {
"base" : {
"limiters" : [
{
"parameters" : [ "DRAGON_NATURE" ],
"type" : "HAS_ANOTHER_BONUS_LIMITER"
}
],
"type" : "PRIMARY_SKILL",
"val" : 5
},
"bonuses" : {
"attack" : { "subtype" : "primSkill.attack" },
"defence" : { "subtype" : "primSkill.defence" }
}
},
"army" :
[
{
@ -194,10 +272,9 @@
{ "skill" : "tactics", "level": "basic" },
{ "skill" : "offence", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 90 }
],
"specialty" : {
"creature" : "ogre"
},
"army" :
[
{
@ -225,12 +302,32 @@
{ "skill" : "leadership", "level": "basic" },
{ "skill" : "tactics", "level": "basic" }
],
"specialties":
[
{ "type":4, "val": 4, "subtype": 1, "info": 54 },
{ "type":4, "val": 2, "subtype": 2, "info": 54 },
{ "type":4, "val": 1, "subtype": 5, "info": 54 }
],
"specialty" : {
"base" : {
"limiters" : [
{
"parameters" : [ "devil", true ],
"type" : "CREATURE_TYPE_LIMITER"
}
]
},
"bonuses" : {
"attack" : {
"subtype" : "primSkill.attack",
"type" : "PRIMARY_SKILL",
"val" : 4
},
"defence" : {
"subtype" : "primSkill.defence",
"type" : "PRIMARY_SKILL",
"val" : 2
},
"speed" : {
"type" : "STACKS_SPEED",
"val" : 1
}
}
},
"army" :
[
{

View File

@ -9,10 +9,9 @@
{ "skill" : "offence", "level": "basic" },
{ "skill" : "ballistics", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 94 }
]
"specialty" : {
"creature" : "cyclop"
}
},
"gurnisson":
{
@ -24,10 +23,9 @@
{ "skill" : "offence", "level": "basic" },
{ "skill" : "artillery", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 146 }
]
"specialty" : {
"creature" : "ballista"
}
},
"jabarkas":
{
@ -39,10 +37,9 @@
{ "skill" : "offence", "level": "basic" },
{ "skill" : "archery", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 88 }
]
"specialty" : {
"creature" : "orc"
}
},
"shiva":
{
@ -54,10 +51,9 @@
{ "skill" : "offence", "level": "basic" },
{ "skill" : "scouting", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 92 }
]
"specialty" : {
"creature" : "roc"
}
},
"gretchin":
{
@ -69,10 +65,9 @@
{ "skill" : "offence", "level": "basic" },
{ "skill" : "pathfinding", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 84 }
]
"specialty" : {
"creature" : "goblin"
}
},
"krellion":
{
@ -84,10 +79,9 @@
{ "skill" : "offence", "level": "basic" },
{ "skill" : "resistance", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 90 }
]
"specialty" : {
"creature" : "ogre"
}
},
"cragHack":
{
@ -98,10 +92,17 @@
[
{ "skill" : "offence", "level": "advanced" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 22, "info": 0 }
]
"specialty" : {
"bonuses" : {
"offence" : {
"subtype" : "skill.offence",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"tyraxor":
{
@ -113,10 +114,9 @@
{ "skill" : "offence", "level": "basic" },
{ "skill" : "tactics", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 86 }
]
"specialty" : {
"creature" : "goblinWolfRider"
}
},
"gird":
{
@ -129,10 +129,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "sorcery", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 25, "info": 0 }
]
"specialty" : {
"bonuses" : {
"sorcery" : {
"subtype" : "skill.sorcery",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"vey":
{
@ -145,10 +152,9 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "leadership", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 90 }
]
"specialty" : {
"creature" : "ogre"
}
},
"dessa":
{
@ -161,10 +167,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "logistics", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 2, "info": 0 }
]
"specialty" : {
"bonuses" : {
"logistics" : {
"subtype" : "skill.logistics",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"terek":
{
@ -177,10 +190,15 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "tactics", "level": "basic" }
],
"specialties":
[
{ "type":8, "val": 0, "subtype": 53, "info": 0 }
]
"specialty" : {
"bonuses" : {
"haste" : {
"addInfo" : 0,
"subtype" : "spell.haste",
"type" : "SPECIAL_PECULIAR_ENCHANT"
}
}
}
},
"zubin":
{
@ -193,10 +211,15 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "artillery", "level": "basic" }
],
"specialties":
[
{ "type":8, "val": 0, "subtype": 44, "info": 0 }
]
"specialty" : {
"bonuses" : {
"precision" : {
"addInfo" : 0,
"subtype" : "spell.precision",
"type" : "SPECIAL_PECULIAR_ENCHANT"
}
}
}
},
"gundula":
{
@ -209,10 +232,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "offence", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 25, "info": 0 }
]
"specialty" : {
"bonuses" : {
"sorcery" : {
"subtype" : "skill.sorcery",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"oris":
{
@ -225,10 +255,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "eagleEye", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 11, "info": 0 }
]
"specialty" : {
"bonuses" : {
"eagleEye" : {
"subtype" : "skill.eagleEye",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"saurug":
{
@ -241,9 +278,14 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "resistance", "level": "basic" }
],
"specialties":
[
{ "type":10, "val": 1, "subtype": 5, "info": 0 }
]
"specialty" : {
"bonuses" : {
"gems" : {
"subtype" : "resource.gems",
"type" : "GENERATE_RESOURCE",
"val" : 1
}
}
}
}
}

View File

@ -10,10 +10,9 @@
{ "skill" : "scouting", "level": "basic" },
{ "skill" : "mysticism", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 30 }
]
"specialty" : {
"creature" : "stoneGargoyle"
}
},
"thane":
{
@ -25,10 +24,9 @@
[
{ "skill" : "scholar", "level": "advanced" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 36 }
]
"specialty" : {
"creature" : "genie"
}
},
"josephine":
{
@ -41,10 +39,9 @@
{ "skill" : "mysticism", "level": "basic" },
{ "skill" : "sorcery", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 32 }
]
"specialty" : {
"creature" : "ironGolem"
}
},
"neela":
{
@ -57,10 +54,17 @@
{ "skill" : "scholar", "level": "basic" },
{ "skill" : "armorer", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 23, "info": 0 }
]
"specialty" : {
"bonuses" : {
"armorer" : {
"subtype" : "skill.armorer",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"torosar ":
{
@ -73,10 +77,9 @@
{ "skill" : "mysticism", "level": "basic" },
{ "skill" : "tactics", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 146 }
]
"specialty" : {
"creature" : "ballista"
}
},
"fafner":
{
@ -89,10 +92,9 @@
{ "skill" : "scholar", "level": "basic" },
{ "skill" : "resistance", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 38 }
]
"specialty" : {
"creature" : "naga"
}
},
"rissa":
{
@ -105,10 +107,15 @@
{ "skill" : "mysticism", "level": "basic" },
{ "skill" : "offence", "level": "basic" }
],
"specialties":
[
{ "type":10, "val": 1, "subtype": 1, "info": 0 }
]
"specialty" : {
"bonuses" : {
"mercury" : {
"subtype" : "resource.mercury",
"type" : "GENERATE_RESOURCE",
"val" : 1
}
}
}
},
"iona":
{
@ -121,10 +128,9 @@
{ "skill" : "scholar", "level": "basic" },
{ "skill" : "intelligence", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 36 }
]
"specialty" : {
"creature" : "genie"
}
},
"astral":
{
@ -136,10 +142,16 @@
[
{ "skill" : "wisdom", "level": "advanced" }
],
"specialties":
[
{ "type":3, "val": 3, "subtype": 60, "info": 0 }
]
"specialty" : {
"bonuses" : {
"hypnotize" : {
"subtype" : "spell.hypnotize",
"type" : "SPECIAL_SPELL_LEV",
"updater" : "TIMES_HERO_LEVEL",
"val" : 3
}
}
}
},
"halon":
{
@ -152,10 +164,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "mysticism", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 8, "info": 1 }
]
"specialty" : {
"bonuses" : {
"mysticism" : {
"subtype" : "skill.mysticism",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"serena":
{
@ -168,10 +187,17 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "eagleEye", "level": "basic" }
],
"specialties":
[
{ "type":2, "val": 5, "subtype": 11, "info": 0 }
]
"specialty" : {
"bonuses" : {
"eagleEye" : {
"subtype" : "skill.eagleEye",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
},
"daremyth":
{
@ -184,10 +210,14 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "intelligence", "level": "basic" }
],
"specialties":
[
{ "type":7, "val": 0, "subtype": 51, "info": 0 }
]
"specialty" : {
"bonuses" : {
"fortune" : {
"subtype" : "spell.fortune",
"type" : "MAXED_SPELL"
}
}
}
},
"theodorus":
{
@ -200,10 +230,9 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "ballistics", "level": "basic" }
],
"specialties":
[
{ "type":1, "val": 0, "subtype": 0, "info": 34 }
]
"specialty" : {
"creature" : "mage"
}
},
"solmyr":
{
@ -216,10 +245,16 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "sorcery", "level": "basic" }
],
"specialties":
[
{ "type":3, "val": 3, "subtype": 19, "info": 0 }
]
"specialty" : {
"bonuses" : {
"chainLightning" : {
"subtype" : "spell.chainLightning",
"type" : "SPECIAL_SPELL_LEV",
"updater" : "TIMES_HERO_LEVEL",
"val" : 3
}
}
}
},
"cyra":
{
@ -232,10 +267,15 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "diplomacy", "level": "basic" }
],
"specialties":
[
{ "type":8, "val": 0, "subtype": 53, "info": 0 }
]
"specialty" : {
"bonuses" : {
"haste" : {
"addInfo" : 0,
"subtype" : "spell.haste",
"type" : "SPECIAL_PECULIAR_ENCHANT"
}
}
}
},
"aine":
{
@ -248,9 +288,14 @@
{ "skill" : "wisdom", "level": "basic" },
{ "skill" : "scholar", "level": "basic" }
],
"specialties":
[
{ "type":10, "val": 350, "subtype": 6, "info": 0 }
]
"specialty" : {
"bonuses" : {
"gold" : {
"subtype" : "resource.gold",
"type" : "GENERATE_RESOURCE",
"val" : 350
}
}
}
}
}

View File

@ -70,6 +70,30 @@
}
]
},
"updater" : {
"anyOf" : [
{
"type" : "string"
},
{
"description" : "updater",
"type" : "object",
"required" : ["type", "parameters"],
"additionalProperties" : false,
"properties" : {
"type" : {
"type" : "string",
"description" : "type"
},
"parameters": {
"type" : "array",
"description" : "parameters",
"additionalItems" : true
}
}
}
]
},
"sourceID": {
"type":"number",
"description": "sourceID"

View File

@ -115,24 +115,48 @@
"additionalItems" : true
},
"specialty": {
"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"
},
"bonuses": {
"type":"array",
"description": "List of bonuses",
"items": { "$ref" : "vcmi:bonus" }
"anyOf" : [
{
"type":"array",
"description": "Description of hero specialty using bonus system (deprecated)",
"items": {
"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" }
}
}
}
},
{
"type" : "object",
"description": "Description of hero specialty using bonus system",
"additionalProperties" : false,
"properties" : {
"base" : {
"type" : "object",
"description" : "Will be merged with all bonuses."
},
"bonuses" : {
"type" : "object",
"description" : "Set of bonuses",
"additionalProperties" : { "$ref" : "vcmi:bonus" }
},
"creature" : {
"type" : "string",
"description" : "Name of base creature to grant standard specialty to."
}
}
}
}
]
},
"spellbook": {
"type":"array",

View File

@ -226,7 +226,8 @@
"base" : {
"effects" : {
"main" : {
"type" : "MANA_REGENERATION",
"subtype" : "skill.mysticism",
"type" : "SECONDARY_SKILL_PREMY",
"valueType" : "BASE_NUMBER"
}
}

View File

@ -379,31 +379,310 @@ void CHeroHandler::loadHeroSkills(CHero * hero, const JsonNode & node)
}
}
// add standard creature specialty to result
void AddSpecialtyForCreature(int creatureID, std::shared_ptr<Bonus> bonus, std::vector<std::shared_ptr<Bonus>> &result)
{
const CCreature &specBaseCreature = *VLC->creh->creatures[creatureID]; //base creature in which we have specialty
bonus->limiter.reset(new CCreatureTypeLimiter(specBaseCreature, true));
bonus->type = Bonus::STACKS_SPEED;
bonus->valType = Bonus::ADDITIVE_VALUE;
bonus->val = 1;
result.push_back(bonus);
// attack and defense may differ for upgraded creatures => separate bonuses
std::vector<int> specTargets;
specTargets.push_back(creatureID);
specTargets.insert(specTargets.end(), specBaseCreature.upgrades.begin(), specBaseCreature.upgrades.end());
for(int cid : specTargets)
{
const CCreature &specCreature = *VLC->creh->creatures[cid];
bonus = std::make_shared<Bonus>(*bonus);
bonus->limiter.reset(new CCreatureTypeLimiter(specCreature, false));
bonus->type = Bonus::PRIMARY_SKILL;
bonus->val = 0;
int stepSize = specCreature.level ? specCreature.level : 5;
bonus->subtype = PrimarySkill::ATTACK;
bonus->updater.reset(new GrowsWithLevelUpdater(specCreature.getAttack(false), stepSize));
result.push_back(bonus);
bonus = std::make_shared<Bonus>(*bonus);
bonus->subtype = PrimarySkill::DEFENSE;
bonus->updater.reset(new GrowsWithLevelUpdater(specCreature.getDefence(false), stepSize));
result.push_back(bonus);
}
}
// 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
AddSpecialtyForCreature(spec.additionalinfo, bonus, result);
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 TimesHeroLevelUpdater());
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;
bonus->updater.reset(new TimesHeroLevelUpdater());
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
bonus->updater.reset(new TimesHeroLevelUpdater());
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)
{
std::shared_ptr<Bonus> upgradeUpgradedVersion = std::make_shared<Bonus>(*bonus);
upgradeUpgradedVersion->subtype = cre_id;
result.push_back(upgradeUpgradedVersion);
}
}
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;
}
// convert deprecated format
std::vector<std::shared_ptr<Bonus>> SpecialtyBonusToBonuses(const SSpecialtyBonus & spec)
{
std::vector<std::shared_ptr<Bonus>> result;
for(std::shared_ptr<Bonus> oldBonus : spec.bonuses)
{
if(oldBonus->type == Bonus::SPECIAL_SPELL_LEV || oldBonus->type == Bonus::SPECIAL_BLESS_DAMAGE)
{
// these bonuses used to auto-scale with hero level
std::shared_ptr<Bonus> newBonus = std::make_shared<Bonus>(*oldBonus);
newBonus->updater = std::make_shared<TimesHeroLevelUpdater>();
result.push_back(newBonus);
}
else if(spec.growsWithLevel)
{
std::shared_ptr<Bonus> newBonus = std::make_shared<Bonus>(*oldBonus);
switch(newBonus->type)
{
case Bonus::SECONDARY_SKILL_PREMY:
break; // ignore - used to be overwritten based on SPECIAL_SECONDARY_SKILL
case Bonus::SPECIAL_SECONDARY_SKILL:
newBonus->type = Bonus::SECONDARY_SKILL_PREMY;
newBonus->updater = std::make_shared<TimesHeroLevelUpdater>();
result.push_back(newBonus);
break;
case Bonus::PRIMARY_SKILL:
if((newBonus->subtype == PrimarySkill::ATTACK || newBonus->subtype == PrimarySkill::DEFENSE) && newBonus->limiter)
{
const std::shared_ptr<CCreatureTypeLimiter> creatureLimiter = std::dynamic_pointer_cast<CCreatureTypeLimiter>(newBonus->limiter);
if(creatureLimiter)
{
const CCreature * cre = creatureLimiter->creature;
int creStat = newBonus->subtype == PrimarySkill::ATTACK ? cre->getAttack(false) : cre->getDefence(false);
int creLevel = cre->level ? cre->level : 5;
newBonus->updater = std::make_shared<GrowsWithLevelUpdater>(creStat, creLevel);
}
result.push_back(newBonus);
}
break;
default:
result.push_back(newBonus);
}
}
else
{
result.push_back(oldBonus);
}
}
return result;
}
void CHeroHandler::beforeValidate(JsonNode & object)
{
//handle "base" specialty info
JsonNode & specialtyNode = object["specialty"];
if(specialtyNode.getType() == JsonNode::JsonType::DATA_STRUCT)
{
const JsonNode & base = specialtyNode["base"];
if(!base.isNull())
{
if(specialtyNode["bonuses"].isNull())
{
logMod->warn("specialty has base without bonuses");
}
else
{
JsonMap & bonuses = specialtyNode["bonuses"].Struct();
for(std::pair<std::string, JsonNode> keyValue : bonuses)
JsonUtils::inherit(bonuses[keyValue.first], base);
}
}
}
}
void CHeroHandler::loadHeroSpecialty(CHero * hero, const JsonNode & node)
{
//deprecated, used only for original spciealties
for(const JsonNode &specialty : node["specialties"].Vector())
int sid = hero->ID.getNum();
auto prepSpec = [=](std::shared_ptr<Bonus> bonus)
{
SSpecialtyInfo spec;
bonus->duration = Bonus::PERMANENT;
bonus->source = Bonus::HERO_SPECIAL;
bonus->sid = sid;
return bonus;
};
spec.type = specialty["type"].Float();
spec.val = specialty["val"].Float();
spec.subtype = specialty["subtype"].Float();
spec.additionalinfo = specialty["info"].Float();
hero->spec.push_back(spec); //put a copy of dummy
}
//new format, using bonus system
for(const JsonNode &specialty : node["specialty"].Vector())
//deprecated, used only for original specialties
const JsonNode & specialtiesNode = node["specialties"];
if (!specialtiesNode.isNull())
{
SSpecialtyBonus hs;
hs.growsWithLevel = specialty["growsWithLevel"].Bool();
for (const JsonNode & bonus : specialty["bonuses"].Vector())
logMod->warn("Hero %s has deprecated specialties format.", hero->identifier);
for(const JsonNode &specialty : specialtiesNode.Vector())
{
auto b = JsonUtils::parseBonus(bonus);
hs.bonuses.push_back (b);
SSpecialtyInfo spec;
spec.type = specialty["type"].Integer();
spec.val = specialty["val"].Integer();
spec.subtype = specialty["subtype"].Integer();
spec.additionalinfo = specialty["info"].Integer();
//we convert after loading completes, to have all identifiers for json logging
hero->specDeprecated.push_back(spec);
}
}
//new(er) format, using bonus system
const JsonNode & specialtyNode = node["specialty"];
if(specialtyNode.getType() == JsonNode::JsonType::DATA_VECTOR)
{
//deprecated middle-aged format
for(const JsonNode & specialty : node["specialty"].Vector())
{
SSpecialtyBonus hs;
hs.growsWithLevel = specialty["growsWithLevel"].Bool();
for (const JsonNode & bonus : specialty["bonuses"].Vector())
hs.bonuses.push_back(prepSpec(JsonUtils::parseBonus(bonus)));
hero->specialtyDeprecated.push_back(hs);
}
}
else if(specialtyNode.getType() == JsonNode::JsonType::DATA_STRUCT)
{
//creature specialty - alias for simplicity
if(!specialtyNode["creature"].isNull())
{
VLC->modh->identifiers.requestIdentifier("creature", specialtyNode["creature"], [hero](si32 creature) {
// use legacy format for delayed conversion (must have all creature data loaded, also for upgrades)
SSpecialtyInfo spec;
spec.type = 1;
spec.additionalinfo = creature;
hero->specDeprecated.push_back(spec);
});
}
if(!specialtyNode["bonuses"].isNull())
{
//proper new format
for(auto keyValue : specialtyNode["bonuses"].Struct())
hero->specialty.push_back(prepSpec(JsonUtils::parseBonus(keyValue.second)));
}
hero->specialty.push_back (hs); //now, how to get CGHeroInstance from it?
}
}
@ -561,6 +840,65 @@ void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNod
VLC->modh->identifiers.registerObject(scope, "hero", name, object->ID.getNum());
}
void CHeroHandler::afterLoadFinalization()
{
for(ConstTransitivePtr<CHero> hero : heroes)
{
if(hero->specDeprecated.size() > 0 || hero->specialtyDeprecated.size() > 0)
{
logMod->debug("Converting specialty format for hero %s(%s)", hero->identifier, VLC->townh->encodeFaction(hero->heroClass->faction));
std::vector<std::shared_ptr<Bonus>> convertedBonuses;
for(const SSpecialtyInfo & spec : hero->specDeprecated)
{
for(std::shared_ptr<Bonus> b : SpecialtyInfoToBonuses(spec, hero->ID.getNum()))
convertedBonuses.push_back(b);
}
for(const SSpecialtyBonus & spec : hero->specialtyDeprecated)
{
for(std::shared_ptr<Bonus> b : SpecialtyBonusToBonuses(spec))
convertedBonuses.push_back(b);
}
hero->specDeprecated.clear();
hero->specialtyDeprecated.clear();
// store and create json for logging
std::vector<JsonNode> specVec;
std::vector<std::string> specNames;
for(std::shared_ptr<Bonus> bonus : convertedBonuses)
{
hero->specialty.push_back(bonus);
specVec.push_back(bonus->toJsonNode());
// find fitting & unique bonus name
std::string bonusName = bonus->nameForBonus();
if(vstd::contains(specNames, bonusName))
{
int suffix = 2;
while(vstd::contains(specNames, bonusName + std::to_string(suffix)))
suffix++;
bonusName += std::to_string(suffix);
}
specNames.push_back(bonusName);
}
// log new format for easy copy-and-paste
JsonNode specNode(JsonNode::JsonType::DATA_STRUCT);
if(specVec.size() > 1)
{
JsonNode base = JsonUtils::intersect(specVec);
if(base.containsBaseData())
{
specNode["base"] = base;
for(JsonNode & node : specVec)
node = JsonUtils::difference(node, base);
}
}
// add json for bonuses
specNode["bonuses"].Struct();
for(int i = 0; i < specVec.size(); i++)
specNode["bonuses"][specNames[i]] = specVec[i];
logMod->trace("\"specialty\" : %s", specNode.toJson(true));
}
}
}
ui32 CHeroHandler::level (ui64 experience) const
{
return boost::range::upper_bound(expPerLevel, experience) - std::begin(expPerLevel);

View File

@ -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,8 +99,15 @@ public:
h & initialArmy;
h & heroClass;
h & secSkillsInit;
h & spec;
h & specialty;
if(version >= 781)
{
h & specialty;
}
else
{
h & specDeprecated;
h & specialtyDeprecated;
}
h & spells;
h & haveSpellBook;
h & sex;
@ -120,6 +128,10 @@ public:
}
};
// convert deprecated format
std::vector<std::shared_ptr<Bonus>> SpecialtyInfoToBonuses(const SSpecialtyInfo & spec, int sid);
std::vector<std::shared_ptr<Bonus>> SpecialtyBonusToBonuses(const SSpecialtyBonus & spec);
class DLL_LINKAGE CHeroClass
{
public:
@ -289,8 +301,10 @@ public:
std::vector<JsonNode> loadLegacyData(size_t dataSize) override;
void beforeValidate(JsonNode & object);
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();

View File

@ -20,6 +20,7 @@
#include "CSkillHandler.h"
#include "CStack.h"
#include "CArtHandler.h"
#include "StringConstants.h"
#define FOREACH_PARENT(pname) TNodes lparents; getParents(lparents); for(CBonusSystemNode *pname : lparents)
#define FOREACH_CPARENT(pname) TCNodes lparents; getParents(lparents); for(const CBonusSystemNode *pname : lparents)
@ -80,6 +81,12 @@ const std::map<std::string, TPropagatorPtr> bonusPropagatorMap =
{"GLOBAL_EFFECT", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::GLOBAL_EFFECTS)}
}; //untested
const std::map<std::string, TUpdaterPtr> bonusUpdaterMap =
{
{"TIMES_HERO_LEVEL", std::make_shared<TimesHeroLevelUpdater>()},
{"TIMES_STACK_LEVEL", std::make_shared<TimesStackLevelUpdater>()}
};
///CBonusProxy
CBonusProxy::CBonusProxy(const IBonusBearer * Target, CSelector Selector)
: cachedLast(0),
@ -579,22 +586,28 @@ void CBonusSystemNode::getParents(TNodes &out)
void CBonusSystemNode::getBonusesRec(BonusList &out, const CSelector &selector, const CSelector &limit) const
{
BonusList beforeUpdate;
FOREACH_CPARENT(p)
{
p->getBonusesRec(out, selector, limit);
p->getBonusesRec(beforeUpdate, selector, limit);
}
bonuses.getBonuses(beforeUpdate, selector, limit);
bonuses.getBonuses(out, selector, limit);
for(auto b : beforeUpdate)
out.push_back(update(b));
}
void CBonusSystemNode::getAllBonusesRec(BonusList &out) const
{
BonusList beforeUpdate;
FOREACH_CPARENT(p)
{
p->getAllBonusesRec(out);
p->getAllBonusesRec(beforeUpdate);
}
bonuses.getAllBonuses(beforeUpdate);
bonuses.getAllBonuses(out);
for(auto b : beforeUpdate)
out.push_back(update(b));
}
const TBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root, const std::string &cachingStr) const
@ -686,6 +699,13 @@ const TBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelecto
return ret;
}
const std::shared_ptr<Bonus> CBonusSystemNode::update(const std::shared_ptr<Bonus> b) const
{
if(b->updater)
return b->updater->update(b, *this);
return b;
}
CBonusSystemNode::CBonusSystemNode()
: bonuses(true),
exportedBonuses(true),
@ -782,7 +802,7 @@ void CBonusSystemNode::popBonuses(const CSelector &s)
child->popBonuses(s);
}
void CBonusSystemNode::updateBonuses(const CSelector &s)
void CBonusSystemNode::reduceBonusDurations(const CSelector &s)
{
BonusList bl;
exportedBonuses.getBonuses(bl, s, Selector::all);
@ -794,7 +814,7 @@ void CBonusSystemNode::updateBonuses(const CSelector &s)
}
for(CBonusSystemNode *child : children)
child->updateBonuses(s);
child->reduceBonusDurations(s);
}
void CBonusSystemNode::addNewBonus(const std::shared_ptr<Bonus>& b)
@ -1142,6 +1162,85 @@ std::string Bonus::Description() const
return str.str();
}
JsonNode subtypeToJson(Bonus::BonusType type, int subtype)
{
switch(type)
{
case Bonus::PRIMARY_SKILL:
return JsonUtils::stringNode("primSkill." + PrimarySkill::names[subtype]);
case Bonus::SECONDARY_SKILL_PREMY:
return JsonUtils::stringNode("skill." + NSecondarySkill::names[subtype]);
case Bonus::SPECIAL_SPELL_LEV:
case Bonus::SPECIFIC_SPELL_DAMAGE:
case Bonus::SPECIAL_BLESS_DAMAGE:
case Bonus::MAXED_SPELL:
case Bonus::SPECIAL_PECULIAR_ENCHANT:
return JsonUtils::stringNode("spell." + (*VLC->spellh)[SpellID::ESpellID(subtype)]->identifier);
case Bonus::SPECIAL_UPGRADE:
return JsonUtils::stringNode("creature." + CreatureID::encode(subtype));
case Bonus::GENERATE_RESOURCE:
return JsonUtils::stringNode("resource." + GameConstants::RESOURCE_NAMES[subtype]);
default:
return JsonUtils::intNode(subtype);
}
}
JsonNode additionalInfoToJson(Bonus::BonusType type, int addInfo)
{
switch(type)
{
case Bonus::SPECIAL_UPGRADE:
return JsonUtils::stringNode("creature." + CreatureID::encode(addInfo));
default:
return JsonUtils::intNode(addInfo);
}
}
JsonNode Bonus::toJsonNode() const
{
JsonNode root(JsonNode::JsonType::DATA_STRUCT);
root["type"].String() = vstd::findKey(bonusNameMap, type);
if(subtype != -1)
root["subtype"] = subtypeToJson(type, subtype);
if(additionalInfo != -1)
root["addInfo"] = additionalInfoToJson(type, additionalInfo);
if(val != 0)
root["val"].Integer() = val;
if(valType != ADDITIVE_VALUE)
root["valueType"].String() = vstd::findKey(bonusValueMap, valType);
if(limiter)
root["limiters"].Vector().push_back(limiter->toJsonNode());
if(updater)
root["updater"] = updater->toJsonNode();
return root;
}
std::string Bonus::nameForBonus() const
{
switch(type)
{
case Bonus::PRIMARY_SKILL:
return PrimarySkill::names[subtype];
case Bonus::SECONDARY_SKILL_PREMY:
return NSecondarySkill::names[subtype];
case Bonus::SPECIAL_SPELL_LEV:
case Bonus::SPECIFIC_SPELL_DAMAGE:
case Bonus::SPECIAL_BLESS_DAMAGE:
case Bonus::MAXED_SPELL:
case Bonus::SPECIAL_PECULIAR_ENCHANT:
return (*VLC->spellh)[SpellID::ESpellID(subtype)]->identifier;
case Bonus::SPECIAL_UPGRADE:
return CreatureID::encode(subtype) + "2" + CreatureID::encode(additionalInfo);
case Bonus::GENERATE_RESOURCE:
return GameConstants::RESOURCE_NAMES[subtype];
case Bonus::STACKS_SPEED:
return "speed";
default:
return vstd::findKey(bonusNameMap, type);
}
}
Bonus::Bonus(ui16 Dur, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::string Desc, si32 Subtype)
: duration(Dur), type(Type), subtype(Subtype), source(Src), val(Val), sid(ID), description(Desc)
{
@ -1306,6 +1405,11 @@ DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const Bonus &bonus)
printField(effectRange);
#undef printField
if(bonus.limiter)
out << "\tLimiter: " << bonus.limiter->toString() << "\n";
if(bonus.updater)
out << "\tUpdater: " << bonus.updater->toString() << "\n";
return out;
}
@ -1341,6 +1445,18 @@ int ILimiter::limit(const BonusLimitationContext &context) const /*return true t
return false;
}
std::string ILimiter::toString() const
{
return typeid(*this).name();
}
JsonNode ILimiter::toJsonNode() const
{
JsonNode root(JsonNode::JsonType::DATA_STRUCT);
root["type"].String() = toString();
return root;
}
int CCreatureTypeLimiter::limit(const BonusLimitationContext &context) const
{
const CCreature *c = retrieveCreature(&context.node);
@ -1366,6 +1482,26 @@ void CCreatureTypeLimiter::setCreature (CreatureID id)
creature = VLC->creh->creatures[id];
}
std::string CCreatureTypeLimiter::toString() const
{
char buf[100];
sprintf(buf, "CCreatureTypeLimiter(creature=%s, includeUpgrades=%s)",
creature->identifier.c_str(),
(includeUpgrades ? "true" : "false"));
return std::string(buf);
}
JsonNode CCreatureTypeLimiter::toJsonNode() const
{
JsonNode root(JsonNode::JsonType::DATA_STRUCT);
root["type"].String() = "CREATURE_TYPE_LIMITER";
root["parameters"].Vector().push_back(JsonUtils::stringNode(creature->identifier));
root["parameters"].Vector().push_back(JsonUtils::boolNode(includeUpgrades));
return root;
}
HasAnotherBonusLimiter::HasAnotherBonusLimiter( Bonus::BonusType bonus )
: type(bonus), subtype(0), isSubtypeRelevant(false)
{
@ -1390,6 +1526,32 @@ int HasAnotherBonusLimiter::limit(const BonusLimitationContext &context) const
return NOT_SURE;
}
std::string HasAnotherBonusLimiter::toString() const
{
char buf[100];
std::string typeName = vstd::findKey(bonusNameMap, type);
if(isSubtypeRelevant)
sprintf(buf, "HasAnotherBonusLimiter(type=%s, subtype=%d)", typeName.c_str(), subtype);
else
sprintf(buf, "HasAnotherBonusLimiter(type=%s)", typeName.c_str());
return std::string(buf);
}
JsonNode HasAnotherBonusLimiter::toJsonNode() const
{
JsonNode root(JsonNode::JsonType::DATA_STRUCT);
std::string typeName = vstd::findKey(bonusNameMap, type);
root["type"].String() = "HAS_ANOTHER_BONUS_LIMITER";
root["parameters"].Vector().push_back(JsonUtils::stringNode(typeName));
if(isSubtypeRelevant)
root["parameters"].Vector().push_back(JsonUtils::intNode(subtype));
return root;
}
IPropagator::~IPropagator()
{
@ -1543,3 +1705,136 @@ void LimiterList::add( TLimiterPtr limiter )
{
limiters.push_back(limiter);
}
// Updaters
std::shared_ptr<Bonus> Bonus::addUpdater(TUpdaterPtr Updater)
{
updater = Updater;
return this->shared_from_this();
}
IUpdater::~IUpdater()
{
}
const std::shared_ptr<Bonus> IUpdater::update(const std::shared_ptr<Bonus> b, const CBonusSystemNode & context) const
{
return b;
}
std::string IUpdater::toString() const
{
return typeid(*this).name();
}
JsonNode IUpdater::toJsonNode() const
{
return JsonNode(JsonNode::JsonType::DATA_NULL);
}
GrowsWithLevelUpdater::GrowsWithLevelUpdater() : valPer20(0), stepSize(1)
{
}
GrowsWithLevelUpdater::GrowsWithLevelUpdater(int valPer20, int stepSize) : valPer20(valPer20), stepSize(stepSize)
{
}
const std::shared_ptr<Bonus> GrowsWithLevelUpdater::update(const std::shared_ptr<Bonus> b, const CBonusSystemNode & context) const
{
if(context.getNodeType() == CBonusSystemNode::HERO)
{
int level = static_cast<const CGHeroInstance &>(context).level;
int steps = stepSize ? level / stepSize : level;
//rounding follows format for HMM3 creature specialty bonus
int newVal = (valPer20 * steps + 19) / 20;
//return copy of bonus with updated val
std::shared_ptr<Bonus> newBonus = std::make_shared<Bonus>(*b);
newBonus->val = newVal;
return newBonus;
}
return b;
}
std::string GrowsWithLevelUpdater::toString() const
{
return boost::str(boost::format("GrowsWithLevelUpdater(valPer20=%d, stepSize=%d)") % valPer20 % stepSize);
}
JsonNode GrowsWithLevelUpdater::toJsonNode() const
{
JsonNode root(JsonNode::JsonType::DATA_STRUCT);
root["type"].String() = "GROWS_WITH_LEVEL";
root["parameters"].Vector().push_back(JsonUtils::intNode(valPer20));
if(stepSize > 1)
root["parameters"].Vector().push_back(JsonUtils::intNode(stepSize));
return root;
}
TimesHeroLevelUpdater::TimesHeroLevelUpdater()
{
}
const std::shared_ptr<Bonus> TimesHeroLevelUpdater::update(const std::shared_ptr<Bonus> b, const CBonusSystemNode & context) const
{
if(context.getNodeType() == CBonusSystemNode::HERO)
{
int level = static_cast<const CGHeroInstance &>(context).level;
std::shared_ptr<Bonus> newBonus = std::make_shared<Bonus>(*b);
newBonus->val *= level;
return newBonus;
}
return b;
}
std::string TimesHeroLevelUpdater::toString() const
{
return "TimesHeroLevelUpdater";
}
JsonNode TimesHeroLevelUpdater::toJsonNode() const
{
return JsonUtils::stringNode("TIMES_HERO_LEVEL");
}
TimesStackLevelUpdater::TimesStackLevelUpdater()
{
}
const std::shared_ptr<Bonus> TimesStackLevelUpdater::update(const std::shared_ptr<Bonus> b, const CBonusSystemNode & context) const
{
if(context.getNodeType() == CBonusSystemNode::STACK_INSTANCE)
{
int level = static_cast<const CStackInstance &>(context).getLevel();
std::shared_ptr<Bonus> newBonus = std::make_shared<Bonus>(*b);
newBonus->val *= level;
return newBonus;
}
else if(context.getNodeType() == CBonusSystemNode::STACK_BATTLE)
{
const CStack & stack = static_cast<const CStack &>(context);
//only update if stack doesn't have an instance (summons, war machines)
//otherwise we'd end up multiplying twice
if(stack.base == nullptr)
{
int level = stack.type->level;
std::shared_ptr<Bonus> newBonus = std::make_shared<Bonus>(*b);
newBonus->val *= level;
return newBonus;
}
}
return b;
}
std::string TimesStackLevelUpdater::toString() const
{
return "TimesStackLevelUpdater";
}
JsonNode TimesStackLevelUpdater::toJsonNode() const
{
return JsonUtils::stringNode("TIMES_STACK_LEVEL");
}

View File

@ -10,6 +10,7 @@
#pragma once
#include "GameConstants.h"
#include "JsonNode.h"
class CCreature;
struct Bonus;
@ -17,11 +18,13 @@ class IBonusBearer;
class CBonusSystemNode;
class ILimiter;
class IPropagator;
class IUpdater;
class BonusList;
typedef std::shared_ptr<BonusList> TBonusListPtr;
typedef std::shared_ptr<ILimiter> TLimiterPtr;
typedef std::shared_ptr<IPropagator> TPropagatorPtr;
typedef std::shared_ptr<IUpdater> TUpdaterPtr;
typedef std::set<CBonusSystemNode*> TNodes;
typedef std::set<const CBonusSystemNode*> TCNodes;
typedef std::vector<CBonusSystemNode *> TNodesVector;
@ -204,14 +207,14 @@ private:
BONUS_NAME(NO_LUCK) /*eg. when fighting on cursed ground*/ \
BONUS_NAME(NO_MORALE) /*eg. when fighting on cursed ground*/ \
BONUS_NAME(DARKNESS) /*val = radius */ \
BONUS_NAME(SPECIAL_SECONDARY_SKILL) /*val = id, additionalInfo = value per level in percent*/ \
BONUS_NAME(SPECIAL_SPELL_LEV) /*val = id, additionalInfo = value per level in percent*/\
BONUS_NAME(SPECIAL_SECONDARY_SKILL) /*subtype = id, val = value per level in percent*/ \
BONUS_NAME(SPECIAL_SPELL_LEV) /*subtype = id, val = value per level in percent*/\
BONUS_NAME(SPELL_DAMAGE) /*val = value*/\
BONUS_NAME(SPECIFIC_SPELL_DAMAGE) /*subtype = id of spell, val = value*/\
BONUS_NAME(SPECIAL_BLESS_DAMAGE) /*val = spell (bless), additionalInfo = value per level in percent*/\
BONUS_NAME(MAXED_SPELL) /*val = id*/\
BONUS_NAME(SPECIAL_PECULIAR_ENCHANT) /*blesses and curses with id = val dependent on unit's level, subtype = 0 or 1 for Coronius*/\
BONUS_NAME(SPECIAL_UPGRADE) /*val = base, additionalInfo = target */\
BONUS_NAME(SPECIAL_UPGRADE) /*subtype = base, additionalInfo = target */\
BONUS_NAME(DRAGON_NATURE) \
BONUS_NAME(CREATURE_DAMAGE)/*subtype 0 = both, 1 = min, 2 = max*/\
BONUS_NAME(EXP_MULTIPLIER)/* val - percent of additional exp gained by stack/commander (base value 100)*/\
@ -340,6 +343,7 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
TLimiterPtr limiter;
TPropagatorPtr propagator;
TUpdaterPtr updater;
std::string description;
@ -362,6 +366,10 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
h & effectRange;
h & limiter;
h & propagator;
if(version >= 781)
{
h & updater;
}
}
template <typename Ptr>
@ -419,9 +427,12 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
}
std::string Description() const;
JsonNode toJsonNode() const;
std::string nameForBonus() const; // generate suitable name for bonus - e.g. for storing in json struct
std::shared_ptr<Bonus> addLimiter(TLimiterPtr Limiter); //returns this for convenient chain-calls
std::shared_ptr<Bonus> addPropagator(TPropagatorPtr Propagator); //returns this for convenient chain-calls
std::shared_ptr<Bonus> addUpdater(TUpdaterPtr Updater); //returns this for convenient chain-calls
};
DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const Bonus &bonus);
@ -583,6 +594,8 @@ public:
virtual ~ILimiter();
virtual int limit(const BonusLimitationContext &context) const; //0 - accept bonus; 1 - drop bonus; 2 - delay (drops eventually)
virtual std::string toString() const;
virtual JsonNode toJsonNode() const;
template <typename Handler> void serialize(Handler &h, const int version)
{
@ -663,6 +676,7 @@ private:
void getBonusesRec(BonusList &out, const CSelector &selector, const CSelector &limit) const;
void getAllBonusesRec(BonusList &out) const;
const TBonusListPtr getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr) const;
const std::shared_ptr<Bonus> update(const std::shared_ptr<Bonus> b) const;
public:
explicit CBonusSystemNode();
@ -703,7 +717,7 @@ public:
///removes bonuses by selector
void popBonuses(const CSelector &s);
///updates count of remaining turns and removes outdated bonuses by selector
void updateBonuses(const CSelector &s);
void reduceBonusDurations(const CSelector &s);
virtual std::string bonusToString(const std::shared_ptr<Bonus>& bonus, bool description) const {return "";}; //description or bonus name
virtual std::string nodeName() const;
@ -847,6 +861,8 @@ public:
void setCreature (CreatureID id);
int limit(const BonusLimitationContext &context) const override;
virtual std::string toString() const override;
virtual JsonNode toJsonNode() const override;
template <typename Handler> void serialize(Handler &h, const int version)
{
@ -867,6 +883,8 @@ public:
HasAnotherBonusLimiter(Bonus::BonusType bonus, TBonusSubtype _subtype);
int limit(const BonusLimitationContext &context) const override;
virtual std::string toString() const override;
virtual JsonNode toJsonNode() const override;
template <typename Handler> void serialize(Handler &h, const int version)
{
@ -997,7 +1015,7 @@ extern DLL_LINKAGE const std::map<std::string, ui16> bonusDurationMap;
extern DLL_LINKAGE const std::map<std::string, Bonus::LimitEffect> bonusLimitEffect;
extern DLL_LINKAGE const std::map<std::string, TLimiterPtr> bonusLimiterMap;
extern DLL_LINKAGE const std::map<std::string, TPropagatorPtr> bonusPropagatorMap;
extern DLL_LINKAGE const std::map<std::string, TUpdaterPtr> bonusUpdaterMap;
// BonusList template that requires full interface of CBonusSystemNode
template <class InputIterator>
@ -1006,3 +1024,70 @@ void BonusList::insert(const int position, InputIterator first, InputIterator la
bonuses.insert(bonuses.begin() + position, first, last);
changed();
}
// observers for updating bonuses based on certain events (e.g. hero gaining level)
class DLL_LINKAGE IUpdater
{
public:
virtual ~IUpdater();
virtual const std::shared_ptr<Bonus> update(const std::shared_ptr<Bonus> b, const CBonusSystemNode & context) const;
virtual std::string toString() const;
virtual JsonNode toJsonNode() const;
template <typename Handler> void serialize(Handler & h, const int version)
{
}
};
class DLL_LINKAGE GrowsWithLevelUpdater : public IUpdater
{
public:
int valPer20;
int stepSize;
GrowsWithLevelUpdater();
GrowsWithLevelUpdater(int valPer20, int stepSize = 1);
template <typename Handler> void serialize(Handler & h, const int version)
{
h & static_cast<IUpdater &>(*this);
h & valPer20;
h & stepSize;
}
const std::shared_ptr<Bonus> update(const std::shared_ptr<Bonus> b, const CBonusSystemNode & context) const override;
virtual std::string toString() const override;
virtual JsonNode toJsonNode() const override;
};
class DLL_LINKAGE TimesHeroLevelUpdater : public IUpdater
{
public:
TimesHeroLevelUpdater();
template <typename Handler> void serialize(Handler & h, const int version)
{
h & static_cast<IUpdater &>(*this);
}
const std::shared_ptr<Bonus> update(const std::shared_ptr<Bonus> b, const CBonusSystemNode & context) const override;
virtual std::string toString() const override;
virtual JsonNode toJsonNode() const override;
};
class DLL_LINKAGE TimesStackLevelUpdater : public IUpdater
{
public:
TimesStackLevelUpdater();
template <typename Handler> void serialize(Handler & h, const int version)
{
h & static_cast<IUpdater &>(*this);
}
const std::shared_ptr<Bonus> update(const std::shared_ptr<Bonus> b, const CBonusSystemNode & context) const override;
virtual std::string toString() const override;
virtual JsonNode toJsonNode() const override;
};

View File

@ -32,19 +32,22 @@ void JsonWriter::writeContainer(Iterator begin, Iterator end)
while (begin != end)
{
out<<",\n";
out << (compactMode ? ", " : ",\n");
writeEntry(begin++);
}
out<<"\n";
out << (compactMode ? "" : "\n");
prefix.resize(prefix.size()-1);
}
void JsonWriter::writeEntry(JsonMap::const_iterator entry)
{
if (!entry->second.meta.empty())
out << prefix << " // " << entry->second.meta << "\n";
out << prefix;
if(!compactMode)
{
if (!entry->second.meta.empty())
out << prefix << " // " << entry->second.meta << "\n";
out << prefix;
}
writeString(entry->first);
out << " : ";
writeNode(entry->second);
@ -52,9 +55,12 @@ void JsonWriter::writeEntry(JsonMap::const_iterator entry)
void JsonWriter::writeEntry(JsonVector::const_iterator entry)
{
if (!entry->meta.empty())
out << prefix << " // " << entry->meta << "\n";
out << prefix;
if(!compactMode)
{
if (!entry->meta.empty())
out << prefix << " // " << entry->meta << "\n";
out << prefix;
}
writeNode(*entry);
}
@ -94,6 +100,10 @@ void JsonWriter::writeString(const std::string &string)
void JsonWriter::writeNode(const JsonNode &node)
{
bool originalMode = compactMode;
if(compact && !compactMode && node.isCompact())
compactMode = true;
switch(node.getType())
{
break; case JsonNode::JsonType::DATA_NULL:
@ -112,21 +122,24 @@ void JsonWriter::writeNode(const JsonNode &node)
writeString(node.String());
break; case JsonNode::JsonType::DATA_VECTOR:
out << "[" << "\n";
out << "[" << (compactMode ? " " : "\n");
writeContainer(node.Vector().begin(), node.Vector().end());
out << prefix << "]";
out << (compactMode ? " " : prefix) << "]";
break; case JsonNode::JsonType::DATA_STRUCT:
out << "{" << "\n";
out << "{" << (compactMode ? " " : "\n");
writeContainer(node.Struct().begin(), node.Struct().end());
out << prefix << "}";
out << (compactMode ? " " : prefix) << "}";
break; case JsonNode::JsonType::DATA_INTEGER:
out << node.Integer();
}
compactMode = originalMode;
}
JsonWriter::JsonWriter(std::ostream & output)
: out(output)
JsonWriter::JsonWriter(std::ostream & output, bool compact)
: out(output), compact(compact)
{
}

View File

@ -16,6 +16,10 @@ class JsonWriter
//prefix for each line (tabulation)
std::string prefix;
std::ostream & out;
//sets whether compact nodes are written in single-line format
bool compact;
//tracks whether we are currently using single-line format
bool compactMode = false;
public:
template<typename Iterator>
void writeContainer(Iterator begin, Iterator end);
@ -23,7 +27,7 @@ public:
void writeEntry(JsonVector::const_iterator entry);
void writeString(const std::string & string);
void writeNode(const JsonNode & node);
JsonWriter(std::ostream & output);
JsonWriter(std::ostream & output, bool compact = false);
};
//Tiny string class that uses const char* as data for speed, members are private

View File

@ -214,6 +214,50 @@ bool JsonNode::isNumber() const
return type == JsonType::DATA_INTEGER || type == JsonType::DATA_FLOAT;
}
bool JsonNode::containsBaseData() const
{
switch(type)
{
case JsonType::DATA_NULL:
return false;
case JsonType::DATA_STRUCT:
for(auto elem : *data.Struct)
{
if(elem.second.containsBaseData())
return true;
}
return false;
default:
//other types (including vector) cannot be extended via merge
return true;
}
}
bool JsonNode::isCompact() const
{
switch(type)
{
case JsonType::DATA_VECTOR:
for(JsonNode & elem : *data.Vector)
{
if(!elem.isCompact())
return false;
}
return true;
case JsonType::DATA_STRUCT:
{
int propertyCount = data.Struct->size();
if(propertyCount == 0)
return true;
else if(propertyCount == 1)
return data.Struct->begin()->second.isCompact();
}
return false;
default:
return true;
}
}
void JsonNode::clear()
{
setType(JsonType::DATA_NULL);
@ -367,10 +411,10 @@ JsonNode & JsonNode::resolvePointer(const std::string &jsonPointer)
return ::resolvePointer(*this, jsonPointer);
}
std::string JsonNode::toJson() const
std::string JsonNode::toJson(bool compact) const
{
std::ostringstream out;
JsonWriter writer(out);
JsonWriter writer(out, compact);
writer.writeNode(*this);
out << "\n";
return out.str();
@ -395,7 +439,7 @@ std::shared_ptr<Bonus> JsonUtils::parseBonus (const JsonVector &ability_vec) //T
auto it = bonusNameMap.find(type);
if (it == bonusNameMap.end())
{
logMod->error("Error: invalid ability type %s", type);
logMod->error("Error: invalid ability type %s.", type);
return b;
}
b->type = it->second;
@ -413,7 +457,7 @@ const T & parseByMap(const std::map<std::string, T> & map, const JsonNode * val,
auto it = map.find(type);
if (it == map.end())
{
logMod->error("Error: invalid %s%s", err, type);
logMod->error("Error: invalid %s%s.", err, type);
return defaultValue;
}
else
@ -445,7 +489,7 @@ void JsonUtils::resolveIdentifier(si32 &var, const JsonNode &node, std::string n
});
break;
default:
logMod->error("Error! Wrong identifier used for value of %s", name);
logMod->error("Error! Wrong identifier used for value of %s.", name);
}
}
}
@ -489,7 +533,7 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
auto it = bonusNameMap.find(type);
if (it == bonusNameMap.end())
{
logMod->error("Error: invalid ability type %s", type);
logMod->error("Error: invalid ability type %s.", type);
return false;
}
b->type = it->second;
@ -580,7 +624,7 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
auto it = bonusNameMap.find(anotherBonusType);
if (it == bonusNameMap.end())
{
logMod->error("Error: invalid ability type %s", anotherBonusType);
logMod->error("Error: invalid ability type %s.", anotherBonusType);
continue;
}
l2->type = it->second;
@ -603,6 +647,31 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
if (!value->isNull())
b->propagator = parseByMap(bonusPropagatorMap, value, "propagator type ");
value = &ability["updater"];
if(!value->isNull())
{
const JsonNode & updaterJson = *value;
switch(updaterJson.getType())
{
case JsonNode::JsonType::DATA_STRING:
b->addUpdater(parseByMap(bonusUpdaterMap, &updaterJson, "updater type "));
break;
case JsonNode::JsonType::DATA_STRUCT:
if(updaterJson["type"].String() == "GROWS_WITH_LEVEL")
{
std::shared_ptr<GrowsWithLevelUpdater> updater = std::make_shared<GrowsWithLevelUpdater>();
const JsonVector param = updaterJson["parameters"].Vector();
updater->valPer20 = param[0].Integer();
if(param.size() > 1)
updater->stepSize = param[1].Integer();
b->addUpdater(updater);
}
else
logMod->warn("Unknown updater type \"%s\"", updaterJson["type"].String());
break;
}
}
return true;
}
@ -725,6 +794,7 @@ bool JsonUtils::validate(const JsonNode &node, std::string schemaName, std::stri
{
logMod->warn("Data in %s is invalid!", dataName);
logMod->warn(log);
logMod->trace("%s json: %s", dataName, node.toJson(true));
}
return log.empty();
}
@ -822,6 +892,90 @@ void JsonUtils::inherit(JsonNode & descendant, const JsonNode & base)
descendant.swap(inheritedNode);
}
JsonNode JsonUtils::intersect(const std::vector<JsonNode> & nodes, bool pruneEmpty)
{
if(nodes.size() == 0)
return nullNode;
JsonNode result = nodes[0];
for(int i = 1; i < nodes.size(); i++)
{
if(result.isNull())
break;
result = JsonUtils::intersect(result, nodes[i], pruneEmpty);
}
return result;
}
JsonNode JsonUtils::intersect(const JsonNode & a, const JsonNode & b, bool pruneEmpty)
{
if(a.getType() == JsonNode::JsonType::DATA_STRUCT && b.getType() == JsonNode::JsonType::DATA_STRUCT)
{
// intersect individual properties
JsonNode result(JsonNode::JsonType::DATA_STRUCT);
for(auto property : a.Struct())
{
if(vstd::contains(b.Struct(), property.first))
{
JsonNode propertyIntersect = JsonUtils::intersect(property.second, b.Struct().find(property.first)->second);
if(pruneEmpty && !propertyIntersect.containsBaseData())
continue;
result[property.first] = propertyIntersect;
}
}
return result;
}
else
{
// not a struct - same or different, no middle ground
if(a == b)
return a;
}
return nullNode;
}
JsonNode JsonUtils::difference(const JsonNode & node, const JsonNode & base)
{
auto addsInfo = [](JsonNode diff) -> bool
{
switch(diff.getType())
{
case JsonNode::JsonType::DATA_NULL:
return false;
case JsonNode::JsonType::DATA_STRUCT:
return diff.Struct().size() > 0;
default:
return true;
}
};
if(node.getType() == JsonNode::JsonType::DATA_STRUCT && base.getType() == JsonNode::JsonType::DATA_STRUCT)
{
// subtract individual properties
JsonNode result(JsonNode::JsonType::DATA_STRUCT);
for(auto property : node.Struct())
{
if(vstd::contains(base.Struct(), property.first))
{
const JsonNode propertyDifference = JsonUtils::difference(property.second, base.Struct().find(property.first)->second);
if(addsInfo(propertyDifference))
result[property.first] = propertyDifference;
}
else
{
result[property.first] = property.second;
}
}
return result;
}
else
{
if(node == base)
return nullNode;
}
return node;
}
JsonNode JsonUtils::assembleFromFiles(std::vector<std::string> files)
{
bool isValid;
@ -860,3 +1014,31 @@ JsonNode JsonUtils::assembleFromFiles(std::string filename)
}
return result;
}
DLL_LINKAGE JsonNode JsonUtils::boolNode(bool value)
{
JsonNode node;
node.Bool() = value;
return node;
}
DLL_LINKAGE JsonNode JsonUtils::floatNode(double value)
{
JsonNode node;
node.Float() = value;
return node;
}
DLL_LINKAGE JsonNode JsonUtils::stringNode(std::string value)
{
JsonNode node;
node.String() = value;
return node;
}
DLL_LINKAGE JsonNode JsonUtils::intNode(si64 value)
{
JsonNode node;
node.Integer() = value;
return node;
}

View File

@ -75,6 +75,10 @@ public:
bool isNull() const;
bool isNumber() const;
/// true if node contains not-null data that cannot be extended via merging
/// used for generating common base node from multiple nodes (e.g. bonuses)
bool containsBaseData() const;
bool isCompact() const;
/// removes all data from node and sets type to null
void clear();
@ -110,7 +114,7 @@ public:
JsonNode & operator[](std::string child);
const JsonNode & operator[](std::string child) const;
std::string toJson() const;
std::string toJson(bool compact = false) const;
template <typename Handler> void serialize(Handler &h, const int version)
{
@ -188,6 +192,22 @@ namespace JsonUtils
*/
DLL_LINKAGE void inherit(JsonNode & descendant, const JsonNode & base);
/**
* @brief construct node representing the common structure of input nodes
* @param pruneEmpty - omit common properties whose intersection is empty
* different types: null
* struct: recursive intersect on common properties
* other: input if equal, null otherwise
*/
DLL_LINKAGE JsonNode intersect(const JsonNode & a, const JsonNode & b, bool pruneEmpty = true);
DLL_LINKAGE JsonNode intersect(const std::vector<JsonNode> & nodes, bool pruneEmpty = true);
/**
* @brief construct node representing the difference "node - base"
* merging difference with base gives node
*/
DLL_LINKAGE JsonNode difference(const JsonNode & node, const JsonNode & base);
/**
* @brief generate one Json structure from multiple files
* @param files - list of filenames with parts of json structure
@ -220,6 +240,12 @@ namespace JsonUtils
/// get schema by json URI: vcmi:<name of file in schemas directory>#<entry in file, optional>
/// example: schema "vcmi:settings" is used to check user settings
DLL_LINKAGE const JsonNode & getSchema(std::string URI);
/// for easy construction of JsonNodes; helps with inserting primitives into vector node
DLL_LINKAGE JsonNode boolNode(bool value);
DLL_LINKAGE JsonNode floatNode(double value);
DLL_LINKAGE JsonNode stringNode(std::string value);
DLL_LINKAGE JsonNode intNode(si64 value);
}
namespace JsonDetail

View File

@ -1085,8 +1085,8 @@ DLL_LINKAGE void NewTurn::applyGs(CGameState *gs)
// Update bonuses before doing anything else so hero don't get more MP than needed
gs->globalEffects.popBonuses(Bonus::OneDay); //works for children -> all game objs
gs->globalEffects.updateBonuses(Bonus::NDays);
gs->globalEffects.updateBonuses(Bonus::OneWeek);
gs->globalEffects.reduceBonusDurations(Bonus::NDays);
gs->globalEffects.reduceBonusDurations(Bonus::OneWeek);
//TODO not really a single root hierarchy, what about bonuses placed elsewhere? [not an issue with H3 mechanics but in the future...]
for(NewTurn::Hero h : heroes) //give mana/movement point

View File

@ -765,7 +765,7 @@ void BattleInfo::nextRound(int32_t roundNr)
for(CStack * s : stacks)
{
// new turn effects
s->updateBonuses(Bonus::NTurns);
s->reduceBonusDurations(Bonus::NTurns);
s->afterNewRound();
}

View File

@ -496,9 +496,6 @@ 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?
if(!type)
initHero(rand); //TODO: set up everything for prison before specialties are configured
@ -514,246 +511,23 @@ 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)
addNewBonus(b);
//dito for old-style bonuses -> compatibility for old savegames
for(SSpecialtyBonus & sb : type->specialtyDeprecated)
for(std::shared_ptr<Bonus> b : sb.bonuses)
addNewBonus(b);
for(SSpecialtyInfo & spec : type->specDeprecated)
for(std::shared_ptr<Bonus> b : SpecialtyInfoToBonuses(spec, type->ID.getNum()))
addNewBonus(b);
//initialize bonuses
recreateSecondarySkillsBonuses();
Updatespecialty();
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()
{
@ -766,6 +540,23 @@ void CGHeroInstance::recreateSecondarySkillsBonuses()
updateSkill(SecondarySkill(skill_info.first), level);
}
void CGHeroInstance::recreateSpecialtyBonuses(std::vector<HeroSpecial *> & specialtyDeprecated)
{
auto HeroSpecialToSpecialtyBonus = [](HeroSpecial & hs) -> SSpecialtyBonus
{
SSpecialtyBonus sb;
sb.growsWithLevel = hs.growsWithLevel;
sb.bonuses = hs.getBonusList();
return sb;
};
for(HeroSpecial * hs : specialtyDeprecated)
{
for(std::shared_ptr<Bonus> b : SpecialtyBonusToBonuses(HeroSpecialToSpecialtyBonus(*hs)))
addNewBonus(b);
}
}
void CGHeroInstance::updateSkill(SecondarySkill which, int val)
{
auto skillBonus = (*VLC->skillh)[which]->getBonus(val);
@ -863,7 +654,7 @@ int64_t CGHeroInstance::getSpellBonus(const spells::Spell * spell, int64_t base,
base *= (100.0 + maxSchoolBonus) / 100.0;
if(affectedStack && affectedStack->creatureLevel() > 0) //Hero specials like Solmyr, Deemer
base *= (100. + ((valOfBonuses(Bonus::SPECIAL_SPELL_LEV, spell->getIndex()) * level) / affectedStack->creatureLevel())) / 100.0;
base *= (100. + valOfBonuses(Bonus::SPECIAL_SPELL_LEV, spell->getIndex()) / affectedStack->creatureLevel()) / 100.0;
return base;
}
@ -1214,11 +1005,6 @@ int CGHeroInstance::maxSpellLevel() const
void CGHeroInstance::deserializationFix()
{
artDeserializationFix(this);
for (auto hs : specialty)
{
attachTo (hs);
}
}
CBonusSystemNode * CGHeroInstance::whereShouldBeAttached(CGameState *gs)
@ -1471,8 +1257,8 @@ void CGHeroInstance::levelUp(std::vector<SecondarySkill> skills)
}
}
//specialty
Updatespecialty();
//update specialty and other bonuses that scale with level
treeHasChanged();
}
void CGHeroInstance::levelUpAutomatically(CRandomGenerator & rand)

View File

@ -95,7 +95,8 @@ public:
}
} patrol;
struct DLL_LINKAGE HeroSpecial : CBonusSystemNode
// deprecated - used only for loading of old saves
struct HeroSpecial : CBonusSystemNode
{
bool growsWithLevel;
@ -108,8 +109,6 @@ public:
}
};
std::vector<HeroSpecial*> specialty;
struct DLL_LINKAGE SecondarySkillsInfo
{
//skills are determined, initialized at map start
@ -215,7 +214,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);
@ -269,6 +267,7 @@ protected:
private:
void levelUpAutomatically(CRandomGenerator & rand);
void recreateSpecialtyBonuses(std::vector<HeroSpecial*> & specialtyDeprecated);
public:
std::string getHeroTypeName() const;
@ -297,7 +296,13 @@ public:
h & visitedTown;
h & boat;
h & type;
h & specialty;
if(version < 781)
{
std::vector<HeroSpecial*> specialtyDeprecated;
h & specialtyDeprecated;
if(!h.saving)
recreateSpecialtyBonuses(specialtyDeprecated);
}
h & commander;
h & visitedObjects;
BONUS_TREE_DESERIALIZATION_FIX

View File

@ -135,6 +135,12 @@ void registerTypesMapObjectTypes(Serializer &s)
REGISTER_GENERIC_HANDLER(CGWitchHut);
#undef REGISTER_GENERIC_HANDLER
s.template registerType<IUpdater, GrowsWithLevelUpdater>();
s.template registerType<IUpdater, TimesHeroLevelUpdater>();
s.template registerType<IUpdater, TimesStackLevelUpdater>();
//new types (other than netpacks) must register here
//order of type registration is critical for loading old savegames
}
template<typename Serializer>

View File

@ -12,7 +12,7 @@
#include "../ConstTransitivePtr.h"
#include "../GameConstants.h"
const ui32 SERIALIZATION_VERSION = 780;
const ui32 SERIALIZATION_VERSION = 781;
const ui32 MINIMAL_SERIALIZATION_VERSION = 753;
const std::string SAVEGAME_MAGIC = "VCMISVG";

View File

@ -219,7 +219,7 @@ void Timed::prepareEffects(SetStackEffect & sse, const Mechanics * m, const Effe
}
if(casterHero && casterHero->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, m->getSpellIndex())) //TODO: better handling of bonus percentages
{
int damagePercent = casterHero->level * casterHero->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, m->getSpellIndex()) / tier;
int damagePercent = casterHero->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, m->getSpellIndex()) / tier;
Bonus specialBonus(Bonus::N_TURNS, Bonus::CREATURE_DAMAGE, Bonus::SPELL_EFFECT, damagePercent, m->getSpellIndex(), 0, Bonus::PERCENT_TO_ALL);
specialBonus.turnsRemain = duration;
buffer.push_back(specialBonus);