2023-08-14 19:46:42 +03:00
/*
* BattleResultProcessor . cpp , part of VCMI engine
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
# include "StdInc.h"
# include "BattleResultProcessor.h"
2025-04-07 08:25:32 +02:00
# include "battle/BattleInfo.h"
2023-08-14 19:46:42 +03:00
# include "../CGameHandler.h"
2024-04-26 13:26:33 +03:00
# include "../TurnTimerHandler.h"
2023-08-14 19:46:42 +03:00
# include "../processors/HeroPoolProcessor.h"
# include "../queries/QueriesProcessor.h"
# include "../queries/BattleQueries.h"
# include "../../lib/CStack.h"
2024-01-25 23:44:41 +01:00
# include "../../lib/CPlayerState.h"
2024-08-31 11:00:36 +00:00
# include "../../lib/IGameSettings.h"
2023-08-28 17:43:57 +03:00
# include "../../lib/battle/SideInBattle.h"
2025-04-29 12:10:19 +03:00
# include "../../lib/entities/artifact/ArtifactUtils.h"
2025-05-09 22:56:22 +02:00
# include "../../lib/entities/artifact/CArtifact.h"
2025-04-29 12:10:19 +03:00
# include "../../lib/entities/artifact/CArtifactFittingSet.h"
2023-08-14 19:46:42 +03:00
# include "../../lib/gameState/CGameState.h"
# include "../../lib/mapObjects/CGTownInstance.h"
2023-10-23 13:59:15 +03:00
# include "../../lib/networkPacks/PacksForClientBattle.h"
2023-08-14 19:46:42 +03:00
# include "../../lib/spells/CSpellHandler.h"
2025-03-18 14:37:22 +00:00
# include <boost/lexical_cast.hpp>
2024-04-26 12:44:57 +03:00
BattleResultProcessor : : BattleResultProcessor ( BattleProcessor * owner , CGameHandler * newGameHandler )
2023-08-17 00:51:50 +03:00
// : owner(owner)
2024-04-26 12:44:57 +03:00
: gameHandler ( newGameHandler )
2023-08-14 19:46:42 +03:00
{
}
2024-08-11 20:22:35 +00:00
CasualtiesAfterBattle : : CasualtiesAfterBattle ( const CBattleInfoCallback & battle , BattleSide sideInBattle ) :
2023-08-28 17:43:57 +03:00
army ( battle . battleGetArmyObject ( sideInBattle ) )
2023-08-14 19:46:42 +03:00
{
heroWithDeadCommander = ObjectInstanceID ( ) ;
2023-08-28 17:43:57 +03:00
PlayerColor color = battle . sideToPlayer ( sideInBattle ) ;
2023-08-14 19:46:42 +03:00
2024-02-12 21:53:38 +02:00
auto allStacks = battle . battleGetStacksIf ( [ color ] ( const CStack * stack ) {
if ( stack - > summoned ) //don't take into account temporary summoned stacks
return false ;
if ( stack - > unitOwner ( ) ! = color ) //remove only our stacks
return false ;
if ( stack - > isTurret ( ) )
return false ;
return true ;
} ) ;
for ( const CStack * stConst : allStacks )
2023-08-14 19:46:42 +03:00
{
2023-08-28 17:43:57 +03:00
// Use const cast - in order to call non-const "takeResurrected" for proper calculation of casualties
// TODO: better solution
CStack * st = const_cast < CStack * > ( stConst ) ;
2023-08-14 19:46:42 +03:00
logGlobal - > debug ( " Calculating casualties for %s " , st - > nodeName ( ) ) ;
st - > health . takeResurrected ( ) ;
2024-02-12 21:53:38 +02:00
if ( st - > unitSlot ( ) = = SlotID : : WAR_MACHINES_SLOT )
2023-08-14 19:46:42 +03:00
{
auto warMachine = st - > unitType ( ) - > warMachine ;
if ( warMachine = = ArtifactID : : NONE )
{
logGlobal - > error ( " Invalid creature in war machine virtual slot. Stack: %s " , st - > nodeName ( ) ) ;
}
//catapult artifact remain even if "creature" killed in siege
else if ( warMachine ! = ArtifactID : : CATAPULT & & st - > getCount ( ) < = 0 )
{
logGlobal - > debug ( " War machine has been destroyed " ) ;
2024-09-04 14:32:36 +00:00
auto hero = dynamic_cast < const CGHeroInstance * > ( army ) ;
2023-08-14 19:46:42 +03:00
if ( hero )
2023-10-14 22:00:39 +03:00
removedWarMachines . push_back ( ArtifactLocation ( hero - > id , hero - > getArtPos ( warMachine , true ) ) ) ;
2023-08-14 19:46:42 +03:00
else
logGlobal - > error ( " War machine in army without hero " ) ;
}
}
else if ( st - > unitSlot ( ) = = SlotID : : SUMMONED_SLOT_PLACEHOLDER )
{
if ( st - > alive ( ) & & st - > getCount ( ) > 0 )
{
logGlobal - > debug ( " Permanently summoned %d units. " , st - > getCount ( ) ) ;
const CreatureID summonedType = st - > creatureId ( ) ;
summoned [ summonedType ] + = st - > getCount ( ) ;
}
}
else if ( st - > unitSlot ( ) = = SlotID : : COMMANDER_SLOT_PLACEHOLDER )
{
if ( nullptr = = st - > base )
{
logGlobal - > error ( " Stack with no base in commander slot. Stack: %s " , st - > nodeName ( ) ) ;
}
else
{
auto c = dynamic_cast < const CCommanderInstance * > ( st - > base ) ;
if ( c )
{
auto h = dynamic_cast < const CGHeroInstance * > ( army ) ;
2025-03-09 21:51:33 +00:00
if ( h & & h - > getCommander ( ) = = c & & ( st - > getCount ( ) = = 0 | | ! st - > alive ( ) ) )
2023-08-14 19:46:42 +03:00
{
logGlobal - > debug ( " Commander is dead. " ) ;
heroWithDeadCommander = army - > id ; //TODO: unify commander handling
}
}
else
logGlobal - > error ( " Stack with invalid instance in commander slot. Stack: %s " , st - > nodeName ( ) ) ;
}
}
else if ( st - > base & & ! army - > slotEmpty ( st - > unitSlot ( ) ) )
{
logGlobal - > debug ( " Count: %d; base count: %d " , st - > getCount ( ) , army - > getStackCount ( st - > unitSlot ( ) ) ) ;
if ( st - > getCount ( ) = = 0 | | ! st - > alive ( ) )
{
logGlobal - > debug ( " Stack has been destroyed. " ) ;
2025-03-03 14:08:33 +00:00
StackLocation sl ( army - > id , st - > unitSlot ( ) ) ;
2023-08-14 19:46:42 +03:00
newStackCounts . push_back ( TStackAndItsNewCount ( sl , 0 ) ) ;
}
2024-02-12 21:53:38 +02:00
else if ( st - > getCount ( ) ! = army - > getStackCount ( st - > unitSlot ( ) ) )
2023-08-14 19:46:42 +03:00
{
2024-02-12 21:53:38 +02:00
logGlobal - > debug ( " Stack size changed: %d -> %d units. " , army - > getStackCount ( st - > unitSlot ( ) ) , st - > getCount ( ) ) ;
2025-03-03 14:08:33 +00:00
StackLocation sl ( army - > id , st - > unitSlot ( ) ) ;
2023-08-14 19:46:42 +03:00
newStackCounts . push_back ( TStackAndItsNewCount ( sl , st - > getCount ( ) ) ) ;
}
}
else
{
logGlobal - > warn ( " Unable to process stack: %s " , st - > nodeName ( ) ) ;
}
}
}
void CasualtiesAfterBattle : : updateArmy ( CGameHandler * gh )
{
2025-05-20 19:01:25 +03:00
if ( gh - > gameInfo ( ) . getObjInstance ( army - > id ) = = nullptr )
2024-05-28 14:41:05 +00:00
throw std : : runtime_error ( " Object " + army - > getObjectName ( ) + " is not on the map! " ) ;
2023-08-14 19:46:42 +03:00
for ( TStackAndItsNewCount & ncount : newStackCounts )
{
if ( ncount . second > 0 )
2025-05-15 15:47:34 +03:00
gh - > changeStackCount ( ncount . first , ncount . second , ChangeValueMode : : ABSOLUTE ) ;
2023-08-14 19:46:42 +03:00
else
gh - > eraseStack ( ncount . first , true ) ;
}
for ( auto summoned_iter : summoned )
{
SlotID slot = army - > getSlotFor ( summoned_iter . first ) ;
if ( slot . validSlot ( ) )
{
2025-03-03 14:08:33 +00:00
StackLocation location ( army - > id , slot ) ;
2023-08-14 19:46:42 +03:00
gh - > addToSlot ( location , summoned_iter . first . toCreature ( ) , summoned_iter . second ) ;
}
else
{
//even if it will be possible to summon anything permanently it should be checked for free slot
//necromancy is handled separately
gh - > complain ( " No free slot to put summoned creature " ) ;
}
}
for ( auto al : removedWarMachines )
{
gh - > removeArtifact ( al ) ;
}
if ( heroWithDeadCommander ! = ObjectInstanceID ( ) )
{
SetCommanderProperty scp ;
scp . heroid = heroWithDeadCommander ;
scp . which = SetCommanderProperty : : ALIVE ;
scp . amount = 0 ;
2024-10-04 18:59:51 +00:00
gh - > sendAndApply ( scp ) ;
2023-08-14 19:46:42 +03:00
}
}
2023-08-31 18:45:52 +03:00
FinishingBattleHelper : : FinishingBattleHelper ( const CBattleInfoCallback & info , const BattleResult & result , int remainingBattleQueriesCount )
2023-08-14 19:46:42 +03:00
{
2025-04-07 08:25:32 +02:00
const auto attackerHero = info . getBattle ( ) - > getSideHero ( BattleSide : : ATTACKER ) ;
const auto defenderHero = info . getBattle ( ) - > getSideHero ( BattleSide : : DEFENDER ) ;
2023-08-28 17:43:57 +03:00
if ( result . winner = = BattleSide : : ATTACKER )
{
2025-04-07 08:25:32 +02:00
winnerId = attackerHero ? attackerHero - > id : ObjectInstanceID : : NONE ;
loserId = defenderHero ? defenderHero - > id : ObjectInstanceID : : NONE ;
2023-08-31 18:45:52 +03:00
victor = info . getBattle ( ) - > getSidePlayer ( BattleSide : : ATTACKER ) ;
loser = info . getBattle ( ) - > getSidePlayer ( BattleSide : : DEFENDER ) ;
2023-08-28 17:43:57 +03:00
}
else
{
2025-04-07 08:25:32 +02:00
winnerId = defenderHero ? defenderHero - > id : ObjectInstanceID : : NONE ;
loserId = attackerHero ? attackerHero - > id : ObjectInstanceID : : NONE ;
2023-08-31 18:45:52 +03:00
victor = info . getBattle ( ) - > getSidePlayer ( BattleSide : : DEFENDER ) ;
loser = info . getBattle ( ) - > getSidePlayer ( BattleSide : : ATTACKER ) ;
2023-08-28 17:43:57 +03:00
}
2023-08-14 19:46:42 +03:00
winnerSide = result . winner ;
2023-08-28 17:43:57 +03:00
2023-08-14 19:46:42 +03:00
this - > remainingBattleQueriesCount = remainingBattleQueriesCount ;
}
2023-08-28 17:43:57 +03:00
void BattleResultProcessor : : endBattle ( const CBattleInfoCallback & battle )
2023-08-14 19:46:42 +03:00
{
2024-08-11 20:22:35 +00:00
auto const & giveExp = [ & battle ] ( BattleResult & r )
2023-08-14 19:46:42 +03:00
{
2024-08-11 20:22:35 +00:00
if ( r . winner = = BattleSide : : NONE )
2023-08-14 19:46:42 +03:00
{
// draw
return ;
}
2024-08-11 20:22:35 +00:00
r . exp [ BattleSide : : ATTACKER ] = 0 ;
r . exp [ BattleSide : : DEFENDER ] = 0 ;
for ( auto i = r . casualties [ battle . otherSide ( r . winner ) ] . begin ( ) ; i ! = r . casualties [ battle . otherSide ( r . winner ) ] . end ( ) ; i + + )
2023-08-14 19:46:42 +03:00
{
2025-04-19 16:12:07 +03:00
r . exp [ r . winner ] + = i - > first . toCreature ( ) - > valOfBonuses ( BonusType : : STACK_HEALTH ) * i - > second ;
2023-08-14 19:46:42 +03:00
}
} ;
LOG_TRACE ( logGlobal ) ;
2023-08-28 17:43:57 +03:00
auto * battleResult = battleResults . at ( battle . getBattle ( ) - > getBattleID ( ) ) . get ( ) ;
const auto * heroAttacker = battle . battleGetFightingHero ( BattleSide : : ATTACKER ) ;
const auto * heroDefender = battle . battleGetFightingHero ( BattleSide : : DEFENDER ) ;
2023-08-25 18:23:15 +03:00
2023-08-14 19:46:42 +03:00
//Fill BattleResult structure with exp info
giveExp ( * battleResult ) ;
if ( battleResult - > result = = EBattleResult : : NORMAL ) // give 500 exp for defeating hero, unless he escaped
{
if ( heroAttacker )
2024-08-11 20:22:35 +00:00
battleResult - > exp [ BattleSide : : DEFENDER ] + = 500 ;
2023-08-14 19:46:42 +03:00
if ( heroDefender )
2024-08-11 20:22:35 +00:00
battleResult - > exp [ BattleSide : : ATTACKER ] + = 500 ;
2023-08-14 19:46:42 +03:00
}
2023-09-11 19:51:23 +02:00
// Give 500 exp to winner if a town was conquered during the battle
const auto * defendedTown = battle . battleGetDefendedTown ( ) ;
if ( defendedTown & & battleResult - > winner = = BattleSide : : ATTACKER )
battleResult - > exp [ BattleSide : : ATTACKER ] + = 500 ;
2023-08-14 19:46:42 +03:00
if ( heroAttacker )
2024-08-11 20:22:35 +00:00
battleResult - > exp [ BattleSide : : ATTACKER ] = heroAttacker - > calculateXp ( battleResult - > exp [ BattleSide : : ATTACKER ] ) ; //scholar skill
2023-08-14 19:46:42 +03:00
if ( heroDefender )
2024-08-11 20:22:35 +00:00
battleResult - > exp [ BattleSide : : DEFENDER ] = heroDefender - > calculateXp ( battleResult - > exp [ BattleSide : : DEFENDER ] ) ;
2023-08-14 19:46:42 +03:00
2024-08-11 20:22:35 +00:00
auto battleQuery = std : : dynamic_pointer_cast < CBattleQuery > ( gameHandler - > queries - > topQuery ( battle . sideToPlayer ( BattleSide : : ATTACKER ) ) ) ;
2024-01-27 01:57:28 +01:00
if ( ! battleQuery )
2024-08-11 20:22:35 +00:00
battleQuery = std : : dynamic_pointer_cast < CBattleQuery > ( gameHandler - > queries - > topQuery ( battle . sideToPlayer ( BattleSide : : DEFENDER ) ) ) ;
2023-08-14 19:46:42 +03:00
if ( ! battleQuery )
{
logGlobal - > error ( " Cannot find battle query! " ) ;
2024-08-11 20:22:35 +00:00
gameHandler - > complain ( " Player " + boost : : lexical_cast < std : : string > ( battle . sideToPlayer ( BattleSide : : ATTACKER ) ) + " has no battle query at the top! " ) ;
2023-08-14 19:46:42 +03:00
return ;
}
battleQuery - > result = std : : make_optional ( * battleResult ) ;
//Check how many battle gameHandler->queries were created (number of players blocked by battle)
const int queriedPlayers = battleQuery ? ( int ) boost : : count ( gameHandler - > queries - > allQueries ( ) , battleQuery ) : 0 ;
2023-08-25 18:23:15 +03:00
2023-08-28 17:43:57 +03:00
assert ( finishingBattles . count ( battle . getBattle ( ) - > getBattleID ( ) ) = = 0 ) ;
2023-08-31 18:45:52 +03:00
finishingBattles [ battle . getBattle ( ) - > getBattleID ( ) ] = std : : make_unique < FinishingBattleHelper > ( battle , * battleResult , queriedPlayers ) ;
2023-08-14 19:46:42 +03:00
// in battles against neutrals, 1st player can ask to replay battle manually
2025-05-20 19:01:25 +03:00
const auto * attackerPlayer = gameHandler - > gameInfo ( ) . getPlayerState ( battle . getBattle ( ) - > getSidePlayer ( BattleSide : : ATTACKER ) ) ;
const auto * defenderPlayer = gameHandler - > gameInfo ( ) . getPlayerState ( battle . getBattle ( ) - > getSidePlayer ( BattleSide : : DEFENDER ) ) ;
2024-01-25 23:44:41 +01:00
bool isAttackerHuman = attackerPlayer & & attackerPlayer - > isHuman ( ) ;
bool isDefenderHuman = defenderPlayer & & defenderPlayer - > isHuman ( ) ;
bool onlyOnePlayerHuman = isAttackerHuman ! = isDefenderHuman ;
// in battles against neutrals attacker can ask to replay battle manually, additionally in battles against AI player human side can also ask for replay
if ( onlyOnePlayerHuman )
2023-08-14 19:46:42 +03:00
{
2024-02-28 22:25:14 +01:00
auto battleDialogQuery = std : : make_shared < CBattleDialogQuery > ( gameHandler , battle . getBattle ( ) , battleQuery - > result ) ;
2023-08-14 19:46:42 +03:00
battleResult - > queryID = battleDialogQuery - > queryID ;
gameHandler - > queries - > addQuery ( battleDialogQuery ) ;
}
else
2023-09-28 19:43:04 +03:00
battleResult - > queryID = QueryID : : NONE ;
2023-08-14 19:46:42 +03:00
//set same battle result for all gameHandler->queries
for ( auto q : gameHandler - > queries - > allQueries ( ) )
{
auto otherBattleQuery = std : : dynamic_pointer_cast < CBattleQuery > ( q ) ;
2023-09-05 17:22:11 +03:00
if ( otherBattleQuery & & otherBattleQuery - > battleID = = battle . getBattle ( ) - > getBattleID ( ) )
2023-08-14 19:46:42 +03:00
otherBattleQuery - > result = battleQuery - > result ;
}
2024-04-26 13:15:39 +03:00
gameHandler - > turnTimerHandler - > onBattleEnd ( battle . getBattle ( ) - > getBattleID ( ) ) ;
2024-10-04 18:59:51 +00:00
gameHandler - > sendAndApply ( * battleResult ) ;
2023-08-14 19:46:42 +03:00
2023-08-20 00:22:31 +03:00
if ( battleResult - > queryID = = QueryID : : NONE )
2023-08-25 18:23:15 +03:00
endBattleConfirm ( battle ) ;
2023-08-14 19:46:42 +03:00
}
2023-08-28 17:43:57 +03:00
void BattleResultProcessor : : endBattleConfirm ( const CBattleInfoCallback & battle )
2023-08-14 19:46:42 +03:00
{
2025-04-07 08:25:32 +02:00
auto battleQuery = std : : dynamic_pointer_cast < CBattleQuery > ( gameHandler - > queries - > topQuery ( battle . sideToPlayer ( BattleSide : : ATTACKER ) ) ) ;
if ( ! battleQuery )
battleQuery = std : : dynamic_pointer_cast < CBattleQuery > ( gameHandler - > queries - > topQuery ( battle . sideToPlayer ( BattleSide : : DEFENDER ) ) ) ;
if ( ! battleQuery )
{
logGlobal - > trace ( " No battle query, battle end was confirmed by another player " ) ;
return ;
}
2023-08-28 17:43:57 +03:00
auto * battleResult = battleResults . at ( battle . getBattle ( ) - > getBattleID ( ) ) . get ( ) ;
auto * finishingBattle = finishingBattles . at ( battle . getBattle ( ) - > getBattleID ( ) ) . get ( ) ;
2023-08-25 18:23:15 +03:00
2023-08-28 17:43:57 +03:00
//calculate casualties before deleting battle
CasualtiesAfterBattle cab1 ( battle , BattleSide : : ATTACKER ) ;
CasualtiesAfterBattle cab2 ( battle , BattleSide : : DEFENDER ) ;
2024-05-10 17:57:15 +03:00
cab1 . updateArmy ( gameHandler ) ;
cab2 . updateArmy ( gameHandler ) ; //take casualties after battle is deleted
2025-04-07 08:25:32 +02:00
const auto winnerHero = battle . battleGetFightingHero ( finishingBattle - > winnerSide ) ;
const auto loserHero = battle . battleGetFightingHero ( CBattleInfoEssentials : : otherSide ( finishingBattle - > winnerSide ) ) ;
2025-04-05 18:41:22 +02:00
2024-05-10 17:57:15 +03:00
if ( battleResult - > winner = = BattleSide : : DEFENDER
2025-04-05 18:41:22 +02:00
& & winnerHero
2025-03-09 21:51:33 +00:00
& & winnerHero - > getVisitedTown ( )
& & ! winnerHero - > isGarrisoned ( )
& & winnerHero - > getVisitedTown ( ) - > getGarrisonHero ( ) = = winnerHero )
2024-05-10 17:57:15 +03:00
{
2025-03-09 21:51:33 +00:00
gameHandler - > swapGarrisonOnSiege ( winnerHero - > getVisitedTown ( ) - > id ) ; //return defending visitor from garrison to its rightful place
2024-05-10 17:57:15 +03:00
}
//give exp
2025-04-05 18:41:22 +02:00
if ( ! finishingBattle - > isDraw ( ) & & battleResult - > exp [ finishingBattle - > winnerSide ] & & winnerHero )
gameHandler - > giveExperience ( winnerHero , battleResult - > exp [ finishingBattle - > winnerSide ] ) ;
2023-08-14 19:46:42 +03:00
2025-04-07 08:25:32 +02:00
// Add statistics
if ( loserHero & & ! finishingBattle - > isDraw ( ) )
{
2025-03-09 21:51:33 +00:00
const CGHeroInstance * strongestHero = nullptr ;
2025-04-19 14:14:12 +03:00
for ( auto & hero : gameHandler - > gameState ( ) . getPlayerState ( finishingBattle - > loser ) - > getHeroes ( ) )
2025-04-07 08:25:32 +02:00
if ( ! strongestHero | | hero - > exp > strongestHero - > exp )
strongestHero = hero ;
if ( strongestHero - > id = = finishingBattle - > loserId & & strongestHero - > level > 5 )
2025-05-20 21:40:05 +03:00
gameHandler - > statistics - > accumulatedValues [ finishingBattle - > victor ] . lastDefeatedStrongestHeroDay = gameHandler - > gameState ( ) . getDate ( Date : : DAY ) ;
2025-04-07 08:25:32 +02:00
}
if ( battle . sideToPlayer ( BattleSide : : ATTACKER ) = = PlayerColor : : NEUTRAL | | battle . sideToPlayer ( BattleSide : : DEFENDER ) = = PlayerColor : : NEUTRAL )
{
2025-05-20 21:40:05 +03:00
gameHandler - > statistics - > accumulatedValues [ battle . sideToPlayer ( BattleSide : : ATTACKER ) ] . numBattlesNeutral + + ;
gameHandler - > statistics - > accumulatedValues [ battle . sideToPlayer ( BattleSide : : DEFENDER ) ] . numBattlesNeutral + + ;
2025-04-07 08:25:32 +02:00
if ( ! finishingBattle - > isDraw ( ) )
2025-05-20 21:40:05 +03:00
gameHandler - > statistics - > accumulatedValues [ battle . sideToPlayer ( finishingBattle - > winnerSide ) ] . numWinBattlesNeutral + + ;
2025-04-07 08:25:32 +02:00
}
else
{
2025-05-20 21:40:05 +03:00
gameHandler - > statistics - > accumulatedValues [ battle . sideToPlayer ( BattleSide : : ATTACKER ) ] . numBattlesPlayer + + ;
gameHandler - > statistics - > accumulatedValues [ battle . sideToPlayer ( BattleSide : : DEFENDER ) ] . numBattlesPlayer + + ;
2025-04-07 08:25:32 +02:00
if ( ! finishingBattle - > isDraw ( ) )
2025-05-20 21:40:05 +03:00
gameHandler - > statistics - > accumulatedValues [ battle . sideToPlayer ( finishingBattle - > winnerSide ) ] . numWinBattlesPlayer + + ;
2025-04-07 08:25:32 +02:00
}
BattleResultAccepted raccepted ;
raccepted . battleID = battle . getBattle ( ) - > getBattleID ( ) ;
2025-03-14 17:07:30 +00:00
raccepted . heroResult [ finishingBattle - > winnerSide ] . heroID = winnerHero ? winnerHero - > id : ObjectInstanceID : : NONE ;
raccepted . heroResult [ CBattleInfoEssentials : : otherSide ( finishingBattle - > winnerSide ) ] . heroID = loserHero ? loserHero - > id : ObjectInstanceID : : NONE ;
raccepted . heroResult [ BattleSide : : ATTACKER ] . armyID = battle . battleGetArmyObject ( BattleSide : : ATTACKER ) - > id ;
raccepted . heroResult [ BattleSide : : DEFENDER ] . armyID = battle . battleGetArmyObject ( BattleSide : : DEFENDER ) - > id ;
2025-04-07 08:25:32 +02:00
raccepted . heroResult [ BattleSide : : ATTACKER ] . exp = battleResult - > exp [ BattleSide : : ATTACKER ] ;
raccepted . heroResult [ BattleSide : : DEFENDER ] . exp = battleResult - > exp [ BattleSide : : DEFENDER ] ;
raccepted . winnerSide = finishingBattle - > winnerSide ;
gameHandler - > sendAndApply ( raccepted ) ;
2025-04-16 17:13:20 +02:00
gameHandler - > queries - > popIfTop ( battleQuery ) ; // Workaround to remove battle query for AI case. TODO Think of a cleaner solution.
2025-04-07 20:10:14 +02:00
//--> continuation (battleFinalize) occurs after level-up gameHandler->queries are handled or on removing query
2025-04-07 08:25:32 +02:00
}
void BattleResultProcessor : : battleFinalize ( const BattleID & battleID , const BattleResult & result )
{
LOG_TRACE ( logGlobal ) ;
assert ( finishingBattles . count ( battleID ) ! = 0 ) ;
if ( finishingBattles . count ( battleID ) = = 0 )
return ;
auto & finishingBattle = finishingBattles [ battleID ] ;
finishingBattle - > remainingBattleQueriesCount - - ;
logGlobal - > trace ( " Decremented gameHandler->queries count to %d " , finishingBattle - > remainingBattleQueriesCount ) ;
if ( finishingBattle - > remainingBattleQueriesCount > 0 )
//Battle results will be handled when all battle gameHandler->queries are closed
return ;
//TODO consider if we really want it to work like above. ATM each player as unblocked as soon as possible
// but the battle consequences are applied after final player is unblocked. Hard to abuse...
// Still, it looks like a hole.
2025-04-19 14:14:12 +03:00
const auto battle = std : : find_if ( gameHandler - > gameState ( ) . currentBattles . begin ( ) , gameHandler - > gameState ( ) . currentBattles . end ( ) ,
2025-04-07 20:10:14 +02:00
[ battleID ] ( const auto & desiredBattle )
2025-04-07 08:25:32 +02:00
{
2025-04-07 20:10:14 +02:00
return desiredBattle - > battleID = = battleID ;
2025-04-07 08:25:32 +02:00
} ) ;
2025-04-19 14:14:12 +03:00
assert ( battle ! = gameHandler - > gameState ( ) . currentBattles . end ( ) ) ;
2025-04-07 08:25:32 +02:00
const auto winnerHero = ( * battle ) - > battleGetFightingHero ( finishingBattle - > winnerSide ) ;
const auto loserHero = ( * battle ) - > battleGetFightingHero ( CBattleInfoEssentials : : otherSide ( finishingBattle - > winnerSide ) ) ;
BattleResultsApplied resultsApplied ;
2024-05-10 17:57:15 +03:00
// Eagle Eye handling
2025-04-05 18:41:22 +02:00
if ( ! finishingBattle - > isDraw ( ) & & winnerHero )
2023-08-14 19:46:42 +03:00
{
2025-04-05 18:41:22 +02:00
if ( auto eagleEyeLevel = winnerHero - > valOfBonuses ( BonusType : : LEARN_BATTLE_SPELL_LEVEL_LIMIT ) )
2023-08-14 19:46:42 +03:00
{
2025-04-07 20:10:14 +02:00
resultsApplied . learnedSpells . learn = 1 ;
resultsApplied . learnedSpells . hid = finishingBattle - > winnerId ;
2025-04-07 08:25:32 +02:00
for ( const auto & spellId : ( * battle ) - > getUsedSpells ( CBattleInfoEssentials : : otherSide ( result . winner ) ) )
2023-08-14 19:46:42 +03:00
{
2025-04-07 20:10:14 +02:00
const auto spell = spellId . toEntity ( LIBRARY - > spells ( ) ) ;
2024-05-10 17:57:15 +03:00
if ( spell
& & spell - > getLevel ( ) < = eagleEyeLevel
2025-04-05 18:41:22 +02:00
& & ! winnerHero - > spellbookContainsSpell ( spell - > getId ( ) )
2025-04-07 20:10:14 +02:00
& & gameHandler - > getRandomGenerator ( ) . nextInt ( 99 ) < winnerHero - > valOfBonuses ( BonusType : : LEARN_BATTLE_SPELL_CHANCE ) )
2024-05-10 17:57:15 +03:00
{
2025-04-07 20:10:14 +02:00
resultsApplied . learnedSpells . spells . insert ( spell - > getId ( ) ) ;
2024-05-10 17:57:15 +03:00
}
2023-08-14 19:46:42 +03:00
}
}
2024-05-10 17:57:15 +03:00
}
2025-04-07 08:25:32 +02:00
2025-05-09 22:56:22 +02:00
// Moving artifacts handling
2025-04-07 08:25:32 +02:00
if ( result . result = = EBattleResult : : NORMAL & & ! finishingBattle - > isDraw ( ) & & winnerHero )
2023-08-14 19:46:42 +03:00
{
2025-04-05 18:41:22 +02:00
CArtifactFittingSet artFittingSet ( * winnerHero ) ;
const auto addArtifactToTransfer = [ & artFittingSet ] ( BulkMoveArtifacts & pack , const ArtifactPosition & srcSlot , const CArtifactInstance * art )
2023-08-14 19:46:42 +03:00
{
2024-03-28 16:16:10 +02:00
assert ( art ) ;
2024-03-28 00:28:31 +02:00
const auto dstSlot = ArtifactUtils : : getArtAnyPosition ( & artFittingSet , art - > getTypeId ( ) ) ;
if ( dstSlot ! = ArtifactPosition : : PRE_FIRST )
2023-08-14 19:46:42 +03:00
{
2025-04-05 22:55:57 +02:00
pack . artsPack0 . emplace_back ( MoveArtifactInfo ( srcSlot , dstSlot ) ) ;
2024-06-22 19:29:39 +03:00
if ( ArtifactUtils : : isSlotEquipment ( dstSlot ) )
pack . artsPack0 . back ( ) . askAssemble = true ;
2024-03-28 00:28:31 +02:00
artFittingSet . putArtifact ( dstSlot , const_cast < CArtifactInstance * > ( art ) ) ;
2023-08-14 19:46:42 +03:00
}
} ;
2025-04-05 18:41:22 +02:00
if ( loserHero )
2023-08-14 19:46:42 +03:00
{
2025-05-09 22:56:22 +02:00
auto & packHero = resultsApplied . movingArtifacts . emplace_back ( finishingBattle - > victor , finishingBattle - > loserId , finishingBattle - > winnerId , false ) ;
2025-04-05 18:41:22 +02:00
packHero . srcArtHolder = finishingBattle - > loserId ;
2024-07-20 15:07:34 +03:00
for ( const auto & slot : ArtifactUtils : : commonWornSlots ( ) )
2023-08-14 19:46:42 +03:00
{
2025-04-05 18:41:22 +02:00
if ( const auto artSlot = loserHero - > artifactsWorn . find ( slot ) ; artSlot ! = loserHero - > artifactsWorn . end ( ) & & ArtifactUtils : : isArtRemovable ( * artSlot ) )
2024-07-20 15:07:34 +03:00
{
addArtifactToTransfer ( packHero , artSlot - > first , artSlot - > second . getArt ( ) ) ;
}
2023-08-14 19:46:42 +03:00
}
2025-04-05 18:41:22 +02:00
for ( const auto & artSlot : loserHero - > artifactsInBackpack )
2023-08-14 19:46:42 +03:00
{
2024-03-28 16:16:10 +02:00
if ( const auto art = artSlot . getArt ( ) ; art - > getTypeId ( ) ! = ArtifactID : : GRAIL )
2025-04-05 18:41:22 +02:00
addArtifactToTransfer ( packHero , loserHero - > getArtPos ( art ) , art ) ;
2023-08-14 19:46:42 +03:00
}
2024-03-28 00:28:31 +02:00
2025-03-09 21:51:33 +00:00
if ( loserHero - > getCommander ( ) )
2023-08-14 19:46:42 +03:00
{
2025-05-09 22:56:22 +02:00
auto & packCommander = resultsApplied . movingArtifacts . emplace_back ( finishingBattle - > victor , finishingBattle - > loserId , finishingBattle - > winnerId , false ) ;
2025-03-09 21:51:33 +00:00
packCommander . srcCreature = loserHero - > findStack ( loserHero - > getCommander ( ) ) ;
for ( const auto & artSlot : loserHero - > getCommander ( ) - > artifactsWorn )
2024-05-10 17:57:15 +03:00
addArtifactToTransfer ( packCommander , artSlot . first , artSlot . second . getArt ( ) ) ;
2023-08-14 19:46:42 +03:00
}
2025-05-20 19:01:25 +03:00
auto armyObj = dynamic_cast < const CArmedInstance * > ( gameHandler - > gameInfo ( ) . getObj ( finishingBattle - > loserId ) ) ;
2024-08-08 13:57:06 +03:00
for ( const auto & armySlot : armyObj - > stacks )
{
2025-05-09 22:56:22 +02:00
auto & packsArmy = resultsApplied . movingArtifacts . emplace_back ( finishingBattle - > victor , finishingBattle - > loserId , finishingBattle - > winnerId , false ) ;
2024-08-08 13:57:06 +03:00
packsArmy . srcArtHolder = armyObj - > id ;
packsArmy . srcCreature = armySlot . first ;
for ( const auto & artSlot : armySlot . second - > artifactsWorn )
addArtifactToTransfer ( packsArmy , artSlot . first , armySlot . second - > getArt ( artSlot . first ) ) ;
}
2023-08-14 19:46:42 +03:00
}
}
2025-05-09 22:56:22 +02:00
// Growing artifacts handling
if ( ! finishingBattle - > isDraw ( ) & & winnerHero )
{
const auto addArtifactToGrowing = [ & resultsApplied ] ( const std : : map < ArtifactPosition , ArtSlotInfo > & artMap )
{
for ( const auto & [ slot , slotInfo ] : artMap )
{
const auto artInst = slotInfo . getArt ( ) ;
assert ( artInst ) ;
if ( artInst - > getType ( ) - > isGrowing ( ) )
resultsApplied . growingArtifacts . emplace_back ( artInst - > getId ( ) ) ;
}
} ;
if ( const auto commander = winnerHero - > getCommander ( ) ; commander & & commander - > alive )
addArtifactToGrowing ( commander - > artifactsWorn ) ;
addArtifactToGrowing ( winnerHero - > artifactsWorn ) ;
}
2025-05-12 20:53:08 +02:00
// Charged artifacts handling
2025-05-14 15:07:21 +02:00
const auto addArtifactToDischarging = [ & resultsApplied ] ( const std : : map < ArtifactPosition , ArtSlotInfo > & artMap ,
const ObjectInstanceID & id , const std : : optional < SlotID > & creature = std : : nullopt )
2025-05-12 20:53:08 +02:00
{
for ( const auto & [ slot , slotInfo ] : artMap )
{
auto artInst = slotInfo . getArt ( ) ;
assert ( artInst ) ;
2025-05-17 19:01:04 +02:00
if ( const auto condition = artInst - > getType ( ) - > getDischargeCondition ( ) ; condition = = DischargeArtifactCondition : : BATTLE )
2025-05-14 15:07:21 +02:00
{
auto & discharging = resultsApplied . dischargingArtifacts . emplace_back ( artInst - > getId ( ) , 1 ) ;
discharging . artLoc . emplace ( id , creature , slot ) ;
}
2025-05-12 20:53:08 +02:00
}
} ;
if ( winnerHero )
{
2025-05-14 15:07:21 +02:00
addArtifactToDischarging ( winnerHero - > artifactsWorn , winnerHero - > id ) ;
2025-05-12 20:53:08 +02:00
if ( const auto commander = winnerHero - > getCommander ( ) )
2025-05-14 15:07:21 +02:00
addArtifactToDischarging ( commander - > artifactsWorn , winnerHero - > id , winnerHero - > findStack ( winnerHero - > getCommander ( ) ) ) ;
2025-05-12 20:53:08 +02:00
}
if ( loserHero )
{
2025-05-14 15:07:21 +02:00
addArtifactToDischarging ( loserHero - > artifactsWorn , loserHero - > id ) ;
2025-05-12 20:53:08 +02:00
if ( const auto commander = loserHero - > getCommander ( ) )
2025-05-14 15:07:21 +02:00
addArtifactToDischarging ( commander - > artifactsWorn , loserHero - > id , loserHero - > findStack ( loserHero - > getCommander ( ) ) ) ;
2025-05-12 20:53:08 +02:00
}
2025-04-07 20:10:14 +02:00
// Necromancy handling
// Give raised units to winner, if any were raised, units will be given after casualties are taken
if ( winnerHero )
2023-08-14 19:46:42 +03:00
{
2025-04-07 20:10:14 +02:00
resultsApplied . raisedStack = winnerHero - > calculateNecromancy ( result ) ;
const SlotID necroSlot = resultsApplied . raisedStack . getCreature ( ) ? winnerHero - > getSlotFor ( resultsApplied . raisedStack . getCreature ( ) ) : SlotID ( ) ;
if ( necroSlot ! = SlotID ( ) & & ! finishingBattle - > isDraw ( ) )
2025-05-01 13:41:48 +03:00
gameHandler - > addToSlot ( StackLocation ( finishingBattle - > winnerId , necroSlot ) , resultsApplied . raisedStack . getCreature ( ) , resultsApplied . raisedStack . getCount ( ) ) ;
2023-08-14 19:46:42 +03:00
}
2023-08-31 18:45:52 +03:00
resultsApplied . battleID = battleID ;
2025-04-05 17:45:24 +02:00
resultsApplied . victor = finishingBattle - > victor ;
resultsApplied . loser = finishingBattle - > loser ;
2024-10-04 18:59:51 +00:00
gameHandler - > sendAndApply ( resultsApplied ) ;
2023-08-14 19:46:42 +03:00
//handle victory/loss of engaged players
2025-04-07 20:10:14 +02:00
gameHandler - > checkVictoryLossConditions ( { finishingBattle - > loser , finishingBattle - > victor } ) ;
2023-08-14 19:46:42 +03:00
2025-04-07 08:25:32 +02:00
// Remove beaten hero
if ( loserHero )
{
RemoveObject ro ( loserHero - > id , finishingBattle - > victor ) ;
gameHandler - > sendAndApply ( ro ) ;
}
// For draw case both heroes should be removed
if ( finishingBattle - > isDraw ( ) & & winnerHero )
{
RemoveObject ro ( winnerHero - > id , finishingBattle - > loser ) ;
gameHandler - > sendAndApply ( ro ) ;
2025-05-20 19:01:25 +03:00
if ( gameHandler - > gameInfo ( ) . getSettings ( ) . getBoolean ( EGameSettings : : HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS ) )
2025-04-07 08:25:32 +02:00
gameHandler - > heroPool - > onHeroEscaped ( finishingBattle - > victor , winnerHero ) ;
}
2025-04-28 19:36:14 +03:00
if ( result . result = = EBattleResult : : SURRENDER )
{
2025-05-20 21:40:05 +03:00
gameHandler - > statistics - > accumulatedValues [ finishingBattle - > loser ] . numHeroSurrendered + + ;
2025-04-28 19:36:14 +03:00
gameHandler - > heroPool - > onHeroSurrendered ( finishingBattle - > loser , loserHero ) ;
}
if ( result . result = = EBattleResult : : ESCAPE )
{
2025-05-20 21:40:05 +03:00
gameHandler - > statistics - > accumulatedValues [ finishingBattle - > loser ] . numHeroEscaped + + ;
2025-04-28 19:36:14 +03:00
gameHandler - > heroPool - > onHeroEscaped ( finishingBattle - > loser , loserHero ) ;
}
2023-08-31 18:45:52 +03:00
finishingBattles . erase ( battleID ) ;
battleResults . erase ( battleID ) ;
2023-08-14 19:46:42 +03:00
}
2024-08-11 20:22:35 +00:00
void BattleResultProcessor : : setBattleResult ( const CBattleInfoCallback & battle , EBattleResult resultType , BattleSide victoriusSide )
2023-08-14 19:46:42 +03:00
{
2023-08-28 17:43:57 +03:00
assert ( battleResults . count ( battle . getBattle ( ) - > getBattleID ( ) ) = = 0 ) ;
2023-08-25 18:23:15 +03:00
2023-08-28 17:43:57 +03:00
battleResults [ battle . getBattle ( ) - > getBattleID ( ) ] = std : : make_unique < BattleResult > ( ) ;
2023-08-25 18:23:15 +03:00
2023-08-28 17:43:57 +03:00
auto & battleResult = battleResults [ battle . getBattle ( ) - > getBattleID ( ) ] ;
2023-08-31 18:45:52 +03:00
battleResult - > battleID = battle . getBattle ( ) - > getBattleID ( ) ;
2023-08-14 19:46:42 +03:00
battleResult - > result = resultType ;
battleResult - > winner = victoriusSide ; //surrendering side loses
2023-08-28 17:43:57 +03:00
2024-02-12 21:53:38 +02:00
auto allStacks = battle . battleGetStacksIf ( [ ] ( const CStack * stack ) {
if ( stack - > summoned ) //don't take into account temporary summoned stacks
return false ;
if ( stack - > isTurret ( ) )
return false ;
return true ;
} ) ;
for ( const auto & st : allStacks ) //setting casualties
2023-08-28 17:43:57 +03:00
{
si32 killed = st - > getKilled ( ) ;
if ( killed > 0 )
battleResult - > casualties [ st - > unitSide ( ) ] [ st - > creatureId ( ) ] + = killed ;
}
2023-08-23 22:46:42 +03:00
}
2023-08-28 17:43:57 +03:00
bool BattleResultProcessor : : battleIsEnding ( const CBattleInfoCallback & battle ) const
2023-08-22 20:57:58 +03:00
{
2023-08-28 17:43:57 +03:00
return battleResults . count ( battle . getBattle ( ) - > getBattleID ( ) ) ! = 0 ;
2023-08-22 20:57:58 +03:00
}