1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-02-03 13:01:33 +02:00

Refactoring of hero specialty loading code:

- removed no longer used code
- follow creature upgrade chains (e.g. third upgrades)
This commit is contained in:
Ivan Savenko 2023-03-28 17:09:54 +03:00
parent bdc7b38b0f
commit 8d0d5341db
4 changed files with 78 additions and 268 deletions

View File

@ -504,182 +504,60 @@ void CHeroHandler::loadHeroSkills(CHero * hero, const JsonNode & node) const
}
}
// 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->objects[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->objects[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.getDefense(false), stepSize));
result.push_back(bonus);
}
}
// convert deprecated format
std::vector<std::shared_ptr<Bonus>> SpecialtyInfoToBonuses(const SSpecialtyInfo & spec, int sid)
/// creates standard H3 hero specialty for creatures
static std::vector<std::shared_ptr<Bonus>> createCreatureSpecialty(CreatureID baseCreatureID)
{
std::vector<std::shared_ptr<Bonus>> result;
std::set<CreatureID> targets;
targets.insert(baseCreatureID);
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)
// go through entire upgrade chain and collect all creatures to which baseCreatureID can be upgraded
for (;;)
{
case 1: //creature specialty
AddSpecialtyForCreature(spec.additionalinfo, bonus, result);
break;
case 2: //secondary skill
std::set<CreatureID> oldTargets = targets;
for (auto const & upgradeSourceID : oldTargets)
{
auto params = BonusParams("SECONDARY_SKILL_PREMY", "", spec.subtype);
bonus->type = params.type;
if(params.subtypeRelevant)
bonus->subtype = params.subtype;
bonus->valType = Bonus::PERCENT_TO_TARGET_TYPE;
bonus->targetSourceType = Bonus::SECONDARY_SKILL;
bonus->updater.reset(new TimesHeroLevelUpdater());
result.push_back(bonus);
break;
const CCreature * upgradeSource = VLC->creh->objects[upgradeSourceID];
targets.insert(upgradeSource->upgrades.begin(), upgradeSource->upgrades.end());
}
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)
if (oldTargets.size() == targets.size())
break;
}
for(CreatureID cid : targets)
{
const CCreature &specCreature = *VLC->creh->objects[cid];
int stepSize = specCreature.level ? specCreature.level : 5;
{
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:
std::shared_ptr<Bonus> bonus = std::make_shared<Bonus>();
bonus->limiter.reset(new CCreatureTypeLimiter(specCreature, false));
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->objects[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)
{
auto limiter = std::make_shared<HasAnotherBonusLimiter>(Bonus::GENERAL_DAMAGE_PREMY,Bonus::SPELL_EFFECT);
limiter->sid = spec.subtype; //spell id if you ever wanted to use it otherwise
limiter->isSourceIDRelevant = true;
bonus->type = Bonus::GENERAL_DAMAGE_PREMY;
bonus->updater.reset(new TimesHeroLevelUpdater());
bonus->addLimiter(limiter);
bonus->val = 1;
result.push_back(bonus);
break;
}
case 7: //maxed mastery for spell
bonus->type = Bonus::SPELL;
bonus->subtype = spec.subtype; //spell id
bonus->val = 3; //to match MAXED_SPELL
bonus->valType = Bonus::INDEPENDENT_MAX;
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->objects;
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(const 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:
std::shared_ptr<Bonus> bonus = std::make_shared<Bonus>();
bonus->type = Bonus::PRIMARY_SKILL;
bonus->subtype = PrimarySkill::ATTACK;
break;
case 2:
bonus->subtype = PrimarySkill::DEFENSE;
break;
bonus->val = 0;
bonus->limiter.reset(new CCreatureTypeLimiter(specCreature, false));
bonus->updater.reset(new GrowsWithLevelUpdater(specCreature.getAttack(false), stepSize));
result.push_back(bonus);
}
{
std::shared_ptr<Bonus> bonus = std::make_shared<Bonus>();
bonus->type = Bonus::PRIMARY_SKILL;
bonus->subtype = PrimarySkill::DEFENSE;
bonus->val = 0;
bonus->limiter.reset(new CCreatureTypeLimiter(specCreature, false));
bonus->updater.reset(new GrowsWithLevelUpdater(specCreature.getDefense(false), stepSize));
result.push_back(bonus);
}
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;
@ -708,41 +586,51 @@ void CHeroHandler::beforeValidate(JsonNode & object)
}
}
void CHeroHandler::loadHeroSpecialty(CHero * hero, const JsonNode & node) const
void CHeroHandler::afterLoadFinalization()
{
for (auto const & functor : callAfterLoadFinalization)
functor();
callAfterLoadFinalization.clear();
}
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;
bonus->sid = hero->getIndex();
return bonus;
};
//new format, using bonus system
const JsonNode & specialtyNode = node["specialty"];
if(specialtyNode.getType() == JsonNode::JsonType::DATA_STRUCT)
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(const auto & keyValue : specialtyNode["bonuses"].Struct())
hero->specialty.push_back(prepSpec(JsonUtils::parseBonus(keyValue.second)));
}
}
else
logMod->error("Unsupported speciality format for hero %s!", hero->getNameTranslated());
return;
}
//creature specialty - alias for simplicity
if(!specialtyNode["creature"].isNull())
{
JsonNode creatureNode = specialtyNode["creature"];
std::function<void()> specialtyLoader = [creatureNode, hero, prepSpec]
{
VLC->modh->identifiers.requestIdentifier("creature", creatureNode, [hero, prepSpec](si32 creature)
{
for (const auto & bonus : createCreatureSpecialty(CreatureID(creature)))
hero->specialty.push_back(prepSpec(bonus));
});
};
callAfterLoadFinalization.push_back(specialtyLoader);
}
for(const auto & keyValue : specialtyNode["bonuses"].Struct())
hero->specialty.push_back(prepSpec(JsonUtils::parseBonus(keyValue.second)));
}
void CHeroHandler::loadExperience()
@ -848,64 +736,6 @@ void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNod
registerObject(scope, "hero", name, object->getIndex());
}
void CHeroHandler::afterLoadFinalization()
{
for(auto & hero : objects)
{
for(const auto & bonus : hero->specialty)
{
bonus->sid = hero->getIndex();
}
if(!hero->specDeprecated.empty())
{
logMod->debug("Converting specialty format for hero %s(%s)", hero->getNameTranslated(), FactionID::encode(hero->heroClass->faction));
std::vector<std::shared_ptr<Bonus>> convertedBonuses;
for(const SSpecialtyInfo & spec : hero->specDeprecated)
{
for(const std::shared_ptr<Bonus> & b : SpecialtyInfoToBonuses(spec, hero->ID.getNum()))
convertedBonuses.push_back(b);
}
hero->specDeprecated.clear();
// store and create json for logging
std::vector<JsonNode> specVec;
std::vector<std::string> specNames;
for(const std::shared_ptr<Bonus> & bonus : convertedBonuses)
{
hero->specialty.push_back(bonus);
specVec.push_back(bonus->toJsonNode());
// find fitting & unique bonus name
std::string bonusName = bonus->nameForBonus();
if(vstd::contains(specNames, bonusName))
{
int suffix = 2;
while(vstd::contains(specNames, bonusName + std::to_string(suffix)))
suffix++;
bonusName += std::to_string(suffix);
}
specNames.push_back(bonusName);
}
// log new format for easy copy-and-paste
JsonNode specNode(JsonNode::JsonType::DATA_STRUCT);
if(specVec.size() > 1)
{
JsonNode base = JsonUtils::intersect(specVec);
if(base.containsBaseData())
{
specNode["base"] = base;
for(JsonNode & node : specVec)
node = JsonUtils::difference(node, base);
}
}
// add json for bonuses
specNode["bonuses"].Struct();
for(int i = 0; i < specVec.size(); i++)
specNode["bonuses"][specNames[i]] = specVec[i];
logMod->trace("\"specialty\" : %s", specNode.toJson(true));
}
}
}
ui32 CHeroHandler::level (ui64 experience) const
{
return static_cast<ui32>(boost::range::upper_bound(expPerLevel, experience) - std::begin(expPerLevel));

View File

@ -29,21 +29,6 @@ class CRandomGenerator;
class JsonSerializeFormat;
class BattleField;
struct SSpecialtyInfo
{
si32 type;
si32 val;
si32 subtype;
si32 additionalinfo;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & type;
h & val;
h & subtype;
h & additionalinfo;
}
};
class DLL_LINKAGE CHero : public HeroType
{
friend class CHeroHandler;
@ -72,7 +57,6 @@ 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> specDeprecated;
BonusList specialty;
std::set<SpellID> spells;
bool haveSpellBook = false;
@ -132,9 +116,6 @@ public:
}
};
// convert deprecated format
std::vector<std::shared_ptr<Bonus>> SpecialtyInfoToBonuses(const SSpecialtyInfo & spec, int sid = 0);
class DLL_LINKAGE CHeroClass : public HeroClass
{
friend class CHeroClassHandler;
@ -251,10 +232,12 @@ class DLL_LINKAGE CHeroHandler : public CHandlerBase<HeroTypeID, HeroType, CHero
/// helpers for loading to avoid huge load functions
void loadHeroArmy(CHero * hero, const JsonNode & node) const;
void loadHeroSkills(CHero * hero, const JsonNode & node) const;
void loadHeroSpecialty(CHero * hero, const JsonNode & node) const;
void loadHeroSpecialty(CHero * hero, const JsonNode & node);
void loadExperience();
std::vector<std::function<void()>> callAfterLoadFinalization;
public:
CHeroClassHandler classes;

View File

@ -1047,7 +1047,7 @@ public:
bool includeUpgrades = false;
CCreatureTypeLimiter() = default;
CCreatureTypeLimiter(const CCreature & creature_, bool IncludeUpgrades = true);
CCreatureTypeLimiter(const CCreature & creature_, bool IncludeUpgrades);
void setCreature(const CreatureID & id);
int limit(const BonusLimitationContext &context) const override;

View File

@ -510,9 +510,6 @@ void CGHeroInstance::initObj(CRandomGenerator & rand)
//copy active (probably growing) bonuses from hero prototype to hero object
for(const std::shared_ptr<Bonus> & b : type->specialty)
addNewBonus(b);
for(SSpecialtyInfo & spec : type->specDeprecated)
for(const std::shared_ptr<Bonus> & b : SpecialtyInfoToBonuses(spec, type->getIndex()))
addNewBonus(b);
//initialize bonuses
recreateSecondarySkillsBonuses();