1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-29 23:07:48 +02:00

Fix possible overflow errors on leveling up beyond int64_t limit

- added separate giveExperience method instead of weird changePrimSkill
- experience is now always used in form of int64_t
- max supported level reduced from 201 to 197 to fit into int64_t
- fixed undefined behavior in experience calculation
This commit is contained in:
Ivan Savenko
2024-01-04 23:57:36 +02:00
parent ceea341bb0
commit edb2ecd751
12 changed files with 87 additions and 59 deletions

View File

@@ -365,52 +365,60 @@ void CGameHandler::expGiven(const CGHeroInstance *hero)
// levelUpHero(hero);
}
void CGameHandler::changePrimSkill(const CGHeroInstance * hero, PrimarySkill which, si64 val, bool abs)
void CGameHandler::giveExperience(const CGHeroInstance * hero, TExpType amountToGain)
{
if (which == PrimarySkill::EXPERIENCE) // Check if scenario limit reached
{
if (gs->map->levelLimit != 0)
{
TExpType expLimit = VLC->heroh->reqExp(gs->map->levelLimit);
TExpType resultingExp = abs ? val : hero->exp + val;
if (resultingExp > expLimit)
{
// set given experience to max possible, but don't decrease if hero already over top
abs = true;
val = std::max(expLimit, hero->exp);
TExpType maxExp = VLC->heroh->reqExp(VLC->heroh->maxSupportedLevel());
TExpType currExp = hero->exp;
InfoWindow iw;
iw.player = hero->tempOwner;
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 1); //can gain no more XP
iw.text.replaceRawString(hero->getNameTranslated());
sendAndApply(&iw);
}
}
if (gs->map->levelLimit != 0)
maxExp = VLC->heroh->reqExp(gs->map->levelLimit);
TExpType canGainExp = 0;
if (maxExp > currExp)
canGainExp = maxExp - currExp;
if (amountToGain > canGainExp)
{
// set given experience to max possible, but don't decrease if hero already over top
amountToGain = canGainExp;
InfoWindow iw;
iw.player = hero->tempOwner;
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 1); //can gain no more XP
iw.text.replaceRawString(hero->getNameTranslated());
sendAndApply(&iw);
}
SetPrimSkill sps;
sps.id = hero->id;
sps.which = PrimarySkill::EXPERIENCE;
sps.abs = false;
sps.val = amountToGain;
sendAndApply(&sps);
//hero may level up
if (hero->commander && hero->commander->alive)
{
//FIXME: trim experience according to map limit?
SetCommanderProperty scp;
scp.heroid = hero->id;
scp.which = SetCommanderProperty::EXPERIENCE;
scp.amount = amountToGain;
sendAndApply (&scp);
CBonusSystemNode::treeHasChanged();
}
expGiven(hero);
}
void CGameHandler::changePrimSkill(const CGHeroInstance * hero, PrimarySkill which, si64 val, bool abs)
{
SetPrimSkill sps;
sps.id = hero->id;
sps.which = which;
sps.abs = abs;
sps.val = val;
sendAndApply(&sps);
//only for exp - hero may level up
if (which == PrimarySkill::EXPERIENCE)
{
if (hero->commander && hero->commander->alive)
{
//FIXME: trim experience according to map limit?
SetCommanderProperty scp;
scp.heroid = hero->id;
scp.which = SetCommanderProperty::EXPERIENCE;
scp.amount = val;
sendAndApply (&scp);
CBonusSystemNode::treeHasChanged();
}
expGiven(hero);
}
}
void CGameHandler::changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, bool abs)
@@ -658,7 +666,7 @@ void CGameHandler::onNewTurn()
{
if (obj && obj->ID == Obj::PRISON) //give imprisoned hero 0 exp to level him up. easiest to do at this point
{
changePrimSkill (getHero(obj->id), PrimarySkill::EXPERIENCE, 0);
giveExperience(getHero(obj->id), 0);
}
}
}
@@ -3708,7 +3716,7 @@ bool CGameHandler::sacrificeCreatures(const IMarket * market, const CGHeroInstan
int expSum = 0;
auto finish = [this, &hero, &expSum]()
{
changePrimSkill(hero, PrimarySkill::EXPERIENCE, hero->calculateXp(expSum));
giveExperience(hero, hero->calculateXp(expSum));
};
for(int i = 0; i < slot.size(); ++i)
@@ -3749,7 +3757,7 @@ bool CGameHandler::sacrificeArtifact(const IMarket * m, const CGHeroInstance * h
int expSum = 0;
auto finish = [this, &hero, &expSum]()
{
changePrimSkill(hero, PrimarySkill::EXPERIENCE, hero->calculateXp(expSum));
giveExperience(hero, hero->calculateXp(expSum));
};
for(int i = 0; i < slot.size(); ++i)