1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-25 22:42:04 +02:00

GameRandomizer is now in lib, add implementation

This commit is contained in:
Ivan Savenko
2025-05-18 10:52:48 +03:00
parent 54a46b77a9
commit cc274c4d34
31 changed files with 455 additions and 463 deletions

View File

@@ -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,96 +1430,33 @@ 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);
{
skills.push_back(gameRandomizer.rollSecondarySkillForLevelup(this, basicAndAdv));
basicAndAdv.erase(skills.back());
}
if (canLearnSkill() && !none.empty() && skills.size() < 2)
chooseSkill(none);
if (!none.empty() && skills.size() < 2)
{
skills.push_back(gameRandomizer.rollSecondarySkillForLevelup(this, none));
none.erase(skills.back());
}
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));
}
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())
{
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));
}
}
return chosenSecondarySkill;
}
void CGHeroInstance::setPrimarySkill(PrimarySkill primarySkill, si64 value, ChangeValueMode mode)
{
if(primarySkill < PrimarySkill::EXPERIENCE)
@@ -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);
const auto primarySkill = gameRandomizer.rollPrimarySkillForLevelup(this);
const auto proposedSecondarySkills = getLevelupSkillCandidates(gameRandomizer);
setPrimarySkill(primarySkill, 1, ChangeValueMode::RELATIVE);
if(!proposedSecondarySkills.empty())
setSecSkillLevel(proposedSecondarySkills.front(), 1, ChangeValueMode::RELATIVE);
auto proposedSecondarySkills = getLevelUpProposedSecondarySkills(rand);
const auto secondarySkill = nextSecondarySkill(rand);
if(secondarySkill)
{
setSecSkillLevel(*secondarySkill, 1, ChangeValueMode::RELATIVE);
}
//TODO why has the secondary skills to be passed to the method?
levelUp(proposedSecondarySkills);
levelUp();
}
}