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-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
{
}
2023-08-28 16:43:57 +02:00
CasualtiesAfterBattle : : CasualtiesAfterBattle ( const CBattleInfoCallback & battle , uint8_t sideInBattle ) :
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-31 17:45:52 +02:00
//FinishingBattleHelper::FinishingBattleHelper()
//{
// winnerHero = loserHero = nullptr;
// winnerSide = 0;
// remainingBattleQueriesCount = 0;
//}
2023-08-14 18:46:42 +02:00
2023-08-28 16:43:57 +02:00
void BattleResultProcessor : : endBattle ( const CBattleInfoCallback & battle )
2023-08-14 18:46:42 +02:00
{
auto const & giveExp = [ ] ( BattleResult & r )
{
if ( r . winner > 1 )
{
// draw
return ;
}
r . exp [ 0 ] = 0 ;
r . exp [ 1 ] = 0 ;
for ( auto i = r . casualties [ ! r . winner ] . begin ( ) ; i ! = r . casualties [ ! r . winner ] . end ( ) ; i + + )
{
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 )
battleResult - > exp [ 1 ] + = 500 ;
if ( heroDefender )
battleResult - > exp [ 0 ] + = 500 ;
}
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 )
battleResult - > exp [ 0 ] = heroAttacker - > calculateXp ( battleResult - > exp [ 0 ] ) ; //scholar skill
if ( heroDefender )
battleResult - > exp [ 1 ] = heroDefender - > calculateXp ( battleResult - > exp [ 1 ] ) ;
2023-08-28 16:43:57 +02:00
auto battleQuery = std : : dynamic_pointer_cast < CBattleQuery > ( gameHandler - > queries - > topQuery ( battle . sideToPlayer ( 0 ) ) ) ;
2024-01-27 02:57:28 +02:00
if ( ! battleQuery )
battleQuery = std : : dynamic_pointer_cast < CBattleQuery > ( gameHandler - > queries - > topQuery ( battle . sideToPlayer ( 1 ) ) ) ;
2023-08-14 18:46:42 +02:00
if ( ! battleQuery )
{
logGlobal - > error ( " Cannot find battle query! " ) ;
2023-08-28 16:43:57 +02:00
gameHandler - > complain ( " Player " + boost : : lexical_cast < std : : string > ( battle . sideToPlayer ( 0 ) ) + " 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
{
2023-08-28 16:43:57 +02:00
auto battleQuery = std : : dynamic_pointer_cast < CBattleQuery > ( gameHandler - > queries - > topQuery ( battle . sideToPlayer ( 0 ) ) ) ;
2024-01-27 02:57:28 +02:00
if ( ! battleQuery )
battleQuery = std : : dynamic_pointer_cast < CBattleQuery > ( gameHandler - > queries - > topQuery ( battle . sideToPlayer ( 1 ) ) ) ;
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 ) ;
2023-08-14 18:46:42 +02:00
ChangeSpells cs ; //for Eagle Eye
if ( ! finishingBattle - > isDraw ( ) & & finishingBattle - > winnerHero )
{
2023-10-05 17:18:14 +02:00
if ( int eagleEyeLevel = finishingBattle - > winnerHero - > valOfBonuses ( BonusType : : LEARN_BATTLE_SPELL_LEVEL_LIMIT ) )
2023-08-14 18:46:42 +02:00
{
2023-10-05 17:18:14 +02:00
double 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 ( ) ) ;
2023-08-14 18:46:42 +02:00
if ( spell & & spell - > getLevel ( ) < = eagleEyeLevel & & ! finishingBattle - > winnerHero - > spellbookContainsSpell ( spell - > getId ( ) ) & & gameHandler - > getRandomGenerator ( ) . nextInt ( 99 ) < eagleEyeChance )
cs . spells . insert ( spell - > getId ( ) ) ;
}
}
}
std : : vector < const CArtifactInstance * > arts ; //display them in window
if ( result = = EBattleResult : : NORMAL & & ! finishingBattle - > isDraw ( ) & & finishingBattle - > winnerHero )
{
auto sendMoveArtifact = [ & ] ( const CArtifactInstance * art , MoveArtifact * ma )
{
const auto slot = ArtifactUtils : : getArtAnyPosition ( finishingBattle - > winnerHero , art - > getTypeId ( ) ) ;
if ( slot ! = ArtifactPosition : : PRE_FIRST )
{
arts . push_back ( art ) ;
2023-10-14 21:00:39 +02:00
ma - > dst = ArtifactLocation ( finishingBattle - > winnerHero - > id , slot ) ;
2023-08-14 18:46:42 +02:00
if ( ArtifactUtils : : isSlotBackpack ( slot ) )
ma - > askAssemble = false ;
gameHandler - > sendAndApply ( ma ) ;
}
} ;
if ( finishingBattle - > loserHero )
{
//TODO: wrap it into a function, somehow (std::variant -_-)
auto artifactsWorn = finishingBattle - > loserHero - > artifactsWorn ;
for ( auto artSlot : artifactsWorn )
{
MoveArtifact ma ;
2023-10-14 21:00:39 +02:00
ma . src = ArtifactLocation ( finishingBattle - > loserHero - > id , artSlot . first ) ;
const CArtifactInstance * art = finishingBattle - > loserHero - > getArt ( artSlot . first ) ;
2023-08-14 18:46:42 +02:00
if ( art & & ! art - > artType - > isBig ( ) & &
art - > artType - > getId ( ) ! = ArtifactID : : SPELLBOOK )
// don't move war machines or locked arts (spellbook)
{
sendMoveArtifact ( art , & ma ) ;
}
}
for ( int slotNumber = finishingBattle - > loserHero - > artifactsInBackpack . size ( ) - 1 ; slotNumber > = 0 ; slotNumber - - )
{
//we assume that no big artifacts can be found
MoveArtifact ma ;
2023-10-14 21:00:39 +02:00
ma . src = ArtifactLocation ( finishingBattle - > loserHero - > id ,
2023-08-19 23:22:31 +02:00
ArtifactPosition ( ArtifactPosition : : BACKPACK_START + slotNumber ) ) ; //backpack automatically shifts arts to beginning
2023-10-14 21:00:39 +02:00
const CArtifactInstance * art = finishingBattle - > loserHero - > getArt ( ArtifactPosition : : BACKPACK_START + slotNumber ) ;
2023-08-14 18:46:42 +02:00
if ( art - > artType - > getId ( ) ! = ArtifactID : : GRAIL ) //grail may not be won
{
sendMoveArtifact ( art , & ma ) ;
}
}
if ( finishingBattle - > loserHero - > commander ) //TODO: what if commanders belong to no hero?
{
artifactsWorn = finishingBattle - > loserHero - > commander - > artifactsWorn ;
for ( auto artSlot : artifactsWorn )
{
MoveArtifact ma ;
2023-10-14 21:00:39 +02:00
ma . src = ArtifactLocation ( finishingBattle - > loserHero - > id , artSlot . first ) ;
2023-10-23 18:37:18 +02:00
ma . src . creature = finishingBattle - > loserHero - > findStack ( finishingBattle - > loserHero - > commander ) ;
const auto art = finishingBattle - > loserHero - > commander - > getArt ( artSlot . first ) ;
2023-08-14 18:46:42 +02:00
if ( art & & ! art - > artType - > isBig ( ) )
{
sendMoveArtifact ( art , & ma ) ;
2023-10-23 18:37:18 +02:00
}
2023-08-14 18:46:42 +02:00
}
}
}
2023-08-28 16:43:57 +02:00
auto loser = battle . otherSide ( battleResult - > winner ) ;
for ( auto armySlot : battle . battleGetArmyObject ( loser ) - > stacks )
2023-08-14 18:46:42 +02:00
{
auto artifactsWorn = armySlot . second - > artifactsWorn ;
2023-10-24 16:32:09 +02:00
for ( const auto & artSlot : artifactsWorn )
2023-08-14 18:46:42 +02:00
{
MoveArtifact ma ;
2023-10-23 18:37:18 +02:00
ma . src = ArtifactLocation ( finishingBattle - > loserHero - > id , artSlot . first ) ;
ma . src . creature = finishingBattle - > loserHero - > findStack ( finishingBattle - > loserHero - > commander ) ;
const auto art = finishingBattle - > loserHero - > commander - > getArt ( artSlot . first ) ;
2023-08-14 18:46:42 +02:00
if ( art & & ! art - > artType - > isBig ( ) )
{
sendMoveArtifact ( art , & ma ) ;
}
2023-10-23 18:37:18 +02:00
}
2023-08-14 18:46:42 +02:00
}
}
if ( arts . size ( ) ) //display loot
{
InfoWindow iw ;
iw . player = finishingBattle - > winnerHero - > tempOwner ;
iw . text . appendLocalString ( EMetaText : : GENERAL_TXT , 30 ) ; //You have captured enemy artifact
2024-06-24 03:23:26 +02:00
for ( auto art : arts ) //TODO; separate function to display loot for various objects?
2023-08-14 18:46:42 +02:00
{
2023-10-31 11:09:56 +02:00
if ( art - > artType - > getId ( ) = = ArtifactID : : SPELL_SCROLL )
iw . components . emplace_back ( ComponentType : : SPELL_SCROLL , art - > getScrollSpellID ( ) ) ;
else
iw . components . emplace_back ( ComponentType : : ARTIFACT , art - > artType - > getId ( ) ) ;
2023-08-14 18:46:42 +02:00
if ( iw . components . size ( ) > = 14 )
{
gameHandler - > sendAndApply ( & iw ) ;
iw . components . clear ( ) ;
}
}
if ( iw . components . size ( ) )
{
gameHandler - > sendAndApply ( & iw ) ;
}
}
//Eagle Eye secondary skill handling
if ( ! cs . spells . empty ( ) )
{
cs . learn = 1 ;
cs . 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 < cs . spells . size ( ) ; i + + )
{
names < < " %s " ;
if ( i < cs . spells . size ( ) - 2 )
names < < " , " ;
else if ( i < cs . spells . size ( ) - 1 )
names < < " %s " ;
}
names < < " . " ;
iw . text . replaceRawString ( names . str ( ) ) ;
auto it = cs . spells . begin ( ) ;
for ( int i = 0 ; i < cs . spells . size ( ) ; i + + , it + + )
{
2023-11-02 22:01:49 +02:00
iw . text . replaceName ( * it ) ;
2023-08-14 18:46:42 +02:00
if ( i = = cs . spells . size ( ) - 2 ) //we just added pre-last name
iw . text . replaceLocalString ( EMetaText : : GENERAL_TXT , 141 ) ; // " and "
2023-10-31 11:09:56 +02:00
iw . components . emplace_back ( ComponentType : : SPELL , * it ) ;
2023-08-14 18:46:42 +02:00
}
gameHandler - > sendAndApply ( & iw ) ;
gameHandler - > sendAndApply ( & cs ) ;
}
cab1 . updateArmy ( gameHandler ) ;
cab2 . updateArmy ( gameHandler ) ; //take casualties after battle is deleted
if ( finishingBattle - > loserHero ) //remove beaten hero
{
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 ) ;
}
if ( finishingBattle - > isDraw ( ) & & finishingBattle - > winnerHero ) //for draw case both heroes should be removed
{
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 ) ;
}
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 )
2024-01-04 23:57:36 +02:00
gameHandler - > giveExperience ( finishingBattle - > winnerHero , battleResult - > exp [ finishingBattle - > winnerSide ] ) ;
2023-08-14 18:46:42 +02:00
BattleResultAccepted raccepted ;
2023-08-31 17:45:52 +02:00
raccepted . battleID = battle . getBattle ( ) - > getBattleID ( ) ;
2024-01-05 17:56:00 +02:00
raccepted . heroResult [ 0 ] . army = const_cast < CArmedInstance * > ( battle . battleGetArmyObject ( BattleSide : : ATTACKER ) ) ;
raccepted . heroResult [ 1 ] . army = const_cast < CArmedInstance * > ( battle . battleGetArmyObject ( BattleSide : : DEFENDER ) ) ;
raccepted . heroResult [ 0 ] . hero = const_cast < CGHeroInstance * > ( battle . battleGetFightingHero ( BattleSide : : ATTACKER ) ) ;
raccepted . heroResult [ 1 ] . hero = const_cast < CGHeroInstance * > ( battle . battleGetFightingHero ( BattleSide : : DEFENDER ) ) ;
2023-08-14 18:46:42 +02:00
raccepted . heroResult [ 0 ] . exp = battleResult - > exp [ 0 ] ;
raccepted . heroResult [ 1 ] . exp = battleResult - > exp [ 1 ] ;
2023-08-30 00:17:29 +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 )
gameHandler - > heroPool - > onHeroSurrendered ( finishingBattle - > loser , finishingBattle - > loserHero ) ;
if ( result . result = = EBattleResult : : ESCAPE )
gameHandler - > heroPool - > onHeroEscaped ( finishingBattle - > loser , finishingBattle - > loserHero ) ;
if ( result . winner ! = 2 & & finishingBattle - > winnerHero & & finishingBattle - > winnerHero - > stacks . empty ( )
& & ( ! 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
}
2023-08-28 16:43:57 +02:00
void BattleResultProcessor : : setBattleResult ( const CBattleInfoCallback & battle , EBattleResult resultType , int 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
}