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

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

This commit is contained in:
Henning Koehler 2017-09-10 14:10:50 +12:00
parent f867b1a37e
commit 3e0022be27
7 changed files with 234 additions and 274 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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