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 * Various bug fixes
Henning Koehler, <henning.koehler.nz@gmail.com> 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 * Improve support for WoG commander artifacts and skill descriptions
* Added basic support for secondary skill modding * Added basic support for secondary skill modding
* Map object sounds can now be configured via json * Map object sounds can now be configured via json
* Added bonus updaters for hero specialties
SOUND: SOUND:
* Fixed many mising or wrong pickup and visit sounds for map objects * 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); 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 //removes element i from container c, returns false if c does not contain i
template <typename Container, typename Item> template <typename Container, typename Item>
typename Container::size_type operator-=(Container &c, const Item &i) typename Container::size_type operator-=(Container &c, const Item &i)

View File

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

View File

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

View File

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

View File

@ -9,10 +9,9 @@
{ "skill" : "armorer", "level": "basic" }, { "skill" : "armorer", "level": "basic" },
{ "skill" : "resistance", "level": "basic" } { "skill" : "resistance", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "creature" : "basilisk"
{ "type":1, "val": 0, "subtype": 0, "info": 106 } },
],
"army" : "army" :
[ [
{ {
@ -39,10 +38,9 @@
{ "skill" : "armorer", "level": "basic" }, { "skill" : "armorer", "level": "basic" },
{ "skill" : "leadership", "level": "basic" } { "skill" : "leadership", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "creature" : "gnoll"
{ "type":1, "val": 0, "subtype": 0, "info": 98 } }
]
}, },
"wystan": "wystan":
{ {
@ -54,10 +52,9 @@
{ "skill" : "armorer", "level": "basic" }, { "skill" : "armorer", "level": "basic" },
{ "skill" : "archery", "level": "basic" } { "skill" : "archery", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "creature" : "lizardman"
{ "type":1, "val": 0, "subtype": 0, "info": 100 } }
]
}, },
"tazar": "tazar":
{ {
@ -68,10 +65,17 @@
[ [
{ "skill" : "armorer", "level": "advanced" } { "skill" : "armorer", "level": "advanced" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":2, "val": 5, "subtype": 23, "info": 0 } "armorer" : {
] "subtype" : "skill.armorer",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
}, },
"alkin": "alkin":
{ {
@ -83,10 +87,9 @@
{ "skill" : "armorer", "level": "basic" }, { "skill" : "armorer", "level": "basic" },
{ "skill" : "offence", "level": "basic" } { "skill" : "offence", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "creature" : "gorgon"
{ "type":1, "val": 0, "subtype": 0, "info": 102 } }
]
}, },
"korbac": "korbac":
{ {
@ -98,10 +101,9 @@
{ "skill" : "armorer", "level": "basic" }, { "skill" : "armorer", "level": "basic" },
{ "skill" : "pathfinding", "level": "basic" } { "skill" : "pathfinding", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "creature" : "serpentFly"
{ "type":1, "val": 0, "subtype": 0, "info": 104 } }
]
}, },
"gerwulf": "gerwulf":
{ {
@ -113,10 +115,9 @@
{ "skill" : "armorer", "level": "basic" }, { "skill" : "armorer", "level": "basic" },
{ "skill" : "artillery", "level": "basic" } { "skill" : "artillery", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "creature" : "ballista"
{ "type":1, "val": 0, "subtype": 0, "info": 146 } }
]
}, },
"broghild": "broghild":
{ {
@ -128,10 +129,9 @@
{ "skill" : "armorer", "level": "basic" }, { "skill" : "armorer", "level": "basic" },
{ "skill" : "scouting", "level": "basic" } { "skill" : "scouting", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "creature" : "wyvern"
{ "type":1, "val": 0, "subtype": 0, "info": 108 } }
]
}, },
"mirlanda": "mirlanda":
{ {
@ -143,10 +143,15 @@
[ [
{ "skill" : "wisdom", "level": "advanced" } { "skill" : "wisdom", "level": "advanced" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":8, "val": 0, "subtype": 45, "info": 0 } "weakness" : {
] "addInfo" : 0,
"subtype" : "spell.weakness",
"type" : "SPECIAL_PECULIAR_ENCHANT"
}
}
}
}, },
"rosic": "rosic":
{ {
@ -159,10 +164,17 @@
{ "skill" : "wisdom", "level": "basic" }, { "skill" : "wisdom", "level": "basic" },
{ "skill" : "mysticism", "level": "basic" } { "skill" : "mysticism", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":2, "val": 5, "subtype": 8, "info": 1 } "mysticism" : {
] "subtype" : "skill.mysticism",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
}, },
"voy": "voy":
{ {
@ -175,10 +187,17 @@
{ "skill" : "wisdom", "level": "basic" }, { "skill" : "wisdom", "level": "basic" },
{ "skill" : "navigation", "level": "basic" } { "skill" : "navigation", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":2, "val": 2, "subtype": 5, "info": 1 } "navigation" : {
] "subtype" : "skill.navigation",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
}, },
"verdish": "verdish":
{ {
@ -191,10 +210,17 @@
{ "skill" : "wisdom", "level": "basic" }, { "skill" : "wisdom", "level": "basic" },
{ "skill" : "firstAid", "level": "basic" } { "skill" : "firstAid", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":2, "val": 5, "subtype": 27, "info": 1 } "firstAid" : {
] "subtype" : "skill.firstAid",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
}, },
"merist": "merist":
{ {
@ -207,10 +233,15 @@
{ "skill" : "wisdom", "level": "basic" }, { "skill" : "wisdom", "level": "basic" },
{ "skill" : "learning", "level": "basic" } { "skill" : "learning", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":8, "val": 0, "subtype": 46, "info": 0 } "stoneSkin" : {
] "addInfo" : 0,
"subtype" : "spell.stoneSkin",
"type" : "SPECIAL_PECULIAR_ENCHANT"
}
}
}
}, },
"styg": "styg":
{ {
@ -223,10 +254,17 @@
{ "skill" : "wisdom", "level": "basic" }, { "skill" : "wisdom", "level": "basic" },
{ "skill" : "sorcery", "level": "basic" } { "skill" : "sorcery", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":2, "val": 5, "subtype": 25, "info": 0 } "sorcery" : {
] "subtype" : "skill.sorcery",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
}, },
"andra": "andra":
{ {
@ -239,10 +277,17 @@
{ "skill" : "wisdom", "level": "basic" }, { "skill" : "wisdom", "level": "basic" },
{ "skill" : "intelligence", "level": "basic" } { "skill" : "intelligence", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":2, "val": 5, "subtype": 24, "info": 0 } "intelligence" : {
] "subtype" : "skill.intelligence",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
}, },
"tiva": "tiva":
{ {
@ -255,9 +300,16 @@
{ "skill" : "wisdom", "level": "basic" }, { "skill" : "wisdom", "level": "basic" },
{ "skill" : "eagleEye", "level": "basic" } { "skill" : "eagleEye", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":2, "val": 5, "subtype": 11, "info": 1 } "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" } { "skill" : "scouting", "level": "advanced" }
], ],
"specialties": "specialty" : {
[ "creature" : "hellHound"
{ "type":1, "val": 0, "subtype": 0, "info": 46 } }
]
}, },
"rashka": "rashka":
{ {
@ -23,10 +22,9 @@
{ "skill" : "wisdom", "level": "basic" }, { "skill" : "wisdom", "level": "basic" },
{ "skill" : "scholar", "level": "basic" } { "skill" : "scholar", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "creature" : "efreet"
{ "type":1, "val": 0, "subtype": 0, "info": 52 } }
]
}, },
"marius": "marius":
{ {
@ -37,10 +35,9 @@
[ [
{ "skill" : "armorer", "level": "advanced" } { "skill" : "armorer", "level": "advanced" }
], ],
"specialties": "specialty" : {
[ "creature" : "demon"
{ "type":1, "val": 0, "subtype": 0, "info": 48 } }
]
}, },
"ignatius": "ignatius":
{ {
@ -52,10 +49,9 @@
{ "skill" : "tactics", "level": "basic" }, { "skill" : "tactics", "level": "basic" },
{ "skill" : "resistance", "level": "basic" } { "skill" : "resistance", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "creature" : "imp"
{ "type":1, "val": 0, "subtype": 0, "info": 42 } }
]
}, },
"octavia": "octavia":
{ {
@ -67,10 +63,15 @@
{ "skill" : "scholar", "level": "basic" }, { "skill" : "scholar", "level": "basic" },
{ "skill" : "offence", "level": "basic" } { "skill" : "offence", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":10, "val": 350, "subtype": 6, "info": 0 } "gold" : {
] "subtype" : "resource.gold",
"type" : "GENERATE_RESOURCE",
"val" : 350
}
}
}
}, },
"calh": "calh":
{ {
@ -82,10 +83,9 @@
{ "skill" : "archery", "level": "basic" }, { "skill" : "archery", "level": "basic" },
{ "skill" : "scouting", "level": "basic" } { "skill" : "scouting", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "creature" : "gog"
{ "type":1, "val": 0, "subtype": 0, "info": 42 } }
]
}, },
"pyre": "pyre":
{ {
@ -97,10 +97,9 @@
{ "skill" : "artillery", "level": "basic" }, { "skill" : "artillery", "level": "basic" },
{ "skill" : "logistics", "level": "basic" } { "skill" : "logistics", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "creature" : "ballista"
{ "type":1, "val": 0, "subtype": 0, "info": 146 } }
]
}, },
"nymus": "nymus":
{ {
@ -111,10 +110,9 @@
[ [
{ "skill" : "offence", "level": "advanced" } { "skill" : "offence", "level": "advanced" }
], ],
"specialties": "specialty" : {
[ "creature" : "pitFiend"
{ "type":1, "val": 0, "subtype": 0, "info": 50 } }
]
}, },
"ayden": "ayden":
{ {
@ -127,10 +125,17 @@
{ "skill" : "wisdom", "level": "basic" }, { "skill" : "wisdom", "level": "basic" },
{ "skill" : "intelligence", "level": "basic" } { "skill" : "intelligence", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":2, "val": 5, "subtype": 24, "info": 0 } "intelligence" : {
] "subtype" : "skill.intelligence",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
}, },
"xyron": "xyron":
{ {
@ -143,10 +148,16 @@
{ "skill" : "wisdom", "level": "basic" }, { "skill" : "wisdom", "level": "basic" },
{ "skill" : "scholar", "level": "basic" } { "skill" : "scholar", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":3, "val": 3, "subtype": 22, "info": 0 } "inferno" : {
] "subtype" : "spell.inferno",
"type" : "SPECIAL_SPELL_LEV",
"updater" : "TIMES_HERO_LEVEL",
"val" : 3
}
}
}
}, },
"axsis": "axsis":
{ {
@ -159,10 +170,17 @@
{ "skill" : "wisdom", "level": "basic" }, { "skill" : "wisdom", "level": "basic" },
{ "skill" : "mysticism", "level": "basic" } { "skill" : "mysticism", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":2, "val": 5, "subtype": 8, "info": 1 } "mysticism" : {
] "subtype" : "skill.mysticism",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
}, },
"olema": "olema":
{ {
@ -175,10 +193,15 @@
{ "skill" : "wisdom", "level": "basic" }, { "skill" : "wisdom", "level": "basic" },
{ "skill" : "ballistics", "level": "basic" } { "skill" : "ballistics", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":8, "val": 0, "subtype": 45, "info": 0 } "weakness" : {
] "addInfo" : 0,
"subtype" : "spell.weakness",
"type" : "SPECIAL_PECULIAR_ENCHANT"
}
}
}
}, },
"calid": "calid":
{ {
@ -191,10 +214,15 @@
{ "skill" : "wisdom", "level": "basic" }, { "skill" : "wisdom", "level": "basic" },
{ "skill" : "learning", "level": "basic" } { "skill" : "learning", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":10, "val": 1, "subtype": 3, "info": 0 } "sulfur" : {
] "subtype" : "resource.sulfur",
"type" : "GENERATE_RESOURCE",
"val" : 1
}
}
}
}, },
"ash": "ash":
{ {
@ -207,10 +235,15 @@
{ "skill" : "wisdom", "level": "basic" }, { "skill" : "wisdom", "level": "basic" },
{ "skill" : "eagleEye", "level": "basic" } { "skill" : "eagleEye", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":8, "val": 0, "subtype": 43, "info": 0 } "bloodlust" : {
] "addInfo" : 0,
"subtype" : "spell.bloodlust",
"type" : "SPECIAL_PECULIAR_ENCHANT"
}
}
}
}, },
"zydar": "zydar":
{ {
@ -223,10 +256,17 @@
{ "skill" : "wisdom", "level": "basic" }, { "skill" : "wisdom", "level": "basic" },
{ "skill" : "sorcery", "level": "basic" } { "skill" : "sorcery", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":2, "val": 5, "subtype": 25, "info": 0 } "sorcery" : {
] "subtype" : "skill.sorcery",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
}, },
"xarfax": "xarfax":
{ {
@ -239,9 +279,15 @@
{ "skill" : "wisdom", "level": "basic" }, { "skill" : "wisdom", "level": "basic" },
{ "skill" : "leadership", "level": "basic" } { "skill" : "leadership", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":3, "val": 3, "subtype": 21, "info": 0 } "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" : "necromancy", "level": "basic" },
{ "skill" : "resistance", "level": "basic" } { "skill" : "resistance", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "creature" : "walkingDead"
{ "type":1, "val": 0, "subtype": 0, "info": 58 } }
]
}, },
"vokial": "vokial":
{ {
@ -26,10 +25,9 @@
{ "skill" : "necromancy", "level": "basic" }, { "skill" : "necromancy", "level": "basic" },
{ "skill" : "artillery", "level": "basic" } { "skill" : "artillery", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "creature" : "vampire"
{ "type":1, "val": 0, "subtype": 0, "info": 62 } }
]
}, },
"moandor": "moandor":
{ {
@ -42,10 +40,9 @@
{ "skill" : "necromancy", "level": "basic" }, { "skill" : "necromancy", "level": "basic" },
{ "skill" : "learning", "level": "basic" } { "skill" : "learning", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "creature" : "lich"
{ "type":1, "val": 0, "subtype": 0, "info": 64 } }
]
}, },
"charna": "charna":
{ {
@ -58,10 +55,9 @@
{ "skill" : "necromancy", "level": "basic" }, { "skill" : "necromancy", "level": "basic" },
{ "skill" : "tactics", "level": "basic" } { "skill" : "tactics", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "creature" : "wight"
{ "type":1, "val": 0, "subtype": 0, "info": 60 } }
]
}, },
"tamika": "tamika":
{ {
@ -74,10 +70,9 @@
{ "skill" : "necromancy", "level": "basic" }, { "skill" : "necromancy", "level": "basic" },
{ "skill" : "offence", "level": "basic" } { "skill" : "offence", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "creature" : "blackKnight"
{ "type":1, "val": 0, "subtype": 0, "info": 66 } }
]
}, },
"isra": "isra":
{ {
@ -89,10 +84,17 @@
[ [
{ "skill" : "necromancy", "level": "advanced" } { "skill" : "necromancy", "level": "advanced" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":2, "val": 5, "subtype": 12, "info": 0 } "necromancy" : {
] "subtype" : "skill.necromancy",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
}, },
"clavius": "clavius":
{ {
@ -105,10 +107,15 @@
{ "skill" : "necromancy", "level": "basic" }, { "skill" : "necromancy", "level": "basic" },
{ "skill" : "offence", "level": "basic" } { "skill" : "offence", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":10, "val": 350, "subtype": 6, "info": 0 } "gold" : {
] "subtype" : "resource.gold",
"type" : "GENERATE_RESOURCE",
"val" : 350
}
}
}
}, },
"galthran": "galthran":
{ {
@ -121,10 +128,9 @@
{ "skill" : "necromancy", "level": "basic" }, { "skill" : "necromancy", "level": "basic" },
{ "skill" : "armorer", "level": "basic" } { "skill" : "armorer", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "creature" : "skeleton"
{ "type":1, "val": 0, "subtype": 0, "info": 56 } }
]
}, },
"septienna": "septienna":
{ {
@ -137,10 +143,16 @@
{ "skill" : "necromancy", "level": "basic" }, { "skill" : "necromancy", "level": "basic" },
{ "skill" : "scholar", "level": "basic" } { "skill" : "scholar", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":3, "val": 3, "subtype": 24, "info": 0 } "deathRipple" : {
] "subtype" : "spell.deathRipple",
"type" : "SPECIAL_SPELL_LEV",
"updater" : "TIMES_HERO_LEVEL",
"val" : 3
}
}
}
}, },
"aislinn": "aislinn":
{ {
@ -153,10 +165,16 @@
{ "skill" : "necromancy", "level": "basic" }, { "skill" : "necromancy", "level": "basic" },
{ "skill" : "wisdom", "level": "basic" } { "skill" : "wisdom", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":3, "val": 3, "subtype": 23, "info": 0 } "meteorShower" : {
] "subtype" : "spell.meteorShower",
"type" : "SPECIAL_SPELL_LEV",
"updater" : "TIMES_HERO_LEVEL",
"val" : 3
}
}
}
}, },
"sandro": "sandro":
{ {
@ -169,10 +187,17 @@
{ "skill" : "necromancy", "level": "basic" }, { "skill" : "necromancy", "level": "basic" },
{ "skill" : "sorcery", "level": "basic" } { "skill" : "sorcery", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":2, "val": 5, "subtype": 25, "info": 0 } "sorcery" : {
] "subtype" : "skill.sorcery",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
}, },
"nimbus": "nimbus":
{ {
@ -185,10 +210,17 @@
{ "skill" : "necromancy", "level": "basic" }, { "skill" : "necromancy", "level": "basic" },
{ "skill" : "eagleEye", "level": "basic" } { "skill" : "eagleEye", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":2, "val": 5, "subtype": 11, "info": 1 } "eagleEye" : {
] "subtype" : "skill.eagleEye",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
}, },
"thant": "thant":
{ {
@ -201,10 +233,16 @@
{ "skill" : "necromancy", "level": "basic" }, { "skill" : "necromancy", "level": "basic" },
{ "skill" : "mysticism", "level": "basic" } { "skill" : "mysticism", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":3, "val": 39, "subtype": 0, "info": 3 } "animateDead" : {
] "subtype" : "spell.animateDead",
"type" : "SPECIAL_SPELL_LEV",
"updater" : "TIMES_HERO_LEVEL",
"val" : 3
}
}
}
}, },
"xsi": "xsi":
{ {
@ -217,10 +255,15 @@
{ "skill" : "necromancy", "level": "basic" }, { "skill" : "necromancy", "level": "basic" },
{ "skill" : "learning", "level": "basic" } { "skill" : "learning", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":8, "val": 0, "subtype": 46, "info": 0 } "stoneSkin" : {
] "addInfo" : 0,
"subtype" : "spell.stoneSkin",
"type" : "SPECIAL_PECULIAR_ENCHANT"
}
}
}
}, },
"vidomina": "vidomina":
{ {
@ -232,10 +275,17 @@
[ [
{ "skill" : "necromancy", "level": "advanced" } { "skill" : "necromancy", "level": "advanced" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":2, "val": 5, "subtype": 12, "info": 0 } "necromancy" : {
] "subtype" : "skill.necromancy",
"type" : "SECONDARY_SKILL_PREMY",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
}
}
}
}, },
"nagash": "nagash":
{ {
@ -248,9 +298,14 @@
{ "skill" : "necromancy", "level": "basic" }, { "skill" : "necromancy", "level": "basic" },
{ "skill" : "intelligence", "level": "basic" } { "skill" : "intelligence", "level": "basic" }
], ],
"specialties": "specialty" : {
[ "bonuses" : {
{ "type":10, "val": 350, "subtype": 6, "info": 0 } "gold" : {
] "subtype" : "resource.gold",
"type" : "GENERATE_RESOURCE",
"val" : 350
}
}
}
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -115,24 +115,48 @@
"additionalItems" : true "additionalItems" : true
}, },
"specialty": { "specialty": {
"type":"array", "anyOf" : [
"description": "Description of hero specialty using bonus system", {
"items": { "type":"array",
"type":"object", "description": "Description of hero specialty using bonus system (deprecated)",
"additionalProperties" : false, "items": {
"required" : [ "bonuses" ], "type" : "object",
"properties":{ "additionalProperties" : false,
"growsWithLevel" : { "required" : [ "bonuses" ],
"type" : "boolean", "properties" : {
"description" : "Specialty growth with level, so far only SECONDARY_SKILL_PREMY and PRIMATY SKILL with creature limiter can grow" "growsWithLevel" : {
}, "type" : "boolean",
"bonuses": { "description" : "Specialty growth with level. Deprecated, use bonuses with updaters instead."
"type":"array", },
"description": "List of bonuses", "bonuses" : {
"items": { "$ref" : "vcmi:bonus" } "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": { "spellbook": {
"type":"array", "type":"array",

View File

@ -226,7 +226,8 @@
"base" : { "base" : {
"effects" : { "effects" : {
"main" : { "main" : {
"type" : "MANA_REGENERATION", "subtype" : "skill.mysticism",
"type" : "SECONDARY_SKILL_PREMY",
"valueType" : "BASE_NUMBER" "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) void CHeroHandler::loadHeroSpecialty(CHero * hero, const JsonNode & node)
{ {
//deprecated, used only for original spciealties int sid = hero->ID.getNum();
for(const JsonNode &specialty : node["specialties"].Vector()) 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(); //deprecated, used only for original specialties
spec.val = specialty["val"].Float(); const JsonNode & specialtiesNode = node["specialties"];
spec.subtype = specialty["subtype"].Float(); if (!specialtiesNode.isNull())
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())
{ {
SSpecialtyBonus hs; logMod->warn("Hero %s has deprecated specialties format.", hero->identifier);
hs.growsWithLevel = specialty["growsWithLevel"].Bool(); for(const JsonNode &specialty : specialtiesNode.Vector())
for (const JsonNode & bonus : specialty["bonuses"].Vector())
{ {
auto b = JsonUtils::parseBonus(bonus); SSpecialtyInfo spec;
hs.bonuses.push_back (b); 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()); 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 ui32 CHeroHandler::level (ui64 experience) const
{ {
return boost::range::upper_bound(expPerLevel, experience) - std::begin(expPerLevel); return boost::range::upper_bound(expPerLevel, experience) - std::begin(expPerLevel);

View File

@ -71,8 +71,9 @@ public:
CHeroClass * heroClass; 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<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<SSpecialtyInfo> specDeprecated;
std::vector<SSpecialtyBonus> specialty; std::vector<SSpecialtyBonus> specialtyDeprecated;
BonusList specialty;
std::set<SpellID> spells; std::set<SpellID> spells;
bool haveSpellBook; bool haveSpellBook;
bool special; // hero is special and won't be placed in game (unless preset on map), e.g. campaign heroes 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 & initialArmy;
h & heroClass; h & heroClass;
h & secSkillsInit; h & secSkillsInit;
h & spec; if(version >= 781)
h & specialty; {
h & specialty;
}
else
{
h & specDeprecated;
h & specialtyDeprecated;
}
h & spells; h & spells;
h & haveSpellBook; h & haveSpellBook;
h & sex; 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 class DLL_LINKAGE CHeroClass
{ {
public: public:
@ -289,8 +301,10 @@ public:
std::vector<JsonNode> loadLegacyData(size_t dataSize) override; 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) override;
void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override; void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override;
void afterLoadFinalization() override;
CHeroHandler(); CHeroHandler();
~CHeroHandler(); ~CHeroHandler();

View File

@ -20,6 +20,7 @@
#include "CSkillHandler.h" #include "CSkillHandler.h"
#include "CStack.h" #include "CStack.h"
#include "CArtHandler.h" #include "CArtHandler.h"
#include "StringConstants.h"
#define FOREACH_PARENT(pname) TNodes lparents; getParents(lparents); for(CBonusSystemNode *pname : lparents) #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) #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)} {"GLOBAL_EFFECT", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::GLOBAL_EFFECTS)}
}; //untested }; //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::CBonusProxy(const IBonusBearer * Target, CSelector Selector) CBonusProxy::CBonusProxy(const IBonusBearer * Target, CSelector Selector)
: cachedLast(0), : cachedLast(0),
@ -579,22 +586,28 @@ void CBonusSystemNode::getParents(TNodes &out)
void CBonusSystemNode::getBonusesRec(BonusList &out, const CSelector &selector, const CSelector &limit) const void CBonusSystemNode::getBonusesRec(BonusList &out, const CSelector &selector, const CSelector &limit) const
{ {
BonusList beforeUpdate;
FOREACH_CPARENT(p) 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 void CBonusSystemNode::getAllBonusesRec(BonusList &out) const
{ {
BonusList beforeUpdate;
FOREACH_CPARENT(p) 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 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; 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() CBonusSystemNode::CBonusSystemNode()
: bonuses(true), : bonuses(true),
exportedBonuses(true), exportedBonuses(true),
@ -782,7 +802,7 @@ void CBonusSystemNode::popBonuses(const CSelector &s)
child->popBonuses(s); child->popBonuses(s);
} }
void CBonusSystemNode::updateBonuses(const CSelector &s) void CBonusSystemNode::reduceBonusDurations(const CSelector &s)
{ {
BonusList bl; BonusList bl;
exportedBonuses.getBonuses(bl, s, Selector::all); exportedBonuses.getBonuses(bl, s, Selector::all);
@ -794,7 +814,7 @@ void CBonusSystemNode::updateBonuses(const CSelector &s)
} }
for(CBonusSystemNode *child : children) for(CBonusSystemNode *child : children)
child->updateBonuses(s); child->reduceBonusDurations(s);
} }
void CBonusSystemNode::addNewBonus(const std::shared_ptr<Bonus>& b) void CBonusSystemNode::addNewBonus(const std::shared_ptr<Bonus>& b)
@ -1142,6 +1162,85 @@ std::string Bonus::Description() const
return str.str(); 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) 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) : 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); printField(effectRange);
#undef printField #undef printField
if(bonus.limiter)
out << "\tLimiter: " << bonus.limiter->toString() << "\n";
if(bonus.updater)
out << "\tUpdater: " << bonus.updater->toString() << "\n";
return out; return out;
} }
@ -1341,6 +1445,18 @@ int ILimiter::limit(const BonusLimitationContext &context) const /*return true t
return false; 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 int CCreatureTypeLimiter::limit(const BonusLimitationContext &context) const
{ {
const CCreature *c = retrieveCreature(&context.node); const CCreature *c = retrieveCreature(&context.node);
@ -1366,6 +1482,26 @@ void CCreatureTypeLimiter::setCreature (CreatureID id)
creature = VLC->creh->creatures[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 ) HasAnotherBonusLimiter::HasAnotherBonusLimiter( Bonus::BonusType bonus )
: type(bonus), subtype(0), isSubtypeRelevant(false) : type(bonus), subtype(0), isSubtypeRelevant(false)
{ {
@ -1390,6 +1526,32 @@ int HasAnotherBonusLimiter::limit(const BonusLimitationContext &context) const
return NOT_SURE; 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() IPropagator::~IPropagator()
{ {
@ -1543,3 +1705,136 @@ void LimiterList::add( TLimiterPtr limiter )
{ {
limiters.push_back(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 #pragma once
#include "GameConstants.h" #include "GameConstants.h"
#include "JsonNode.h"
class CCreature; class CCreature;
struct Bonus; struct Bonus;
@ -17,11 +18,13 @@ class IBonusBearer;
class CBonusSystemNode; class CBonusSystemNode;
class ILimiter; class ILimiter;
class IPropagator; class IPropagator;
class IUpdater;
class BonusList; class BonusList;
typedef std::shared_ptr<BonusList> TBonusListPtr; typedef std::shared_ptr<BonusList> TBonusListPtr;
typedef std::shared_ptr<ILimiter> TLimiterPtr; typedef std::shared_ptr<ILimiter> TLimiterPtr;
typedef std::shared_ptr<IPropagator> TPropagatorPtr; typedef std::shared_ptr<IPropagator> TPropagatorPtr;
typedef std::shared_ptr<IUpdater> TUpdaterPtr;
typedef std::set<CBonusSystemNode*> TNodes; typedef std::set<CBonusSystemNode*> TNodes;
typedef std::set<const CBonusSystemNode*> TCNodes; typedef std::set<const CBonusSystemNode*> TCNodes;
typedef std::vector<CBonusSystemNode *> TNodesVector; typedef std::vector<CBonusSystemNode *> TNodesVector;
@ -204,14 +207,14 @@ private:
BONUS_NAME(NO_LUCK) /*eg. when fighting on cursed ground*/ \ BONUS_NAME(NO_LUCK) /*eg. when fighting on cursed ground*/ \
BONUS_NAME(NO_MORALE) /*eg. when fighting on cursed ground*/ \ BONUS_NAME(NO_MORALE) /*eg. when fighting on cursed ground*/ \
BONUS_NAME(DARKNESS) /*val = radius */ \ BONUS_NAME(DARKNESS) /*val = radius */ \
BONUS_NAME(SPECIAL_SECONDARY_SKILL) /*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) /*val = id, additionalInfo = 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(SPELL_DAMAGE) /*val = value*/\
BONUS_NAME(SPECIFIC_SPELL_DAMAGE) /*subtype = id of spell, 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(SPECIAL_BLESS_DAMAGE) /*val = spell (bless), additionalInfo = value per level in percent*/\
BONUS_NAME(MAXED_SPELL) /*val = id*/\ 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_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(DRAGON_NATURE) \
BONUS_NAME(CREATURE_DAMAGE)/*subtype 0 = both, 1 = min, 2 = max*/\ 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)*/\ 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; TLimiterPtr limiter;
TPropagatorPtr propagator; TPropagatorPtr propagator;
TUpdaterPtr updater;
std::string description; std::string description;
@ -362,6 +366,10 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
h & effectRange; h & effectRange;
h & limiter; h & limiter;
h & propagator; h & propagator;
if(version >= 781)
{
h & updater;
}
} }
template <typename Ptr> template <typename Ptr>
@ -419,9 +427,12 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
} }
std::string Description() const; 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> 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> 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); DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const Bonus &bonus);
@ -583,6 +594,8 @@ public:
virtual ~ILimiter(); virtual ~ILimiter();
virtual int limit(const BonusLimitationContext &context) const; //0 - accept bonus; 1 - drop bonus; 2 - delay (drops eventually) 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) 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 getBonusesRec(BonusList &out, const CSelector &selector, const CSelector &limit) const;
void getAllBonusesRec(BonusList &out) const; void getAllBonusesRec(BonusList &out) const;
const TBonusListPtr getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr) 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: public:
explicit CBonusSystemNode(); explicit CBonusSystemNode();
@ -703,7 +717,7 @@ public:
///removes bonuses by selector ///removes bonuses by selector
void popBonuses(const CSelector &s); void popBonuses(const CSelector &s);
///updates count of remaining turns and removes outdated bonuses by selector ///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 bonusToString(const std::shared_ptr<Bonus>& bonus, bool description) const {return "";}; //description or bonus name
virtual std::string nodeName() const; virtual std::string nodeName() const;
@ -847,6 +861,8 @@ public:
void setCreature (CreatureID id); void setCreature (CreatureID id);
int limit(const BonusLimitationContext &context) const override; 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) template <typename Handler> void serialize(Handler &h, const int version)
{ {
@ -867,6 +883,8 @@ public:
HasAnotherBonusLimiter(Bonus::BonusType bonus, TBonusSubtype _subtype); HasAnotherBonusLimiter(Bonus::BonusType bonus, TBonusSubtype _subtype);
int limit(const BonusLimitationContext &context) const override; 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) 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, Bonus::LimitEffect> bonusLimitEffect;
extern DLL_LINKAGE const std::map<std::string, TLimiterPtr> bonusLimiterMap; 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, TPropagatorPtr> bonusPropagatorMap;
extern DLL_LINKAGE const std::map<std::string, TUpdaterPtr> bonusUpdaterMap;
// BonusList template that requires full interface of CBonusSystemNode // BonusList template that requires full interface of CBonusSystemNode
template <class InputIterator> template <class InputIterator>
@ -1006,3 +1024,70 @@ void BonusList::insert(const int position, InputIterator first, InputIterator la
bonuses.insert(bonuses.begin() + position, first, last); bonuses.insert(bonuses.begin() + position, first, last);
changed(); 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) while (begin != end)
{ {
out<<",\n"; out << (compactMode ? ", " : ",\n");
writeEntry(begin++); writeEntry(begin++);
} }
out<<"\n"; out << (compactMode ? "" : "\n");
prefix.resize(prefix.size()-1); prefix.resize(prefix.size()-1);
} }
void JsonWriter::writeEntry(JsonMap::const_iterator entry) void JsonWriter::writeEntry(JsonMap::const_iterator entry)
{ {
if (!entry->second.meta.empty()) if(!compactMode)
out << prefix << " // " << entry->second.meta << "\n"; {
out << prefix; if (!entry->second.meta.empty())
out << prefix << " // " << entry->second.meta << "\n";
out << prefix;
}
writeString(entry->first); writeString(entry->first);
out << " : "; out << " : ";
writeNode(entry->second); writeNode(entry->second);
@ -52,9 +55,12 @@ void JsonWriter::writeEntry(JsonMap::const_iterator entry)
void JsonWriter::writeEntry(JsonVector::const_iterator entry) void JsonWriter::writeEntry(JsonVector::const_iterator entry)
{ {
if (!entry->meta.empty()) if(!compactMode)
out << prefix << " // " << entry->meta << "\n"; {
out << prefix; if (!entry->meta.empty())
out << prefix << " // " << entry->meta << "\n";
out << prefix;
}
writeNode(*entry); writeNode(*entry);
} }
@ -94,6 +100,10 @@ void JsonWriter::writeString(const std::string &string)
void JsonWriter::writeNode(const JsonNode &node) void JsonWriter::writeNode(const JsonNode &node)
{ {
bool originalMode = compactMode;
if(compact && !compactMode && node.isCompact())
compactMode = true;
switch(node.getType()) switch(node.getType())
{ {
break; case JsonNode::JsonType::DATA_NULL: break; case JsonNode::JsonType::DATA_NULL:
@ -112,21 +122,24 @@ void JsonWriter::writeNode(const JsonNode &node)
writeString(node.String()); writeString(node.String());
break; case JsonNode::JsonType::DATA_VECTOR: break; case JsonNode::JsonType::DATA_VECTOR:
out << "[" << "\n"; out << "[" << (compactMode ? " " : "\n");
writeContainer(node.Vector().begin(), node.Vector().end()); writeContainer(node.Vector().begin(), node.Vector().end());
out << prefix << "]"; out << (compactMode ? " " : prefix) << "]";
break; case JsonNode::JsonType::DATA_STRUCT: break; case JsonNode::JsonType::DATA_STRUCT:
out << "{" << "\n"; out << "{" << (compactMode ? " " : "\n");
writeContainer(node.Struct().begin(), node.Struct().end()); writeContainer(node.Struct().begin(), node.Struct().end());
out << prefix << "}"; out << (compactMode ? " " : prefix) << "}";
break; case JsonNode::JsonType::DATA_INTEGER: break; case JsonNode::JsonType::DATA_INTEGER:
out << node.Integer(); out << node.Integer();
} }
compactMode = originalMode;
} }
JsonWriter::JsonWriter(std::ostream & output) JsonWriter::JsonWriter(std::ostream & output, bool compact)
: out(output) : out(output), compact(compact)
{ {
} }

View File

@ -16,6 +16,10 @@ class JsonWriter
//prefix for each line (tabulation) //prefix for each line (tabulation)
std::string prefix; std::string prefix;
std::ostream & out; 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: public:
template<typename Iterator> template<typename Iterator>
void writeContainer(Iterator begin, Iterator end); void writeContainer(Iterator begin, Iterator end);
@ -23,7 +27,7 @@ public:
void writeEntry(JsonVector::const_iterator entry); void writeEntry(JsonVector::const_iterator entry);
void writeString(const std::string & string); void writeString(const std::string & string);
void writeNode(const JsonNode & node); 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 //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; 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() void JsonNode::clear()
{ {
setType(JsonType::DATA_NULL); setType(JsonType::DATA_NULL);
@ -367,10 +411,10 @@ JsonNode & JsonNode::resolvePointer(const std::string &jsonPointer)
return ::resolvePointer(*this, jsonPointer); return ::resolvePointer(*this, jsonPointer);
} }
std::string JsonNode::toJson() const std::string JsonNode::toJson(bool compact) const
{ {
std::ostringstream out; std::ostringstream out;
JsonWriter writer(out); JsonWriter writer(out, compact);
writer.writeNode(*this); writer.writeNode(*this);
out << "\n"; out << "\n";
return out.str(); return out.str();
@ -395,7 +439,7 @@ std::shared_ptr<Bonus> JsonUtils::parseBonus (const JsonVector &ability_vec) //T
auto it = bonusNameMap.find(type); auto it = bonusNameMap.find(type);
if (it == bonusNameMap.end()) if (it == bonusNameMap.end())
{ {
logMod->error("Error: invalid ability type %s", type); logMod->error("Error: invalid ability type %s.", type);
return b; return b;
} }
b->type = it->second; 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); auto it = map.find(type);
if (it == map.end()) if (it == map.end())
{ {
logMod->error("Error: invalid %s%s", err, type); logMod->error("Error: invalid %s%s.", err, type);
return defaultValue; return defaultValue;
} }
else else
@ -445,7 +489,7 @@ void JsonUtils::resolveIdentifier(si32 &var, const JsonNode &node, std::string n
}); });
break; break;
default: 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); auto it = bonusNameMap.find(type);
if (it == bonusNameMap.end()) if (it == bonusNameMap.end())
{ {
logMod->error("Error: invalid ability type %s", type); logMod->error("Error: invalid ability type %s.", type);
return false; return false;
} }
b->type = it->second; b->type = it->second;
@ -580,7 +624,7 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
auto it = bonusNameMap.find(anotherBonusType); auto it = bonusNameMap.find(anotherBonusType);
if (it == bonusNameMap.end()) if (it == bonusNameMap.end())
{ {
logMod->error("Error: invalid ability type %s", anotherBonusType); logMod->error("Error: invalid ability type %s.", anotherBonusType);
continue; continue;
} }
l2->type = it->second; l2->type = it->second;
@ -603,6 +647,31 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
if (!value->isNull()) if (!value->isNull())
b->propagator = parseByMap(bonusPropagatorMap, value, "propagator type "); 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; 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("Data in %s is invalid!", dataName);
logMod->warn(log); logMod->warn(log);
logMod->trace("%s json: %s", dataName, node.toJson(true));
} }
return log.empty(); return log.empty();
} }
@ -822,6 +892,90 @@ void JsonUtils::inherit(JsonNode & descendant, const JsonNode & base)
descendant.swap(inheritedNode); 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) JsonNode JsonUtils::assembleFromFiles(std::vector<std::string> files)
{ {
bool isValid; bool isValid;
@ -860,3 +1014,31 @@ JsonNode JsonUtils::assembleFromFiles(std::string filename)
} }
return result; 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 isNull() const;
bool isNumber() 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 /// removes all data from node and sets type to null
void clear(); void clear();
@ -110,7 +114,7 @@ public:
JsonNode & operator[](std::string child); JsonNode & operator[](std::string child);
const JsonNode & operator[](std::string child) const; 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) 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); 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 * @brief generate one Json structure from multiple files
* @param files - list of filenames with parts of json structure * @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> /// 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 /// example: schema "vcmi:settings" is used to check user settings
DLL_LINKAGE const JsonNode & getSchema(std::string URI); 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 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 // 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.popBonuses(Bonus::OneDay); //works for children -> all game objs
gs->globalEffects.updateBonuses(Bonus::NDays); gs->globalEffects.reduceBonusDurations(Bonus::NDays);
gs->globalEffects.updateBonuses(Bonus::OneWeek); 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...] //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 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) for(CStack * s : stacks)
{ {
// new turn effects // new turn effects
s->updateBonuses(Bonus::NTurns); s->reduceBonusDurations(Bonus::NTurns);
s->afterNewRound(); s->afterNewRound();
} }

View File

@ -496,9 +496,6 @@ void CGHeroInstance::SecondarySkillsInfo::resetWisdomCounter()
void CGHeroInstance::initObj(CRandomGenerator & rand) void CGHeroInstance::initObj(CRandomGenerator & rand)
{ {
blockVisit = true; blockVisit = true;
auto hs = new HeroSpecial();
hs->setNodeType(CBonusSystemNode::SPECIALTY);
attachTo(hs); //do we ever need to detach it?
if(!type) if(!type)
initHero(rand); //TODO: set up everything for prison before specialties are configured initHero(rand); //TODO: set up everything for prison before specialties are configured
@ -514,246 +511,23 @@ void CGHeroInstance::initObj(CRandomGenerator & rand)
appearance = customApp.get(); appearance = customApp.get();
} }
for(const auto &spec : type->spec) //TODO: unfity with bonus system //copy active (probably growing) bonuses from hero prototype to hero object
{ for(std::shared_ptr<Bonus> b : type->specialty)
auto bonus = std::make_shared<Bonus>(); addNewBonus(b);
bonus->val = spec.val; //dito for old-style bonuses -> compatibility for old savegames
bonus->sid = id.getNum(); //from the hero, specialty has no unique id for(SSpecialtyBonus & sb : type->specialtyDeprecated)
bonus->duration = Bonus::PERMANENT; for(std::shared_ptr<Bonus> b : sb.bonuses)
bonus->source = Bonus::HERO_SPECIAL; addNewBonus(b);
switch (spec.type) for(SSpecialtyInfo & spec : type->specDeprecated)
{ for(std::shared_ptr<Bonus> b : SpecialtyInfoToBonuses(spec, type->ID.getNum()))
case 1:// creature specialty addNewBonus(b);
{
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?
}
//initialize bonuses //initialize bonuses
recreateSecondarySkillsBonuses(); recreateSecondarySkillsBonuses();
Updatespecialty();
mana = manaLimit(); //after all bonuses are taken into account, make sure this line is the last one mana = manaLimit(); //after all bonuses are taken into account, make sure this line is the last one
type->name = name; 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() void CGHeroInstance::recreateSecondarySkillsBonuses()
{ {
@ -766,6 +540,23 @@ void CGHeroInstance::recreateSecondarySkillsBonuses()
updateSkill(SecondarySkill(skill_info.first), level); 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) void CGHeroInstance::updateSkill(SecondarySkill which, int val)
{ {
auto skillBonus = (*VLC->skillh)[which]->getBonus(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; base *= (100.0 + maxSchoolBonus) / 100.0;
if(affectedStack && affectedStack->creatureLevel() > 0) //Hero specials like Solmyr, Deemer 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; return base;
} }
@ -1214,11 +1005,6 @@ int CGHeroInstance::maxSpellLevel() const
void CGHeroInstance::deserializationFix() void CGHeroInstance::deserializationFix()
{ {
artDeserializationFix(this); artDeserializationFix(this);
for (auto hs : specialty)
{
attachTo (hs);
}
} }
CBonusSystemNode * CGHeroInstance::whereShouldBeAttached(CGameState *gs) CBonusSystemNode * CGHeroInstance::whereShouldBeAttached(CGameState *gs)
@ -1471,8 +1257,8 @@ void CGHeroInstance::levelUp(std::vector<SecondarySkill> skills)
} }
} }
//specialty //update specialty and other bonuses that scale with level
Updatespecialty(); treeHasChanged();
} }
void CGHeroInstance::levelUpAutomatically(CRandomGenerator & rand) void CGHeroInstance::levelUpAutomatically(CRandomGenerator & rand)

View File

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

View File

@ -135,6 +135,12 @@ void registerTypesMapObjectTypes(Serializer &s)
REGISTER_GENERIC_HANDLER(CGWitchHut); REGISTER_GENERIC_HANDLER(CGWitchHut);
#undef REGISTER_GENERIC_HANDLER #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> template<typename Serializer>

View File

@ -12,7 +12,7 @@
#include "../ConstTransitivePtr.h" #include "../ConstTransitivePtr.h"
#include "../GameConstants.h" #include "../GameConstants.h"
const ui32 SERIALIZATION_VERSION = 780; const ui32 SERIALIZATION_VERSION = 781;
const ui32 MINIMAL_SERIALIZATION_VERSION = 753; const ui32 MINIMAL_SERIALIZATION_VERSION = 753;
const std::string SAVEGAME_MAGIC = "VCMISVG"; 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 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); Bonus specialBonus(Bonus::N_TURNS, Bonus::CREATURE_DAMAGE, Bonus::SPELL_EFFECT, damagePercent, m->getSpellIndex(), 0, Bonus::PERCENT_TO_ALL);
specialBonus.turnsRemain = duration; specialBonus.turnsRemain = duration;
buffer.push_back(specialBonus); buffer.push_back(specialBonus);