1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-06-17 00:07:41 +02:00

Merge pull request #5764 from IvanSavenko/stack_experience_bonus

Implement STACK_EXPERIENCE_GAIN_PERCENT bonus
This commit is contained in:
Ivan Savenko
2025-06-04 21:09:38 +03:00
committed by GitHub
24 changed files with 167 additions and 65 deletions

View File

@ -326,6 +326,12 @@ void AIGateway::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID her
});
}
void AIGateway::heroExperienceChanged(const CGHeroInstance * hero, si64 val)
{
LOG_TRACE_PARAMS(logAi, "val '%i'", val);
NET_EVENT_HANDLER;
}
void AIGateway::heroPrimarySkillChanged(const CGHeroInstance * hero, PrimarySkill which, si64 val)
{
LOG_TRACE_PARAMS(logAi, "which '%i', val '%i'", which.getNum() % val);

View File

@ -123,6 +123,7 @@ public:
void heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance * town) override;
void tileRevealed(const std::unordered_set<int3> & pos) override;
void heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query) override;
void heroExperienceChanged(const CGHeroInstance * hero, si64 val) override;
void heroPrimarySkillChanged(const CGHeroInstance * hero, PrimarySkill which, si64 val) override;
void showRecruitmentDialog(const CGDwelling * dwelling, const CArmedInstance * dst, int level, QueryID queryID) override;
void heroMovePointsChanged(const CGHeroInstance * hero) override;

View File

@ -364,6 +364,12 @@ void VCAI::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, Q
});
}
void VCAI::heroExperienceChanged(const CGHeroInstance * hero, si64 val)
{
LOG_TRACE_PARAMS(logAi, "val '%i'", val);
NET_EVENT_HANDLER;
}
void VCAI::heroPrimarySkillChanged(const CGHeroInstance * hero, PrimarySkill which, si64 val)
{
LOG_TRACE_PARAMS(logAi, "which '%i', val '%i'", which.getNum() % val);

View File

@ -164,6 +164,7 @@ public:
void heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance * town) override;
void tileRevealed(const std::unordered_set<int3> & pos) override;
void heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query) override;
void heroExperienceChanged(const CGHeroInstance * hero, si64 val) override;
void heroPrimarySkillChanged(const CGHeroInstance * hero, PrimarySkill which, si64 val) override;
void showRecruitmentDialog(const CGDwelling * dwelling, const CArmedInstance * dst, int level, QueryID queryID) override;
void heroMovePointsChanged(const CGHeroInstance * hero) override;

View File

@ -462,18 +462,17 @@ void CPlayerInterface::openTownWindow(const CGTownInstance * town)
ENGINE->windows().pushWindow(newCastleInt);
}
void CPlayerInterface::heroExperienceChanged(const CGHeroInstance * hero, si64 val)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
for(auto ctw : ENGINE->windows().findWindows<IMarketHolder>())
ctw->updateExperience();
}
void CPlayerInterface::heroPrimarySkillChanged(const CGHeroInstance * hero, PrimarySkill which, si64 val)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
if (which == PrimarySkill::EXPERIENCE)
{
for(auto ctw : ENGINE->windows().findWindows<IMarketHolder>())
ctw->updateExperience();
}
else
{
adventureInt->onHeroChanged(hero);
}
adventureInt->onHeroChanged(hero);
}
void CPlayerInterface::heroSecondarySkillChanged(const CGHeroInstance * hero, int which, int val)

View File

@ -109,6 +109,7 @@ protected: // Call-ins from server, should not be called directly, but only via
void commanderGotLevel (const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID) override;
void heroInGarrisonChange(const CGTownInstance *town) override;
void heroMoved(const TryMoveHero & details, bool verbose = true) override;
void heroExperienceChanged(const CGHeroInstance * hero, si64 val) override;
void heroPrimarySkillChanged(const CGHeroInstance * hero, PrimarySkill which, si64 val) override;
void heroSecondarySkillChanged(const CGHeroInstance * hero, int which, int val) override;
void heroManaPointsChanged(const CGHeroInstance * hero) override;

View File

@ -32,7 +32,8 @@ public:
}
void visitSetResources(SetResources & pack) override;
void visitSetPrimSkill(SetPrimSkill & pack) override;
void visitSetPrimarySkill(SetPrimarySkill & pack) override;
void visitSetHeroExperience(SetHeroExperience & pack) override;
void visitSetSecSkill(SetSecSkill & pack) override;
void visitHeroVisitCastle(HeroVisitCastle & pack) override;
void visitSetMana(SetMana & pack) override;

View File

@ -122,7 +122,18 @@ void ApplyClientNetPackVisitor::visitSetResources(SetResources & pack)
callInterfaceIfPresent(cl, pack.player, &IGameEventsReceiver::receivedResource);
}
void ApplyClientNetPackVisitor::visitSetPrimSkill(SetPrimSkill & pack)
void ApplyClientNetPackVisitor::visitSetHeroExperience(SetHeroExperience & pack)
{
const CGHeroInstance * h = cl.gameInfo().getHero(pack.id);
if(!h)
{
logNetwork->error("Cannot find hero with pack.id %d", pack.id.getNum());
return;
}
callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroExperienceChanged, h, pack.val);
}
void ApplyClientNetPackVisitor::visitSetPrimarySkill(SetPrimarySkill & pack)
{
const CGHeroInstance * h = cl.gameInfo().getHero(pack.id);
if(!h)

View File

@ -190,6 +190,12 @@ Increases experience gain from all sources by affected heroes
- val: additional experience bonus, percentage
### STACK_EXPERIENCE_GAIN_PERCENT
Increases experience gain from combat by affected units. No effect if stack experience is off. Has no effect on commanders
- val: additional experience bonus, percentage
### UNDEAD_RAISE_PERCENTAGE
Defines percentage of enemy troops that will be raised after battle into own army (Necromancy). Raised unit is determined by IMPROVED_NECROMANCY bonus

View File

@ -188,6 +188,7 @@ class JsonNode;
BONUS_NAME(MULTIHEX_UNIT_ATTACK) /*eg. dragons*/ \
BONUS_NAME(MULTIHEX_ENEMY_ATTACK) /*eg. dragons*/ \
BONUS_NAME(MULTIHEX_ANIMATION) /*eg. dragons*/ \
BONUS_NAME(STACK_EXPERIENCE_GAIN_PERCENT) /*modifies all stack experience gains*/\
/* end of list */

View File

@ -56,6 +56,7 @@ public:
virtual void heroCreated(const CGHeroInstance*){};
virtual void heroInGarrisonChange(const CGTownInstance *town){};
virtual void heroMoved(const TryMoveHero & details, bool verbose = true){};
virtual void heroExperienceChanged(const CGHeroInstance * hero, si64 val){};
virtual void heroPrimarySkillChanged(const CGHeroInstance * hero, PrimarySkill which, si64 val){};
virtual void heroSecondarySkillChanged(const CGHeroInstance * hero, int which, int val){};
virtual void heroManaPointsChanged(const CGHeroInstance * hero){} //not called at the beginning of turn and after spell casts

View File

@ -100,7 +100,6 @@ const PrimarySkill PrimarySkill::ATTACK(0);
const PrimarySkill PrimarySkill::DEFENSE(1);
const PrimarySkill PrimarySkill::SPELL_POWER(2);
const PrimarySkill PrimarySkill::KNOWLEDGE(3);
const PrimarySkill PrimarySkill::EXPERIENCE(4);
const BoatId BoatId::NONE(-1);
const BoatId BoatId::NECROPOLIS(0);

View File

@ -250,8 +250,6 @@ public:
static const std::array<PrimarySkill, 4> & ALL_SKILLS();
static const PrimarySkill EXPERIENCE;
static si32 decode(const std::string& identifier);
static std::string encode(const si32 index);
static std::string entityType();

View File

@ -48,13 +48,30 @@ void GameStatePackVisitor::visitSetResources(SetResources & pack)
gs.getPlayerState(pack.player)->resources.positive();
}
void GameStatePackVisitor::visitSetPrimSkill(SetPrimSkill & pack)
void GameStatePackVisitor::visitSetPrimarySkill(SetPrimarySkill & pack)
{
CGHeroInstance * hero = gs.getHero(pack.id);
assert(hero);
hero->setPrimarySkill(pack.which, pack.val, pack.mode);
}
void GameStatePackVisitor::visitSetHeroExperience(SetHeroExperience & pack)
{
CGHeroInstance * hero = gs.getHero(pack.id);
assert(hero);
hero->setExperience(pack.val, pack.mode);
}
void GameStatePackVisitor::visitGiveStackExperience(GiveStackExperience & pack)
{
auto * army = gs.getArmyInstance(pack.id);
for (const auto & slot : pack.val)
army->getStackPtr(slot.first)->giveAverageStackExperience(slot.second);
army->nodeHasChanged();
}
void GameStatePackVisitor::visitSetSecSkill(SetSecSkill & pack)
{
CGHeroInstance *hero = gs.getHero(pack.id);
@ -1233,15 +1250,6 @@ void GameStatePackVisitor::visitBattleResultAccepted(BattleResultAccepted & pack
attackerHero->removeBonusesRecursive(Bonus::OneBattle);
if(const auto defenderHero = gs.getHero(pack.heroResult[BattleSide::DEFENDER].heroID))
defenderHero->removeBonusesRecursive(Bonus::OneBattle);
if(gs.getSettings().getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
{
if(const auto attackerArmy = gs.getArmyInstance(pack.heroResult[BattleSide::ATTACKER].armyID))
attackerArmy->giveAverageStackExperience(pack.heroResult[BattleSide::ATTACKER].exp);
if(const auto defenderArmy = gs.getArmyInstance(pack.heroResult[BattleSide::DEFENDER].armyID))
defenderArmy->giveAverageStackExperience(pack.heroResult[BattleSide::DEFENDER].exp);
}
}
void GameStatePackVisitor::visitBattleStackMoved(BattleStackMoved & pack)

View File

@ -27,7 +27,9 @@ public:
}
void visitSetResources(SetResources & pack) override;
void visitSetPrimSkill(SetPrimSkill & pack) override;
void visitSetPrimarySkill(SetPrimarySkill & pack) override;
void visitSetHeroExperience(SetHeroExperience & pack) override;
void visitGiveStackExperience(GiveStackExperience & pack) override;
void visitSetSecSkill(SetSecSkill & pack) override;
void visitHeroVisitCastle(HeroVisitCastle & pack) override;
void visitSetMana(SetMana & pack) override;

View File

@ -1451,35 +1451,34 @@ std::vector<SecondarySkill> CGHeroInstance::getLevelupSkillCandidates(IGameRando
return skills;
}
void CGHeroInstance::setPrimarySkill(PrimarySkill primarySkill, si64 value, ChangeValueMode mode)
{
if(primarySkill < PrimarySkill::EXPERIENCE)
{
auto skill = getLocalBonus(Selector::type()(BonusType::PRIMARY_SKILL)
.And(Selector::subtype()(BonusSubtypeID(primarySkill)))
.And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)));
assert(skill);
auto skill = getLocalBonus(Selector::type()(BonusType::PRIMARY_SKILL)
.And(Selector::subtype()(BonusSubtypeID(primarySkill)))
.And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)));
assert(skill);
if(mode == ChangeValueMode::ABSOLUTE)
{
skill->val = static_cast<si32>(value);
}
else
{
skill->val += static_cast<si32>(value);
}
nodeHasChanged();
}
else if(primarySkill == PrimarySkill::EXPERIENCE)
if(mode == ChangeValueMode::ABSOLUTE)
{
if(mode == ChangeValueMode::ABSOLUTE)
{
exp = value;
}
else
{
exp += value;
}
skill->val = static_cast<si32>(value);
}
else
{
skill->val += static_cast<si32>(value);
}
nodeHasChanged();
}
void CGHeroInstance::setExperience(si64 value, ChangeValueMode mode)
{
if(mode == ChangeValueMode::ABSOLUTE)
{
exp = value;
}
else
{
exp += value;
}
}

View File

@ -193,6 +193,7 @@ public:
bool canLearnSkill() const;
bool canLearnSkill(const SecondarySkill & which) const;
void setExperience(si64 value, ChangeValueMode mode);
void setPrimarySkill(PrimarySkill primarySkill, si64 value, ChangeValueMode mode);
void setSecSkillLevel(const SecondarySkill & which, int val, ChangeValueMode mode); // abs == 0 - changes by value; 1 - sets to value
void levelUp();

View File

@ -38,7 +38,9 @@ public:
virtual void visitEntitiesChanged(EntitiesChanged & pack) {}
virtual void visitSetRewardableConfiguration(SetRewardableConfiguration & pack) {}
virtual void visitSetResources(SetResources & pack) {}
virtual void visitSetPrimSkill(SetPrimSkill & pack) {}
virtual void visitSetPrimarySkill(SetPrimarySkill & pack) {}
virtual void visitSetHeroExperience(SetHeroExperience & pack) {}
virtual void visitGiveStackExperience(GiveStackExperience & pack) {}
virtual void visitSetSecSkill(SetSecSkill & pack) {}
virtual void visitHeroVisitCastle(HeroVisitCastle & pack) {}
virtual void visitChangeSpells(ChangeSpells & pack) {}

View File

@ -114,9 +114,19 @@ void SetResources::visitTyped(ICPackVisitor & visitor)
visitor.visitSetResources(*this);
}
void SetPrimSkill::visitTyped(ICPackVisitor & visitor)
void SetPrimarySkill::visitTyped(ICPackVisitor & visitor)
{
visitor.visitSetPrimSkill(*this);
visitor.visitSetPrimarySkill(*this);
}
void SetHeroExperience::visitTyped(ICPackVisitor & visitor)
{
visitor.visitSetHeroExperience(*this);
}
void GiveStackExperience::visitTyped(ICPackVisitor & visitor)
{
visitor.visitGiveStackExperience(*this);
}
void SetSecSkill::visitTyped(ICPackVisitor & visitor)

View File

@ -199,7 +199,7 @@ struct DLL_LINKAGE SetResources : public CPackForClient
}
};
struct DLL_LINKAGE SetPrimSkill : public CPackForClient
struct DLL_LINKAGE SetPrimarySkill : public CPackForClient
{
void visitTyped(ICPackVisitor & visitor) override;
@ -217,6 +217,36 @@ struct DLL_LINKAGE SetPrimSkill : public CPackForClient
}
};
struct DLL_LINKAGE SetHeroExperience : public CPackForClient
{
void visitTyped(ICPackVisitor & visitor) override;
ChangeValueMode mode = ChangeValueMode::RELATIVE;
ObjectInstanceID id;
si64 val = 0;
template <typename Handler> void serialize(Handler & h)
{
h & mode;
h & id;
h & val;
}
};
struct DLL_LINKAGE GiveStackExperience : public CPackForClient
{
void visitTyped(ICPackVisitor & visitor) override;
ObjectInstanceID id;
std::map<SlotID, si64> val;
template <typename Handler> void serialize(Handler & h)
{
h & id;
h & val;
}
};
struct DLL_LINKAGE SetSecSkill : public CPackForClient
{
void visitTyped(ICPackVisitor & visitor) override;

View File

@ -145,7 +145,7 @@ void registerTypes(Serializer &s)
s.template registerType<DaysWithoutTown>(89);
s.template registerType<TurnTimeUpdate>(90);
s.template registerType<SetResources>(91);
s.template registerType<SetPrimSkill>(92);
s.template registerType<SetPrimarySkill>(92);
s.template registerType<SetSecSkill>(93);
s.template registerType<HeroVisitCastle>(94);
s.template registerType<ChangeSpells>(95);
@ -296,6 +296,8 @@ void registerTypes(Serializer &s)
s.template registerType<LobbyDelete>(244);
s.template registerType<TimesHeroLevelDivideStackLevelUpdater>(245);
s.template registerType<DivideStackLevelUpdater>(246);
s.template registerType<SetHeroExperience>(247);
s.template registerType<GiveStackExperience>(248);
}
VCMI_LIB_NAMESPACE_END

View File

@ -154,7 +154,7 @@ void CGameHandler::levelUpHero(const CGHeroInstance * hero)
logGlobal->trace("%s got level %d", hero->getNameTranslated(), hero->level);
auto primarySkill = randomizer->rollPrimarySkillForLevelup(hero);
SetPrimSkill sps;
SetPrimarySkill sps;
sps.id = hero->id;
sps.which = primarySkill;
sps.mode = ChangeValueMode::RELATIVE;
@ -338,6 +338,19 @@ void CGameHandler::expGiven(const CGHeroInstance *hero)
levelUpCommander(hero->getCommander());
}
void CGameHandler::giveStackExperience(const CArmedInstance * army, TExpType val)
{
GiveStackExperience gse;
gse.id = army->id;
for (const auto & stack : army->Slots())
{
int experienceBonusMultiplier = stack.second->valOfBonuses(BonusType::STACK_EXPERIENCE_GAIN_PERCENT);
gse.val[stack.first] = val + val * experienceBonusMultiplier / 100;
}
sendAndApply(gse);
}
void CGameHandler::giveExperience(const CGHeroInstance * hero, TExpType amountToGain)
{
TExpType maxExp = LIBRARY->heroh->reqExp(LIBRARY->heroh->maxSupportedLevel());
@ -362,12 +375,11 @@ void CGameHandler::giveExperience(const CGHeroInstance * hero, TExpType amountTo
sendAndApply(iw);
}
SetPrimSkill sps;
sps.id = hero->id;
sps.which = PrimarySkill::EXPERIENCE;
sps.mode = ChangeValueMode::RELATIVE;
sps.val = amountToGain;
sendAndApply(sps);
SetHeroExperience she;
she.id = hero->id;
she.mode = ChangeValueMode::RELATIVE;
she.val = amountToGain;
sendAndApply(she);
//hero may level up
if (hero->getCommander() && hero->getCommander()->alive)
@ -385,7 +397,7 @@ void CGameHandler::giveExperience(const CGHeroInstance * hero, TExpType amountTo
void CGameHandler::changePrimSkill(const CGHeroInstance * hero, PrimarySkill which, si64 val, ChangeValueMode mode)
{
SetPrimSkill sps;
SetPrimarySkill sps;
sps.id = hero->id;
sps.which = which;
sps.mode = mode;

View File

@ -121,6 +121,7 @@ public:
bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override;
void setOwner(const CGObjectInstance * obj, PlayerColor owner) override;
void giveExperience(const CGHeroInstance * hero, TExpType val) override;
void giveStackExperience(const CArmedInstance * army, TExpType val);
void changePrimSkill(const CGHeroInstance * hero, PrimarySkill which, si64 val, ChangeValueMode mode) override;
void changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, ChangeValueMode mode) override;

View File

@ -331,8 +331,12 @@ void BattleResultProcessor::endBattleConfirm(const CBattleInfoCallback & battle)
gameHandler->swapGarrisonOnSiege(winnerHero->getVisitedTown()->id); //return defending visitor from garrison to its rightful place
}
//give exp
if(!finishingBattle->isDraw() && battleResult->exp[finishingBattle->winnerSide] && winnerHero)
gameHandler->giveExperience(winnerHero, battleResult->exp[finishingBattle->winnerSide]);
if(!finishingBattle->isDraw() && battleResult->exp[finishingBattle->winnerSide])
{
gameHandler->giveStackExperience(battle.battleGetArmyObject(finishingBattle->winnerSide), battleResult->exp[finishingBattle->winnerSide]);
if (winnerHero)
gameHandler->giveExperience(winnerHero, battleResult->exp[finishingBattle->winnerSide]);
}
// Add statistics
if(loserHero && !finishingBattle->isDraw())