2023-07-23 23:00:37 +02:00
/*
* BattleProcessor . 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 "BattleProcessor.h"
2023-08-14 18:46:42 +02:00
# include "BattleActionProcessor.h"
# include "BattleFlowProcessor.h"
# include "BattleResultProcessor.h"
2023-07-23 23:00:37 +02:00
2023-08-14 18:46:42 +02:00
# include "../CGameHandler.h"
# include "../queries/QueriesProcessor.h"
# include "../queries/BattleQueries.h"
2023-07-23 23:00:37 +02:00
2023-08-14 18:46:42 +02:00
# include "../../lib/TerrainHandler.h"
2023-08-28 16:43:57 +02:00
# include "../../lib/battle/CBattleInfoCallback.h"
2023-08-14 18:46:42 +02:00
# include "../../lib/battle/BattleInfo.h"
# include "../../lib/gameState/CGameState.h"
# include "../../lib/mapping/CMap.h"
# include "../../lib/modding/IdentifierStorage.h"
2023-09-20 03:50:35 +02:00
# include "../../lib/CPlayerState.h"
2023-07-23 23:00:37 +02:00
2023-08-14 18:46:42 +02:00
BattleProcessor : : BattleProcessor ( CGameHandler * gameHandler )
: gameHandler ( gameHandler )
, flowProcessor ( std : : make_unique < BattleFlowProcessor > ( this ) )
, actionsProcessor ( std : : make_unique < BattleActionProcessor > ( this ) )
, resultProcessor ( std : : make_unique < BattleResultProcessor > ( this ) )
{
setGameHandler ( gameHandler ) ;
}
2023-07-23 23:00:37 +02:00
2023-08-14 18:46:42 +02:00
BattleProcessor : : BattleProcessor ( ) :
BattleProcessor ( nullptr )
{
}
2023-07-23 23:00:37 +02:00
2023-08-14 18:46:42 +02:00
BattleProcessor : : ~ BattleProcessor ( ) = default ;
2023-07-23 23:00:37 +02:00
2023-08-14 18:46:42 +02:00
void BattleProcessor : : engageIntoBattle ( PlayerColor player )
{
//notify interfaces
PlayerBlocked pb ;
pb . player = player ;
pb . reason = PlayerBlocked : : UPCOMING_BATTLE ;
pb . startOrEnd = PlayerBlocked : : BLOCKADE_STARTED ;
gameHandler - > sendAndApply ( & pb ) ;
}
2023-07-23 23:00:37 +02:00
2023-09-05 16:22:11 +02:00
void BattleProcessor : : restartBattlePrimary ( const BattleID & battleID , const CArmedInstance * army1 , const CArmedInstance * army2 , int3 tile ,
const CGHeroInstance * hero1 , const CGHeroInstance * hero2 , bool creatureBank ,
const CGTownInstance * town )
{
auto battle = gameHandler - > gameState ( ) - > getBattle ( battleID ) ;
auto lastBattleQuery = std : : dynamic_pointer_cast < CBattleQuery > ( gameHandler - > queries - > topQuery ( battle - > sides [ 0 ] . color ) ) ;
assert ( lastBattleQuery ) ;
//existing battle query for retying auto-combat
if ( lastBattleQuery )
{
const CGHeroInstance * heroes [ 2 ] ;
heroes [ 0 ] = hero1 ;
heroes [ 1 ] = hero2 ;
for ( int i : { 0 , 1 } )
{
if ( heroes [ i ] )
{
SetMana restoreInitialMana ;
restoreInitialMana . val = lastBattleQuery - > initialHeroMana [ i ] ;
restoreInitialMana . hid = heroes [ i ] - > id ;
gameHandler - > sendAndApply ( & restoreInitialMana ) ;
}
}
lastBattleQuery - > result = std : : nullopt ;
assert ( lastBattleQuery - > belligerents [ 0 ] = = battle - > sides [ 0 ] . armyObject ) ;
assert ( lastBattleQuery - > belligerents [ 1 ] = = battle - > sides [ 1 ] . armyObject ) ;
}
BattleCancelled bc ;
bc . battleID = battleID ;
gameHandler - > sendAndApply ( & bc ) ;
startBattlePrimary ( army1 , army2 , tile , hero1 , hero2 , creatureBank , town ) ;
}
2023-08-14 18:46:42 +02:00
void BattleProcessor : : startBattlePrimary ( const CArmedInstance * army1 , const CArmedInstance * army2 , int3 tile ,
const CGHeroInstance * hero1 , const CGHeroInstance * hero2 , bool creatureBank ,
2023-09-05 16:22:11 +02:00
const CGTownInstance * town )
2023-08-14 18:46:42 +02:00
{
2023-08-25 17:23:15 +02:00
assert ( gameHandler - > gameState ( ) - > getBattle ( army1 - > getOwner ( ) ) = = nullptr ) ;
assert ( gameHandler - > gameState ( ) - > getBattle ( army2 - > getOwner ( ) ) = = nullptr ) ;
2023-07-23 23:00:37 +02:00
2023-08-14 18:46:42 +02:00
engageIntoBattle ( army1 - > tempOwner ) ;
engageIntoBattle ( army2 - > tempOwner ) ;
2023-07-23 23:00:37 +02:00
2023-09-05 16:22:11 +02:00
const CArmedInstance * armies [ 2 ] ;
2023-08-14 18:46:42 +02:00
armies [ 0 ] = army1 ;
armies [ 1 ] = army2 ;
2023-09-05 16:22:11 +02:00
const CGHeroInstance * heroes [ 2 ] ;
2023-08-14 18:46:42 +02:00
heroes [ 0 ] = hero1 ;
heroes [ 1 ] = hero2 ;
2023-07-23 23:00:37 +02:00
2023-08-25 17:23:15 +02:00
auto battleID = setupBattle ( tile , armies , heroes , creatureBank , town ) ; //initializes stacks, places creatures on battlefield, blocks and informs player interfaces
2023-07-23 23:00:37 +02:00
2023-08-25 17:23:15 +02:00
const auto * battle = gameHandler - > gameState ( ) - > getBattle ( battleID ) ;
assert ( battle ) ;
2023-09-20 03:50:35 +02:00
//add battle bonuses based from player state only when attacks neutral creatures
const auto * attackerInfo = gameHandler - > getPlayerState ( army1 - > getOwner ( ) , false ) ;
2023-09-22 00:52:19 +02:00
if ( attackerInfo & & ! army2 - > getOwner ( ) . isValidPlayer ( ) )
2023-09-20 03:50:35 +02:00
{
for ( auto bonus : attackerInfo - > battleBonuses )
{
GiveBonus giveBonus ( GiveBonus : : ETarget : : HERO ) ;
giveBonus . id = hero1 - > id . getNum ( ) ;
giveBonus . bonus = bonus ;
gameHandler - > sendAndApply ( & giveBonus ) ;
}
}
2023-08-25 17:23:15 +02:00
auto lastBattleQuery = std : : dynamic_pointer_cast < CBattleQuery > ( gameHandler - > queries - > topQuery ( battle - > sides [ 0 ] . color ) ) ;
2023-07-23 23:00:37 +02:00
2023-09-05 16:22:11 +02:00
if ( lastBattleQuery )
2023-08-14 18:46:42 +02:00
{
2023-09-05 16:22:11 +02:00
lastBattleQuery - > battleID = battleID ;
}
else
{
auto newBattleQuery = std : : make_shared < CBattleQuery > ( gameHandler , battle ) ;
// store initial mana to reset if battle has been restarted
2023-08-14 18:46:42 +02:00
for ( int i : { 0 , 1 } )
if ( heroes [ i ] )
2023-09-05 16:22:11 +02:00
newBattleQuery - > initialHeroMana [ i ] = heroes [ i ] - > mana ;
2023-07-23 23:00:37 +02:00
2023-09-05 16:22:11 +02:00
gameHandler - > queries - > addQuery ( newBattleQuery ) ;
2023-07-23 23:00:37 +02:00
}
2023-08-14 18:46:42 +02:00
2023-08-25 17:23:15 +02:00
flowProcessor - > onBattleStarted ( * battle ) ;
2023-07-23 23:00:37 +02:00
}
2023-08-14 18:46:42 +02:00
void BattleProcessor : : startBattleI ( const CArmedInstance * army1 , const CArmedInstance * army2 , int3 tile , bool creatureBank )
2023-07-23 23:00:37 +02:00
{
2023-08-14 18:46:42 +02:00
startBattlePrimary ( army1 , army2 , tile ,
army1 - > ID = = Obj : : HERO ? static_cast < const CGHeroInstance * > ( army1 ) : nullptr ,
army2 - > ID = = Obj : : HERO ? static_cast < const CGHeroInstance * > ( army2 ) : nullptr ,
creatureBank ) ;
}
2023-07-23 23:00:37 +02:00
2023-08-14 18:46:42 +02:00
void BattleProcessor : : startBattleI ( const CArmedInstance * army1 , const CArmedInstance * army2 , bool creatureBank )
{
startBattleI ( army1 , army2 , army2 - > visitablePos ( ) , creatureBank ) ;
2023-07-23 23:00:37 +02:00
}
2023-08-25 17:23:15 +02:00
BattleID BattleProcessor : : setupBattle ( int3 tile , const CArmedInstance * armies [ 2 ] , const CGHeroInstance * heroes [ 2 ] , bool creatureBank , const CGTownInstance * town )
2023-07-23 23:00:37 +02:00
{
2023-08-14 18:46:42 +02:00
const auto & t = * gameHandler - > getTile ( tile ) ;
TerrainId terrain = t . terType - > getId ( ) ;
if ( gameHandler - > gameState ( ) - > map - > isCoastalTile ( tile ) ) //coastal tile is always ground
terrain = ETerrainId : : SAND ;
2023-07-23 23:00:37 +02:00
2023-08-14 18:46:42 +02:00
BattleField terType = gameHandler - > gameState ( ) - > battleGetBattlefieldType ( tile , gameHandler - > getRandomGenerator ( ) ) ;
if ( heroes [ 0 ] & & heroes [ 0 ] - > boat & & heroes [ 1 ] & & heroes [ 1 ] - > boat )
terType = BattleField ( * VLC - > identifiers ( ) - > getIdentifier ( " core " , " battlefield.ship_to_ship " ) ) ;
2023-07-23 23:00:37 +02:00
2023-08-14 18:46:42 +02:00
//send info about battles
BattleStart bs ;
bs . info = BattleInfo : : setupBattle ( tile , terrain , terType , armies , heroes , creatureBank , town ) ;
2023-08-25 17:23:15 +02:00
bs . battleID = gameHandler - > gameState ( ) - > nextBattleID ;
2023-07-23 23:00:37 +02:00
2023-08-14 18:46:42 +02:00
engageIntoBattle ( bs . info - > sides [ 0 ] . color ) ;
engageIntoBattle ( bs . info - > sides [ 1 ] . color ) ;
2023-07-23 23:00:37 +02:00
2023-08-14 18:46:42 +02:00
auto lastBattleQuery = std : : dynamic_pointer_cast < CBattleQuery > ( gameHandler - > queries - > topQuery ( bs . info - > sides [ 0 ] . color ) ) ;
bs . info - > replayAllowed = lastBattleQuery = = nullptr & & ! bs . info - > sides [ 1 ] . color . isValidPlayer ( ) ;
2023-07-23 23:00:37 +02:00
2023-08-14 18:46:42 +02:00
gameHandler - > sendAndApply ( & bs ) ;
2023-08-25 17:23:15 +02:00
return bs . battleID ;
2023-08-14 18:46:42 +02:00
}
2023-07-23 23:00:37 +02:00
2023-08-28 16:43:57 +02:00
bool BattleProcessor : : checkBattleStateChanges ( const CBattleInfoCallback & battle )
2023-07-23 23:00:37 +02:00
{
2023-08-14 18:46:42 +02:00
//check if drawbridge state need to be changes
2023-08-28 16:43:57 +02:00
if ( battle . battleGetSiegeLevel ( ) > 0 )
2023-08-25 17:23:15 +02:00
updateGateState ( battle ) ;
2023-08-14 18:46:42 +02:00
2023-08-25 17:23:15 +02:00
if ( resultProcessor - > battleIsEnding ( battle ) )
2023-08-22 19:57:58 +02:00
return true ;
2023-08-14 18:46:42 +02:00
//check if battle ended
2023-08-28 16:43:57 +02:00
if ( auto result = battle . battleIsFinished ( ) )
2023-07-23 23:00:37 +02:00
{
2023-08-25 17:23:15 +02:00
setBattleResult ( battle , EBattleResult : : NORMAL , * result ) ;
2023-08-17 14:18:16 +02:00
return true ;
2023-07-23 23:00:37 +02:00
}
2023-08-17 14:18:16 +02:00
return false ;
2023-07-23 23:00:37 +02:00
}
2023-08-28 16:43:57 +02:00
void BattleProcessor : : updateGateState ( const CBattleInfoCallback & battle )
2023-07-23 23:00:37 +02:00
{
// GATE_BRIDGE - leftmost tile, located over moat
// GATE_OUTER - central tile, mostly covered by gate image
// GATE_INNER - rightmost tile, inside the walls
// GATE_OUTER or GATE_INNER:
// - if defender moves unit on these tiles, bridge will open
// - if there is a creature (dead or alive) on these tiles, bridge will always remain open
// - blocked to attacker if bridge is closed
// GATE_BRIDGE
// - if there is a unit or corpse here, bridge can't open (and can't close in fortress)
// - if Force Field is cast here, bridge can't open (but can close, in any town)
// - deals moat damage to attacker if bridge is closed (fortress only)
2023-08-28 16:43:57 +02:00
bool hasForceFieldOnBridge = ! battle . battleGetAllObstaclesOnPos ( BattleHex ( BattleHex : : GATE_BRIDGE ) , true ) . empty ( ) ;
2023-08-25 17:23:15 +02:00
bool hasStackAtGateInner = battle . battleGetUnitByPos ( BattleHex ( BattleHex : : GATE_INNER ) , false ) ! = nullptr ;
bool hasStackAtGateOuter = battle . battleGetUnitByPos ( BattleHex ( BattleHex : : GATE_OUTER ) , false ) ! = nullptr ;
bool hasStackAtGateBridge = battle . battleGetUnitByPos ( BattleHex ( BattleHex : : GATE_BRIDGE ) , false ) ! = nullptr ;
2023-08-28 16:43:57 +02:00
bool hasWideMoat = vstd : : contains_if ( battle . battleGetAllObstaclesOnPos ( BattleHex ( BattleHex : : GATE_BRIDGE ) , false ) , [ ] ( const std : : shared_ptr < const CObstacleInstance > & obst )
2023-07-23 23:00:37 +02:00
{
return obst - > obstacleType = = CObstacleInstance : : MOAT ;
} ) ;
BattleUpdateGateState db ;
2023-08-28 16:43:57 +02:00
db . state = battle . battleGetGateState ( ) ;
db . battleID = battle . getBattle ( ) - > getBattleID ( ) ;
2023-08-25 17:23:15 +02:00
2023-08-28 16:43:57 +02:00
if ( battle . battleGetWallState ( EWallPart : : GATE ) = = EWallState : : DESTROYED )
2023-07-23 23:00:37 +02:00
{
db . state = EGateState : : DESTROYED ;
}
else if ( db . state = = EGateState : : OPENED )
{
bool hasStackOnLongBridge = hasStackAtGateBridge & & hasWideMoat ;
bool gateCanClose = ! hasStackAtGateInner & & ! hasStackAtGateOuter & & ! hasStackOnLongBridge ;
if ( gateCanClose )
db . state = EGateState : : CLOSED ;
else
db . state = EGateState : : OPENED ;
}
else // CLOSED or BLOCKED
{
bool gateBlocked = hasForceFieldOnBridge | | hasStackAtGateBridge ;
if ( gateBlocked )
db . state = EGateState : : BLOCKED ;
else
db . state = EGateState : : CLOSED ;
}
2023-08-28 16:43:57 +02:00
if ( db . state ! = battle . battleGetGateState ( ) )
2023-07-23 23:00:37 +02:00
gameHandler - > sendAndApply ( & db ) ;
}
2023-08-25 17:23:15 +02:00
bool BattleProcessor : : makePlayerBattleAction ( const BattleID & battleID , PlayerColor player , const BattleAction & ba )
2023-08-13 22:08:53 +02:00
{
2023-08-25 17:23:15 +02:00
const auto * battle = gameHandler - > gameState ( ) - > getBattle ( battleID ) ;
if ( ! battle )
return false ;
bool result = actionsProcessor - > makePlayerBattleAction ( * battle , player , ba ) ;
if ( gameHandler - > gameState ( ) - > getBattle ( battleID ) ! = nullptr & & ! resultProcessor - > battleIsEnding ( * battle ) )
flowProcessor - > onActionMade ( * battle , ba ) ;
2023-08-17 15:17:19 +02:00
return result ;
2023-08-14 18:46:42 +02:00
}
2023-08-28 16:43:57 +02:00
void BattleProcessor : : setBattleResult ( const CBattleInfoCallback & battle , EBattleResult resultType , int victoriusSide )
2023-08-14 18:46:42 +02:00
{
2023-08-25 17:23:15 +02:00
resultProcessor - > setBattleResult ( battle , resultType , victoriusSide ) ;
resultProcessor - > endBattle ( battle ) ;
2023-08-14 18:46:42 +02:00
}
2023-08-28 16:43:57 +02:00
bool BattleProcessor : : makeAutomaticBattleAction ( const CBattleInfoCallback & battle , const BattleAction & ba )
2023-08-14 18:46:42 +02:00
{
2023-08-25 17:23:15 +02:00
return actionsProcessor - > makeAutomaticBattleAction ( battle , ba ) ;
2023-08-14 18:46:42 +02:00
}
2023-08-25 17:23:15 +02:00
void BattleProcessor : : endBattleConfirm ( const BattleID & battleID )
2023-08-14 18:46:42 +02:00
{
2023-08-25 17:23:15 +02:00
auto battle = gameHandler - > gameState ( ) - > getBattle ( battleID ) ;
assert ( battle ) ;
if ( ! battle )
return ;
resultProcessor - > endBattleConfirm ( * battle ) ;
2023-08-14 18:46:42 +02:00
}
2023-08-25 17:23:15 +02:00
void BattleProcessor : : battleAfterLevelUp ( const BattleID & battleID , const BattleResult & result )
2023-08-14 18:46:42 +02:00
{
2023-08-31 17:45:52 +02:00
resultProcessor - > battleAfterLevelUp ( battleID , result ) ;
2023-08-14 18:46:42 +02:00
}
void BattleProcessor : : setGameHandler ( CGameHandler * newGameHandler )
{
gameHandler = newGameHandler ;
actionsProcessor - > setGameHandler ( newGameHandler ) ;
flowProcessor - > setGameHandler ( newGameHandler ) ;
resultProcessor - > setGameHandler ( newGameHandler ) ;
2023-08-13 22:08:53 +02:00
}