|
@@ -354,10 +354,10 @@ void CGHeroInstance::initObj(IGameRandomizer & gameRandomizer)
|
|
|
updateAppearance();
|
|
|
}
|
|
|
|
|
|
-void CGHeroInstance::initHero(vstd::RNG & rand, const HeroTypeID & SUBID)
|
|
|
+void CGHeroInstance::initHero(IGameRandomizer & gameRandomizer, const HeroTypeID & SUBID)
|
|
|
{
|
|
|
subID = SUBID.getNum();
|
|
|
- initHero(rand);
|
|
|
+ initHero(gameRandomizer);
|
|
|
}
|
|
|
|
|
|
TObjectTypeHandler CGHeroInstance::getObjectHandler() const
|
|
@@ -377,7 +377,7 @@ void CGHeroInstance::updateAppearance()
|
|
|
appearance = app;
|
|
|
}
|
|
|
|
|
|
-void CGHeroInstance::initHero(vstd::RNG & rand)
|
|
|
+void CGHeroInstance::initHero(IGameRandomizer & gameRandomizer)
|
|
|
{
|
|
|
assert(validTypes(true));
|
|
|
|
|
@@ -430,7 +430,7 @@ void CGHeroInstance::initHero(vstd::RNG & rand)
|
|
|
setFormation(EArmyFormation::LOOSE);
|
|
|
if (!stacksCount()) //standard army//initial army
|
|
|
{
|
|
|
- initArmy(rand);
|
|
|
+ initArmy(gameRandomizer.getDefault());
|
|
|
}
|
|
|
assert(validTypes());
|
|
|
|
|
@@ -439,11 +439,11 @@ void CGHeroInstance::initHero(vstd::RNG & rand)
|
|
|
|
|
|
if(exp == UNINITIALIZED_EXPERIENCE)
|
|
|
{
|
|
|
- initExp(rand);
|
|
|
+ initExp(gameRandomizer.getDefault());
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- levelUpAutomatically(rand);
|
|
|
+ levelUpAutomatically(gameRandomizer);
|
|
|
}
|
|
|
|
|
|
// load base hero bonuses, TODO: per-map loading of base hero bonuses
|
|
@@ -468,8 +468,6 @@ void CGHeroInstance::initHero(vstd::RNG & rand)
|
|
|
commander->giveTotalStackExperience(exp); //after our exp is set
|
|
|
}
|
|
|
|
|
|
- skillsInfo = SecondarySkillsInfo();
|
|
|
-
|
|
|
//copy active (probably growing) bonuses from hero prototype to hero object
|
|
|
for(const std::shared_ptr<Bonus> & b : getHeroType()->specialty)
|
|
|
addNewBonus(b);
|
|
@@ -650,20 +648,6 @@ ui8 CGHeroInstance::maxlevelsToWisdom() const
|
|
|
return getHeroClass()->isMagicHero() ? 3 : 6;
|
|
|
}
|
|
|
|
|
|
-CGHeroInstance::SecondarySkillsInfo::SecondarySkillsInfo():
|
|
|
- magicSchoolCounter(1),
|
|
|
- wisdomCounter(1)
|
|
|
-{}
|
|
|
-
|
|
|
-void CGHeroInstance::SecondarySkillsInfo::resetMagicSchoolCounter()
|
|
|
-{
|
|
|
- magicSchoolCounter = 0;
|
|
|
-}
|
|
|
-void CGHeroInstance::SecondarySkillsInfo::resetWisdomCounter()
|
|
|
-{
|
|
|
- wisdomCounter = 0;
|
|
|
-}
|
|
|
-
|
|
|
void CGHeroInstance::pickRandomObject(IGameRandomizer & gameRandomizer)
|
|
|
{
|
|
|
assert(ID == Obj::HERO || ID == Obj::PRISON || ID == Obj::RANDOM_HERO);
|
|
@@ -1426,34 +1410,18 @@ ArtBearer CGHeroInstance::bearerType() const
|
|
|
return ArtBearer::HERO;
|
|
|
}
|
|
|
|
|
|
-std::vector<SecondarySkill> CGHeroInstance::getLevelUpProposedSecondarySkills(vstd::RNG & rand) const
|
|
|
+std::vector<SecondarySkill> CGHeroInstance::getLevelupSkillCandidates(IGameRandomizer & gameRandomizer) const
|
|
|
{
|
|
|
- auto getObligatorySkills = [](CSkill::Obligatory obl){
|
|
|
- std::set<SecondarySkill> obligatory;
|
|
|
- for(auto i = 0; i < LIBRARY->skillh->size(); i++)
|
|
|
- if((*LIBRARY->skillh)[SecondarySkill(i)]->obligatory(obl))
|
|
|
- obligatory.insert(i); //Always return all obligatory skills
|
|
|
-
|
|
|
- return obligatory;
|
|
|
- };
|
|
|
-
|
|
|
- auto intersect = [](const std::set<SecondarySkill> & left, const std::set<SecondarySkill> & right)
|
|
|
- {
|
|
|
- std::set<SecondarySkill> intersect;
|
|
|
- std::set_intersection(left.begin(), left.end(), right.begin(), right.end(),
|
|
|
- std::inserter(intersect, intersect.begin()));
|
|
|
- return intersect;
|
|
|
- };
|
|
|
-
|
|
|
- std::set<SecondarySkill> wisdomList = getObligatorySkills(CSkill::Obligatory::MAJOR);
|
|
|
- std::set<SecondarySkill> schoolList = getObligatorySkills(CSkill::Obligatory::MINOR);
|
|
|
-
|
|
|
std::set<SecondarySkill> basicAndAdv;
|
|
|
std::set<SecondarySkill> none;
|
|
|
+ std::vector<SecondarySkill> skills;
|
|
|
|
|
|
- for(int i = 0; i < LIBRARY->skillh->size(); i++)
|
|
|
- if (canLearnSkill(SecondarySkill(i)))
|
|
|
- none.insert(SecondarySkill(i));
|
|
|
+ if (canLearnSkill())
|
|
|
+ {
|
|
|
+ for(int i = 0; i < LIBRARY->skillh->size(); i++)
|
|
|
+ if (canLearnSkill(SecondarySkill(i)))
|
|
|
+ none.insert(SecondarySkill(i));
|
|
|
+ }
|
|
|
|
|
|
for(const auto & elem : secSkills)
|
|
|
{
|
|
@@ -1462,94 +1430,31 @@ std::vector<SecondarySkill> CGHeroInstance::getLevelUpProposedSecondarySkills(vs
|
|
|
none.erase(elem.first);
|
|
|
}
|
|
|
|
|
|
- bool wantsWisdom = skillsInfo.wisdomCounter + 1 >= maxlevelsToWisdom();
|
|
|
- bool wantsSchool = skillsInfo.magicSchoolCounter + 1 >= maxlevelsToMagicSchool();
|
|
|
-
|
|
|
- std::vector<SecondarySkill> skills;
|
|
|
-
|
|
|
- auto chooseSkill = [&](std::set<SecondarySkill> & options)
|
|
|
- {
|
|
|
- bool selectWisdom = wantsWisdom && !intersect(options, wisdomList).empty();
|
|
|
- bool selectSchool = wantsSchool && !intersect(options, schoolList).empty();
|
|
|
- SecondarySkill selection;
|
|
|
-
|
|
|
- if (selectWisdom)
|
|
|
- selection = getHeroClass()->chooseSecSkill(intersect(options, wisdomList), rand);
|
|
|
- else if (selectSchool)
|
|
|
- selection = getHeroClass()->chooseSecSkill(intersect(options, schoolList), rand);
|
|
|
- else
|
|
|
- selection = getHeroClass()->chooseSecSkill(options, rand);
|
|
|
-
|
|
|
- skills.push_back(selection);
|
|
|
- options.erase(selection);
|
|
|
-
|
|
|
- if (wisdomList.count(selection))
|
|
|
- wisdomList.clear();
|
|
|
-
|
|
|
- if (schoolList.count(selection))
|
|
|
- schoolList.clear();
|
|
|
- };
|
|
|
-
|
|
|
if (!basicAndAdv.empty())
|
|
|
- chooseSkill(basicAndAdv);
|
|
|
+ {
|
|
|
+ skills.push_back(gameRandomizer.rollSecondarySkillForLevelup(this, basicAndAdv));
|
|
|
+ basicAndAdv.erase(skills.back());
|
|
|
+ }
|
|
|
|
|
|
- if (canLearnSkill() && !none.empty())
|
|
|
- chooseSkill(none);
|
|
|
+ if (!none.empty())
|
|
|
+ {
|
|
|
+ skills.push_back(gameRandomizer.rollSecondarySkillForLevelup(this, none));
|
|
|
+ none.erase(skills.back());
|
|
|
+ }
|
|
|
|
|
|
if (!basicAndAdv.empty() && skills.size() < 2)
|
|
|
- chooseSkill(basicAndAdv);
|
|
|
-
|
|
|
- if (canLearnSkill() && !none.empty() && skills.size() < 2)
|
|
|
- chooseSkill(none);
|
|
|
-
|
|
|
- return skills;
|
|
|
-}
|
|
|
-
|
|
|
-PrimarySkill CGHeroInstance::nextPrimarySkill(vstd::RNG & rand) const
|
|
|
-{
|
|
|
- assert(gainsLevel());
|
|
|
- const auto isLowLevelHero = level < GameConstants::HERO_HIGH_LEVEL;
|
|
|
- const auto & skillChances = isLowLevelHero ? getHeroClass()->primarySkillLowLevel : getHeroClass()->primarySkillHighLevel;
|
|
|
-
|
|
|
- if (isCampaignYog())
|
|
|
{
|
|
|
- // Yog can only receive Attack or Defence on level-up
|
|
|
- std::vector<int> yogChances = { skillChances[0], skillChances[1]};
|
|
|
- return static_cast<PrimarySkill>(RandomGeneratorUtil::nextItemWeighted(yogChances, rand));
|
|
|
+ skills.push_back(gameRandomizer.rollSecondarySkillForLevelup(this, basicAndAdv));
|
|
|
+ basicAndAdv.erase(skills.back());
|
|
|
}
|
|
|
|
|
|
- return static_cast<PrimarySkill>(RandomGeneratorUtil::nextItemWeighted(skillChances, rand));
|
|
|
-}
|
|
|
-
|
|
|
-std::optional<SecondarySkill> CGHeroInstance::nextSecondarySkill(vstd::RNG & rand) const
|
|
|
-{
|
|
|
- assert(gainsLevel());
|
|
|
-
|
|
|
- std::optional<SecondarySkill> chosenSecondarySkill;
|
|
|
- const auto proposedSecondarySkills = getLevelUpProposedSecondarySkills(rand);
|
|
|
- if(!proposedSecondarySkills.empty())
|
|
|
+ if (!none.empty() && skills.size() < 2)
|
|
|
{
|
|
|
- std::vector<SecondarySkill> learnedSecondarySkills;
|
|
|
- for(const auto & secondarySkill : proposedSecondarySkills)
|
|
|
- {
|
|
|
- if(getSecSkillLevel(secondarySkill) > 0)
|
|
|
- {
|
|
|
- learnedSecondarySkills.push_back(secondarySkill);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if(learnedSecondarySkills.empty())
|
|
|
- {
|
|
|
- // there are only new skills to learn, so choose anyone of them
|
|
|
- chosenSecondarySkill = std::make_optional(*RandomGeneratorUtil::nextItem(proposedSecondarySkills, rand));
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // preferably upgrade a already learned secondary skill
|
|
|
- chosenSecondarySkill = std::make_optional(*RandomGeneratorUtil::nextItem(learnedSecondarySkills, rand));
|
|
|
- }
|
|
|
+ skills.push_back(gameRandomizer.rollSecondarySkillForLevelup(this, none));
|
|
|
+ none.erase(skills.back());
|
|
|
}
|
|
|
- return chosenSecondarySkill;
|
|
|
+
|
|
|
+ return skills;
|
|
|
}
|
|
|
|
|
|
void CGHeroInstance::setPrimarySkill(PrimarySkill primarySkill, si64 value, ChangeValueMode mode)
|
|
@@ -1589,22 +1494,9 @@ bool CGHeroInstance::gainsLevel() const
|
|
|
return level < LIBRARY->heroh->maxSupportedLevel() && exp >= static_cast<TExpType>(LIBRARY->heroh->reqExp(level+1));
|
|
|
}
|
|
|
|
|
|
-void CGHeroInstance::levelUp(const std::vector<SecondarySkill> & skills)
|
|
|
+void CGHeroInstance::levelUp()
|
|
|
{
|
|
|
++level;
|
|
|
-
|
|
|
- //deterministic secondary skills
|
|
|
- ++skillsInfo.magicSchoolCounter;
|
|
|
- ++skillsInfo.wisdomCounter;
|
|
|
-
|
|
|
- for(const auto & skill : skills)
|
|
|
- {
|
|
|
- if((*LIBRARY->skillh)[skill]->obligatory(CSkill::Obligatory::MAJOR))
|
|
|
- skillsInfo.resetWisdomCounter();
|
|
|
- if((*LIBRARY->skillh)[skill]->obligatory(CSkill::Obligatory::MINOR))
|
|
|
- skillsInfo.resetMagicSchoolCounter();
|
|
|
- }
|
|
|
-
|
|
|
//update specialty and other bonuses that scale with level
|
|
|
nodeHasChanged();
|
|
|
}
|
|
@@ -1615,23 +1507,18 @@ void CGHeroInstance::attachCommanderToArmy()
|
|
|
commander->setArmy(this);
|
|
|
}
|
|
|
|
|
|
-void CGHeroInstance::levelUpAutomatically(vstd::RNG & rand)
|
|
|
+void CGHeroInstance::levelUpAutomatically(IGameRandomizer & gameRandomizer)
|
|
|
{
|
|
|
while(gainsLevel())
|
|
|
{
|
|
|
- const auto primarySkill = nextPrimarySkill(rand);
|
|
|
- setPrimarySkill(primarySkill, 1, ChangeValueMode::RELATIVE);
|
|
|
-
|
|
|
- auto proposedSecondarySkills = getLevelUpProposedSecondarySkills(rand);
|
|
|
+ const auto primarySkill = gameRandomizer.rollPrimarySkillForLevelup(this);
|
|
|
+ const auto proposedSecondarySkills = getLevelupSkillCandidates(gameRandomizer);
|
|
|
|
|
|
- const auto secondarySkill = nextSecondarySkill(rand);
|
|
|
- if(secondarySkill)
|
|
|
- {
|
|
|
- setSecSkillLevel(*secondarySkill, 1, ChangeValueMode::RELATIVE);
|
|
|
- }
|
|
|
+ setPrimarySkill(primarySkill, 1, ChangeValueMode::RELATIVE);
|
|
|
+ if(!proposedSecondarySkills.empty())
|
|
|
+ setSecSkillLevel(proposedSecondarySkills.front(), 1, ChangeValueMode::RELATIVE);
|
|
|
|
|
|
- //TODO why has the secondary skills to be passed to the method?
|
|
|
- levelUp(proposedSecondarySkills);
|
|
|
+ levelUp();
|
|
|
}
|
|
|
}
|
|
|
|