2012-12-19 17:54:10 +03:00
/*
2017-07-13 10:26:03 +02:00
* CBattleInfoCallback . cpp , part of VCMI engine
2012-12-19 17:54:10 +03:00
*
* 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
*
*/
2017-07-01 20:05:10 +02:00
# include "StdInc.h"
2017-06-24 15:51:07 +02:00
# include "CBattleInfoCallback.h"
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
# include <vcmi/scripting/Service.h>
2017-06-29 01:02:05 +02:00
# include "../CStack.h"
2017-06-24 15:51:07 +02:00
# include "BattleInfo.h"
2023-01-14 19:01:53 +02:00
# include "DamageCalculator.h"
2017-06-29 01:02:05 +02:00
# include "../NetPacks.h"
# include "../spells/CSpellHandler.h"
# include "../mapObjects/CGTownInstance.h"
2022-06-28 10:05:30 +02:00
# include "../BattleFieldHandler.h"
2022-12-31 17:25:40 +02:00
# include "../CModHandler.h"
2023-01-17 21:43:44 +02:00
# include "../Rect.h"
2012-12-19 17:54:10 +03:00
2022-07-26 15:07:42 +02:00
VCMI_LIB_NAMESPACE_BEGIN
2017-06-26 18:50:35 +02:00
namespace SiegeStuffThatShouldBeMovedToHandlers // <=== TODO
2012-08-26 12:07:48 +03:00
{
2023-01-12 23:52:03 +02:00
2017-06-24 15:51:07 +02:00
static BattleHex lineToWallHex ( int line ) //returns hex with wall in given line (y coordinate)
2012-08-26 12:07:48 +03:00
{
2023-01-13 01:59:09 +02:00
static const BattleHex lineToHex [ ] = { 12 , 29 , 45 , 62 , 78 , 96 , 112 , 130 , 147 , 165 , 182 } ;
2012-08-26 12:07:48 +03:00
2017-06-24 15:51:07 +02:00
return lineToHex [ line ] ;
2012-08-26 12:07:48 +03:00
}
2017-06-24 15:51:07 +02:00
static bool sameSideOfWall ( BattleHex pos1 , BattleHex pos2 )
2012-08-26 12:07:48 +03:00
{
2017-06-24 15:51:07 +02:00
const int wallInStackLine = lineToWallHex ( pos1 . getY ( ) ) ;
const int wallInDestLine = lineToWallHex ( pos2 . getY ( ) ) ;
2012-08-26 12:07:48 +03:00
2017-06-24 15:51:07 +02:00
const bool stackLeft = pos1 < wallInStackLine ;
const bool destLeft = pos2 < wallInDestLine ;
2012-08-26 12:07:48 +03:00
2017-06-24 15:51:07 +02:00
return stackLeft = = destLeft ;
2012-08-26 12:07:48 +03:00
}
2017-06-24 15:51:07 +02:00
// parts of wall
2023-01-13 00:35:58 +02:00
static const std : : pair < int , EWallPart > wallParts [ ] =
2012-08-26 12:07:48 +03:00
{
2017-06-26 18:50:35 +02:00
std : : make_pair ( 50 , EWallPart : : KEEP ) ,
2017-06-24 15:51:07 +02:00
std : : make_pair ( 183 , EWallPart : : BOTTOM_TOWER ) ,
std : : make_pair ( 182 , EWallPart : : BOTTOM_WALL ) ,
std : : make_pair ( 130 , EWallPart : : BELOW_GATE ) ,
2017-06-26 18:50:35 +02:00
std : : make_pair ( 78 , EWallPart : : OVER_GATE ) ,
std : : make_pair ( 29 , EWallPart : : UPPER_WALL ) ,
std : : make_pair ( 12 , EWallPart : : UPPER_TOWER ) ,
std : : make_pair ( 95 , EWallPart : : INDESTRUCTIBLE_PART_OF_GATE ) ,
std : : make_pair ( 96 , EWallPart : : GATE ) ,
std : : make_pair ( 45 , EWallPart : : INDESTRUCTIBLE_PART ) ,
std : : make_pair ( 62 , EWallPart : : INDESTRUCTIBLE_PART ) ,
2017-06-24 15:51:07 +02:00
std : : make_pair ( 112 , EWallPart : : INDESTRUCTIBLE_PART ) ,
std : : make_pair ( 147 , EWallPart : : INDESTRUCTIBLE_PART ) ,
std : : make_pair ( 165 , EWallPart : : INDESTRUCTIBLE_PART )
} ;
2012-08-26 12:07:48 +03:00
2023-01-13 00:35:58 +02:00
static EWallPart hexToWallPart ( BattleHex hex )
2013-07-22 01:01:29 +03:00
{
2023-02-15 00:44:59 +02:00
for ( const auto & elem : wallParts )
2013-07-22 01:01:29 +03:00
{
2017-06-24 15:51:07 +02:00
if ( elem . first = = hex )
return elem . second ;
2013-07-22 01:01:29 +03:00
}
2017-06-24 15:51:07 +02:00
return EWallPart : : INVALID ; //not found!
2012-08-26 12:07:48 +03:00
}
2023-01-13 00:35:58 +02:00
static BattleHex WallPartToHex ( EWallPart part )
2013-01-20 23:29:35 +03:00
{
2023-02-15 00:44:59 +02:00
for ( const auto & elem : wallParts )
2013-06-23 14:25:48 +03:00
{
2017-06-24 15:51:07 +02:00
if ( elem . second = = part )
return elem . first ;
2013-06-23 14:25:48 +03:00
}
2013-01-20 23:29:35 +03:00
2017-06-24 15:51:07 +02:00
return BattleHex : : INVALID ; //not found!
2013-01-20 23:29:35 +03:00
}
2012-08-26 12:07:48 +03:00
}
2017-06-24 15:51:07 +02:00
using namespace SiegeStuffThatShouldBeMovedToHandlers ;
2015-09-16 17:28:14 +02:00
2017-07-20 06:08:49 +02:00
ESpellCastProblem : : ESpellCastProblem CBattleInfoCallback : : battleCanCastSpell ( const spells : : Caster * caster , spells : : Mode mode ) const
2012-08-26 12:07:48 +03:00
{
RETURN_IF_NOT_BATTLE ( ESpellCastProblem : : INVALID ) ;
2016-09-18 17:12:07 +02:00
if ( caster = = nullptr )
{
2017-06-26 18:50:35 +02:00
logGlobal - > error ( " CBattleInfoCallback::battleCanCastSpell: no spellcaster. " ) ;
2016-09-18 17:12:07 +02:00
return ESpellCastProblem : : INVALID ;
}
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
const PlayerColor player = caster - > getCasterOwner ( ) ;
2017-07-01 10:34:00 +02:00
const auto side = playerToSide ( player ) ;
if ( ! side )
2016-11-26 20:14:21 +02:00
return ESpellCastProblem : : INVALID ;
2017-07-01 10:34:00 +02:00
if ( ! battleDoWeKnowAbout ( side . get ( ) ) )
2012-08-26 12:07:48 +03:00
{
2017-08-10 18:39:27 +02:00
logGlobal - > warn ( " You can't check if enemy can cast given spell! " ) ;
2012-08-26 12:07:48 +03:00
return ESpellCastProblem : : INVALID ;
}
2016-09-18 17:12:07 +02:00
if ( battleTacticDist ( ) )
return ESpellCastProblem : : ONGOING_TACTIC_PHASE ;
2017-07-20 06:08:49 +02:00
switch ( mode )
2012-08-26 12:07:48 +03:00
{
2017-07-20 06:08:49 +02:00
case spells : : Mode : : HERO :
2017-06-24 15:51:07 +02:00
{
2017-07-01 10:34:00 +02:00
if ( battleCastSpells ( side . get ( ) ) > 0 )
2017-07-20 06:08:49 +02:00
return ESpellCastProblem : : CASTS_PER_TURN_LIMIT ;
2012-08-26 12:07:48 +03:00
2023-02-15 00:44:59 +02:00
const auto * hero = dynamic_cast < const CGHeroInstance * > ( caster ) ;
2012-08-26 12:07:48 +03:00
2017-06-24 15:51:07 +02:00
if ( ! hero )
return ESpellCastProblem : : NO_HERO_TO_CAST_SPELL ;
if ( hero - > hasBonusOfType ( Bonus : : BLOCK_ALL_MAGIC ) )
return ESpellCastProblem : : MAGIC_IS_BLOCKED ;
}
2012-08-26 12:07:48 +03:00
break ;
default :
break ;
}
return ESpellCastProblem : : OK ;
}
2017-07-20 06:08:49 +02:00
bool CBattleInfoCallback : : battleHasWallPenalty ( const IBonusBearer * shooter , BattleHex shooterPosition , BattleHex destHex ) const
2012-08-26 12:07:48 +03:00
{
2023-01-13 01:59:09 +02:00
auto isTileBlocked = [ & ] ( BattleHex tile )
{
EWallPart wallPart = battleHexToWallPart ( tile ) ;
if ( wallPart = = EWallPart : : INDESTRUCTIBLE_PART_OF_GATE )
return false ; // does not blocks ranged attacks
if ( wallPart = = EWallPart : : INDESTRUCTIBLE_PART )
return true ; // always blocks ranged attacks
assert ( isWallPartPotentiallyAttackable ( wallPart ) ) ;
EWallState state = battleGetWallState ( wallPart ) ;
return state ! = EWallState : : DESTROYED ;
} ;
auto needWallPenalty = [ & ] ( BattleHex from , BattleHex dest )
{
2023-01-14 23:01:33 +02:00
// arbitrary selected cell size for virtual grid
// any even number can be selected (for division by two)
static const int cellSize = 10 ;
// create line that goes from center of shooter cell to center of target cell
Point line1 { from . getX ( ) * cellSize + cellSize / 2 , from . getY ( ) * cellSize + cellSize / 2 } ;
Point line2 { dest . getX ( ) * cellSize + cellSize / 2 , dest . getY ( ) * cellSize + cellSize / 2 } ;
2023-01-13 01:59:09 +02:00
for ( int y = 0 ; y < GameConstants : : BFIELD_HEIGHT ; + + y )
{
BattleHex obstacle = lineToWallHex ( y ) ;
if ( ! isTileBlocked ( obstacle ) )
continue ;
2023-01-14 23:01:33 +02:00
// create rect around cell with an obstacle
2023-01-17 21:43:44 +02:00
Rect rect {
Point ( obstacle . getX ( ) , obstacle . getY ( ) ) * cellSize ,
Point ( cellSize , cellSize )
} ;
2023-01-13 01:59:09 +02:00
2023-01-17 21:43:44 +02:00
if ( rect . intersectionTest ( line1 , line2 ) )
2023-01-13 01:59:09 +02:00
return true ;
}
return false ;
} ;
2012-08-26 12:07:48 +03:00
RETURN_IF_NOT_BATTLE ( false ) ;
2017-07-20 06:08:49 +02:00
if ( ! battleGetSiegeLevel ( ) )
return false ;
const std : : string cachingStrNoWallPenalty = " type_NO_WALL_PENALTY " ;
2020-11-11 21:43:40 +02:00
static const auto selectorNoWallPenalty = Selector : : type ( ) ( Bonus : : NO_WALL_PENALTY ) ;
2017-07-20 06:08:49 +02:00
if ( shooter - > hasBonus ( selectorNoWallPenalty , cachingStrNoWallPenalty ) )
2012-08-26 12:07:48 +03:00
return false ;
2012-09-20 19:55:21 +03:00
const int wallInStackLine = lineToWallHex ( shooterPosition . getY ( ) ) ;
2023-01-13 01:59:09 +02:00
const bool shooterOutsideWalls = shooterPosition < wallInStackLine ;
2012-08-26 12:07:48 +03:00
2023-01-13 01:59:09 +02:00
return shooterOutsideWalls & & needWallPenalty ( shooterPosition , destHex ) ;
2012-08-26 12:07:48 +03:00
}
2017-07-20 06:08:49 +02:00
si8 CBattleInfoCallback : : battleCanTeleportTo ( const battle : : Unit * stack , BattleHex destHex , int telportLevel ) const
2012-08-26 12:07:48 +03:00
{
RETURN_IF_NOT_BATTLE ( false ) ;
2013-07-25 14:53:36 +03:00
if ( ! getAccesibility ( stack ) . accessible ( destHex , stack ) )
2012-08-26 12:07:48 +03:00
return false ;
2016-09-24 08:27:58 +02:00
const ui8 siegeLevel = battleGetSiegeLevel ( ) ;
//check for wall
//advanced teleport can pass wall of fort|citadel, expert - of castle
if ( ( siegeLevel > CGTownInstance : : NONE & & telportLevel < 2 ) | | ( siegeLevel > = CGTownInstance : : CASTLE & & telportLevel < 3 ) )
2017-07-20 06:08:49 +02:00
return sameSideOfWall ( stack - > getPosition ( ) , destHex ) ;
2012-08-26 12:07:48 +03:00
return true ;
}
2019-05-04 05:42:55 +02:00
std : : vector < PossiblePlayerBattleAction > CBattleInfoCallback : : getClientActionsForStack ( const CStack * stack , const BattleClientInterfaceData & data )
{
RETURN_IF_NOT_BATTLE ( std : : vector < PossiblePlayerBattleAction > ( ) ) ;
std : : vector < PossiblePlayerBattleAction > allowedActionList ;
if ( data . tacticsMode ) //would "if(battleGetTacticDist() > 0)" work?
{
allowedActionList . push_back ( PossiblePlayerBattleAction : : MOVE_TACTICS ) ;
allowedActionList . push_back ( PossiblePlayerBattleAction : : CHOOSE_TACTICS_STACK ) ;
}
else
{
if ( stack - > canCast ( ) ) //TODO: check for battlefield effects that prevent casting?
{
if ( stack - > hasBonusOfType ( Bonus : : SPELLCASTER ) & & data . creatureSpellToCast ! = - 1 )
{
const CSpell * spell = SpellID ( data . creatureSpellToCast ) . toSpell ( ) ;
PossiblePlayerBattleAction act = getCasterAction ( spell , stack , spells : : Mode : : CREATURE_ACTIVE ) ;
allowedActionList . push_back ( act ) ;
}
if ( stack - > hasBonusOfType ( Bonus : : RANDOM_SPELLCASTER ) )
allowedActionList . push_back ( PossiblePlayerBattleAction : : RANDOM_GENIE_SPELL ) ;
}
if ( stack - > canShoot ( ) )
allowedActionList . push_back ( PossiblePlayerBattleAction : : SHOOT ) ;
if ( stack - > hasBonusOfType ( Bonus : : RETURN_AFTER_STRIKE ) )
allowedActionList . push_back ( PossiblePlayerBattleAction : : ATTACK_AND_RETURN ) ;
allowedActionList . push_back ( PossiblePlayerBattleAction : : ATTACK ) ; //all active stacks can attack
allowedActionList . push_back ( PossiblePlayerBattleAction : : WALK_AND_ATTACK ) ; //not all stacks can always walk, but we will check this elsewhere
if ( stack - > canMove ( ) & & stack - > Speed ( 0 , true ) ) //probably no reason to try move war machines or bound stacks
allowedActionList . push_back ( PossiblePlayerBattleAction : : MOVE_STACK ) ;
2023-02-15 00:44:59 +02:00
const auto * siegedTown = battleGetDefendedTown ( ) ;
2019-05-04 05:42:55 +02:00
if ( siegedTown & & siegedTown - > hasFort ( ) & & stack - > hasBonusOfType ( Bonus : : CATAPULT ) ) //TODO: check shots
allowedActionList . push_back ( PossiblePlayerBattleAction : : CATAPULT ) ;
if ( stack - > hasBonusOfType ( Bonus : : HEALER ) )
allowedActionList . push_back ( PossiblePlayerBattleAction : : HEAL ) ;
}
return allowedActionList ;
}
PossiblePlayerBattleAction CBattleInfoCallback : : getCasterAction ( const CSpell * spell , const spells : : Caster * caster , spells : : Mode mode ) const
{
RETURN_IF_NOT_BATTLE ( PossiblePlayerBattleAction : : INVALID ) ;
PossiblePlayerBattleAction spellSelMode = PossiblePlayerBattleAction : : ANY_LOCATION ;
const CSpell : : TargetInfo ti ( spell , caster - > getSpellSchoolLevel ( spell ) , mode ) ;
if ( ti . massive | | ti . type = = spells : : AimType : : NO_TARGET )
spellSelMode = PossiblePlayerBattleAction : : NO_LOCATION ;
else if ( ti . type = = spells : : AimType : : LOCATION & & ti . clearAffected )
spellSelMode = PossiblePlayerBattleAction : : FREE_LOCATION ;
else if ( ti . type = = spells : : AimType : : CREATURE )
spellSelMode = PossiblePlayerBattleAction : : AIMED_SPELL_CREATURE ;
else if ( ti . type = = spells : : AimType : : OBSTACLE )
spellSelMode = PossiblePlayerBattleAction : : OBSTACLE ;
return spellSelMode ;
}
2017-07-15 13:08:20 +02:00
std : : set < BattleHex > CBattleInfoCallback : : battleGetAttackedHexes ( const CStack * attacker , BattleHex destinationTile , BattleHex attackerPos ) const
2012-08-26 12:07:48 +03:00
{
std : : set < BattleHex > attackedHexes ;
RETURN_IF_NOT_BATTLE ( attackedHexes ) ;
AttackableTiles at = getPotentiallyAttackableHexes ( attacker , destinationTile , attackerPos ) ;
2013-06-29 16:05:48 +03:00
for ( BattleHex tile : at . hostileCreaturePositions )
2012-08-26 12:07:48 +03:00
{
const CStack * st = battleGetStackByPos ( tile , true ) ;
if ( st & & st - > owner ! = attacker - > owner ) //only hostile stacks - does it work well with Berserk?
{
attackedHexes . insert ( tile ) ;
}
}
2013-06-29 16:05:48 +03:00
for ( BattleHex tile : at . friendlyCreaturePositions )
2012-08-26 12:07:48 +03:00
{
2012-08-26 12:59:07 +03:00
if ( battleGetStackByPos ( tile , true ) ) //friendly stacks can also be damaged by Dragon Breath
2012-08-26 12:07:48 +03:00
{
attackedHexes . insert ( tile ) ;
}
}
return attackedHexes ;
}
2016-09-09 19:30:36 +02:00
SpellID CBattleInfoCallback : : battleGetRandomStackSpell ( CRandomGenerator & rand , const CStack * stack , ERandomSpell mode ) const
2012-08-26 12:07:48 +03:00
{
switch ( mode )
{
case RANDOM_GENIE :
2016-09-09 19:30:36 +02:00
return getRandomBeneficialSpell ( rand , stack ) ; //target
2012-08-26 12:07:48 +03:00
break ;
case RANDOM_AIMED :
2016-09-09 19:30:36 +02:00
return getRandomCastedSpell ( rand , stack ) ; //caster
2012-08-26 12:07:48 +03:00
break ;
default :
2017-08-12 14:43:41 +02:00
logGlobal - > error ( " Incorrect mode of battleGetRandomSpell (%d) " , static_cast < int > ( mode ) ) ;
2013-02-14 02:55:42 +03:00
return SpellID : : NONE ;
2012-08-26 12:07:48 +03:00
}
}
const CStack * CBattleInfoCallback : : battleGetStackByPos ( BattleHex pos , bool onlyAlive ) const
{
RETURN_IF_NOT_BATTLE ( nullptr ) ;
2023-02-15 00:44:59 +02:00
for ( const auto * s : battleGetAllStacks ( true ) )
2013-07-27 22:01:31 +03:00
if ( vstd : : contains ( s - > getHexes ( ) , pos ) & & ( ! onlyAlive | | s - > alive ( ) ) )
2017-06-24 15:51:07 +02:00
return s ;
2012-08-26 12:07:48 +03:00
return nullptr ;
}
2017-07-20 06:08:49 +02:00
const battle : : Unit * CBattleInfoCallback : : battleGetUnitByPos ( BattleHex pos , bool onlyAlive ) const
2012-08-26 12:07:48 +03:00
{
2017-07-20 06:08:49 +02:00
RETURN_IF_NOT_BATTLE ( nullptr ) ;
2012-08-26 12:07:48 +03:00
2017-07-20 06:08:49 +02:00
auto ret = battleGetUnitsIf ( [ = ] ( const battle : : Unit * unit )
2012-08-26 12:07:48 +03:00
{
2017-07-20 06:08:49 +02:00
return ! unit - > isGhost ( )
& & vstd : : contains ( battle : : Unit : : getHexes ( unit - > getPosition ( ) , unit - > doubleWide ( ) , unit - > unitSide ( ) ) , pos )
& & ( ! onlyAlive | | unit - > alive ( ) ) ;
} ) ;
2012-08-26 12:07:48 +03:00
2017-07-20 06:08:49 +02:00
if ( ! ret . empty ( ) )
return ret . front ( ) ;
else
return nullptr ;
}
battle : : Units CBattleInfoCallback : : battleAliveUnits ( ) const
{
return battleGetUnitsIf ( [ ] ( const battle : : Unit * unit )
{
return unit - > isValidTarget ( false ) ;
} ) ;
}
battle : : Units CBattleInfoCallback : : battleAliveUnits ( ui8 side ) const
{
return battleGetUnitsIf ( [ = ] ( const battle : : Unit * unit )
{
return unit - > isValidTarget ( false ) & & unit - > unitSide ( ) = = side ;
} ) ;
}
2023-03-01 17:35:20 +02:00
using namespace battle ;
2017-07-20 06:08:49 +02:00
//T is battle::Unit descendant
template < typename T >
2023-02-25 00:25:30 +02:00
const T * takeOneUnit ( std : : vector < const T * > & allUnits , const int turn , int8_t & sideThatLastMoved , int phase )
2017-07-20 06:08:49 +02:00
{
2019-05-17 10:34:59 +02:00
const T * returnedUnit = nullptr ;
size_t currentUnitIndex = 0 ;
2012-08-26 12:07:48 +03:00
2023-02-25 00:25:30 +02:00
for ( size_t i = 0 ; i < allUnits . size ( ) ; i + + )
2017-07-20 06:08:49 +02:00
{
2023-02-25 00:25:30 +02:00
int32_t currentUnitInitiative = - 1 ;
int32_t returnedUnitInitiative = - 1 ;
2019-05-17 10:34:59 +02:00
if ( returnedUnit )
2023-02-25 00:25:30 +02:00
returnedUnitInitiative = returnedUnit - > getInitiative ( turn ) ;
if ( ! allUnits [ i ] )
continue ;
auto currentUnit = allUnits [ i ] ;
currentUnitInitiative = currentUnit - > getInitiative ( turn ) ;
switch ( phase )
2012-08-26 12:07:48 +03:00
{
2023-03-01 17:35:20 +02:00
case BattlePhases : : NORMAL : // Faster first, attacker priority, higher slot first
2023-02-25 00:25:30 +02:00
if ( returnedUnit = = nullptr | | currentUnitInitiative > returnedUnitInitiative )
2019-05-16 15:41:02 +02:00
{
2023-02-25 00:25:30 +02:00
returnedUnit = currentUnit ;
currentUnitIndex = i ;
}
else if ( currentUnitInitiative = = returnedUnitInitiative )
{
if ( sideThatLastMoved = = - 1 & & turn < = 0 & & currentUnit - > unitSide ( ) = = BattleSide : : ATTACKER
& & ! ( returnedUnit - > unitSide ( ) = = currentUnit - > unitSide ( ) & & returnedUnit - > unitSlot ( ) < currentUnit - > unitSlot ( ) ) ) // Turn 0 attacker priority
2019-05-17 10:34:59 +02:00
{
2023-02-25 00:25:30 +02:00
returnedUnit = currentUnit ;
2019-05-17 10:34:59 +02:00
currentUnitIndex = i ;
}
2023-02-25 00:25:30 +02:00
else if ( sideThatLastMoved ! = - 1 & & currentUnit - > unitSide ( ) ! = sideThatLastMoved
& & ! ( returnedUnit - > unitSide ( ) = = currentUnit - > unitSide ( ) & & returnedUnit - > unitSlot ( ) < currentUnit - > unitSlot ( ) ) ) // Alternate equal speeds units
2019-05-17 10:34:59 +02:00
{
2023-02-25 00:25:30 +02:00
returnedUnit = currentUnit ;
2019-05-17 10:34:59 +02:00
currentUnitIndex = i ;
}
2019-05-16 15:41:02 +02:00
}
2023-02-25 00:25:30 +02:00
break ;
2023-03-01 17:35:20 +02:00
case BattlePhases : : WAIT_MORALE : // Slower first, higher slot first
case BattlePhases : : WAIT :
2023-02-25 00:25:30 +02:00
if ( returnedUnit = = nullptr | | currentUnitInitiative < returnedUnitInitiative )
{
returnedUnit = currentUnit ;
currentUnitIndex = i ;
}
else if ( currentUnitInitiative = = returnedUnitInitiative & & sideThatLastMoved ! = - 1 & & currentUnit - > unitSide ( ) ! = sideThatLastMoved
& & ! ( returnedUnit - > unitSide ( ) = = currentUnit - > unitSide ( ) & & returnedUnit - > unitSlot ( ) < currentUnit - > unitSlot ( ) ) ) // Alternate equal speeds units
{
returnedUnit = currentUnit ;
currentUnitIndex = i ;
}
break ;
default :
break ;
2012-08-26 12:07:48 +03:00
}
2017-07-20 06:08:49 +02:00
}
2012-08-26 12:07:48 +03:00
2019-05-17 10:34:59 +02:00
if ( ! returnedUnit )
2019-05-16 15:41:02 +02:00
return nullptr ;
2023-02-25 00:25:30 +02:00
allUnits [ currentUnitIndex ] = nullptr ;
2019-05-17 10:34:59 +02:00
return returnedUnit ;
2017-07-20 06:08:49 +02:00
}
2023-02-25 00:25:30 +02:00
void CBattleInfoCallback : : battleGetTurnOrder ( std : : vector < battle : : Units > & turns , const size_t maxUnits , const int maxTurns , const int turn , int8_t sideThatLastMoved ) const
2017-07-20 06:08:49 +02:00
{
RETURN_IF_NOT_BATTLE ( ) ;
if ( maxUnits = = 0 & & maxTurns = = 0 )
{
logGlobal - > error ( " Attempt to get infinite battle queue " ) ;
return ;
}
auto actualTurn = turn > 0 ? turn : 0 ;
2023-02-25 00:25:30 +02:00
auto turnsIsFull = [ & ] ( ) - > bool
2017-07-20 06:08:49 +02:00
{
if ( maxUnits = = 0 )
return false ; //no limit
2023-02-25 00:25:30 +02:00
size_t turnsSize = 0 ;
for ( const auto & oneTurn : turns )
turnsSize + = oneTurn . size ( ) ;
return turnsSize > = maxUnits ;
2012-08-26 12:07:48 +03:00
} ;
2023-02-25 00:25:30 +02:00
turns . emplace_back ( ) ;
2017-07-20 06:08:49 +02:00
2023-03-01 17:35:20 +02:00
// We'll split creatures with remaining movement to 4 buckets (SIEGE, NORMAL, WAIT_MORALE, WAIT)
std : : array < battle : : Units , BattlePhases : : NUMBER_OF_PHASES > phases ; // Access using BattlePhases enum
2017-07-20 06:08:49 +02:00
2023-02-25 00:25:30 +02:00
const battle : : Unit * activeUnit = battleActiveUnit ( ) ;
2012-08-26 12:07:48 +03:00
2023-02-25 00:25:30 +02:00
if ( activeUnit )
2012-08-26 12:07:48 +03:00
{
2017-07-20 06:08:49 +02:00
//its first turn and active unit hasn't taken any action yet - must be placed at the beginning of queue, no matter what
2023-02-25 00:25:30 +02:00
if ( turn = = 0 & & activeUnit - > willMove ( ) & & ! activeUnit - > waited ( ) )
2017-07-20 06:08:49 +02:00
{
2023-02-25 00:25:30 +02:00
turns . back ( ) . push_back ( activeUnit ) ;
if ( turnsIsFull ( ) )
2017-07-20 06:08:49 +02:00
return ;
}
//its first or current turn, turn priority for active stack side
//TODO: what if active stack mind-controlled?
2023-02-25 00:25:30 +02:00
if ( turn < = 0 & & sideThatLastMoved < 0 )
sideThatLastMoved = activeUnit - > unitSide ( ) ;
2012-08-26 12:07:48 +03:00
}
2023-02-25 00:25:30 +02:00
auto allUnits = battleGetUnitsIf ( [ ] ( const battle : : Unit * unit )
2012-08-31 19:33:30 +03:00
{
2017-07-20 06:08:49 +02:00
return ! unit - > isGhost ( ) ;
} ) ;
2023-02-25 00:25:30 +02:00
// If no unit will be EVER! able to move, battle is over.
if ( ! vstd : : contains_if ( allUnits , [ ] ( const battle : : Unit * unit ) { return unit - > willMove ( 100000 ) ; } ) ) //little evil, but 100000 should be enough for all effects to disappear
2017-07-20 06:08:49 +02:00
{
2023-02-25 00:25:30 +02:00
turns . clear ( ) ;
2012-08-31 19:33:30 +03:00
return ;
}
2023-02-25 00:25:30 +02:00
for ( const auto * unit : allUnits )
2012-08-26 12:07:48 +03:00
{
2023-02-25 00:25:30 +02:00
if ( ( actualTurn = = 0 & & ! unit - > willMove ( ) ) //we are considering current round and unit won't move
| | ( actualTurn > 0 & & ! unit - > canMove ( turn ) ) //unit won't be able to move in later rounds
| | ( actualTurn = = 0 & & unit = = activeUnit & & ! turns . at ( 0 ) . empty ( ) & & unit = = turns . front ( ) . front ( ) ) ) //it's active unit already added at the beginning of queue
2012-08-26 12:07:48 +03:00
{
continue ;
}
2023-02-25 00:25:30 +02:00
int unitPhase = unit - > battleQueuePhase ( turn ) ;
2012-08-26 12:07:48 +03:00
2023-02-25 00:25:30 +02:00
phases [ unitPhase ] . push_back ( unit ) ;
2012-08-26 12:07:48 +03:00
}
2023-03-01 17:35:20 +02:00
boost : : sort ( phases [ BattlePhases : : SIEGE ] , CMP_stack ( BattlePhases : : SIEGE , actualTurn , sideThatLastMoved ) ) ;
std : : copy ( phases [ BattlePhases : : SIEGE ] . begin ( ) , phases [ BattlePhases : : SIEGE ] . end ( ) , std : : back_inserter ( turns . back ( ) ) ) ;
2012-08-26 12:07:48 +03:00
2023-02-25 00:25:30 +02:00
if ( turnsIsFull ( ) )
2012-08-26 12:07:48 +03:00
return ;
2023-03-01 17:35:20 +02:00
for ( uint8_t phase = BattlePhases : : NORMAL ; phase < BattlePhases : : NUMBER_OF_PHASES ; phase + + )
2023-02-25 00:25:30 +02:00
boost : : sort ( phases [ phase ] , CMP_stack ( phase , actualTurn , sideThatLastMoved ) ) ;
2012-08-26 12:07:48 +03:00
2023-03-01 17:35:20 +02:00
uint8_t phase = BattlePhases : : NORMAL ;
while ( ! turnsIsFull ( ) & & phase < BattlePhases : : NUMBER_OF_PHASES )
2012-08-26 12:07:48 +03:00
{
2023-02-25 00:25:30 +02:00
const battle : : Unit * currentUnit = nullptr ;
if ( phases [ phase ] . empty ( ) )
phase + + ;
2012-08-26 12:07:48 +03:00
else
2019-05-16 15:41:02 +02:00
{
2023-02-25 00:25:30 +02:00
currentUnit = takeOneUnit ( phases [ phase ] , actualTurn , sideThatLastMoved , phase ) ;
if ( ! currentUnit )
2019-05-18 18:37:02 +02:00
{
2023-02-25 00:25:30 +02:00
phase + + ;
2019-05-18 18:37:02 +02:00
}
2019-05-16 15:41:02 +02:00
else
{
2023-02-25 00:25:30 +02:00
turns . back ( ) . push_back ( currentUnit ) ;
sideThatLastMoved = currentUnit - > unitSide ( ) ;
2019-05-16 15:41:02 +02:00
}
}
2012-08-26 12:07:48 +03:00
}
2017-07-20 06:08:49 +02:00
2023-02-25 00:25:30 +02:00
if ( sideThatLastMoved < 0 )
sideThatLastMoved = BattleSide : : ATTACKER ;
2019-05-16 15:41:02 +02:00
2023-02-25 00:25:30 +02:00
if ( ! turnsIsFull ( ) & & ( maxTurns = = 0 | | turns . size ( ) < maxTurns ) )
battleGetTurnOrder ( turns , maxUnits , maxTurns , actualTurn + 1 , sideThatLastMoved ) ;
2012-08-26 12:07:48 +03:00
}
2017-07-20 06:08:49 +02:00
std : : vector < BattleHex > CBattleInfoCallback : : battleGetAvailableHexes ( const battle : : Unit * unit ) const
{
RETURN_IF_NOT_BATTLE ( std : : vector < BattleHex > ( ) ) ;
if ( ! unit - > getPosition ( ) . isValid ( ) ) //turrets
return std : : vector < BattleHex > ( ) ;
auto reachability = getReachability ( unit ) ;
return battleGetAvailableHexes ( reachability , unit ) ;
}
std : : vector < BattleHex > CBattleInfoCallback : : battleGetAvailableHexes ( const ReachabilityInfo & cache , const battle : : Unit * unit ) const
2012-08-26 12:07:48 +03:00
{
std : : vector < BattleHex > ret ;
RETURN_IF_NOT_BATTLE ( ret ) ;
2017-07-20 06:08:49 +02:00
if ( ! unit - > getPosition ( ) . isValid ( ) ) //turrets
2012-08-26 12:07:48 +03:00
return ret ;
2017-07-20 06:08:49 +02:00
auto unitSpeed = unit - > Speed ( 0 , true ) ;
2012-08-26 12:07:48 +03:00
2017-07-20 06:08:49 +02:00
const bool tacticPhase = battleTacticDist ( ) & & battleGetTacticsSide ( ) = = unit - > unitSide ( ) ;
for ( int i = 0 ; i < GameConstants : : BFIELD_SIZE ; + + i )
2012-08-26 12:07:48 +03:00
{
// If obstacles or other stacks makes movement impossible, it can't be helped.
2017-07-20 06:08:49 +02:00
if ( ! cache . isReachable ( i ) )
2012-08-26 12:07:48 +03:00
continue ;
2017-07-20 06:08:49 +02:00
if ( tacticPhase )
2012-08-26 12:07:48 +03:00
{
//Stack has to perform tactic-phase movement -> can enter any reachable tile within given range
if ( ! isInTacticRange ( i ) )
continue ;
}
else
{
2017-07-20 06:08:49 +02:00
//Not tactics phase -> destination must be reachable and within unit range.
2023-02-15 00:44:59 +02:00
if ( cache . distances [ i ] > static_cast < int > ( unitSpeed ) )
2012-08-26 12:07:48 +03:00
continue ;
}
2023-02-15 00:44:59 +02:00
ret . emplace_back ( i ) ;
2017-07-20 06:08:49 +02:00
}
2012-08-26 12:07:48 +03:00
2017-07-20 06:08:49 +02:00
return ret ;
}
std : : vector < BattleHex > CBattleInfoCallback : : battleGetAvailableHexes ( const battle : : Unit * unit , bool addOccupiable , std : : vector < BattleHex > * attackable ) const
{
std : : vector < BattleHex > ret = battleGetAvailableHexes ( unit ) ;
if ( ret . empty ( ) )
return ret ;
if ( addOccupiable & & unit - > doubleWide ( ) )
{
std : : vector < BattleHex > occupiable ;
2023-02-15 00:44:59 +02:00
occupiable . reserve ( ret . size ( ) ) ;
2017-07-20 06:08:49 +02:00
for ( auto hex : ret )
occupiable . push_back ( unit - > occupiedHex ( hex ) ) ;
vstd : : concatenate ( ret , occupiable ) ;
2012-08-26 12:07:48 +03:00
}
if ( attackable )
{
auto meleeAttackable = [ & ] ( BattleHex hex ) - > bool
{
// Return true if given hex has at least one available neighbour.
// Available hexes are already present in ret vector.
auto availableNeighbor = boost : : find_if ( ret , [ = ] ( BattleHex availableHex )
2017-06-26 18:50:35 +02:00
{
return BattleHex : : mutualPosition ( hex , availableHex ) > = 0 ;
} ) ;
2012-08-26 12:07:48 +03:00
return availableNeighbor ! = ret . end ( ) ;
} ;
2023-02-15 00:44:59 +02:00
for ( const auto * otherSt : battleAliveUnits ( otherSide ( unit - > unitSide ( ) ) ) )
2012-08-26 12:07:48 +03:00
{
if ( ! otherSt - > isValidTarget ( false ) )
continue ;
std : : vector < BattleHex > occupied = otherSt - > getHexes ( ) ;
2017-07-20 06:08:49 +02:00
if ( battleCanShoot ( unit , otherSt - > getPosition ( ) ) )
2012-08-26 12:07:48 +03:00
{
attackable - > insert ( attackable - > end ( ) , occupied . begin ( ) , occupied . end ( ) ) ;
continue ;
}
2013-06-29 16:05:48 +03:00
for ( BattleHex he : occupied )
2012-08-26 12:07:48 +03:00
{
if ( meleeAttackable ( he ) )
attackable - > push_back ( he ) ;
}
}
}
//adding occupiable likely adds duplicates to ret -> clean it up
boost : : sort ( ret ) ;
ret . erase ( boost : : unique ( ret ) . end ( ) , ret . end ( ) ) ;
return ret ;
}
2015-04-09 21:49:11 +02:00
bool CBattleInfoCallback : : battleCanAttack ( const CStack * stack , const CStack * target , BattleHex dest ) const
{
RETURN_IF_NOT_BATTLE ( false ) ;
2016-02-01 19:03:57 +02:00
2015-04-09 21:49:11 +02:00
if ( battleTacticDist ( ) )
return false ;
2016-02-01 19:03:57 +02:00
2015-04-09 21:49:11 +02:00
if ( ! stack | | ! target )
return false ;
2016-02-01 19:03:57 +02:00
2016-09-29 22:14:22 +02:00
if ( ! battleMatchOwner ( stack , target ) )
2015-04-09 21:49:11 +02:00
return false ;
2016-02-01 19:03:57 +02:00
2023-01-02 18:00:51 +02:00
auto id = stack - > getCreature ( ) - > getId ( ) ;
2015-04-09 21:49:11 +02:00
if ( id = = CreatureID : : FIRST_AID_TENT | | id = = CreatureID : : CATAPULT )
return false ;
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
2019-03-20 21:58:15 +02:00
return target - > alive ( ) ;
2015-04-09 21:49:11 +02:00
}
2019-06-28 20:05:25 +02:00
bool CBattleInfoCallback : : battleCanShoot ( const battle : : Unit * attacker ) const
2012-08-26 12:07:48 +03:00
{
RETURN_IF_NOT_BATTLE ( false ) ;
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
if ( battleTacticDist ( ) ) //no shooting during tactics
2012-08-26 12:59:07 +03:00
return false ;
2012-08-26 12:07:48 +03:00
2019-06-28 20:05:25 +02:00
if ( ! attacker )
return false ;
if ( attacker - > creatureIndex ( ) = = CreatureID : : CATAPULT ) //catapult cannot attack creatures
2012-08-26 12:07:48 +03:00
return false ;
2016-11-18 11:56:13 +02:00
//forgetfulness
2020-11-11 21:43:40 +02:00
TConstBonusListPtr forgetfulList = attacker - > getBonuses ( Selector : : type ( ) ( Bonus : : FORGETFULL ) ) ;
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
if ( ! forgetfulList - > empty ( ) )
2016-11-18 11:56:13 +02:00
{
2020-11-11 21:43:40 +02:00
int forgetful = forgetfulList - > valOfBonuses ( Selector : : type ( ) ( Bonus : : FORGETFULL ) ) ;
2016-11-18 11:56:13 +02:00
//advanced+ level
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
if ( forgetful > 1 )
2016-11-18 11:56:13 +02:00
return false ;
}
2012-08-26 12:07:48 +03:00
2019-06-28 20:05:25 +02:00
return attacker - > canShoot ( ) & & ( ! battleIsUnitBlocked ( attacker )
| | attacker - > hasBonusOfType ( Bonus : : FREE_SHOOTING ) ) ;
}
bool CBattleInfoCallback : : battleCanShoot ( const battle : : Unit * attacker , BattleHex dest ) const
{
RETURN_IF_NOT_BATTLE ( false ) ;
const battle : : Unit * defender = battleGetUnitByPos ( dest ) ;
if ( ! attacker | | ! defender )
2012-08-26 12:07:48 +03:00
return false ;
2019-06-28 20:05:25 +02:00
if ( battleMatchOwner ( attacker , defender ) & & defender - > alive ( ) )
2023-01-12 16:25:12 +02:00
{
if ( battleCanShoot ( attacker ) )
{
2023-01-12 17:27:21 +02:00
auto limitedRangeBonus = attacker - > getBonus ( Selector : : type ( ) ( Bonus : : LIMITED_SHOOTING_RANGE ) ) ;
if ( limitedRangeBonus = = nullptr )
2023-01-12 16:25:12 +02:00
{
return true ;
}
2023-01-12 17:27:21 +02:00
int shootingRange = limitedRangeBonus - > val ;
2023-01-12 18:07:40 +02:00
return isEnemyUnitWithinSpecifiedRange ( attacker - > getPosition ( ) , defender , shootingRange ) ;
2023-01-12 16:25:12 +02:00
}
}
2019-06-28 20:05:25 +02:00
return false ;
2012-08-26 12:07:48 +03:00
}
2017-06-26 18:50:35 +02:00
TDmgRange CBattleInfoCallback : : calculateDmgRange ( const BattleAttackInfo & info ) const
2012-08-26 12:07:48 +03:00
{
2023-01-14 19:01:53 +02:00
DamageCalculator calculator ( * this , info ) ;
2017-07-20 06:08:49 +02:00
2023-01-14 19:01:53 +02:00
return calculator . calculateDmgRange ( ) ;
}
2012-08-26 12:07:48 +03:00
2023-01-14 19:01:53 +02:00
TDmgRange CBattleInfoCallback : : battleEstimateDamage ( const battle : : Unit * attacker , const battle : : Unit * defender , BattleHex attackerPosition , TDmgRange * retaliationDmg ) const
{
RETURN_IF_NOT_BATTLE ( std : : make_pair ( 0 , 0 ) ) ;
auto reachability = battleGetDistances ( attacker , attacker - > getPosition ( ) ) ;
int movementDistance = reachability [ attackerPosition ] ;
return battleEstimateDamage ( attacker , defender , movementDistance , retaliationDmg ) ;
2012-08-26 12:07:48 +03:00
}
2023-01-14 19:01:53 +02:00
TDmgRange CBattleInfoCallback : : battleEstimateDamage ( const battle : : Unit * attacker , const battle : : Unit * defender , int movementDistance , TDmgRange * retaliationDmg ) const
2012-08-26 12:07:48 +03:00
{
RETURN_IF_NOT_BATTLE ( std : : make_pair ( 0 , 0 ) ) ;
2017-07-20 06:08:49 +02:00
const bool shooting = battleCanShoot ( attacker , defender - > getPosition ( ) ) ;
2023-01-14 19:01:53 +02:00
const BattleAttackInfo bai ( attacker , defender , movementDistance , shooting ) ;
2017-07-20 06:08:49 +02:00
return battleEstimateDamage ( bai , retaliationDmg ) ;
2012-09-20 19:55:21 +03:00
}
2017-07-20 06:08:49 +02:00
TDmgRange CBattleInfoCallback : : battleEstimateDamage ( const BattleAttackInfo & bai , TDmgRange * retaliationDmg ) const
2012-09-20 19:55:21 +03:00
{
RETURN_IF_NOT_BATTLE ( std : : make_pair ( 0 , 0 ) ) ;
TDmgRange ret = calculateDmgRange ( bai ) ;
2012-08-26 12:07:48 +03:00
if ( retaliationDmg )
{
2012-09-20 19:55:21 +03:00
if ( bai . shooting )
2012-08-26 12:07:48 +03:00
{
2017-07-20 06:08:49 +02:00
//FIXME: handle RANGED_RETALIATION
2012-08-26 12:07:48 +03:00
retaliationDmg - > first = retaliationDmg - > second = 0 ;
}
else
{
2017-07-20 06:08:49 +02:00
//TODO: rewrite using boost::numeric::interval
//TODO: rewire once more using interval-based fuzzy arithmetic
int64_t TDmgRange : : * pairElems [ ] = { & TDmgRange : : first , & TDmgRange : : second } ;
2012-08-26 12:07:48 +03:00
for ( int i = 0 ; i < 2 ; + + i )
{
2012-09-20 19:55:21 +03:00
auto retaliationAttack = bai . reverse ( ) ;
2017-07-20 06:08:49 +02:00
int64_t dmg = ret . * pairElems [ i ] ;
auto state = retaliationAttack . attacker - > acquireState ( ) ;
state - > damage ( dmg ) ;
retaliationAttack . attacker = state . get ( ) ;
2012-09-20 19:55:21 +03:00
retaliationDmg - > * pairElems [ ! i ] = calculateDmgRange ( retaliationAttack ) . * pairElems [ ! i ] ;
2012-08-26 12:07:48 +03:00
}
}
}
return ret ;
}
2017-07-01 17:59:53 +02:00
std : : vector < std : : shared_ptr < const CObstacleInstance > > CBattleInfoCallback : : battleGetAllObstaclesOnPos ( BattleHex tile , bool onlyBlocking ) const
2012-08-26 12:07:48 +03:00
{
2017-07-01 17:59:53 +02:00
std : : vector < std : : shared_ptr < const CObstacleInstance > > obstacles = std : : vector < std : : shared_ptr < const CObstacleInstance > > ( ) ;
RETURN_IF_NOT_BATTLE ( obstacles ) ;
for ( auto & obs : battleGetAllObstacles ( ) )
2012-08-26 12:07:48 +03:00
{
if ( vstd : : contains ( obs - > getBlockedTiles ( ) , tile )
2017-07-01 17:59:53 +02:00
| | ( ! onlyBlocking & & vstd : : contains ( obs - > getAffectedTiles ( ) , tile ) ) )
2012-08-26 12:07:48 +03:00
{
2017-07-01 17:59:53 +02:00
obstacles . push_back ( obs ) ;
2012-08-26 12:07:48 +03:00
}
}
2017-07-01 17:59:53 +02:00
return obstacles ;
2012-08-26 12:07:48 +03:00
}
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
std : : vector < std : : shared_ptr < const CObstacleInstance > > CBattleInfoCallback : : getAllAffectedObstaclesByStack ( const battle : : Unit * unit ) const
2017-07-09 10:18:46 +02:00
{
std : : vector < std : : shared_ptr < const CObstacleInstance > > affectedObstacles = std : : vector < std : : shared_ptr < const CObstacleInstance > > ( ) ;
RETURN_IF_NOT_BATTLE ( affectedObstacles ) ;
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
if ( unit - > alive ( ) )
2017-07-09 10:18:46 +02:00
{
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
affectedObstacles = battleGetAllObstaclesOnPos ( unit - > getPosition ( ) , false ) ;
if ( unit - > doubleWide ( ) )
2017-07-09 10:18:46 +02:00
{
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
BattleHex otherHex = unit - > occupiedHex ( unit - > getPosition ( ) ) ;
2017-07-09 10:18:46 +02:00
if ( otherHex . isValid ( ) )
for ( auto & i : battleGetAllObstaclesOnPos ( otherHex , false ) )
affectedObstacles . push_back ( i ) ;
}
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
for ( auto hex : unit - > getHexes ( ) )
2017-07-20 17:15:47 +02:00
if ( hex = = ESiegeHex : : GATE_BRIDGE )
if ( battleGetGateState ( ) = = EGateState : : OPENED | | battleGetGateState ( ) = = EGateState : : DESTROYED )
for ( int i = 0 ; i < affectedObstacles . size ( ) ; i + + )
if ( affectedObstacles . at ( i ) - > obstacleType = = CObstacleInstance : : MOAT )
affectedObstacles . erase ( affectedObstacles . begin ( ) + i ) ;
2017-07-09 10:18:46 +02:00
}
return affectedObstacles ;
}
2012-08-26 12:07:48 +03:00
AccessibilityInfo CBattleInfoCallback : : getAccesibility ( ) const
{
AccessibilityInfo ret ;
ret . fill ( EAccessibility : : ACCESSIBLE ) ;
//removing accessibility for side columns of hexes
for ( int y = 0 ; y < GameConstants : : BFIELD_HEIGHT ; y + + )
{
ret [ BattleHex ( GameConstants : : BFIELD_WIDTH - 1 , y ) ] = EAccessibility : : SIDE_COLUMN ;
ret [ BattleHex ( 0 , y ) ] = EAccessibility : : SIDE_COLUMN ;
}
2017-07-13 16:47:28 +02:00
//special battlefields with logically unavailable tiles
2022-06-28 10:05:30 +02:00
auto bFieldType = battleGetBattlefieldType ( ) ;
if ( bFieldType ! = BattleField : : NONE )
2017-07-15 23:01:33 +02:00
{
2022-06-28 10:05:30 +02:00
std : : vector < BattleHex > impassableHexes = bFieldType . getInfo ( ) - > impassableHexes ;
for ( auto hex : impassableHexes )
ret [ hex ] = EAccessibility : : UNAVAILABLE ;
2017-07-15 23:01:33 +02:00
}
2017-07-13 16:47:28 +02:00
2012-09-29 23:25:54 +03:00
//gate -> should be before stacks
2016-01-29 21:43:35 +02:00
if ( battleGetSiegeLevel ( ) > 0 )
2012-09-29 23:25:54 +03:00
{
2017-06-26 18:50:35 +02:00
EAccessibility accessability = EAccessibility : : ACCESSIBLE ;
2016-02-13 16:40:31 +02:00
switch ( battleGetGateState ( ) )
2016-01-29 21:43:35 +02:00
{
2016-02-13 16:40:31 +02:00
case EGateState : : CLOSED :
2016-01-29 21:43:35 +02:00
accessability = EAccessibility : : GATE ;
break ;
2016-02-13 16:40:31 +02:00
case EGateState : : BLOCKED :
2016-01-29 21:43:35 +02:00
accessability = EAccessibility : : UNAVAILABLE ;
break ;
}
2016-02-09 16:38:59 +02:00
ret [ ESiegeHex : : GATE_OUTER ] = ret [ ESiegeHex : : GATE_INNER ] = accessability ;
2012-09-29 23:25:54 +03:00
}
2012-08-26 12:07:48 +03:00
//tiles occupied by standing stacks
2023-02-15 00:44:59 +02:00
for ( const auto * unit : battleAliveUnits ( ) )
2012-08-26 12:07:48 +03:00
{
2017-07-20 06:08:49 +02:00
for ( auto hex : unit - > getHexes ( ) )
2012-08-26 12:07:48 +03:00
if ( hex . isAvailable ( ) ) //towers can have <0 pos; we don't also want to overwrite side columns
ret [ hex ] = EAccessibility : : ALIVE_STACK ;
}
//obstacles
2013-06-29 16:05:48 +03:00
for ( const auto & obst : battleGetAllObstacles ( ) )
2012-08-26 12:07:48 +03:00
{
2013-06-29 16:05:48 +03:00
for ( auto hex : obst - > getBlockedTiles ( ) )
2012-08-26 12:07:48 +03:00
ret [ hex ] = EAccessibility : : OBSTACLE ;
}
//walls
if ( battleGetSiegeLevel ( ) > 0 )
{
2016-02-09 16:38:59 +02:00
static const int permanentlyLocked [ ] = { 12 , 45 , 62 , 112 , 147 , 165 } ;
2013-06-29 16:05:48 +03:00
for ( auto hex : permanentlyLocked )
2012-08-26 12:07:48 +03:00
ret [ hex ] = EAccessibility : : UNAVAILABLE ;
//TODO likely duplicated logic
2023-01-13 00:35:58 +02:00
static const std : : pair < EWallPart , BattleHex > lockedIfNotDestroyed [ ] =
2016-02-09 16:38:59 +02:00
{
//which part of wall, which hex is blocked if this part of wall is not destroyed
2023-01-13 00:35:58 +02:00
std : : make_pair ( EWallPart : : BOTTOM_WALL , BattleHex ( ESiegeHex : : DESTRUCTIBLE_WALL_4 ) ) ,
std : : make_pair ( EWallPart : : BELOW_GATE , BattleHex ( ESiegeHex : : DESTRUCTIBLE_WALL_3 ) ) ,
std : : make_pair ( EWallPart : : OVER_GATE , BattleHex ( ESiegeHex : : DESTRUCTIBLE_WALL_2 ) ) ,
std : : make_pair ( EWallPart : : UPPER_WALL , BattleHex ( ESiegeHex : : DESTRUCTIBLE_WALL_1 ) )
2016-02-09 16:38:59 +02:00
} ;
2012-08-26 12:07:48 +03:00
2023-02-15 00:44:59 +02:00
for ( const auto & elem : lockedIfNotDestroyed )
2012-08-26 12:07:48 +03:00
{
2013-08-06 14:20:28 +03:00
if ( battleGetWallState ( elem . first ) ! = EWallState : : DESTROYED )
2013-06-29 16:05:48 +03:00
ret [ elem . second ] = EAccessibility : : DESTRUCTIBLE_WALL ;
2012-08-26 12:07:48 +03:00
}
}
return ret ;
}
2017-07-20 06:08:49 +02:00
AccessibilityInfo CBattleInfoCallback : : getAccesibility ( const battle : : Unit * stack ) const
2012-08-28 15:28:13 +03:00
{
2017-07-20 06:08:49 +02:00
return getAccesibility ( battle : : Unit : : getHexes ( stack - > getPosition ( ) , stack - > doubleWide ( ) , stack - > unitSide ( ) ) ) ;
2012-08-28 15:28:13 +03:00
}
2017-06-26 18:50:35 +02:00
AccessibilityInfo CBattleInfoCallback : : getAccesibility ( const std : : vector < BattleHex > & accessibleHexes ) const
2012-08-28 15:28:13 +03:00
{
auto ret = getAccesibility ( ) ;
2013-06-29 16:05:48 +03:00
for ( auto hex : accessibleHexes )
2012-09-30 14:33:19 +03:00
if ( hex . isValid ( ) )
ret [ hex ] = EAccessibility : : ACCESSIBLE ;
2012-08-28 15:28:13 +03:00
return ret ;
}
2017-06-26 18:50:35 +02:00
ReachabilityInfo CBattleInfoCallback : : makeBFS ( const AccessibilityInfo & accessibility , const ReachabilityInfo : : Parameters & params ) const
2012-08-26 12:07:48 +03:00
{
ReachabilityInfo ret ;
ret . accessibility = accessibility ;
ret . params = params ;
ret . predecessors . fill ( BattleHex : : INVALID ) ;
2012-08-26 22:13:57 +03:00
ret . distances . fill ( ReachabilityInfo : : INFINITE_DIST ) ;
2012-08-26 12:07:48 +03:00
2012-08-27 15:34:43 +03:00
if ( ! params . startPosition . isValid ( ) ) //if got call for arrow turrets
return ret ;
2020-11-21 19:01:42 +02:00
const std : : set < BattleHex > obstacles = getStoppers ( params . perspective ) ;
2012-08-26 12:07:48 +03:00
std : : queue < BattleHex > hexq ; //bfs queue
//first element
hexq . push ( params . startPosition ) ;
ret . distances [ params . startPosition ] = 0 ;
2023-02-15 00:44:59 +02:00
std : : array < bool , GameConstants : : BFIELD_SIZE > accessibleCache { } ;
2017-07-20 06:08:49 +02:00
for ( int hex = 0 ; hex < GameConstants : : BFIELD_SIZE ; hex + + )
accessibleCache [ hex ] = accessibility . accessible ( hex , params . doubleWide , params . side ) ;
2012-08-26 12:07:48 +03:00
while ( ! hexq . empty ( ) ) //bfs loop
{
const BattleHex curHex = hexq . front ( ) ;
hexq . pop ( ) ;
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
2020-11-21 19:01:42 +02:00
//walking stack can't step past the obstacles
2020-11-21 22:57:01 +02:00
if ( curHex ! = params . startPosition & & isInObstacle ( curHex , obstacles , params ) )
continue ;
2012-08-26 12:07:48 +03:00
2017-07-20 06:08:49 +02:00
const int costToNeighbour = ret . distances [ curHex . hex ] + 1 ;
for ( BattleHex neighbour : BattleHex : : neighbouringTilesCache [ curHex . hex ] )
2012-08-26 12:07:48 +03:00
{
2017-07-20 06:08:49 +02:00
if ( neighbour . isValid ( ) )
2012-08-26 12:07:48 +03:00
{
2017-07-20 06:08:49 +02:00
const int costFoundSoFar = ret . distances [ neighbour . hex ] ;
if ( accessibleCache [ neighbour . hex ] & & costToNeighbour < costFoundSoFar )
{
hexq . push ( neighbour ) ;
ret . distances [ neighbour . hex ] = costToNeighbour ;
ret . predecessors [ neighbour . hex ] = curHex ;
}
2012-08-26 12:07:48 +03:00
}
}
}
return ret ;
}
2020-11-21 22:57:01 +02:00
bool CBattleInfoCallback : : isInObstacle (
BattleHex hex ,
const std : : set < BattleHex > & obstacles ,
const ReachabilityInfo : : Parameters & params ) const
{
auto occupiedHexes = battle : : Unit : : getHexes ( hex , params . doubleWide , params . side ) ;
for ( auto occupiedHex : occupiedHexes )
{
if ( vstd : : contains ( obstacles , occupiedHex ) )
{
if ( occupiedHex = = ESiegeHex : : GATE_BRIDGE )
{
if ( battleGetGateState ( ) ! = EGateState : : DESTROYED & & params . side = = BattleSide : : ATTACKER )
return true ;
}
else
return true ;
}
}
return false ;
}
2012-08-26 12:07:48 +03:00
std : : set < BattleHex > CBattleInfoCallback : : getStoppers ( BattlePerspective : : BattlePerspective whichSidePerspective ) const
{
std : : set < BattleHex > ret ;
RETURN_IF_NOT_BATTLE ( ret ) ;
2013-06-29 16:05:48 +03:00
for ( auto & oi : battleGetAllObstacles ( whichSidePerspective ) )
2012-08-26 12:07:48 +03:00
{
if ( battleIsObstacleVisibleForSide ( * oi , whichSidePerspective ) )
{
range : : copy ( oi - > getStoppingTile ( ) , vstd : : set_inserter ( ret ) ) ;
}
}
return ret ;
}
2017-07-20 06:08:49 +02:00
std : : pair < const battle : : Unit * , BattleHex > CBattleInfoCallback : : getNearestStack ( const battle : : Unit * closest ) const
2012-08-26 12:07:48 +03:00
{
auto reachability = getReachability ( closest ) ;
2017-07-20 06:08:49 +02:00
auto avHexes = battleGetAvailableHexes ( reachability , closest ) ;
2012-08-26 12:07:48 +03:00
// I hate std::pairs with their undescriptive member names first / second
struct DistStack
{
2022-12-08 23:20:42 +02:00
uint32_t distanceToPred ;
2016-02-01 19:03:57 +02:00
BattleHex destination ;
2017-07-20 06:08:49 +02:00
const battle : : Unit * stack ;
2012-08-26 12:07:48 +03:00
} ;
2015-04-03 04:08:13 +02:00
std : : vector < DistStack > stackPairs ;
2012-08-26 12:07:48 +03:00
2017-07-20 06:08:49 +02:00
std : : vector < const battle : : Unit * > possible = battleGetUnitsIf ( [ = ] ( const battle : : Unit * unit )
2015-04-03 04:08:13 +02:00
{
2017-07-20 06:08:49 +02:00
return unit - > isValidTarget ( false ) & & unit ! = closest ;
2016-02-28 04:10:20 +02:00
} ) ;
2016-02-01 19:03:57 +02:00
2017-07-20 06:08:49 +02:00
for ( const battle : : Unit * st : possible )
{
2015-04-03 04:08:13 +02:00
for ( BattleHex hex : avHexes )
if ( CStack : : isMeleeAttackPossible ( closest , st , hex ) )
2013-05-25 10:06:00 +03:00
{
2016-10-07 08:45:03 +02:00
DistStack hlp = { reachability . distances [ hex ] , hex , st } ;
2013-05-25 10:06:00 +03:00
stackPairs . push_back ( hlp ) ;
}
2017-07-20 06:08:49 +02:00
}
2012-08-26 12:07:48 +03:00
2023-02-15 00:44:59 +02:00
if ( ! stackPairs . empty ( ) )
2012-08-26 12:07:48 +03:00
{
auto comparator = [ ] ( DistStack lhs , DistStack rhs ) { return lhs . distanceToPred < rhs . distanceToPred ; } ;
auto minimal = boost : : min_element ( stackPairs , comparator ) ;
2015-04-03 04:08:13 +02:00
return std : : make_pair ( minimal - > stack , minimal - > destination ) ;
2012-08-26 12:07:48 +03:00
}
2013-05-25 10:06:00 +03:00
else
2017-07-20 06:08:49 +02:00
return std : : make_pair < const battle : : Unit * , BattleHex > ( nullptr , BattleHex : : INVALID ) ;
}
2023-02-15 00:44:59 +02:00
BattleHex CBattleInfoCallback : : getAvaliableHex ( const CreatureID & creID , ui8 side , int initialPos ) const
2017-07-20 06:08:49 +02:00
{
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
bool twoHex = VLC - > creh - > objects [ creID ] - > isDoubleWide ( ) ;
2017-07-20 06:08:49 +02:00
int pos ;
if ( initialPos > - 1 )
pos = initialPos ;
else //summon elementals depending on player side
{
if ( side = = BattleSide : : ATTACKER )
pos = 0 ; //top left
else
pos = GameConstants : : BFIELD_WIDTH - 1 ; //top right
}
auto accessibility = getAccesibility ( ) ;
std : : set < BattleHex > occupyable ;
for ( int i = 0 ; i < accessibility . size ( ) ; i + + )
if ( accessibility . accessible ( i , twoHex , side ) )
occupyable . insert ( i ) ;
if ( occupyable . empty ( ) )
{
return BattleHex : : INVALID ; //all tiles are covered
}
return BattleHex : : getClosestTile ( side , pos , occupyable ) ;
2012-08-26 12:07:48 +03:00
}
2017-07-20 06:08:49 +02:00
2012-08-26 12:07:48 +03:00
si8 CBattleInfoCallback : : battleGetTacticDist ( ) const
{
RETURN_IF_NOT_BATTLE ( 0 ) ;
//TODO get rid of this method
if ( battleDoWeKnowAbout ( battleGetTacticsSide ( ) ) )
return battleTacticDist ( ) ;
return 0 ;
}
bool CBattleInfoCallback : : isInTacticRange ( BattleHex dest ) const
{
RETURN_IF_NOT_BATTLE ( false ) ;
auto side = battleGetTacticsSide ( ) ;
auto dist = battleGetTacticDist ( ) ;
return ( ( ! side & & dest . getX ( ) > 0 & & dest . getX ( ) < = dist )
2017-06-24 15:51:07 +02:00
| | ( side & & dest . getX ( ) < GameConstants : : BFIELD_WIDTH - 1 & & dest . getX ( ) > = GameConstants : : BFIELD_WIDTH - dist - 1 ) ) ;
2012-08-26 12:07:48 +03:00
}
2017-07-20 06:08:49 +02:00
ReachabilityInfo CBattleInfoCallback : : getReachability ( const battle : : Unit * unit ) const
2012-08-26 12:07:48 +03:00
{
2017-07-20 06:08:49 +02:00
ReachabilityInfo : : Parameters params ( unit , unit - > getPosition ( ) ) ;
2012-08-26 12:07:48 +03:00
2017-07-20 06:08:49 +02:00
if ( ! battleDoWeKnowAbout ( unit - > unitSide ( ) ) )
2012-08-26 12:07:48 +03:00
{
//Stack is held by enemy, we can't use his perspective to check for reachability.
2012-08-28 15:28:13 +03:00
// Happens ie. when hovering enemy stack for its range. The arg could be set properly, but it's easier to fix it here.
2012-08-26 12:07:48 +03:00
params . perspective = battleGetMySide ( ) ;
}
return getReachability ( params ) ;
}
ReachabilityInfo CBattleInfoCallback : : getReachability ( const ReachabilityInfo : : Parameters & params ) const
{
if ( params . flying )
return getFlyingReachability ( params ) ;
else
2012-08-28 15:28:13 +03:00
return makeBFS ( getAccesibility ( params . knownAccessible ) , params ) ;
2012-08-26 12:07:48 +03:00
}
2013-11-07 15:48:41 +03:00
ReachabilityInfo CBattleInfoCallback : : getFlyingReachability ( const ReachabilityInfo : : Parameters & params ) const
2012-08-26 12:07:48 +03:00
{
ReachabilityInfo ret ;
2012-08-28 15:28:13 +03:00
ret . accessibility = getAccesibility ( params . knownAccessible ) ;
2012-08-26 12:07:48 +03:00
for ( int i = 0 ; i < GameConstants : : BFIELD_SIZE ; i + + )
{
2017-07-01 10:34:00 +02:00
if ( ret . accessibility . accessible ( i , params . doubleWide , params . side ) )
2012-08-26 12:07:48 +03:00
{
ret . predecessors [ i ] = params . startPosition ;
ret . distances [ i ] = BattleHex : : getDistance ( params . startPosition , i ) ;
}
}
return ret ;
}
2019-06-28 20:05:25 +02:00
AttackableTiles CBattleInfoCallback : : getPotentiallyAttackableHexes ( const battle : : Unit * attacker , BattleHex destinationTile , BattleHex attackerPos ) const
2012-08-26 12:07:48 +03:00
{
2012-11-30 17:06:22 +03:00
//does not return hex attacked directly
2012-08-26 12:07:48 +03:00
AttackableTiles at ;
RETURN_IF_NOT_BATTLE ( at ) ;
2023-01-19 21:51:49 +02:00
BattleHex attackOriginHex = ( attackerPos ! = BattleHex : : INVALID ) ? attackerPos : attacker - > getPosition ( ) ; //real or hypothetical (cursor) position
2012-11-29 18:02:55 +03:00
2023-02-15 00:44:59 +02:00
const auto * defender = battleGetUnitByPos ( destinationTile , true ) ;
2022-12-18 11:42:02 +02:00
if ( ! defender )
return at ; // can't attack thin air
2023-01-19 19:43:16 +02:00
bool reverse = isToReverse ( attacker , defender ) ;
2017-09-05 22:29:31 +02:00
if ( reverse & & attacker - > doubleWide ( ) )
2012-11-29 18:02:55 +03:00
{
2023-01-19 21:51:49 +02:00
attackOriginHex = attacker - > occupiedHex ( attackOriginHex ) ; //the other hex stack stands on
2012-11-29 18:02:55 +03:00
}
2017-09-05 22:29:31 +02:00
if ( attacker - > hasBonusOfType ( Bonus : : ATTACKS_ALL_ADJACENT ) )
2012-08-26 12:07:48 +03:00
{
2017-09-05 22:29:31 +02:00
boost : : copy ( attacker - > getSurroundingHexes ( attackerPos ) , vstd : : set_inserter ( at . hostileCreaturePositions ) ) ;
2012-08-26 12:07:48 +03:00
}
2017-09-05 22:29:31 +02:00
if ( attacker - > hasBonusOfType ( Bonus : : THREE_HEADED_ATTACK ) )
2012-08-26 12:07:48 +03:00
{
std : : vector < BattleHex > hexes = attacker - > getSurroundingHexes ( attackerPos ) ;
2017-09-05 22:29:31 +02:00
for ( BattleHex tile : hexes )
2012-08-26 12:07:48 +03:00
{
2023-01-19 21:51:49 +02:00
if ( ( BattleHex : : mutualPosition ( tile , destinationTile ) > - 1 & & BattleHex : : mutualPosition ( tile , attackOriginHex ) > - 1 ) ) //adjacent both to attacker's head and attacked tile
2012-08-26 12:07:48 +03:00
{
2023-02-15 00:44:59 +02:00
const auto * st = battleGetUnitByPos ( tile , true ) ;
2019-06-28 20:05:25 +02:00
if ( st & & battleMatchOwner ( st , attacker ) ) //only hostile stacks - does it work well with Berserk?
2012-08-26 12:07:48 +03:00
at . hostileCreaturePositions . insert ( tile ) ;
}
}
}
2017-09-05 22:29:31 +02:00
if ( attacker - > hasBonusOfType ( Bonus : : WIDE_BREATH ) )
{
std : : vector < BattleHex > hexes = destinationTile . neighbouringTiles ( ) ;
for ( int i = 0 ; i < hexes . size ( ) ; i + + )
{
2023-01-19 21:51:49 +02:00
if ( hexes . at ( i ) = = attackOriginHex )
2017-11-13 15:13:55 +02:00
{
2017-09-05 22:29:31 +02:00
hexes . erase ( hexes . begin ( ) + i ) ;
i = 0 ;
2017-01-28 13:28:30 +02:00
}
2017-09-05 22:29:31 +02:00
}
for ( BattleHex tile : hexes )
{
//friendly stacks can also be damaged by Dragon Breath
2023-02-15 00:44:59 +02:00
const auto * st = battleGetUnitByPos ( tile , true ) ;
2019-06-28 20:05:25 +02:00
if ( st & & st ! = attacker )
2021-02-20 03:57:50 +02:00
at . friendlyCreaturePositions . insert ( tile ) ;
2017-01-28 13:28:30 +02:00
}
2017-09-05 22:29:31 +02:00
}
2019-06-28 20:05:25 +02:00
else if ( attacker - > hasBonusOfType ( Bonus : : TWO_HEX_ATTACK_BREATH ) )
2012-08-26 12:07:48 +03:00
{
2023-01-19 21:51:49 +02:00
auto direction = BattleHex : : mutualPosition ( attackOriginHex , destinationTile ) ;
2022-12-21 18:04:54 +02:00
if ( direction ! = BattleHex : : NONE ) //only adjacent hexes are subject of dragon breath calculation
2012-08-26 12:07:48 +03:00
{
2022-12-21 18:04:54 +02:00
BattleHex nextHex = destinationTile . cloneInDirection ( direction , false ) ;
2023-01-19 21:51:49 +02:00
if ( defender - > doubleWide ( ) )
{
auto secondHex = destinationTile = = defender - > getPosition ( ) ?
defender - > occupiedHex ( ) :
defender - > getPosition ( ) ;
// if targeted double-wide creature is attacked from above or below ( -> second hex is also adjacent to attack origin)
// then dragon breath should target tile on the opposite side of targeted creature
if ( BattleHex : : mutualPosition ( attackOriginHex , secondHex ) ! = BattleHex : : NONE )
nextHex = secondHex . cloneInDirection ( direction , false ) ;
}
2022-12-21 18:04:54 +02:00
if ( nextHex . isValid ( ) )
2021-02-20 03:57:50 +02:00
{
//friendly stacks can also be damaged by Dragon Breath
2023-02-15 00:44:59 +02:00
const auto * st = battleGetUnitByPos ( nextHex , true ) ;
2021-02-20 03:57:50 +02:00
if ( st ! = nullptr )
2022-12-21 18:04:54 +02:00
at . friendlyCreaturePositions . insert ( nextHex ) ;
2021-02-20 03:57:50 +02:00
}
2012-08-26 12:07:48 +03:00
}
}
return at ;
}
2019-06-28 20:05:25 +02:00
AttackableTiles CBattleInfoCallback : : getPotentiallyShootableHexes ( const battle : : Unit * attacker , BattleHex destinationTile , BattleHex attackerPos ) const
2017-09-05 23:45:31 +02:00
{
//does not return hex attacked directly
AttackableTiles at ;
RETURN_IF_NOT_BATTLE ( at ) ;
if ( attacker - > hasBonusOfType ( Bonus : : SHOOTS_ALL_ADJACENT ) & & ! vstd : : contains ( attackerPos . neighbouringTiles ( ) , destinationTile ) )
{
std : : vector < BattleHex > targetHexes = destinationTile . neighbouringTiles ( ) ;
targetHexes . push_back ( destinationTile ) ;
boost : : copy ( targetHexes , vstd : : set_inserter ( at . hostileCreaturePositions ) ) ;
}
2012-08-26 12:07:48 +03:00
return at ;
}
2019-06-28 20:05:25 +02:00
std : : vector < const battle : : Unit * > CBattleInfoCallback : : getAttackedBattleUnits ( const battle : : Unit * attacker , BattleHex destinationTile , bool rangedAttack , BattleHex attackerPos ) const
{
std : : vector < const battle : : Unit * > units ;
RETURN_IF_NOT_BATTLE ( units ) ;
AttackableTiles at ;
if ( rangedAttack )
at = getPotentiallyShootableHexes ( attacker , destinationTile , attackerPos ) ;
else
at = getPotentiallyAttackableHexes ( attacker , destinationTile , attackerPos ) ;
units = battleGetUnitsIf ( [ = ] ( const battle : : Unit * unit )
{
2020-05-16 15:14:58 +02:00
if ( unit - > isGhost ( ) | | ! unit - > alive ( ) )
2019-06-28 20:05:25 +02:00
return false ;
2020-05-16 15:14:58 +02:00
for ( BattleHex hex : battle : : Unit : : getHexes ( unit - > getPosition ( ) , unit - > doubleWide ( ) , unit - > unitSide ( ) ) )
{
if ( vstd : : contains ( at . hostileCreaturePositions , hex ) )
2019-06-28 20:05:25 +02:00
return true ;
2020-05-16 15:14:58 +02:00
if ( vstd : : contains ( at . friendlyCreaturePositions , hex ) )
2019-06-28 20:05:25 +02:00
return true ;
}
return false ;
} ) ;
return units ;
}
2017-09-04 23:32:24 +02:00
std : : set < const CStack * > CBattleInfoCallback : : getAttackedCreatures ( const CStack * attacker , BattleHex destinationTile , bool rangedAttack , BattleHex attackerPos ) const
2012-08-26 12:07:48 +03:00
{
std : : set < const CStack * > attackedCres ;
RETURN_IF_NOT_BATTLE ( attackedCres ) ;
2017-09-05 23:45:31 +02:00
AttackableTiles at ;
if ( rangedAttack )
at = getPotentiallyShootableHexes ( attacker , destinationTile , attackerPos ) ;
else
at = getPotentiallyAttackableHexes ( attacker , destinationTile , attackerPos ) ;
2013-06-29 16:05:48 +03:00
for ( BattleHex tile : at . hostileCreaturePositions ) //all around & three-headed attack
2012-08-26 12:07:48 +03:00
{
const CStack * st = battleGetStackByPos ( tile , true ) ;
if ( st & & st - > owner ! = attacker - > owner ) //only hostile stacks - does it work well with Berserk?
{
attackedCres . insert ( st ) ;
}
}
2013-06-29 16:05:48 +03:00
for ( BattleHex tile : at . friendlyCreaturePositions )
2012-08-26 12:07:48 +03:00
{
const CStack * st = battleGetStackByPos ( tile , true ) ;
if ( st ) //friendly stacks can also be damaged by Dragon Breath
{
attackedCres . insert ( st ) ;
}
}
return attackedCres ;
}
2022-12-18 11:42:02 +02:00
static bool isHexInFront ( BattleHex hex , BattleHex testHex , BattleSide : : Type side )
2012-11-29 18:02:55 +03:00
{
2022-12-18 11:42:02 +02:00
static const std : : set < BattleHex : : EDir > rightDirs { BattleHex : : BOTTOM_RIGHT , BattleHex : : TOP_RIGHT , BattleHex : : RIGHT } ;
static const std : : set < BattleHex : : EDir > leftDirs { BattleHex : : BOTTOM_LEFT , BattleHex : : TOP_LEFT , BattleHex : : LEFT } ;
2012-11-29 18:02:55 +03:00
2022-12-18 11:42:02 +02:00
auto mutualPos = BattleHex : : mutualPosition ( hex , testHex ) ;
2013-08-06 18:25:51 +03:00
2022-12-18 11:42:02 +02:00
if ( side = = BattleSide : : ATTACKER )
return rightDirs . count ( mutualPos ) ;
else
return leftDirs . count ( mutualPos ) ;
2012-11-29 18:02:55 +03:00
}
2013-08-06 18:25:51 +03:00
//TODO: this should apply also to mechanics and cursor interface
2023-02-15 00:44:59 +02:00
bool CBattleInfoCallback : : isToReverse ( const battle : : Unit * attacker , const battle : : Unit * defender ) const
2012-11-29 18:02:55 +03:00
{
2023-01-19 19:43:16 +02:00
BattleHex attackerHex = attacker - > getPosition ( ) ;
BattleHex defenderHex = defender - > getPosition ( ) ;
2022-12-18 11:42:02 +02:00
if ( attackerHex < 0 ) //turret
2012-11-29 18:02:55 +03:00
return false ;
2023-02-15 00:44:59 +02:00
if ( isHexInFront ( attackerHex , defenderHex , static_cast < BattleSide : : Type > ( attacker - > unitSide ( ) ) ) )
2014-03-23 19:36:16 +03:00
return false ;
2022-12-18 11:42:02 +02:00
if ( defender - > doubleWide ( ) )
{
2023-02-15 00:44:59 +02:00
if ( isHexInFront ( attackerHex , defender - > occupiedHex ( ) , static_cast < BattleSide : : Type > ( attacker - > unitSide ( ) ) ) )
2022-12-18 11:42:02 +02:00
return false ;
2012-11-29 18:02:55 +03:00
}
2022-12-18 11:42:02 +02:00
if ( attacker - > doubleWide ( ) )
2012-11-29 18:02:55 +03:00
{
2023-02-15 00:44:59 +02:00
if ( isHexInFront ( attacker - > occupiedHex ( ) , defenderHex , static_cast < BattleSide : : Type > ( attacker - > unitSide ( ) ) ) )
2022-12-18 11:42:02 +02:00
return false ;
2012-11-29 18:02:55 +03:00
}
2022-12-18 11:42:02 +02:00
// a bit weird case since here defender is slightly behind attacker, so reversing seems preferable,
// but this is how H3 handles it which is important, e.g. for direction of dragon breath attacks
if ( attacker - > doubleWide ( ) & & defender - > doubleWide ( ) )
{
2023-02-15 00:44:59 +02:00
if ( isHexInFront ( attacker - > occupiedHex ( ) , defender - > occupiedHex ( ) , static_cast < BattleSide : : Type > ( attacker - > unitSide ( ) ) ) )
2022-12-18 11:42:02 +02:00
return false ;
}
return true ;
2012-11-29 18:02:55 +03:00
}
2017-07-20 06:08:49 +02:00
ReachabilityInfo : : TDistances CBattleInfoCallback : : battleGetDistances ( const battle : : Unit * unit , BattleHex assumedPosition ) const
2012-08-26 12:07:48 +03:00
{
ReachabilityInfo : : TDistances ret ;
ret . fill ( - 1 ) ;
RETURN_IF_NOT_BATTLE ( ret ) ;
2017-07-20 06:08:49 +02:00
auto reachability = getReachability ( unit ) ;
2012-08-26 12:07:48 +03:00
boost : : copy ( reachability . distances , ret . begin ( ) ) ;
return ret ;
}
2017-07-20 06:08:49 +02:00
bool CBattleInfoCallback : : battleHasDistancePenalty ( const IBonusBearer * shooter , BattleHex shooterPosition , BattleHex destHex ) const
2012-08-26 12:07:48 +03:00
{
RETURN_IF_NOT_BATTLE ( false ) ;
2017-07-20 06:08:49 +02:00
const std : : string cachingStrNoDistancePenalty = " type_NO_DISTANCE_PENALTY " ;
2020-11-11 21:43:40 +02:00
static const auto selectorNoDistancePenalty = Selector : : type ( ) ( Bonus : : NO_DISTANCE_PENALTY ) ;
2017-07-20 06:08:49 +02:00
if ( shooter - > hasBonus ( selectorNoDistancePenalty , cachingStrNoDistancePenalty ) )
2012-08-26 12:07:48 +03:00
return false ;
2023-02-15 00:44:59 +02:00
if ( const auto * target = battleGetUnitByPos ( destHex , true ) )
2012-08-26 12:07:48 +03:00
{
2012-11-29 18:02:55 +03:00
//If any hex of target creature is within range, there is no penalty
2023-01-15 15:52:41 +02:00
int range = GameConstants : : BATTLE_PENALTY_DISTANCE ;
auto bonus = shooter - > getBonus ( Selector : : type ( ) ( Bonus : : LIMITED_SHOOTING_RANGE ) ) ;
if ( bonus ! = nullptr & & bonus - > additionalInfo ! = CAddInfo : : NONE )
range = bonus - > additionalInfo [ 0 ] ;
if ( isEnemyUnitWithinSpecifiedRange ( shooterPosition , target , range ) )
2023-01-12 18:07:40 +02:00
return false ;
2012-08-26 12:07:48 +03:00
}
2012-11-29 18:02:55 +03:00
else
{
2017-07-20 06:08:49 +02:00
if ( BattleHex : : getDistance ( shooterPosition , destHex ) < = GameConstants : : BATTLE_PENALTY_DISTANCE )
2012-11-29 18:02:55 +03:00
return false ;
}
2012-08-26 12:07:48 +03:00
return true ;
}
2023-01-12 18:07:40 +02:00
bool CBattleInfoCallback : : isEnemyUnitWithinSpecifiedRange ( BattleHex attackerPosition , const battle : : Unit * defenderUnit , unsigned int range ) const
{
for ( auto hex : defenderUnit - > getHexes ( ) )
if ( BattleHex : : getDistance ( attackerPosition , hex ) < = range )
return true ;
2023-01-15 21:16:14 +02:00
2023-01-12 18:07:40 +02:00
return false ;
}
2023-01-13 00:35:58 +02:00
BattleHex CBattleInfoCallback : : wallPartToBattleHex ( EWallPart part ) const
2013-08-06 14:20:28 +03:00
{
RETURN_IF_NOT_BATTLE ( BattleHex : : INVALID ) ;
return WallPartToHex ( part ) ;
}
2023-01-13 00:35:58 +02:00
EWallPart CBattleInfoCallback : : battleHexToWallPart ( BattleHex hex ) const
2012-08-26 12:07:48 +03:00
{
2013-12-08 20:54:13 +03:00
RETURN_IF_NOT_BATTLE ( EWallPart : : INVALID ) ;
2012-08-26 12:07:48 +03:00
return hexToWallPart ( hex ) ;
}
2023-01-13 00:35:58 +02:00
bool CBattleInfoCallback : : isWallPartPotentiallyAttackable ( EWallPart wallPart ) const
2013-12-08 20:54:13 +03:00
{
RETURN_IF_NOT_BATTLE ( false ) ;
return wallPart ! = EWallPart : : INDESTRUCTIBLE_PART & & wallPart ! = EWallPart : : INDESTRUCTIBLE_PART_OF_GATE & &
2017-06-26 18:50:35 +02:00
wallPart ! = EWallPart : : INVALID ;
2013-12-08 20:54:13 +03:00
}
std : : vector < BattleHex > CBattleInfoCallback : : getAttackableBattleHexes ( ) const
{
std : : vector < BattleHex > attackableBattleHexes ;
RETURN_IF_NOT_BATTLE ( attackableBattleHexes ) ;
2023-02-15 00:44:59 +02:00
for ( const auto & wallPartPair : wallParts )
2013-12-08 20:54:13 +03:00
{
if ( isWallPartPotentiallyAttackable ( wallPartPair . second ) )
{
2023-01-13 01:09:24 +02:00
auto wallState = battleGetWallState ( wallPartPair . second ) ;
if ( wallState = = EWallState : : REINFORCED | | wallState = = EWallState : : INTACT | | wallState = = EWallState : : DAMAGED )
2013-12-08 20:54:13 +03:00
{
2023-02-15 00:44:59 +02:00
attackableBattleHexes . emplace_back ( wallPartPair . first ) ;
2013-12-08 20:54:13 +03:00
}
}
}
return attackableBattleHexes ;
}
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
int32_t CBattleInfoCallback : : battleGetSpellCost ( const spells : : Spell * sp , const CGHeroInstance * caster ) const
2012-08-26 12:07:48 +03:00
{
RETURN_IF_NOT_BATTLE ( - 1 ) ;
//TODO should be replaced using bonus system facilities (propagation onto battle node)
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
int32_t ret = caster - > getSpellCost ( sp ) ;
2012-08-26 12:07:48 +03:00
//checking for friendly stacks reducing cost of the spell and
//enemy stacks increasing it
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
int32_t manaReduction = 0 ;
int32_t manaIncrease = 0 ;
2012-08-26 12:07:48 +03:00
2023-02-15 00:44:59 +02:00
for ( const auto * unit : battleAliveUnits ( ) )
2012-08-26 12:07:48 +03:00
{
2017-07-20 06:08:49 +02:00
if ( unit - > unitOwner ( ) = = caster - > tempOwner & & unit - > hasBonusOfType ( Bonus : : CHANGES_SPELL_COST_FOR_ALLY ) )
2012-08-26 12:07:48 +03:00
{
2017-07-20 06:08:49 +02:00
vstd : : amax ( manaReduction , unit - > valOfBonuses ( Bonus : : CHANGES_SPELL_COST_FOR_ALLY ) ) ;
2012-08-26 12:07:48 +03:00
}
2017-07-20 06:08:49 +02:00
if ( unit - > unitOwner ( ) ! = caster - > tempOwner & & unit - > hasBonusOfType ( Bonus : : CHANGES_SPELL_COST_FOR_ENEMY ) )
2012-08-26 12:07:48 +03:00
{
2017-07-20 06:08:49 +02:00
vstd : : amax ( manaIncrease , unit - > valOfBonuses ( Bonus : : CHANGES_SPELL_COST_FOR_ENEMY ) ) ;
2012-08-26 12:07:48 +03:00
}
}
return ret - manaReduction + manaIncrease ;
}
2017-07-20 06:08:49 +02:00
bool CBattleInfoCallback : : battleHasShootingPenalty ( const battle : : Unit * shooter , BattleHex destHex ) const
2012-08-26 12:07:48 +03:00
{
2017-07-20 06:08:49 +02:00
return battleHasDistancePenalty ( shooter , shooter - > getPosition ( ) , destHex ) | | battleHasWallPenalty ( shooter , shooter - > getPosition ( ) , destHex ) ;
2017-06-24 15:51:07 +02:00
}
2017-07-20 06:08:49 +02:00
bool CBattleInfoCallback : : battleIsUnitBlocked ( const battle : : Unit * unit ) const
2012-08-26 12:07:48 +03:00
{
RETURN_IF_NOT_BATTLE ( false ) ;
2017-07-20 06:08:49 +02:00
if ( unit - > hasBonusOfType ( Bonus : : SIEGE_WEAPON ) ) //siege weapons cannot be blocked
2012-08-26 12:07:48 +03:00
return false ;
2023-02-15 00:44:59 +02:00
for ( const auto * adjacent : battleAdjacentUnits ( unit ) )
2012-08-26 12:07:48 +03:00
{
2017-07-20 06:08:49 +02:00
if ( adjacent - > unitOwner ( ) ! = unit - > unitOwner ( ) ) //blocked by enemy stack
2012-08-26 12:07:48 +03:00
return true ;
}
return false ;
}
2017-07-20 06:08:49 +02:00
std : : set < const battle : : Unit * > CBattleInfoCallback : : battleAdjacentUnits ( const battle : : Unit * unit ) const
2012-08-26 12:07:48 +03:00
{
2017-07-20 06:08:49 +02:00
std : : set < const battle : : Unit * > ret ;
RETURN_IF_NOT_BATTLE ( ret ) ;
2012-08-26 12:07:48 +03:00
2017-07-20 06:08:49 +02:00
for ( auto hex : unit - > getSurroundingHexes ( ) )
{
2023-02-15 00:44:59 +02:00
if ( const auto * neighbour = battleGetUnitByPos ( hex , true ) )
2017-07-20 06:08:49 +02:00
ret . insert ( neighbour ) ;
}
2012-08-26 12:07:48 +03:00
2017-07-20 06:08:49 +02:00
return ret ;
2012-08-26 12:07:48 +03:00
}
2016-09-09 19:30:36 +02:00
SpellID CBattleInfoCallback : : getRandomBeneficialSpell ( CRandomGenerator & rand , const CStack * subject ) const
2012-08-26 12:07:48 +03:00
{
2013-02-11 02:24:57 +03:00
RETURN_IF_NOT_BATTLE ( SpellID : : NONE ) ;
2014-12-01 23:24:36 +02:00
//This is complete list. No spells from mods.
//todo: this should be Spellbook of caster Stack
2016-02-01 19:03:57 +02:00
static const std : : set < SpellID > allPossibleSpells =
2014-12-01 23:24:36 +02:00
{
SpellID : : AIR_SHIELD ,
SpellID : : ANTI_MAGIC ,
2016-02-01 19:03:57 +02:00
SpellID : : BLESS ,
2014-12-01 23:24:36 +02:00
SpellID : : BLOODLUST ,
SpellID : : COUNTERSTRIKE ,
SpellID : : CURE ,
2016-02-01 19:03:57 +02:00
SpellID : : FIRE_SHIELD ,
2014-12-01 23:24:36 +02:00
SpellID : : FORTUNE ,
SpellID : : HASTE ,
SpellID : : MAGIC_MIRROR ,
2016-02-01 19:03:57 +02:00
SpellID : : MIRTH ,
2014-12-01 23:24:36 +02:00
SpellID : : PRAYER ,
SpellID : : PRECISION ,
SpellID : : PROTECTION_FROM_AIR ,
SpellID : : PROTECTION_FROM_EARTH ,
SpellID : : PROTECTION_FROM_FIRE ,
SpellID : : PROTECTION_FROM_WATER ,
SpellID : : SHIELD ,
SpellID : : SLAYER ,
SpellID : : STONE_SKIN
} ;
std : : vector < SpellID > beneficialSpells ;
2016-02-01 19:03:57 +02:00
2017-07-20 06:08:49 +02:00
auto getAliveEnemy = [ = ] ( const std : : function < bool ( const CStack * ) > & pred ) - > const CStack *
2012-08-26 12:07:48 +03:00
{
2017-07-20 06:08:49 +02:00
auto stacks = battleGetStacksIf ( [ = ] ( const CStack * stack )
2012-08-26 12:07:48 +03:00
{
2017-07-20 06:08:49 +02:00
return pred ( stack ) & & stack - > owner ! = subject - > owner & & stack - > isValidTarget ( false ) ;
2014-12-01 23:24:36 +02:00
} ) ;
2017-07-20 06:08:49 +02:00
if ( stacks . empty ( ) )
return nullptr ;
else
return stacks . front ( ) ;
2014-12-01 23:24:36 +02:00
} ;
2012-08-26 12:07:48 +03:00
2022-05-19 14:14:50 +02:00
for ( const SpellID & spellID : allPossibleSpells )
2014-12-01 23:24:36 +02:00
{
2016-10-01 06:28:03 +02:00
std : : stringstream cachingStr ;
cachingStr < < " source_ " < < Bonus : : SPELL_EFFECT < < " id_ " < < spellID . num ;
if ( subject - > hasBonus ( Selector : : source ( Bonus : : SPELL_EFFECT , spellID ) , Selector : : all , cachingStr . str ( ) )
2017-06-26 18:50:35 +02:00
//TODO: this ability has special limitations
2017-07-20 06:08:49 +02:00
| | ! ( spellID . toSpell ( ) - > canBeCast ( this , spells : : Mode : : CREATURE_ACTIVE , subject ) ) )
2014-12-01 23:24:36 +02:00
continue ;
2012-08-26 12:07:48 +03:00
2014-12-01 23:24:36 +02:00
switch ( spellID )
{
case SpellID : : SHIELD :
case SpellID : : FIRE_SHIELD : // not if all enemy units are shooters
2017-06-24 15:51:07 +02:00
{
2023-02-15 00:44:59 +02:00
const auto * walker = getAliveEnemy ( [ & ] ( const CStack * stack ) //look for enemy, non-shooting stack
2016-02-01 19:03:57 +02:00
{
2017-07-04 13:24:46 +02:00
return ! stack - > canShoot ( ) ;
2017-06-24 15:51:07 +02:00
} ) ;
2012-08-26 12:07:48 +03:00
2023-02-15 00:44:59 +02:00
if ( ! walker )
2017-06-24 15:51:07 +02:00
continue ;
}
2014-12-01 23:24:36 +02:00
break ;
case SpellID : : AIR_SHIELD : //only against active shooters
2017-06-24 15:51:07 +02:00
{
2023-02-15 00:44:59 +02:00
const auto * shooter = getAliveEnemy ( [ & ] ( const CStack * stack ) //look for enemy, non-shooting stack
2014-12-01 23:24:36 +02:00
{
2017-07-04 13:24:46 +02:00
return stack - > canShoot ( ) ;
2017-06-24 15:51:07 +02:00
} ) ;
2023-02-15 00:44:59 +02:00
if ( ! shooter )
2017-06-24 15:51:07 +02:00
continue ;
}
2014-12-01 23:24:36 +02:00
break ;
case SpellID : : ANTI_MAGIC :
case SpellID : : MAGIC_MIRROR :
case SpellID : : PROTECTION_FROM_AIR :
case SpellID : : PROTECTION_FROM_EARTH :
case SpellID : : PROTECTION_FROM_FIRE :
2016-02-01 19:03:57 +02:00
case SpellID : : PROTECTION_FROM_WATER :
2017-06-24 15:51:07 +02:00
{
2017-07-01 10:34:00 +02:00
const ui8 enemySide = 1 - subject - > side ;
2017-06-24 15:51:07 +02:00
//todo: only if enemy has spellbook
if ( ! battleHasHero ( enemySide ) ) //only if there is enemy hero
continue ;
}
2014-12-01 23:24:36 +02:00
break ;
case SpellID : : CURE : //only damaged units
2017-06-24 15:51:07 +02:00
{
//do not cast on affected by debuffs
2017-07-04 13:24:46 +02:00
if ( ! subject - > canBeHealed ( ) )
2017-06-24 15:51:07 +02:00
continue ;
}
2014-12-01 23:24:36 +02:00
break ;
case SpellID : : BLOODLUST :
2017-06-24 15:51:07 +02:00
{
2017-07-04 13:24:46 +02:00
if ( subject - > canShoot ( ) ) //TODO: if can shoot - only if enemy units are adjacent
2017-06-24 15:51:07 +02:00
continue ;
}
2014-12-01 23:24:36 +02:00
break ;
case SpellID : : PRECISION :
2017-06-24 15:51:07 +02:00
{
2017-07-04 13:24:46 +02:00
if ( ! subject - > canShoot ( ) )
2017-06-24 15:51:07 +02:00
continue ;
}
2014-12-01 23:24:36 +02:00
break ;
case SpellID : : SLAYER : //only if monsters are present
2017-06-24 15:51:07 +02:00
{
2023-02-15 00:44:59 +02:00
const auto * kingMonster = getAliveEnemy ( [ & ] ( const CStack * stack ) - > bool //look for enemy, non-shooting stack
2014-12-01 23:24:36 +02:00
{
2020-11-11 21:43:40 +02:00
const auto isKing = Selector : : type ( ) ( Bonus : : KING1 )
. Or ( Selector : : type ( ) ( Bonus : : KING2 ) )
. Or ( Selector : : type ( ) ( Bonus : : KING3 ) ) ;
2013-07-02 15:08:30 +03:00
2017-06-24 15:51:07 +02:00
return stack - > hasBonus ( isKing ) ;
} ) ;
2012-08-26 12:07:48 +03:00
2017-06-24 15:51:07 +02:00
if ( ! kingMonster )
continue ;
}
2014-12-01 23:24:36 +02:00
break ;
2012-08-26 12:07:48 +03:00
}
2016-02-01 19:03:57 +02:00
beneficialSpells . push_back ( spellID ) ;
2012-08-26 12:07:48 +03:00
}
2014-12-01 23:24:36 +02:00
if ( ! beneficialSpells . empty ( ) )
2014-04-10 20:11:09 +03:00
{
2016-09-09 19:30:36 +02:00
return * RandomGeneratorUtil : : nextItem ( beneficialSpells , rand ) ;
2014-04-10 20:11:09 +03:00
}
2012-08-26 12:07:48 +03:00
else
2014-04-10 20:11:09 +03:00
{
2013-02-11 02:24:57 +03:00
return SpellID : : NONE ;
2014-04-10 20:11:09 +03:00
}
2012-08-26 12:07:48 +03:00
}
2016-09-09 19:30:36 +02:00
SpellID CBattleInfoCallback : : getRandomCastedSpell ( CRandomGenerator & rand , const CStack * caster ) const
2012-08-26 12:07:48 +03:00
{
2013-02-11 02:24:57 +03:00
RETURN_IF_NOT_BATTLE ( SpellID : : NONE ) ;
2012-08-26 12:07:48 +03:00
2020-11-11 21:43:40 +02:00
TConstBonusListPtr bl = caster - > getBonuses ( Selector : : type ( ) ( Bonus : : SPELLCASTER ) ) ;
2012-08-26 12:07:48 +03:00
if ( ! bl - > size ( ) )
2013-02-11 02:24:57 +03:00
return SpellID : : NONE ;
2012-08-26 12:07:48 +03:00
int totalWeight = 0 ;
2023-02-15 00:44:59 +02:00
for ( const auto & b : * bl )
2012-08-26 12:07:48 +03:00
{
2018-03-12 07:20:18 +02:00
totalWeight + = std : : max ( b - > additionalInfo [ 0 ] , 1 ) ; //minimal chance to cast is 1
2012-08-26 12:07:48 +03:00
}
2016-09-09 19:30:36 +02:00
int randomPos = rand . nextInt ( totalWeight - 1 ) ;
2023-02-15 00:44:59 +02:00
for ( const auto & b : * bl )
2012-08-26 12:07:48 +03:00
{
2018-03-12 07:20:18 +02:00
randomPos - = std : : max ( b - > additionalInfo [ 0 ] , 1 ) ;
2012-08-26 12:07:48 +03:00
if ( randomPos < 0 )
{
2013-02-11 02:24:57 +03:00
return SpellID ( b - > subtype ) ;
2012-08-26 12:07:48 +03:00
}
}
2013-02-11 02:24:57 +03:00
return SpellID : : NONE ;
2012-08-26 12:07:48 +03:00
}
2023-02-15 00:44:59 +02:00
int CBattleInfoCallback : : battleGetSurrenderCost ( const PlayerColor & Player ) const
2012-08-26 12:07:48 +03:00
{
RETURN_IF_NOT_BATTLE ( - 3 ) ;
if ( ! battleCanSurrender ( Player ) )
return - 1 ;
2017-07-20 06:08:49 +02:00
const auto sideOpt = playerToSide ( Player ) ;
if ( ! sideOpt )
2016-11-28 02:33:39 +02:00
return - 1 ;
2017-07-20 06:08:49 +02:00
const auto side = sideOpt . get ( ) ;
2016-11-28 02:33:39 +02:00
2012-08-26 12:07:48 +03:00
int ret = 0 ;
double discount = 0 ;
2023-02-15 00:44:59 +02:00
for ( const auto * unit : battleAliveUnits ( side ) )
2017-07-20 06:08:49 +02:00
ret + = unit - > getRawSurrenderCost ( ) ;
if ( const CGHeroInstance * h = battleGetFightingHero ( side ) )
2012-08-26 12:07:48 +03:00
discount + = h - > valOfBonuses ( Bonus : : SURRENDER_DISCOUNT ) ;
2020-10-01 10:38:06 +02:00
ret = static_cast < int > ( ret * ( 100.0 - discount ) / 100.0 ) ;
2012-08-26 12:07:48 +03:00
vstd : : amax ( ret , 0 ) ; //no negative costs for >100% discounts (impossible in original H3 mechanics, but some day...)
return ret ;
}
2017-09-06 00:03:32 +02:00
si8 CBattleInfoCallback : : battleMinSpellLevel ( ui8 side ) const
{
2017-11-13 15:13:55 +02:00
const IBonusBearer * node = nullptr ;
2017-09-06 00:03:32 +02:00
if ( const CGHeroInstance * h = battleGetFightingHero ( side ) )
node = h ;
else
node = getBattleNode ( ) ;
if ( ! node )
2017-09-09 21:01:12 +02:00
return 0 ;
2017-09-06 00:03:32 +02:00
2020-11-11 21:43:40 +02:00
auto b = node - > getBonuses ( Selector : : type ( ) ( Bonus : : BLOCK_MAGIC_BELOW ) ) ;
2017-09-06 00:03:32 +02:00
if ( b - > size ( ) )
return b - > totalValue ( ) ;
2017-09-09 21:01:12 +02:00
return 0 ;
2017-09-06 00:03:32 +02:00
}
2015-09-16 17:28:14 +02:00
si8 CBattleInfoCallback : : battleMaxSpellLevel ( ui8 side ) const
2012-08-26 12:07:48 +03:00
{
2015-09-16 17:28:14 +02:00
const IBonusBearer * node = nullptr ;
if ( const CGHeroInstance * h = battleGetFightingHero ( side ) )
2012-08-26 12:07:48 +03:00
node = h ;
2015-09-16 17:28:14 +02:00
else
node = getBattleNode ( ) ;
2012-08-26 12:07:48 +03:00
if ( ! node )
2012-08-26 12:59:07 +03:00
return GameConstants : : SPELL_LEVELS ;
2012-08-26 12:07:48 +03:00
//We can't "just get value" - it'd be 0 if there are bonuses (and all would be blocked)
2020-11-11 21:43:40 +02:00
auto b = node - > getBonuses ( Selector : : type ( ) ( Bonus : : BLOCK_MAGIC_ABOVE ) ) ;
2012-08-26 12:07:48 +03:00
if ( b - > size ( ) )
return b - > totalValue ( ) ;
return GameConstants : : SPELL_LEVELS ;
}
2012-09-20 19:55:21 +03:00
2013-09-29 23:54:29 +03:00
boost : : optional < int > CBattleInfoCallback : : battleIsFinished ( ) const
{
2017-07-20 06:08:49 +02:00
auto units = battleGetUnitsIf ( [ = ] ( const battle : : Unit * unit )
{
2022-04-16 12:18:41 +02:00
return unit - > alive ( ) & & ! unit - > isTurret ( ) & & ! unit - > hasBonusOfType ( Bonus : : SIEGE_WEAPON ) ;
2017-07-20 06:08:49 +02:00
} ) ;
2013-09-29 23:54:29 +03:00
2017-07-20 06:08:49 +02:00
std : : array < bool , 2 > hasUnit = { false , false } ; //index is BattleSide
2013-09-29 23:54:29 +03:00
2017-07-20 06:08:49 +02:00
for ( auto & unit : units )
2013-09-29 23:54:29 +03:00
{
2017-07-20 06:08:49 +02:00
//todo: move SIEGE_WEAPON check to Unit state
2022-04-16 12:18:41 +02:00
hasUnit . at ( unit - > unitSide ( ) ) = true ;
if ( hasUnit [ 0 ] & & hasUnit [ 1 ] )
return boost : : none ;
}
hasUnit = { false , false } ;
for ( auto & unit : units )
{
if ( ! unit - > isClone ( ) & & ! unit - > acquireState ( ) - > summoned & & ! dynamic_cast < const CCommanderInstance * > ( unit ) )
2013-09-29 23:54:29 +03:00
{
2017-07-20 06:08:49 +02:00
hasUnit . at ( unit - > unitSide ( ) ) = true ;
2013-09-29 23:54:29 +03:00
}
}
2017-07-20 06:08:49 +02:00
if ( ! hasUnit [ 0 ] & & ! hasUnit [ 1 ] )
2013-09-29 23:54:29 +03:00
return 2 ;
2017-07-20 06:08:49 +02:00
if ( ! hasUnit [ 1 ] )
2013-09-29 23:54:29 +03:00
return 0 ;
2022-04-16 12:18:41 +02:00
else
2013-09-30 00:31:14 +03:00
return 1 ;
2013-09-29 23:54:29 +03:00
}
2022-07-26 15:07:42 +02:00
VCMI_LIB_NAMESPACE_END