2023-08-14 18:46:42 +02: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"
# include "../CGameHandler.h"
2024-04-26 12:26:33 +02:00
# include "../TurnTimerHandler.h"
2023-08-14 18:46:42 +02:00
# include "../processors/HeroPoolProcessor.h"
# include "../queries/QueriesProcessor.h"
# include "../queries/BattleQueries.h"
# include "../../lib/ArtifactUtils.h"
# include "../../lib/CStack.h"
2024-01-26 00:44:41 +02:00
# include "../../lib/CPlayerState.h"
2023-08-14 18:46:42 +02:00
# include "../../lib/GameSettings.h"
2023-08-28 16:43:57 +02:00
# include "../../lib/battle/CBattleInfoCallback.h"
# include "../../lib/battle/IBattleState.h"
# include "../../lib/battle/SideInBattle.h"
2023-08-14 18:46:42 +02:00
# include "../../lib/gameState/CGameState.h"
# include "../../lib/mapObjects/CGTownInstance.h"
2023-10-23 12:59:15 +02:00
# include "../../lib/networkPacks/PacksForClientBattle.h"
# include "../../lib/networkPacks/PacksForClient.h"
2023-08-14 18:46:42 +02:00
# include "../../lib/serializer/Cast.h"
# include "../../lib/spells/CSpellHandler.h"
2024-06-01 17:28:17 +02:00
# include <vstd/RNG.h>
2024-04-26 11:44:57 +02:00
BattleResultProcessor : : BattleResultProcessor ( BattleProcessor * owner , CGameHandler * newGameHandler )
2023-08-16 23:51:50 +02:00
// : owner(owner)
2024-04-26 11:44:57 +02:00
: gameHandler ( newGameHandler )
2023-08-14 18:46:42 +02:00
{
}
2024-08-11 22:22:35 +02:00
CasualtiesAfterBattle : : CasualtiesAfterBattle ( const CBattleInfoCallback & battle , BattleSide sideInBattle ) :
2023-08-28 16:43:57 +02:00
army ( battle . battleGetArmyObject ( sideInBattle ) )
2023-08-14 18:46:42 +02:00
{
heroWithDeadCommander = ObjectInstanceID ( ) ;
2023-08-28 16:43:57 +02:00
PlayerColor color = battle . sideToPlayer ( sideInBattle ) ;
2023-08-14 18:46:42 +02: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 18:46:42 +02:00
{
2023-08-28 16:43:57 +02: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 18:46:42 +02: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 18:46:42 +02: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 " ) ;
auto hero = dynamic_ptr_cast < CGHeroInstance > ( army ) ;
if ( hero )
2023-10-14 21:00:39 +02:00
removedWarMachines . push_back ( ArtifactLocation ( hero - > id , hero - > getArtPos ( warMachine , true ) ) ) ;
2023-08-14 18:46:42 +02: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 ) ;
if ( h & & h - > commander = = c & & ( st - > getCount ( ) = = 0 | | ! st - > alive ( ) ) )
{
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. " ) ;
StackLocation sl ( army , st - > unitSlot ( ) ) ;
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 18:46:42 +02:00
{
2024-02-12 21:53:38 +02:00
logGlobal - > debug ( " Stack size changed: %d -> %d units. " , army - > getStackCount ( st - > unitSlot ( ) ) , st - > getCount ( ) ) ;
2023-08-14 18:46:42 +02:00
StackLocation sl ( army , st - > unitSlot ( ) ) ;
newStackCounts . push_back ( TStackAndItsNewCount ( sl , st - > getCount ( ) ) ) ;
}
}
else
{
logGlobal - > warn ( " Unable to process stack: %s " , st - > nodeName ( ) ) ;
}
}
}
void CasualtiesAfterBattle : : updateArmy ( CGameHandler * gh )
{
2024-05-28 16:41:05 +02:00
if ( gh - > getObjInstance ( army - > id ) = = nullptr )
throw std : : runtime_error ( " Object " + army - > getObjectName ( ) + " is not on the map! " ) ;
2023-08-14 18:46:42 +02:00
for ( TStackAndItsNewCount & ncount : newStackCounts )
{
if ( ncount . second > 0 )
gh - > changeStackCount ( ncount . first , ncount . second , true ) ;
else
gh - > eraseStack ( ncount . first , true ) ;
}
for ( auto summoned_iter : summoned )
{
SlotID slot = army - > getSlotFor ( summoned_iter . first ) ;
if ( slot . validSlot ( ) )
{
StackLocation location ( army , slot ) ;
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 ;
gh - > sendAndApply ( & scp ) ;
}
}
2023-08-31 17:45:52 +02:00
FinishingBattleHelper : : FinishingBattleHelper ( const CBattleInfoCallback & info , const BattleResult & result , int remainingBattleQueriesCount )
2023-08-14 18:46:42 +02:00
{
2023-08-28 16:43:57 +02:00
if ( result . winner = = BattleSide : : ATTACKER )
{
2023-08-31 17:45:52 +02:00
winnerHero = info . getBattle ( ) - > getSideHero ( BattleSide : : ATTACKER ) ;
loserHero = info . getBattle ( ) - > getSideHero ( BattleSide : : DEFENDER ) ;
victor = info . getBattle ( ) - > getSidePlayer ( BattleSide : : ATTACKER ) ;
loser = info . getBattle ( ) - > getSidePlayer ( BattleSide : : DEFENDER ) ;
2023-08-28 16:43:57 +02:00
}
else
{
2023-08-31 17:45:52 +02:00
winnerHero = info . getBattle ( ) - > getSideHero ( BattleSide : : DEFENDER ) ;
loserHero = info . getBattle ( ) - > getSideHero ( BattleSide : : ATTACKER ) ;
victor = info . getBattle ( ) - > getSidePlayer ( BattleSide : : DEFENDER ) ;
loser = info . getBattle ( ) - > getSidePlayer ( BattleSide : : ATTACKER ) ;
2023-08-28 16:43:57 +02:00
}
2023-08-14 18:46:42 +02:00
winnerSide = result . winner ;
2023-08-28 16:43:57 +02:00
2023-08-14 18:46:42 +02:00
this - > remainingBattleQueriesCount = remainingBattleQueriesCount ;
}
2023-08-28 16:43:57 +02:00
void BattleResultProcessor : : endBattle ( const CBattleInfoCallback & battle )
2023-08-14 18:46:42 +02:00
{
2024-08-11 22:22:35 +02:00
auto const & giveExp = [ & battle ] ( BattleResult & r )
2023-08-14 18:46:42 +02:00
{
2024-08-11 22:22:35 +02:00
if ( r . winner = = BattleSide : : NONE )
2023-08-14 18:46:42 +02:00
{
// draw
return ;
}
2024-08-11 22:22:35 +02: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 18:46:42 +02:00
{
r . exp [ r . winner ] + = VLC - > creh - > objects . at ( i - > first ) - > valOfBonuses ( BonusType : : STACK_HEALTH ) * i - > second ;
}
} ;
LOG_TRACE ( logGlobal ) ;
2023-08-28 16:43:57 +02: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 17:23:15 +02:00
2023-08-14 18:46:42 +02: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 22:22:35 +02:00
battleResult - > exp [ BattleSide : : DEFENDER ] + = 500 ;
2023-08-14 18:46:42 +02:00
if ( heroDefender )
2024-08-11 22:22:35 +02:00
battleResult - > exp [ BattleSide : : ATTACKER ] + = 500 ;
2023-08-14 18:46:42 +02: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 18:46:42 +02:00
if ( heroAttacker )
2024-08-11 22:22:35 +02:00
battleResult - > exp [ BattleSide : : ATTACKER ] = heroAttacker - > calculateXp ( battleResult - > exp [ BattleSide : : ATTACKER ] ) ; //scholar skill
2023-08-14 18:46:42 +02:00
if ( heroDefender )
2024-08-11 22:22:35 +02:00
battleResult - > exp [ BattleSide : : DEFENDER ] = heroDefender - > calculateXp ( battleResult - > exp [ BattleSide : : DEFENDER ] ) ;
2023-08-14 18:46:42 +02:00
2024-08-11 22:22:35 +02:00
auto battleQuery = std : : dynamic_pointer_cast < CBattleQuery > ( gameHandler - > queries - > topQuery ( battle . sideToPlayer ( BattleSide : : ATTACKER ) ) ) ;
2024-01-27 02:57:28 +02:00
if ( ! battleQuery )
2024-08-11 22:22:35 +02:00
battleQuery = std : : dynamic_pointer_cast < CBattleQuery > ( gameHandler - > queries - > topQuery ( battle . sideToPlayer ( BattleSide : : DEFENDER ) ) ) ;
2023-08-14 18:46:42 +02:00
if ( ! battleQuery )
{
logGlobal - > error ( " Cannot find battle query! " ) ;
2024-08-11 22:22:35 +02:00
gameHandler - > complain ( " Player " + boost : : lexical_cast < std : : string > ( battle . sideToPlayer ( BattleSide : : ATTACKER ) ) + " has no battle query at the top! " ) ;
2023-08-14 18:46:42 +02: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 17:23:15 +02:00
2023-08-28 16:43:57 +02:00
assert ( finishingBattles . count ( battle . getBattle ( ) - > getBattleID ( ) ) = = 0 ) ;
2023-08-31 17:45:52 +02:00
finishingBattles [ battle . getBattle ( ) - > getBattleID ( ) ] = std : : make_unique < FinishingBattleHelper > ( battle , * battleResult , queriedPlayers ) ;
2023-08-14 18:46:42 +02:00
// in battles against neutrals, 1st player can ask to replay battle manually
2024-01-26 00:44:41 +02:00
const auto * attackerPlayer = gameHandler - > getPlayerState ( battle . getBattle ( ) - > getSidePlayer ( BattleSide : : ATTACKER ) ) ;
const auto * defenderPlayer = gameHandler - > getPlayerState ( battle . getBattle ( ) - > getSidePlayer ( BattleSide : : DEFENDER ) ) ;
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 18:46:42 +02:00
{
2024-02-28 23:25:14 +02:00
auto battleDialogQuery = std : : make_shared < CBattleDialogQuery > ( gameHandler , battle . getBattle ( ) , battleQuery - > result ) ;
2023-08-14 18:46:42 +02:00
battleResult - > queryID = battleDialogQuery - > queryID ;
gameHandler - > queries - > addQuery ( battleDialogQuery ) ;
}
else
2023-09-28 18:43:04 +02:00
battleResult - > queryID = QueryID : : NONE ;
2023-08-14 18:46:42 +02: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 16:22:11 +02:00
if ( otherBattleQuery & & otherBattleQuery - > battleID = = battle . getBattle ( ) - > getBattleID ( ) )
2023-08-14 18:46:42 +02:00
otherBattleQuery - > result = battleQuery - > result ;
}
2024-04-26 12:15:39 +02:00
gameHandler - > turnTimerHandler - > onBattleEnd ( battle . getBattle ( ) - > getBattleID ( ) ) ;
2023-09-05 16:22:11 +02:00
gameHandler - > sendAndApply ( battleResult ) ;
2023-08-14 18:46:42 +02:00
2023-08-19 23:22:31 +02:00
if ( battleResult - > queryID = = QueryID : : NONE )
2023-08-25 17:23:15 +02:00
endBattleConfirm ( battle ) ;
2023-08-14 18:46:42 +02:00
}
2023-08-28 16:43:57 +02:00
void BattleResultProcessor : : endBattleConfirm ( const CBattleInfoCallback & battle )
2023-08-14 18:46:42 +02:00
{
2024-08-11 22:22:35 +02:00
auto battleQuery = std : : dynamic_pointer_cast < CBattleQuery > ( gameHandler - > queries - > topQuery ( battle . sideToPlayer ( BattleSide : : ATTACKER ) ) ) ;
2024-01-27 02:57:28 +02:00
if ( ! battleQuery )
2024-08-11 22:22:35 +02:00
battleQuery = std : : dynamic_pointer_cast < CBattleQuery > ( gameHandler - > queries - > topQuery ( battle . sideToPlayer ( BattleSide : : DEFENDER ) ) ) ;
2023-08-14 18:46:42 +02:00
if ( ! battleQuery )
{
logGlobal - > trace ( " No battle query, battle end was confirmed by another player " ) ;
return ;
}
2023-08-28 16:43:57 +02:00
auto * battleResult = battleResults . at ( battle . getBattle ( ) - > getBattleID ( ) ) . get ( ) ;
auto * finishingBattle = finishingBattles . at ( battle . getBattle ( ) - > getBattleID ( ) ) . get ( ) ;
2023-08-25 17:23:15 +02:00
const EBattleResult result = battleResult - > result ;
2023-08-14 18:46:42 +02:00
2023-08-28 16:43:57 +02:00
//calculate casualties before deleting battle
CasualtiesAfterBattle cab1 ( battle , BattleSide : : ATTACKER ) ;
CasualtiesAfterBattle cab2 ( battle , BattleSide : : DEFENDER ) ;
2024-05-10 16:57:15 +02:00
cab1 . updateArmy ( gameHandler ) ;
cab2 . updateArmy ( gameHandler ) ; //take casualties after battle is deleted
if ( battleResult - > winner = = BattleSide : : DEFENDER
& & finishingBattle - > winnerHero
& & finishingBattle - > winnerHero - > visitedTown
& & ! finishingBattle - > winnerHero - > inTownGarrison
& & finishingBattle - > winnerHero - > visitedTown - > garrisonHero = = finishingBattle - > winnerHero )
{
gameHandler - > swapGarrisonOnSiege ( finishingBattle - > winnerHero - > visitedTown - > id ) ; //return defending visitor from garrison to its rightful place
}
//give exp
if ( ! finishingBattle - > isDraw ( ) & & battleResult - > exp [ finishingBattle - > winnerSide ] & & finishingBattle - > winnerHero )
gameHandler - > giveExperience ( finishingBattle - > winnerHero , battleResult - > exp [ finishingBattle - > winnerSide ] ) ;
2023-08-14 18:46:42 +02:00
2024-05-10 16:57:15 +02:00
// Eagle Eye handling
2023-08-14 18:46:42 +02:00
if ( ! finishingBattle - > isDraw ( ) & & finishingBattle - > winnerHero )
{
2024-05-10 16:57:15 +02:00
ChangeSpells spells ;
if ( auto eagleEyeLevel = finishingBattle - > winnerHero - > valOfBonuses ( BonusType : : LEARN_BATTLE_SPELL_LEVEL_LIMIT ) )
2023-08-14 18:46:42 +02:00
{
2024-05-10 16:57:15 +02:00
auto eagleEyeChance = finishingBattle - > winnerHero - > valOfBonuses ( BonusType : : LEARN_BATTLE_SPELL_CHANCE ) ;
2023-08-28 16:43:57 +02:00
for ( auto & spellId : battle . getBattle ( ) - > getUsedSpells ( battle . otherSide ( battleResult - > winner ) ) )
2023-08-14 18:46:42 +02:00
{
2023-11-02 18:45:46 +02:00
auto spell = spellId . toEntity ( VLC - > spells ( ) ) ;
2024-05-10 16:57:15 +02:00
if ( spell
& & spell - > getLevel ( ) < = eagleEyeLevel
& & ! finishingBattle - > winnerHero - > spellbookContainsSpell ( spell - > getId ( ) )
& & gameHandler - > getRandomGenerator ( ) . nextInt ( 99 ) < eagleEyeChance )
{
spells . spells . insert ( spell - > getId ( ) ) ;
}
2023-08-14 18:46:42 +02:00
}
}
2024-05-10 16:57:15 +02:00
if ( ! spells . spells . empty ( ) )
{
spells . learn = 1 ;
spells . hid = finishingBattle - > winnerHero - > id ;
InfoWindow iw ;
iw . player = finishingBattle - > winnerHero - > tempOwner ;
iw . text . appendLocalString ( EMetaText : : GENERAL_TXT , 221 ) ; //Through eagle-eyed observation, %s is able to learn %s
iw . text . replaceRawString ( finishingBattle - > winnerHero - > getNameTranslated ( ) ) ;
std : : ostringstream names ;
for ( int i = 0 ; i < spells . spells . size ( ) ; i + + )
{
names < < " %s " ;
if ( i < spells . spells . size ( ) - 2 )
names < < " , " ;
else if ( i < spells . spells . size ( ) - 1 )
names < < " %s " ;
}
names < < " . " ;
iw . text . replaceRawString ( names . str ( ) ) ;
auto it = spells . spells . begin ( ) ;
for ( int i = 0 ; i < spells . spells . size ( ) ; i + + , it + + )
{
iw . text . replaceName ( * it ) ;
if ( i = = spells . spells . size ( ) - 2 ) //we just added pre-last name
iw . text . replaceLocalString ( EMetaText : : GENERAL_TXT , 141 ) ; // " and "
iw . components . emplace_back ( ComponentType : : SPELL , * it ) ;
}
gameHandler - > sendAndApply ( & iw ) ;
gameHandler - > sendAndApply ( & spells ) ;
}
}
// Artifacts handling
2023-08-14 18:46:42 +02:00
if ( result = = EBattleResult : : NORMAL & & ! finishingBattle - > isDraw ( ) & & finishingBattle - > winnerHero )
{
2024-05-10 16:57:15 +02:00
std : : vector < const CArtifactInstance * > arts ; // display them in window
2024-03-28 00:28:31 +02:00
CArtifactFittingSet artFittingSet ( * finishingBattle - > winnerHero ) ;
2024-05-10 16:57:15 +02:00
const auto addArtifactToTransfer = [ & artFittingSet , & arts ] ( BulkMoveArtifacts & pack , const ArtifactPosition & srcSlot , const CArtifactInstance * art )
2023-08-14 18:46:42 +02: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 18:46:42 +02:00
{
2024-05-10 16:57:15 +02:00
pack . artsPack0 . emplace_back ( BulkMoveArtifacts : : LinkedSlots ( srcSlot , dstSlot ) ) ;
2024-06-22 18:29:39 +02:00
if ( ArtifactUtils : : isSlotEquipment ( dstSlot ) )
pack . artsPack0 . back ( ) . askAssemble = true ;
2024-03-28 00:28:31 +02:00
arts . emplace_back ( art ) ;
artFittingSet . putArtifact ( dstSlot , const_cast < CArtifactInstance * > ( art ) ) ;
2023-08-14 18:46:42 +02:00
}
} ;
2024-05-10 16:57:15 +02:00
const auto sendArtifacts = [ this ] ( BulkMoveArtifacts & bma )
2024-03-28 00:28:31 +02:00
{
if ( ! bma . artsPack0 . empty ( ) )
gameHandler - > sendAndApply ( & bma ) ;
} ;
2023-08-14 18:46:42 +02:00
2024-05-10 16:57:15 +02:00
BulkMoveArtifacts packHero ( finishingBattle - > winnerHero - > getOwner ( ) , ObjectInstanceID : : NONE , finishingBattle - > winnerHero - > id , false ) ;
2024-03-28 00:28:31 +02:00
if ( finishingBattle - > loserHero )
2023-08-14 18:46:42 +02:00
{
2024-05-10 16:57:15 +02:00
packHero . srcArtHolder = finishingBattle - > loserHero - > id ;
2024-07-20 14:07:34 +02:00
for ( const auto & slot : ArtifactUtils : : commonWornSlots ( ) )
2023-08-14 18:46:42 +02:00
{
2024-07-20 14:07:34 +02:00
if ( const auto artSlot = finishingBattle - > loserHero - > artifactsWorn . find ( slot ) ;
artSlot ! = finishingBattle - > loserHero - > artifactsWorn . end ( ) & & ArtifactUtils : : isArtRemovable ( * artSlot ) )
{
addArtifactToTransfer ( packHero , artSlot - > first , artSlot - > second . getArt ( ) ) ;
}
2023-08-14 18:46:42 +02:00
}
2024-03-28 00:28:31 +02:00
for ( const auto & artSlot : finishingBattle - > loserHero - > artifactsInBackpack )
2023-08-14 18:46:42 +02:00
{
2024-03-28 16:16:10 +02:00
if ( const auto art = artSlot . getArt ( ) ; art - > getTypeId ( ) ! = ArtifactID : : GRAIL )
2024-05-10 16:57:15 +02:00
addArtifactToTransfer ( packHero , finishingBattle - > loserHero - > getArtPos ( art ) , art ) ;
2023-08-14 18:46:42 +02:00
}
2024-03-28 00:28:31 +02:00
if ( finishingBattle - > loserHero - > commander )
2023-08-14 18:46:42 +02:00
{
2024-05-10 16:57:15 +02:00
BulkMoveArtifacts packCommander ( finishingBattle - > winnerHero - > getOwner ( ) , finishingBattle - > loserHero - > id , finishingBattle - > winnerHero - > id , false ) ;
packCommander . srcCreature = finishingBattle - > loserHero - > findStack ( finishingBattle - > loserHero - > commander ) ;
2024-03-28 00:28:31 +02:00
for ( const auto & artSlot : finishingBattle - > loserHero - > commander - > artifactsWorn )
2024-05-10 16:57:15 +02:00
addArtifactToTransfer ( packCommander , artSlot . first , artSlot . second . getArt ( ) ) ;
sendArtifacts ( packCommander ) ;
2023-08-14 18:46:42 +02:00
}
2024-08-08 12:57:06 +02:00
auto armyObj = battle . battleGetArmyObject ( battle . otherSide ( battleResult - > winner ) ) ;
for ( const auto & armySlot : armyObj - > stacks )
{
BulkMoveArtifacts packsArmy ( finishingBattle - > winnerHero - > getOwner ( ) , finishingBattle - > loserHero - > id , finishingBattle - > winnerHero - > id , false ) ;
packsArmy . srcArtHolder = armyObj - > id ;
packsArmy . srcCreature = armySlot . first ;
for ( const auto & artSlot : armySlot . second - > artifactsWorn )
addArtifactToTransfer ( packsArmy , artSlot . first , armySlot . second - > getArt ( artSlot . first ) ) ;
sendArtifacts ( packsArmy ) ;
}
2023-08-14 18:46:42 +02:00
}
2024-05-10 16:57:15 +02:00
// Display loot
if ( ! arts . empty ( ) )
2023-08-14 18:46:42 +02:00
{
2024-05-10 16:57:15 +02:00
InfoWindow iw ;
iw . player = finishingBattle - > winnerHero - > tempOwner ;
iw . text . appendLocalString ( EMetaText : : GENERAL_TXT , 30 ) ; //You have captured enemy artifact
2023-10-31 11:09:56 +02:00
2024-06-23 22:48:19 +02:00
for ( const auto art : arts ) //TODO; separate function to display loot for various objects?
2023-08-14 18:46:42 +02:00
{
2024-05-10 16:57:15 +02:00
if ( art - > isScroll ( ) )
iw . components . emplace_back ( ComponentType : : SPELL_SCROLL , art - > getScrollSpellID ( ) ) ;
else
iw . components . emplace_back ( ComponentType : : ARTIFACT , art - > getTypeId ( ) ) ;
2023-08-14 18:46:42 +02:00
2024-05-10 16:57:15 +02:00
if ( iw . components . size ( ) > = GameConstants : : INFO_WINDOW_ARTIFACTS_MAX_ITEMS )
{
gameHandler - > sendAndApply ( & iw ) ;
iw . components . clear ( ) ;
}
}
gameHandler - > sendAndApply ( & iw ) ;
2023-08-14 18:46:42 +02:00
}
2024-05-10 16:57:15 +02:00
if ( ! packHero . artsPack0 . empty ( ) )
sendArtifacts ( packHero ) ;
2023-08-14 18:46:42 +02:00
}
2024-05-10 16:57:15 +02:00
// Remove beaten hero
if ( finishingBattle - > loserHero )
2023-08-14 18:46:42 +02:00
{
2024-08-14 21:51:08 +02:00
//add statistics
2024-08-14 19:24:40 +02:00
if ( ! finishingBattle - > isDraw ( ) )
{
ConstTransitivePtr < CGHeroInstance > strongestHero = nullptr ;
for ( auto & hero : gameHandler - > gameState ( ) - > getPlayerState ( finishingBattle - > loser ) - > heroes )
if ( ! strongestHero | | hero - > exp > strongestHero - > exp )
strongestHero = hero ;
if ( strongestHero - > id = = finishingBattle - > loserHero - > id )
gameHandler - > gameState ( ) - > statistic . accumulatedValues [ finishingBattle - > victor ] . lastDefeatedStrongestHeroDay = gameHandler - > gameState ( ) - > getDate ( Date : : DAY ) ;
}
2024-05-28 17:43:06 +02:00
RemoveObject ro ( finishingBattle - > loserHero - > id , finishingBattle - > victor ) ;
2023-08-14 18:46:42 +02:00
gameHandler - > sendAndApply ( & ro ) ;
}
2024-05-10 16:57:15 +02:00
// For draw case both heroes should be removed
if ( finishingBattle - > isDraw ( ) & & finishingBattle - > winnerHero )
2023-08-14 18:46:42 +02:00
{
2024-05-28 17:43:06 +02:00
RemoveObject ro ( finishingBattle - > winnerHero - > id , finishingBattle - > loser ) ;
2023-08-14 18:46:42 +02:00
gameHandler - > sendAndApply ( & ro ) ;
}
2024-08-02 19:38:33 +02:00
// add statistic
2024-08-11 22:22:35 +02:00
if ( battle . sideToPlayer ( BattleSide : : ATTACKER ) = = PlayerColor : : NEUTRAL | | battle . sideToPlayer ( BattleSide : : DEFENDER ) = = PlayerColor : : NEUTRAL )
2024-08-02 19:38:33 +02:00
{
2024-08-11 22:22:35 +02:00
gameHandler - > gameState ( ) - > statistic . accumulatedValues [ battle . sideToPlayer ( BattleSide : : ATTACKER ) ] . numBattlesNeutral + + ;
gameHandler - > gameState ( ) - > statistic . accumulatedValues [ battle . sideToPlayer ( BattleSide : : DEFENDER ) ] . numBattlesNeutral + + ;
2024-08-02 19:38:33 +02:00
if ( ! finishingBattle - > isDraw ( ) )
2024-08-07 21:26:22 +02:00
gameHandler - > gameState ( ) - > statistic . accumulatedValues [ battle . sideToPlayer ( finishingBattle - > winnerSide ) ] . numWinBattlesNeutral + + ;
2024-08-02 19:38:33 +02:00
}
else
{
2024-08-11 22:22:35 +02:00
gameHandler - > gameState ( ) - > statistic . accumulatedValues [ battle . sideToPlayer ( BattleSide : : ATTACKER ) ] . numBattlesPlayer + + ;
gameHandler - > gameState ( ) - > statistic . accumulatedValues [ battle . sideToPlayer ( BattleSide : : DEFENDER ) ] . numBattlesPlayer + + ;
2024-08-02 19:38:33 +02:00
if ( ! finishingBattle - > isDraw ( ) )
2024-08-07 21:26:22 +02:00
gameHandler - > gameState ( ) - > statistic . accumulatedValues [ battle . sideToPlayer ( finishingBattle - > winnerSide ) ] . numWinBattlesPlayer + + ;
2024-08-02 19:38:33 +02:00
}
2023-08-14 18:46:42 +02:00
BattleResultAccepted raccepted ;
2023-08-31 17:45:52 +02:00
raccepted . battleID = battle . getBattle ( ) - > getBattleID ( ) ;
2024-08-11 22:22:35 +02:00
raccepted . heroResult [ BattleSide : : ATTACKER ] . army = const_cast < CArmedInstance * > ( battle . battleGetArmyObject ( BattleSide : : ATTACKER ) ) ;
raccepted . heroResult [ BattleSide : : DEFENDER ] . army = const_cast < CArmedInstance * > ( battle . battleGetArmyObject ( BattleSide : : DEFENDER ) ) ;
raccepted . heroResult [ BattleSide : : ATTACKER ] . hero = const_cast < CGHeroInstance * > ( battle . battleGetFightingHero ( BattleSide : : ATTACKER ) ) ;
raccepted . heroResult [ BattleSide : : DEFENDER ] . hero = const_cast < CGHeroInstance * > ( battle . battleGetFightingHero ( BattleSide : : DEFENDER ) ) ;
raccepted . heroResult [ BattleSide : : ATTACKER ] . exp = battleResult - > exp [ BattleSide : : ATTACKER ] ;
raccepted . heroResult [ BattleSide : : DEFENDER ] . exp = battleResult - > exp [ BattleSide : : DEFENDER ] ;
2024-05-10 16:57:15 +02:00
raccepted . winnerSide = finishingBattle - > winnerSide ;
2023-08-14 18:46:42 +02:00
gameHandler - > sendAndApply ( & raccepted ) ;
gameHandler - > queries - > popIfTop ( battleQuery ) ;
//--> continuation (battleAfterLevelUp) occurs after level-up gameHandler->queries are handled or on removing query
}
2023-08-31 17:45:52 +02:00
void BattleResultProcessor : : battleAfterLevelUp ( const BattleID & battleID , const BattleResult & result )
2023-08-14 18:46:42 +02:00
{
LOG_TRACE ( logGlobal ) ;
2023-08-31 17:45:52 +02:00
assert ( finishingBattles . count ( battleID ) ! = 0 ) ;
if ( finishingBattles . count ( battleID ) = = 0 )
2023-08-14 18:46:42 +02:00
return ;
2023-08-31 17:45:52 +02:00
auto & finishingBattle = finishingBattles [ battleID ] ;
2023-08-25 17:23:15 +02:00
2023-08-14 18:46:42 +02:00
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.
// Necromancy if applicable.
2023-08-25 17:23:15 +02:00
const CStackBasicDescriptor raisedStack = finishingBattle - > winnerHero ? finishingBattle - > winnerHero - > calculateNecromancy ( result ) : CStackBasicDescriptor ( ) ;
2023-08-14 18:46:42 +02:00
// Give raised units to winner and show dialog, if any were raised,
// units will be given after casualties are taken
const SlotID necroSlot = raisedStack . type ? finishingBattle - > winnerHero - > getSlotFor ( raisedStack . type ) : SlotID ( ) ;
2024-05-23 13:53:33 +02:00
if ( necroSlot ! = SlotID ( ) & & ! finishingBattle - > isDraw ( ) )
2023-08-14 18:46:42 +02:00
{
finishingBattle - > winnerHero - > showNecromancyDialog ( raisedStack , gameHandler - > getRandomGenerator ( ) ) ;
gameHandler - > addToSlot ( StackLocation ( finishingBattle - > winnerHero , necroSlot ) , raisedStack . type , raisedStack . count ) ;
}
BattleResultsApplied resultsApplied ;
2023-08-31 17:45:52 +02:00
resultsApplied . battleID = battleID ;
2023-08-14 18:46:42 +02:00
resultsApplied . player1 = finishingBattle - > victor ;
resultsApplied . player2 = finishingBattle - > loser ;
gameHandler - > sendAndApply ( & resultsApplied ) ;
//handle victory/loss of engaged players
std : : set < PlayerColor > playerColors = { finishingBattle - > loser , finishingBattle - > victor } ;
gameHandler - > checkVictoryLossConditions ( playerColors ) ;
if ( result . result = = EBattleResult : : SURRENDER )
2024-08-02 20:40:24 +02:00
{
2024-08-07 21:26:22 +02:00
gameHandler - > gameState ( ) - > statistic . accumulatedValues [ finishingBattle - > loser ] . numHeroSurrendered + + ;
2023-08-14 18:46:42 +02:00
gameHandler - > heroPool - > onHeroSurrendered ( finishingBattle - > loser , finishingBattle - > loserHero ) ;
2024-08-02 20:40:24 +02:00
}
2023-08-14 18:46:42 +02:00
if ( result . result = = EBattleResult : : ESCAPE )
2024-08-02 20:40:24 +02:00
{
2024-08-07 21:26:22 +02:00
gameHandler - > gameState ( ) - > statistic . accumulatedValues [ finishingBattle - > loser ] . numHeroEscaped + + ;
2023-08-14 18:46:42 +02:00
gameHandler - > heroPool - > onHeroEscaped ( finishingBattle - > loser , finishingBattle - > loserHero ) ;
2024-08-02 20:40:24 +02:00
}
2023-08-14 18:46:42 +02:00
2024-08-11 22:22:35 +02:00
if ( result . winner ! = BattleSide : : NONE & & finishingBattle - > winnerHero & & finishingBattle - > winnerHero - > stacks . empty ( )
2023-08-14 18:46:42 +02:00
& & ( ! finishingBattle - > winnerHero - > commander | | ! finishingBattle - > winnerHero - > commander - > alive ) )
{
2023-09-18 21:09:55 +02:00
RemoveObject ro ( finishingBattle - > winnerHero - > id , finishingBattle - > winnerHero - > getOwner ( ) ) ;
2023-08-14 18:46:42 +02:00
gameHandler - > sendAndApply ( & ro ) ;
if ( VLC - > settings ( ) - > getBoolean ( EGameSettings : : HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS ) )
gameHandler - > heroPool - > onHeroEscaped ( finishingBattle - > victor , finishingBattle - > winnerHero ) ;
}
2023-08-31 17:45:52 +02:00
finishingBattles . erase ( battleID ) ;
battleResults . erase ( battleID ) ;
2023-08-14 18:46:42 +02:00
}
2024-08-11 22:22:35 +02:00
void BattleResultProcessor : : setBattleResult ( const CBattleInfoCallback & battle , EBattleResult resultType , BattleSide victoriusSide )
2023-08-14 18:46:42 +02:00
{
2023-08-28 16:43:57 +02:00
assert ( battleResults . count ( battle . getBattle ( ) - > getBattleID ( ) ) = = 0 ) ;
2023-08-25 17:23:15 +02:00
2023-08-28 16:43:57 +02:00
battleResults [ battle . getBattle ( ) - > getBattleID ( ) ] = std : : make_unique < BattleResult > ( ) ;
2023-08-25 17:23:15 +02:00
2023-08-28 16:43:57 +02:00
auto & battleResult = battleResults [ battle . getBattle ( ) - > getBattleID ( ) ] ;
2023-08-31 17:45:52 +02:00
battleResult - > battleID = battle . getBattle ( ) - > getBattleID ( ) ;
2023-08-14 18:46:42 +02:00
battleResult - > result = resultType ;
battleResult - > winner = victoriusSide ; //surrendering side loses
2023-08-28 16:43:57 +02: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 16:43:57 +02:00
{
si32 killed = st - > getKilled ( ) ;
if ( killed > 0 )
battleResult - > casualties [ st - > unitSide ( ) ] [ st - > creatureId ( ) ] + = killed ;
}
2023-08-23 21:46:42 +02:00
}
2023-08-28 16:43:57 +02:00
bool BattleResultProcessor : : battleIsEnding ( const CBattleInfoCallback & battle ) const
2023-08-22 19:57:58 +02:00
{
2023-08-28 16:43:57 +02:00
return battleResults . count ( battle . getBattle ( ) - > getBattleID ( ) ) ! = 0 ;
2023-08-22 19:57:58 +02:00
}