소스 검색

Implement STACK_EXPERIENCE_GAIN_PERCENT bonus

Suggested on Discord

- implements STACK_EXPERIENCE_GAIN_PERCENT that modifies stack
experience received by units after combat
- removed "EXPERIENCE" primary skill. Changes to experience are now
applied through separate netpack
Ivan Savenko 4 달 전
부모
커밋
139f41c9b2

+ 6 - 0
AI/Nullkiller/AIGateway.cpp

@@ -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);

+ 1 - 0
AI/Nullkiller/AIGateway.h

@@ -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;

+ 6 - 0
AI/VCAI/VCAI.cpp

@@ -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);

+ 1 - 0
AI/VCAI/VCAI.h

@@ -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;

+ 8 - 9
client/CPlayerInterface.cpp

@@ -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)

+ 1 - 0
client/CPlayerInterface.h

@@ -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;

+ 2 - 1
client/ClientNetPackVisitors.h

@@ -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;

+ 12 - 1
client/NetPacksClient.cpp

@@ -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)

+ 6 - 0
docs/modders/Bonus/Bonus_Types.md

@@ -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

+ 1 - 0
lib/bonuses/BonusEnum.h

@@ -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 */
 
 

+ 1 - 0
lib/callback/IGameEventsReceiver.h

@@ -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

+ 0 - 1
lib/constants/EntityIdentifiers.cpp

@@ -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);

+ 0 - 2
lib/constants/EntityIdentifiers.h

@@ -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();

+ 18 - 10
lib/gameState/GameStatePackVisitor.cpp

@@ -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)

+ 3 - 1
lib/gameState/GameStatePackVisitor.h

@@ -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;

+ 22 - 23
lib/mapObjects/CGHeroInstance.cpp

@@ -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);
+
+	if(mode == ChangeValueMode::ABSOLUTE)
+	{
+		skill->val = static_cast<si32>(value);
+	}
+	else
 	{
-		auto skill = getLocalBonus(Selector::type()(BonusType::PRIMARY_SKILL)
-			.And(Selector::subtype()(BonusSubtypeID(primarySkill)))
-			.And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)));
-		assert(skill);
+		skill->val += static_cast<si32>(value);
+	}
+	nodeHasChanged();
+}
 
-		if(mode == ChangeValueMode::ABSOLUTE)
-		{
-			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 if(primarySkill == PrimarySkill::EXPERIENCE)
+	else
 	{
-		if(mode == ChangeValueMode::ABSOLUTE)
-		{
-			exp = value;
-		}
-		else
-		{
-			exp += value;
-		}
+		exp += value;
 	}
 }
 

+ 1 - 0
lib/mapObjects/CGHeroInstance.h

@@ -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();

+ 3 - 1
lib/networkPacks/NetPackVisitor.h

@@ -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) {}

+ 12 - 2
lib/networkPacks/NetPacksLib.cpp

@@ -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)

+ 31 - 1
lib/networkPacks/PacksForClient.h

@@ -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;

+ 3 - 1
lib/serializer/RegisterTypes.h

@@ -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

+ 20 - 8
server/CGameHandler.cpp

@@ -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;

+ 1 - 0
server/CGameHandler.h

@@ -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;
 

+ 6 - 2
server/battles/BattleResultProcessor.cpp

@@ -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())