2012-08-26 12:07:48 +03:00
# include "StdInc.h"
# include "CBattleCallback.h"
# include "BattleState.h"
# include "CGameState.h"
# include "NetPacks.h"
2015-02-02 10:25:26 +02:00
# include "spells/CSpellHandler.h"
2012-08-26 12:07:48 +03:00
# include "VCMI_Lib.h"
2013-02-09 00:17:39 +03:00
# include "CTownHandler.h"
2015-12-02 21:05:10 +02:00
# include "mapObjects/CGTownInstance.h"
2012-08-26 12:07:48 +03:00
2012-12-19 17:54:10 +03:00
/*
* CBattleCallback . cpp , part of VCMI engine
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
2013-04-09 17:31:36 +03:00
# define RETURN_IF_NOT_BATTLE(X) if(!duringBattle()) {logGlobal->errorStream() << __FUNCTION__ << " called when no battle!"; return X; }
2012-08-26 12:07:48 +03:00
namespace SiegeStuffThatShouldBeMovedToHandlers // <=== TODO
{
2013-07-22 19:23:23 +03:00
static void retreiveTurretDamageRange ( const CGTownInstance * town , const CStack * turret , double & outMinDmg , double & outMaxDmg )
2012-09-20 19:55:21 +03:00
{
2013-02-09 00:17:39 +03:00
assert ( turret - > getCreature ( ) - > idNumber = = CreatureID : : ARROW_TOWERS ) ;
2013-07-22 19:23:23 +03:00
assert ( town ) ;
assert ( turret - > position > = - 4 & & turret - > position < = - 2 ) ;
2012-09-20 19:55:21 +03:00
2013-07-22 19:23:23 +03:00
float multiplier = ( turret - > position = = - 2 ) ? 1 : 0.5 ;
int baseMin = 6 ;
int baseMax = 10 ;
outMinDmg = multiplier * ( baseMin + town - > getTownLevel ( ) * 2 ) ;
outMaxDmg = multiplier * ( baseMax + town - > getTownLevel ( ) * 3 ) ;
2012-09-20 19:55:21 +03:00
}
2013-02-11 17:42:09 +03:00
static BattleHex lineToWallHex ( int line ) //returns hex with wall in given line (y coordinate)
2012-08-26 12:07:48 +03:00
{
2013-02-11 17:42:09 +03:00
static const BattleHex lineToHex [ ] = { 12 , 29 , 45 , 62 , 78 , 95 , 112 , 130 , 147 , 165 , 182 } ;
2012-08-26 12:07:48 +03:00
return lineToHex [ line ] ;
}
static bool sameSideOfWall ( BattleHex pos1 , BattleHex pos2 )
{
const int wallInStackLine = lineToWallHex ( pos1 . getY ( ) ) ;
const int wallInDestLine = lineToWallHex ( pos2 . getY ( ) ) ;
const bool stackLeft = pos1 < wallInStackLine ;
const bool destLeft = pos2 < wallInDestLine ;
return stackLeft ! = destLeft ;
}
2013-12-08 20:54:13 +03:00
// parts of wall
static const std : : pair < int , EWallPart : : EWallPart > wallParts [ ] =
{
std : : make_pair ( 50 , EWallPart : : KEEP ) ,
std : : make_pair ( 183 , EWallPart : : BOTTOM_TOWER ) ,
std : : make_pair ( 182 , EWallPart : : BOTTOM_WALL ) ,
std : : make_pair ( 130 , EWallPart : : BELOW_GATE ) ,
2016-02-14 13:38:24 +02:00
std : : make_pair ( 78 , EWallPart : : OVER_GATE ) ,
2013-12-08 20:54:13 +03:00
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 ) ,
2016-02-14 13:38:24 +02:00
std : : make_pair ( 62 , EWallPart : : INDESTRUCTIBLE_PART ) ,
2013-12-08 20:54:13 +03:00
std : : make_pair ( 112 , EWallPart : : INDESTRUCTIBLE_PART ) ,
std : : make_pair ( 147 , EWallPart : : INDESTRUCTIBLE_PART )
2013-08-06 14:20:28 +03:00
} ;
2013-12-08 20:54:13 +03:00
static EWallPart : : EWallPart hexToWallPart ( BattleHex hex )
2012-08-26 12:07:48 +03:00
{
2013-12-08 20:54:13 +03:00
for ( auto & elem : wallParts )
2012-08-26 12:07:48 +03:00
{
2013-06-29 16:05:48 +03:00
if ( elem . first = = hex )
return elem . second ;
2012-08-26 12:07:48 +03:00
}
2013-12-08 20:54:13 +03:00
return EWallPart : : INVALID ; //not found!
2012-08-26 12:07:48 +03:00
}
2013-08-06 14:20:28 +03:00
2013-12-08 20:54:13 +03:00
static BattleHex WallPartToHex ( EWallPart : : EWallPart part )
2013-08-06 14:20:28 +03:00
{
2013-12-08 20:54:13 +03:00
for ( auto & elem : wallParts )
2013-08-06 14:20:28 +03:00
{
if ( elem . second = = part )
return elem . first ;
}
return BattleHex : : INVALID ; //not found!
}
2012-08-26 12:07:48 +03:00
}
using namespace SiegeStuffThatShouldBeMovedToHandlers ;
boost : : shared_mutex & CCallbackBase : : getGsMutex ( )
{
return * gs - > mx ;
}
bool CCallbackBase : : duringBattle ( ) const
{
return getBattle ( ) ! = nullptr ;
}
void CCallbackBase : : setBattle ( const BattleInfo * B )
{
battle = B ;
}
2013-03-03 20:06:03 +03:00
boost : : optional < PlayerColor > CCallbackBase : : getPlayerID ( ) const
2012-09-20 19:55:21 +03:00
{
return player ;
}
2013-02-13 01:24:48 +03:00
ETerrainType CBattleInfoEssentials : : battleTerrainType ( ) const
2012-08-26 12:07:48 +03:00
{
2013-02-04 00:05:44 +03:00
RETURN_IF_NOT_BATTLE ( ETerrainType : : WRONG ) ;
2012-08-26 12:07:48 +03:00
return getBattle ( ) - > terrainType ;
}
2013-02-13 01:24:48 +03:00
BFieldType CBattleInfoEssentials : : battleGetBattlefieldType ( ) const
2012-08-26 12:07:48 +03:00
{
2013-02-04 00:05:44 +03:00
RETURN_IF_NOT_BATTLE ( BFieldType : : NONE ) ;
2012-08-26 12:07:48 +03:00
return getBattle ( ) - > battlefieldType ;
}
2015-12-29 04:43:33 +02:00
std : : vector < std : : shared_ptr < const CObstacleInstance > > CBattleInfoEssentials : : battleGetAllObstacles ( boost : : optional < BattlePerspective : : BattlePerspective > perspective /*= boost::none*/ ) const
2012-08-26 12:07:48 +03:00
{
2015-12-29 04:43:33 +02:00
std : : vector < std : : shared_ptr < const CObstacleInstance > > ret ;
2012-08-26 12:07:48 +03:00
RETURN_IF_NOT_BATTLE ( ret ) ;
2012-08-26 12:59:07 +03:00
2012-08-26 12:07:48 +03:00
if ( ! perspective )
{
//if no particular perspective request, use default one
perspective = battleGetMySide ( ) ;
}
else
{
2013-02-11 17:42:09 +03:00
if ( ! ! player & & * perspective ! = battleGetMySide ( ) )
2012-08-26 12:07:48 +03:00
{
2016-03-12 03:41:27 +02:00
logGlobal - > errorStream ( ) < < " Unauthorized access attempt! " ;
2012-08-26 12:07:48 +03:00
assert ( 0 ) ; //I want to notice if that happens
//perspective = battleGetMySide();
}
}
2013-06-29 16:05:48 +03:00
for ( auto oi : getBattle ( ) - > obstacles )
2012-08-26 12:07:48 +03:00
{
if ( getBattle ( ) - > battleIsObstacleVisibleForSide ( * oi , * perspective ) )
ret . push_back ( oi ) ;
}
return ret ;
}
bool CBattleInfoEssentials : : battleIsObstacleVisibleForSide ( const CObstacleInstance & coi , BattlePerspective : : BattlePerspective side ) const
{
RETURN_IF_NOT_BATTLE ( false ) ;
return side = = BattlePerspective : : ALL_KNOWING | | coi . visibleForSide ( side , battleHasNativeStack ( side ) ) ;
}
bool CBattleInfoEssentials : : battleHasNativeStack ( ui8 side ) const
{
RETURN_IF_NOT_BATTLE ( false ) ;
2013-06-29 16:05:48 +03:00
for ( const CStack * s : battleGetAllStacks ( ) )
2012-08-26 12:07:48 +03:00
{
if ( s - > attackerOwned = = ! side & & s - > getCreature ( ) - > isItNativeTerrain ( getBattle ( ) - > terrainType ) )
return true ;
}
return false ;
}
2014-05-17 12:37:12 +03:00
TStacks CBattleInfoEssentials : : battleGetAllStacks ( bool includeTurrets /*= false*/ ) const
{
2016-02-28 04:10:20 +02:00
return battleGetStacksIf ( [ = ] ( const CStack * s )
{
return ! s - > isGhost ( ) & & ( includeTurrets | | ! s - > isTurret ( ) ) ;
} ) ;
2014-05-17 12:37:12 +03:00
}
2016-02-28 04:10:20 +02:00
TStacks CBattleInfoEssentials : : battleGetStacksIf ( TStackFilter predicate ) const
2012-08-26 12:07:48 +03:00
{
TStacks ret ;
RETURN_IF_NOT_BATTLE ( ret ) ;
2016-02-01 19:03:57 +02:00
2016-02-28 04:10:20 +02:00
vstd : : copy_if ( getBattle ( ) - > stacks , std : : back_inserter ( ret ) , predicate ) ;
2016-02-01 19:03:57 +02:00
2012-08-26 12:07:48 +03:00
return ret ;
}
TStacks CBattleInfoEssentials : : battleAliveStacks ( ) const
{
2014-05-17 12:37:12 +03:00
return battleGetStacksIf ( [ ] ( const CStack * s ) {
2016-02-28 04:10:20 +02:00
return s - > isValidTarget ( false ) ;
2014-05-17 12:37:12 +03:00
} ) ;
2012-08-26 12:07:48 +03:00
}
TStacks CBattleInfoEssentials : : battleAliveStacks ( ui8 side ) const
{
2014-05-17 12:37:12 +03:00
return battleGetStacksIf ( [ = ] ( const CStack * s ) {
2016-02-28 04:10:20 +02:00
return s - > isValidTarget ( false ) & & s - > attackerOwned = = ! side ;
2014-05-17 12:37:12 +03:00
} ) ;
2012-08-26 12:07:48 +03:00
}
int CBattleInfoEssentials : : battleGetMoatDmg ( ) const
{
RETURN_IF_NOT_BATTLE ( 0 ) ;
auto town = getBattle ( ) - > town ;
if ( ! town )
return 0 ;
2013-02-09 00:17:39 +03:00
return town - > town - > moatDamage ;
2012-08-26 12:07:48 +03:00
}
const CGTownInstance * CBattleInfoEssentials : : battleGetDefendedTown ( ) const
{
RETURN_IF_NOT_BATTLE ( nullptr ) ;
2012-08-26 12:59:07 +03:00
2012-08-26 12:07:48 +03:00
2013-06-26 14:18:27 +03:00
if ( ! getBattle ( ) | | getBattle ( ) - > town = = nullptr )
return nullptr ;
2012-08-26 12:07:48 +03:00
return getBattle ( ) - > town ;
}
BattlePerspective : : BattlePerspective CBattleInfoEssentials : : battleGetMySide ( ) const
{
RETURN_IF_NOT_BATTLE ( BattlePerspective : : INVALID ) ;
2013-02-11 17:42:09 +03:00
if ( ! player )
2012-08-26 12:07:48 +03:00
return BattlePerspective : : ALL_KNOWING ;
2013-07-22 01:01:29 +03:00
if ( * player = = getBattle ( ) - > sides [ 0 ] . color )
2012-08-26 12:07:48 +03:00
return BattlePerspective : : LEFT_SIDE ;
2013-07-22 01:01:29 +03:00
if ( * player = = getBattle ( ) - > sides [ 1 ] . color )
2012-08-26 12:07:48 +03:00
return BattlePerspective : : RIGHT_SIDE ;
2016-03-12 03:41:27 +02:00
logGlobal - > errorStream ( ) < < " Cannot find player " < < * player < < " in battle! " ;
return BattlePerspective : : INVALID ;
2012-08-26 12:07:48 +03:00
}
const CStack * CBattleInfoEssentials : : battleActiveStack ( ) const
{
RETURN_IF_NOT_BATTLE ( nullptr ) ;
return battleGetStackByID ( getBattle ( ) - > activeStack ) ;
}
const CStack * CBattleInfoEssentials : : battleGetStackByID ( int ID , bool onlyAlive ) const
{
RETURN_IF_NOT_BATTLE ( nullptr ) ;
2016-02-29 03:42:15 +02:00
auto stacks = battleGetStacksIf ( [ = ] ( const CStack * s )
{
return s - > ID = = ID & & ( ! onlyAlive | | s - > alive ( ) ) ;
} ) ;
2012-08-26 12:07:48 +03:00
2016-02-29 03:42:15 +02:00
if ( stacks . empty ( ) )
return nullptr ;
else
return stacks [ 0 ] ;
2012-08-26 12:07:48 +03:00
}
bool CBattleInfoEssentials : : battleDoWeKnowAbout ( ui8 side ) const
{
RETURN_IF_NOT_BATTLE ( false ) ;
auto p = battleGetMySide ( ) ;
return p = = BattlePerspective : : ALL_KNOWING | | p = = side ;
}
si8 CBattleInfoEssentials : : battleTacticDist ( ) const
{
RETURN_IF_NOT_BATTLE ( 0 ) ;
return getBattle ( ) - > tacticDistance ;
}
si8 CBattleInfoEssentials : : battleGetTacticsSide ( ) const
{
RETURN_IF_NOT_BATTLE ( - 1 ) ;
return getBattle ( ) - > tacticsSide ;
}
const CGHeroInstance * CBattleInfoEssentials : : battleGetFightingHero ( ui8 side ) const
{
RETURN_IF_NOT_BATTLE ( nullptr ) ;
if ( side > 1 )
{
2016-03-12 03:41:27 +02:00
logGlobal - > errorStream ( ) < < " FIXME: " < < __FUNCTION__ < < " wrong argument! " ;
2012-08-26 12:07:48 +03:00
return nullptr ;
}
if ( ! battleDoWeKnowAbout ( side ) )
{
2016-03-12 03:41:27 +02:00
logGlobal - > errorStream ( ) < < " FIXME: " < < __FUNCTION__ < < " access check " ;
2012-08-26 12:07:48 +03:00
return nullptr ;
}
2013-07-22 01:01:29 +03:00
return getBattle ( ) - > sides [ side ] . hero ;
}
const CArmedInstance * CBattleInfoEssentials : : battleGetArmyObject ( ui8 side ) const
{
RETURN_IF_NOT_BATTLE ( nullptr ) ;
if ( side > 1 )
{
logGlobal - > errorStream ( ) < < " FIXME: " < < __FUNCTION__ < < " wrong argument! " ;
return nullptr ;
}
if ( ! battleDoWeKnowAbout ( side ) )
{
logGlobal - > errorStream ( ) < < " FIXME: " < < __FUNCTION__ < < " access check " ;
return nullptr ;
}
return getBattle ( ) - > sides [ side ] . armyObject ;
2012-08-26 12:07:48 +03:00
}
2013-01-20 23:29:35 +03:00
InfoAboutHero CBattleInfoEssentials : : battleGetHeroInfo ( ui8 side ) const
{
2013-07-22 01:01:29 +03:00
auto hero = getBattle ( ) - > sides [ side ] . hero ;
2013-01-20 23:29:35 +03:00
if ( ! hero )
2013-06-23 14:25:48 +03:00
{
2016-03-12 03:41:27 +02:00
logGlobal - > warnStream ( ) < < __FUNCTION__ < < " : side " < < ( int ) side < < " does not have hero! " ;
2013-06-23 14:25:48 +03:00
return InfoAboutHero ( ) ;
}
2013-01-20 23:29:35 +03:00
return InfoAboutHero ( hero , battleDoWeKnowAbout ( side ) ) ;
}
2012-08-26 12:07:48 +03:00
int CBattleInfoEssentials : : battleCastSpells ( ui8 side ) const
{
RETURN_IF_NOT_BATTLE ( - 1 ) ;
2013-07-22 01:01:29 +03:00
return getBattle ( ) - > sides [ side ] . castSpellsCount ;
2012-08-26 12:07:48 +03:00
}
2015-09-16 17:28:14 +02:00
const IBonusBearer * CBattleInfoEssentials : : getBattleNode ( ) const
{
return getBattle ( ) ;
}
2013-03-03 20:06:03 +03:00
ESpellCastProblem : : ESpellCastProblem CBattleInfoCallback : : battleCanCastSpell ( PlayerColor player , ECastingMode : : ECastingMode mode ) const
2012-08-26 12:07:48 +03:00
{
RETURN_IF_NOT_BATTLE ( ESpellCastProblem : : INVALID ) ;
const ui8 side = playerToSide ( player ) ;
if ( ! battleDoWeKnowAbout ( side ) )
{
2016-03-12 03:41:27 +02:00
logGlobal - > warnStream ( ) < < " You can't check if enemy can cast given spell! " ;
2012-08-26 12:07:48 +03:00
return ESpellCastProblem : : INVALID ;
}
switch ( mode )
{
case ECastingMode : : HERO_CASTING :
{
if ( battleTacticDist ( ) )
return ESpellCastProblem : : ONGOING_TACTIC_PHASE ;
if ( battleCastSpells ( side ) > 0 )
return ESpellCastProblem : : ALREADY_CASTED_THIS_TURN ;
auto hero = battleGetFightingHero ( side ) ;
if ( ! hero )
return ESpellCastProblem : : NO_HERO_TO_CAST_SPELL ;
if ( ! hero - > getArt ( ArtifactPosition : : SPELLBOOK ) )
return ESpellCastProblem : : NO_SPELLBOOK ;
if ( hero - > hasBonusOfType ( Bonus : : BLOCK_ALL_MAGIC ) )
return ESpellCastProblem : : MAGIC_IS_BLOCKED ;
}
break ;
default :
break ;
}
return ESpellCastProblem : : OK ;
}
2013-03-03 20:06:03 +03:00
bool CBattleInfoEssentials : : battleCanFlee ( PlayerColor player ) const
2012-08-26 12:07:48 +03:00
{
RETURN_IF_NOT_BATTLE ( false ) ;
ui8 mySide = playerToSide ( player ) ;
2012-08-29 18:27:24 +03:00
const CGHeroInstance * myHero = battleGetFightingHero ( mySide ) ;
2012-08-26 12:07:48 +03:00
//current player have no hero
2012-08-26 12:59:07 +03:00
if ( ! myHero )
2012-08-26 12:07:48 +03:00
return false ;
2012-08-29 18:27:24 +03:00
//eg. one of heroes is wearing shakles of war
if ( myHero - > hasBonusOfType ( Bonus : : BATTLE_NO_FLEEING ) )
2012-08-26 12:07:48 +03:00
return false ;
//we are besieged defender
if ( mySide = = BattleSide : : DEFENDER & & battleGetSiegeLevel ( ) )
{
auto town = battleGetDefendedTown ( ) ;
2013-02-11 02:24:57 +03:00
if ( ! town - > hasBuilt ( BuildingID : : ESCAPE_TUNNEL , ETownType : : STRONGHOLD ) )
2012-08-26 12:07:48 +03:00
return false ;
}
2012-08-26 12:59:07 +03:00
2012-08-26 12:07:48 +03:00
return true ;
}
2013-03-03 20:06:03 +03:00
ui8 CBattleInfoEssentials : : playerToSide ( PlayerColor player ) const
2012-08-26 12:07:48 +03:00
{
RETURN_IF_NOT_BATTLE ( - 1 ) ;
2013-07-22 01:01:29 +03:00
int ret = vstd : : find_pos_if ( getBattle ( ) - > sides , [ = ] ( const SideInBattle & side ) { return side . color = = player ; } ) ;
2012-08-26 12:07:48 +03:00
if ( ret < 0 )
2016-03-12 03:41:27 +02:00
logGlobal - > warnStream ( ) < < " Cannot find side for player " < < player ;
2012-08-26 12:07:48 +03:00
return ret ;
}
ui8 CBattleInfoEssentials : : battleGetSiegeLevel ( ) const
{
RETURN_IF_NOT_BATTLE ( 0 ) ;
2014-06-25 17:11:07 +03:00
return getBattle ( ) - > town ? getBattle ( ) - > town - > fortLevel ( ) : CGTownInstance : : NONE ;
2012-08-26 12:07:48 +03:00
}
2013-03-03 20:06:03 +03:00
bool CBattleInfoEssentials : : battleCanSurrender ( PlayerColor player ) const
2012-08-26 12:07:48 +03:00
{
RETURN_IF_NOT_BATTLE ( false ) ;
//conditions like for fleeing + enemy must have a hero
2012-08-30 19:01:19 +03:00
return battleCanFlee ( player ) & & battleHasHero ( ! playerToSide ( player ) ) ;
2012-08-26 12:07:48 +03:00
}
bool CBattleInfoEssentials : : battleHasHero ( ui8 side ) const
{
RETURN_IF_NOT_BATTLE ( false ) ;
2012-08-26 12:59:07 +03:00
assert ( side < 2 ) ;
2013-07-22 01:01:29 +03:00
return getBattle ( ) - > sides [ side ] . hero ;
2012-08-26 12:07:48 +03:00
}
2013-08-06 18:25:51 +03:00
si8 CBattleInfoEssentials : : battleGetWallState ( int partOfWall ) const
2012-08-26 12:07:48 +03:00
{
RETURN_IF_NOT_BATTLE ( 0 ) ;
2014-06-25 17:11:07 +03:00
if ( getBattle ( ) - > town = = nullptr | | getBattle ( ) - > town - > fortLevel ( ) = = CGTownInstance : : NONE )
2013-08-06 14:20:28 +03:00
return EWallState : : NONE ;
2012-08-26 12:07:48 +03:00
2013-12-08 20:54:13 +03:00
assert ( partOfWall > = 0 & & partOfWall < EWallPart : : PARTS_COUNT ) ;
2012-08-26 12:07:48 +03:00
return getBattle ( ) - > si . wallState [ partOfWall ] ;
}
2016-02-13 16:40:31 +02:00
EGateState CBattleInfoEssentials : : battleGetGateState ( ) const
2016-01-29 21:43:35 +02:00
{
2016-02-13 16:40:31 +02:00
RETURN_IF_NOT_BATTLE ( EGateState : : NONE ) ;
2016-01-29 21:43:35 +02:00
if ( getBattle ( ) - > town = = nullptr | | getBattle ( ) - > town - > fortLevel ( ) = = CGTownInstance : : NONE )
2016-02-13 16:40:31 +02:00
return EGateState : : NONE ;
2016-01-29 21:43:35 +02:00
2016-02-13 16:40:31 +02:00
return getBattle ( ) - > si . gateState ;
2016-01-29 21:43:35 +02:00
}
2012-08-26 12:07:48 +03:00
si8 CBattleInfoCallback : : battleHasWallPenalty ( const CStack * stack , BattleHex destHex ) const
2012-09-20 19:55:21 +03:00
{
return battleHasWallPenalty ( stack , stack - > position , destHex ) ;
}
si8 CBattleInfoCallback : : battleHasWallPenalty ( const IBonusBearer * bonusBearer , BattleHex shooterPosition , BattleHex destHex ) const
2012-08-26 12:07:48 +03:00
{
RETURN_IF_NOT_BATTLE ( false ) ;
2012-09-20 19:55:21 +03:00
if ( ! battleGetSiegeLevel ( ) | | bonusBearer - > hasBonusOfType ( Bonus : : NO_WALL_PENALTY ) )
2012-08-26 12:07:48 +03:00
return false ;
2012-09-20 19:55:21 +03:00
const int wallInStackLine = lineToWallHex ( shooterPosition . getY ( ) ) ;
2012-08-26 12:07:48 +03:00
const int wallInDestLine = lineToWallHex ( destHex . getY ( ) ) ;
2012-09-20 19:55:21 +03:00
const bool stackLeft = shooterPosition < wallInStackLine ;
2012-08-26 12:07:48 +03:00
const bool destRight = destHex > wallInDestLine ;
if ( stackLeft & & destRight ) //shooting from outside to inside
{
2012-09-20 19:55:21 +03:00
int row = ( shooterPosition + destHex ) / ( 2 * GameConstants : : BFIELD_WIDTH ) ;
if ( shooterPosition > destHex & & ( ( destHex % GameConstants : : BFIELD_WIDTH - shooterPosition % GameConstants : : BFIELD_WIDTH ) < 2 ) ) //shooting up high
2012-08-26 12:07:48 +03:00
row - = 2 ;
const int wallPos = lineToWallHex ( row ) ;
2013-12-08 20:54:13 +03:00
if ( ! isWallPartPotentiallyAttackable ( battleHexToWallPart ( wallPos ) ) ) return true ;
2012-08-26 12:07:48 +03:00
}
return false ;
}
si8 CBattleInfoCallback : : battleCanTeleportTo ( const CStack * stack , BattleHex destHex , int telportLevel ) const
{
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 ;
if ( battleGetSiegeLevel ( ) & & telportLevel < 2 ) //check for wall
return sameSideOfWall ( stack - > position , destHex ) ;
return true ;
}
std : : set < BattleHex > CBattleInfoCallback : : battleGetAttackedHexes ( const CStack * attacker , BattleHex destinationTile , BattleHex attackerPos /*= BattleHex::INVALID*/ ) const
{
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 ;
}
2013-02-14 02:55:42 +03:00
SpellID CBattleInfoCallback : : battleGetRandomStackSpell ( const CStack * stack , ERandomSpell mode ) const
2012-08-26 12:07:48 +03:00
{
switch ( mode )
{
case RANDOM_GENIE :
return getRandomBeneficialSpell ( stack ) ; //target
break ;
case RANDOM_AIMED :
return getRandomCastedSpell ( stack ) ; //caster
break ;
default :
2016-03-12 03:41:27 +02:00
logGlobal - > errorStream ( ) < < " Incorrect mode of battleGetRandomSpell ( " < < 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 ) ;
2013-09-27 22:42:17 +03:00
for ( auto s : battleGetAllStacks ( true ) )
2013-07-27 22:01:31 +03:00
if ( vstd : : contains ( s - > getHexes ( ) , pos ) & & ( ! onlyAlive | | s - > alive ( ) ) )
return s ;
2012-08-26 12:07:48 +03:00
return nullptr ;
}
void CBattleInfoCallback : : battleGetStackQueue ( std : : vector < const CStack * > & out , const int howMany , const int turn /*= 0*/ , int lastMoved /*= -1*/ ) const
{
RETURN_IF_NOT_BATTLE ( ) ;
//let's define a huge lambda
auto takeStack = [ & ] ( std : : vector < const CStack * > & st ) - > const CStack *
{
2013-06-26 14:18:27 +03:00
const CStack * ret = nullptr ;
2012-08-26 12:07:48 +03:00
unsigned i , //fastest stack
j = 0 ; //fastest stack of the other side
for ( i = 0 ; i < st . size ( ) ; i + + )
if ( st [ i ] )
break ;
//no stacks left
if ( i = = st . size ( ) )
return nullptr ;
2013-06-26 14:18:27 +03:00
const CStack * fastest = st [ i ] , * other = nullptr ;
2012-08-26 12:07:48 +03:00
int bestSpeed = fastest - > Speed ( turn ) ;
2013-11-06 16:42:58 +03:00
//FIXME: comparison between bool and integer. Logic does not makes sense either
2012-08-26 12:07:48 +03:00
if ( fastest - > attackerOwned ! = lastMoved )
{
ret = fastest ;
}
else
{
for ( j = i + 1 ; j < st . size ( ) ; j + + )
{
if ( ! st [ j ] ) continue ;
if ( st [ j ] - > attackerOwned ! = lastMoved | | st [ j ] - > Speed ( turn ) ! = bestSpeed )
break ;
}
if ( j > = st . size ( ) )
{
ret = fastest ;
}
else
{
other = st [ j ] ;
if ( other - > Speed ( turn ) ! = bestSpeed )
ret = fastest ;
else
ret = other ;
}
}
assert ( ret ) ;
if ( ret = = fastest )
2013-06-26 14:18:27 +03:00
st [ i ] = nullptr ;
2012-08-26 12:07:48 +03:00
else
2013-06-26 14:18:27 +03:00
st [ j ] = nullptr ;
2012-08-26 12:07:48 +03:00
lastMoved = ret - > attackerOwned ;
return ret ;
} ;
//We'll split creatures with remaining movement to 4 buckets
2012-08-26 12:59:07 +03:00
// [0] - turrets/catapult,
// [1] - normal (unmoved) creatures, other war machines,
// [2] - waited cres that had morale,
2012-08-26 12:07:48 +03:00
// [3] - rest of waited cres
2012-08-26 12:59:07 +03:00
std : : vector < const CStack * > phase [ 4 ] ;
2012-08-26 12:07:48 +03:00
int toMove = 0 ; //how many stacks still has move
const CStack * active = battleActiveStack ( ) ;
//active stack hasn't taken any action yet - must be placed at the beginning of queue, no matter what
2012-09-20 19:55:21 +03:00
if ( ! turn & & active & & active - > willMove ( ) & & ! active - > waited ( ) )
2012-08-26 12:07:48 +03:00
{
out . push_back ( active ) ;
if ( out . size ( ) = = howMany )
return ;
}
2013-09-27 22:42:17 +03:00
auto allStacks = battleGetAllStacks ( true ) ;
2012-08-31 19:33:30 +03:00
if ( ! vstd : : contains_if ( allStacks , [ ] ( const CStack * stack ) { return stack - > willMove ( 100000 ) ; } ) ) //little evil, but 100000 should be enough for all effects to disappear
{
//No stack will be able to move, battle is over.
out . clear ( ) ;
return ;
}
2013-09-27 22:42:17 +03:00
for ( auto s : battleGetAllStacks ( true ) )
2012-08-26 12:07:48 +03:00
{
if ( ( turn < = 0 & & ! s - > willMove ( ) ) //we are considering current round and stack won't move
| | ( turn > 0 & & ! s - > canMove ( turn ) ) //stack won't be able to move in later rounds
| | ( turn < = 0 & & s = = active & & out . size ( ) & & s = = out . front ( ) ) ) //it's active stack already added at the beginning of queue
{
continue ;
}
int p = - 1 ; //in which phase this tack will move?
2012-09-20 19:55:21 +03:00
if ( turn < = 0 & & s - > waited ( ) ) //consider waiting state only for ongoing round
2012-08-26 12:07:48 +03:00
{
if ( vstd : : contains ( s - > state , EBattleStackState : : HAD_MORALE ) )
p = 2 ;
else
p = 3 ;
}
2013-02-07 20:34:50 +03:00
else if ( s - > getCreature ( ) - > idNumber = = CreatureID : : CATAPULT | | s - > getCreature ( ) - > idNumber = = CreatureID : : ARROW_TOWERS ) //catapult and turrets are first
2012-08-26 12:07:48 +03:00
{
p = 0 ;
}
else
{
p = 1 ;
}
phase [ p ] . push_back ( s ) ;
toMove + + ;
}
for ( int i = 0 ; i < 4 ; i + + )
boost : : sort ( phase [ i ] , CMP_stack ( i , turn > 0 ? turn : 0 ) ) ;
for ( size_t i = 0 ; i < phase [ 0 ] . size ( ) & & i < howMany ; i + + )
out . push_back ( phase [ 0 ] [ i ] ) ;
if ( out . size ( ) = = howMany )
return ;
if ( lastMoved = = - 1 )
{
if ( active )
{
2013-11-06 16:42:58 +03:00
//FIXME: both branches contain same code!!!
2012-08-26 12:07:48 +03:00
if ( out . size ( ) & & out . front ( ) = = active )
lastMoved = active - > attackerOwned ;
else
lastMoved = active - > attackerOwned ;
}
else
{
lastMoved = 0 ;
}
}
int pi = 1 ;
while ( out . size ( ) < howMany )
{
const CStack * hlp = takeStack ( phase [ pi ] ) ;
if ( ! hlp )
{
pi + + ;
if ( pi > 3 )
{
//if(turn != 2)
battleGetStackQueue ( out , howMany , turn + 1 , lastMoved ) ;
return ;
}
}
else
{
out . push_back ( hlp ) ;
}
}
}
void CBattleInfoCallback : : battleGetStackCountOutsideHexes ( bool * ac ) const
{
RETURN_IF_NOT_BATTLE ( ) ;
auto accessibility = getAccesibility ( ) ;
for ( int i = 0 ; i < accessibility . size ( ) ; i + + )
ac [ i ] = ( accessibility [ i ] = = EAccessibility : : ACCESSIBLE ) ;
}
std : : vector < BattleHex > CBattleInfoCallback : : battleGetAvailableHexes ( const CStack * stack , bool addOccupiable , std : : vector < BattleHex > * attackable ) const
{
std : : vector < BattleHex > ret ;
RETURN_IF_NOT_BATTLE ( ret ) ;
if ( ! stack - > position . isValid ( ) ) //turrets
return ret ;
auto reachability = getReachability ( stack ) ;
for ( int i = 0 ; i < GameConstants : : BFIELD_SIZE ; + + i )
{
// If obstacles or other stacks makes movement impossible, it can't be helped.
if ( ! reachability . isReachable ( i ) )
continue ;
if ( battleTacticDist ( ) & & battleGetTacticsSide ( ) = = ! stack - > attackerOwned )
{
//Stack has to perform tactic-phase movement -> can enter any reachable tile within given range
if ( ! isInTacticRange ( i ) )
continue ;
}
else
{
//Not tactics phase -> destination must be reachable and within stack range.
if ( reachability . distances [ i ] > stack - > Speed ( 0 , true ) )
continue ;
}
ret . push_back ( i ) ;
if ( addOccupiable & & stack - > doubleWide ( ) )
{
//If two-hex stack can stand on hex i then obviously it can occupy its second hex from that position
ret . push_back ( stack - > occupiedHex ( i ) ) ;
}
}
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 )
{ return BattleHex : : mutualPosition ( hex , availableHex ) > = 0 ; } ) ;
2012-08-26 12:59:07 +03:00
2012-08-26 12:07:48 +03:00
return availableNeighbor ! = ret . end ( ) ;
} ;
2013-06-29 16:05:48 +03:00
for ( const CStack * otherSt : battleAliveStacks ( stack - > attackerOwned ) )
2012-08-26 12:07:48 +03:00
{
if ( ! otherSt - > isValidTarget ( false ) )
continue ;
std : : vector < BattleHex > occupied = otherSt - > getHexes ( ) ;
if ( battleCanShoot ( stack , otherSt - > position ) )
{
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
2015-04-09 21:49:11 +02:00
if ( stack - > owner = = target - > owner )
return false ;
2016-02-01 19:03:57 +02:00
2015-04-09 21:49:11 +02:00
auto & id = stack - > getCreature ( ) - > idNumber ;
if ( id = = CreatureID : : FIRST_AID_TENT | | id = = CreatureID : : CATAPULT )
return false ;
2016-02-01 19:03:57 +02:00
2015-04-09 21:49:11 +02:00
if ( ! target - > alive ( ) )
return false ;
2016-02-01 19:03:57 +02:00
2015-04-09 21:49:11 +02:00
return true ;
}
2012-08-26 12:07:48 +03:00
bool CBattleInfoCallback : : battleCanShoot ( const CStack * stack , BattleHex dest ) const
{
RETURN_IF_NOT_BATTLE ( false ) ;
if ( battleTacticDist ( ) ) //no shooting during tactics
2012-08-26 12:59:07 +03:00
return false ;
2012-08-26 12:07:48 +03:00
const CStack * dst = battleGetStackByPos ( dest ) ;
if ( ! stack | | ! dst )
return false ;
if ( stack - > hasBonusOfType ( Bonus : : FORGETFULL ) ) //forgetfulness
return false ;
2013-02-07 20:34:50 +03:00
if ( stack - > getCreature ( ) - > idNumber = = CreatureID : : CATAPULT & & dst ) //catapult cannot attack creatures
2012-08-26 12:07:48 +03:00
return false ;
//const CGHeroInstance * stackHero = battleGetOwner(stack);
if ( stack - > hasBonusOfType ( Bonus : : SHOOTER ) //it's shooter
& & stack - > owner ! = dst - > owner
& & dst - > alive ( )
& & ( ! battleIsStackBlocked ( stack ) | | stack - > hasBonusOfType ( Bonus : : FREE_SHOOTING ) )
& & stack - > shots
)
return true ;
return false ;
}
2012-08-26 12:59:07 +03:00
TDmgRange CBattleInfoCallback : : calculateDmgRange ( const CStack * attacker , const CStack * defender , bool shooting ,
2013-05-04 16:14:23 +03:00
ui8 charge , bool lucky , bool unlucky , bool deathBlow , bool ballistaDoubleDmg ) const
2012-08-26 12:07:48 +03:00
{
2013-05-04 16:14:23 +03:00
return calculateDmgRange ( attacker , defender , attacker - > count , shooting , charge , lucky , unlucky , deathBlow , ballistaDoubleDmg ) ;
2012-08-26 12:07:48 +03:00
}
2012-09-20 19:55:21 +03:00
TDmgRange CBattleInfoCallback : : calculateDmgRange ( const BattleAttackInfo & info ) const
2012-08-26 12:07:48 +03:00
{
2013-02-06 11:02:46 +03:00
auto battleBonusValue = [ & ] ( const IBonusBearer * bearer , CSelector selector ) - > int
{
2013-07-02 15:08:30 +03:00
auto noLimit = Selector : : effectRange ( Bonus : : NO_LIMIT ) ;
2014-03-07 16:21:09 +03:00
auto limitMatches = info . shooting
? Selector : : effectRange ( Bonus : : ONLY_DISTANCE_FIGHT )
2013-07-02 15:08:30 +03:00
: Selector : : effectRange ( Bonus : : ONLY_MELEE_FIGHT ) ;
//any regular bonuses or just ones for melee/ranged
return bearer - > getBonuses ( selector , noLimit . Or ( limitMatches ) ) - > totalValue ( ) ;
2013-02-06 11:02:46 +03:00
} ;
2012-08-26 12:07:48 +03:00
double additiveBonus = 1.0 , multBonus = 1.0 ,
2013-02-06 11:02:46 +03:00
minDmg = info . attackerBonuses - > getMinDamage ( ) * info . attackerCount , //TODO: ONLY_MELEE_FIGHT / ONLY_DISTANCE_FIGHT
2012-09-20 19:55:21 +03:00
maxDmg = info . attackerBonuses - > getMaxDamage ( ) * info . attackerCount ;
2012-08-26 12:07:48 +03:00
2013-01-15 17:20:48 +03:00
const CCreature * attackerType = info . attacker - > getCreature ( ) ,
2012-09-20 19:55:21 +03:00
* defenderType = info . defender - > getCreature ( ) ;
2013-02-07 20:34:50 +03:00
if ( attackerType - > idNumber = = CreatureID : : ARROW_TOWERS )
2012-08-26 12:07:48 +03:00
{
2013-07-22 19:23:23 +03:00
SiegeStuffThatShouldBeMovedToHandlers : : retreiveTurretDamageRange ( battleGetDefendedTown ( ) , info . attacker , minDmg , maxDmg ) ;
2012-08-26 12:07:48 +03:00
}
2013-02-07 20:34:50 +03:00
if ( info . attackerBonuses - > hasBonusOfType ( Bonus : : SIEGE_WEAPON ) & & attackerType - > idNumber ! = CreatureID : : ARROW_TOWERS ) //any siege weapon, but only ballista can attack (second condition - not arrow turret)
2012-08-26 12:07:48 +03:00
{ //minDmg and maxDmg are multiplied by hero attack + 1
2012-09-20 19:55:21 +03:00
auto retreiveHeroPrimSkill = [ & ] ( int skill ) - > int
2012-08-26 12:07:48 +03:00
{
2013-07-02 15:08:30 +03:00
const Bonus * b = info . attackerBonuses - > getBonus ( Selector : : sourceTypeSel ( Bonus : : HERO_BASE_SKILL ) . And ( Selector : : typeSubtype ( Bonus : : PRIMARY_SKILL , skill ) ) ) ;
2012-08-26 12:07:48 +03:00
return b ? b - > val : 0 ; //if there is no hero or no info on his primary skill, return 0
} ;
2012-09-20 19:55:21 +03:00
minDmg * = retreiveHeroPrimSkill ( PrimarySkill : : ATTACK ) + 1 ;
maxDmg * = retreiveHeroPrimSkill ( PrimarySkill : : ATTACK ) + 1 ;
2012-08-26 12:07:48 +03:00
}
int attackDefenceDifference = 0 ;
2013-02-06 11:02:46 +03:00
double multAttackReduction = ( 100 - battleBonusValue ( info . attackerBonuses , Selector : : type ( Bonus : : GENERAL_ATTACK_REDUCTION ) ) ) / 100.0 ;
attackDefenceDifference + = battleBonusValue ( info . attackerBonuses , Selector : : typeSubtype ( Bonus : : PRIMARY_SKILL , PrimarySkill : : ATTACK ) ) * multAttackReduction ;
2012-08-26 12:07:48 +03:00
2013-02-06 11:02:46 +03:00
double multDefenceReduction = ( 100 - battleBonusValue ( info . attackerBonuses , Selector : : type ( Bonus : : ENEMY_DEFENCE_REDUCTION ) ) ) / 100.0 ;
attackDefenceDifference - = info . defenderBonuses - > Defense ( ) * multDefenceReduction ;
2012-08-26 12:07:48 +03:00
2013-02-11 02:24:57 +03:00
if ( const Bonus * slayerEffect = info . attackerBonuses - > getEffect ( SpellID : : SLAYER ) ) //slayer handling //TODO: apply only ONLY_MELEE_FIGHT / DISTANCE_FIGHT?
2012-08-26 12:07:48 +03:00
{
std : : vector < int > affectedIds ;
2013-02-13 22:35:43 +03:00
int spLevel = slayerEffect - > val ;
2012-08-26 12:07:48 +03:00
for ( int g = 0 ; g < VLC - > creh - > creatures . size ( ) ; + + g )
{
2013-06-29 16:05:48 +03:00
for ( const Bonus * b : VLC - > creh - > creatures [ g ] - > getBonusList ( ) )
2012-08-26 12:07:48 +03:00
{
if ( ( b - > type = = Bonus : : KING3 & & spLevel > = 3 ) | | //expert
( b - > type = = Bonus : : KING2 & & spLevel > = 2 ) | | //adv +
( b - > type = = Bonus : : KING1 & & spLevel > = 0 ) ) //none or basic +
{
affectedIds . push_back ( g ) ;
break ;
}
}
}
2013-06-29 16:05:48 +03:00
for ( auto & affectedId : affectedIds )
2012-08-26 12:07:48 +03:00
{
2013-06-29 16:05:48 +03:00
if ( defenderType - > idNumber = = affectedId )
2012-08-26 12:07:48 +03:00
{
2014-03-07 16:21:09 +03:00
attackDefenceDifference + = SpellID ( SpellID : : SLAYER ) . toSpell ( ) - > getPower ( spLevel ) ;
2012-08-26 12:07:48 +03:00
break ;
}
}
}
//bonus from attack/defense skills
if ( attackDefenceDifference < 0 ) //decreasing dmg
{
const double dec = std : : min ( 0.025 * ( - attackDefenceDifference ) , 0.7 ) ;
multBonus * = 1.0 - dec ;
}
else //increasing dmg
{
const double inc = std : : min ( 0.05 * attackDefenceDifference , 4.0 ) ;
additiveBonus + = inc ;
}
//applying jousting bonus
2012-09-20 19:55:21 +03:00
if ( info . attackerBonuses - > hasBonusOfType ( Bonus : : JOUSTING ) & & ! info . defenderBonuses - > hasBonusOfType ( Bonus : : CHARGE_IMMUNITY ) )
additiveBonus + = info . chargedFields * 0.05 ;
2012-08-26 12:07:48 +03:00
//handling secondary abilities and artifacts giving premies to them
2012-09-20 19:55:21 +03:00
if ( info . shooting )
2013-02-04 22:43:16 +03:00
additiveBonus + = info . attackerBonuses - > valOfBonuses ( Bonus : : SECONDARY_SKILL_PREMY , SecondarySkill : : ARCHERY ) / 100.0 ;
2012-08-26 12:07:48 +03:00
else
2013-02-04 22:43:16 +03:00
additiveBonus + = info . attackerBonuses - > valOfBonuses ( Bonus : : SECONDARY_SKILL_PREMY , SecondarySkill : : OFFENCE ) / 100.0 ;
2012-08-26 12:07:48 +03:00
2012-09-20 19:55:21 +03:00
if ( info . defenderBonuses )
2013-02-04 22:43:16 +03:00
multBonus * = ( std : : max ( 0 , 100 - info . defenderBonuses - > valOfBonuses ( Bonus : : SECONDARY_SKILL_PREMY , SecondarySkill : : ARMORER ) ) ) / 100.0 ;
2012-08-26 12:07:48 +03:00
//handling hate effect
2013-02-11 02:24:57 +03:00
additiveBonus + = info . attackerBonuses - > valOfBonuses ( Bonus : : HATE , defenderType - > idNumber . toEnum ( ) ) / 100. ;
2012-08-26 12:07:48 +03:00
//luck bonus
2012-09-20 19:55:21 +03:00
if ( info . luckyHit )
2012-08-26 12:07:48 +03:00
{
additiveBonus + = 1.0 ;
}
2013-05-04 16:14:23 +03:00
//unlucky hit, used only if negative luck is enabled
if ( info . unluckyHit )
{
additiveBonus - = 0.5 ; // FIXME: how bad (and luck in general) should work with following bonuses?
}
2012-08-26 12:07:48 +03:00
//ballista double dmg
2012-09-20 19:55:21 +03:00
if ( info . ballistaDoubleDamage )
2012-08-26 12:07:48 +03:00
{
additiveBonus + = 1.0 ;
}
2012-09-20 19:55:21 +03:00
if ( info . deathBlow ) //Dread Knight and many WoGified creatures
2012-08-26 12:07:48 +03:00
{
additiveBonus + = 1.0 ;
}
//handling spell effects
2013-02-06 11:02:46 +03:00
if ( ! info . shooting ) //eg. shield
2012-08-26 12:07:48 +03:00
{
2013-01-29 09:47:20 +03:00
multBonus * = ( 100 - info . defenderBonuses - > valOfBonuses ( Bonus : : GENERAL_DAMAGE_REDUCTION , 0 ) ) / 100.0 ;
2012-08-26 12:07:48 +03:00
}
2013-02-06 11:02:46 +03:00
else if ( info . shooting ) //eg. air shield
2012-08-26 12:07:48 +03:00
{
2013-01-29 09:47:20 +03:00
multBonus * = ( 100 - info . defenderBonuses - > valOfBonuses ( Bonus : : GENERAL_DAMAGE_REDUCTION , 1 ) ) / 100.0 ;
2012-08-26 12:07:48 +03:00
}
2013-02-06 11:02:46 +03:00
TBonusListPtr curseEffects = info . attackerBonuses - > getBonuses ( Selector : : type ( Bonus : : ALWAYS_MINIMUM_DAMAGE ) ) ;
TBonusListPtr blessEffects = info . attackerBonuses - > getBonuses ( Selector : : type ( Bonus : : ALWAYS_MAXIMUM_DAMAGE ) ) ;
2012-08-26 12:07:48 +03:00
int curseBlessAdditiveModifier = blessEffects - > totalValue ( ) - curseEffects - > totalValue ( ) ;
double curseMultiplicativePenalty = curseEffects - > size ( ) ? ( * std : : max_element ( curseEffects - > begin ( ) , curseEffects - > end ( ) , & Bonus : : compareByAdditionalInfo ) ) - > additionalInfo : 0 ;
if ( curseMultiplicativePenalty ) //curse handling (partial, the rest is below)
{
multBonus * = 1.0 - curseMultiplicativePenalty / 100 ;
}
auto isAdvancedAirShield = [ ] ( const Bonus * bonus )
{
2012-08-26 12:59:07 +03:00
return bonus - > source = = Bonus : : SPELL_EFFECT
2013-02-11 02:24:57 +03:00
& & bonus - > sid = = SpellID : : AIR_SHIELD
2012-08-26 12:07:48 +03:00
& & bonus - > val > = SecSkillLevel : : ADVANCED ;
} ;
//wall / distance penalty + advanced air shield
2012-09-20 19:55:21 +03:00
const bool distPenalty = ! info . attackerBonuses - > hasBonusOfType ( Bonus : : NO_DISTANCE_PENALTY ) & & battleHasDistancePenalty ( info . attackerBonuses , info . attackerPosition , info . defenderPosition ) ;
const bool obstaclePenalty = battleHasWallPenalty ( info . attackerBonuses , info . attackerPosition , info . defenderPosition ) ;
2012-08-26 12:07:48 +03:00
2016-02-01 19:03:57 +02:00
if ( info . shooting )
2012-08-26 12:07:48 +03:00
{
2012-09-20 19:55:21 +03:00
if ( distPenalty | | info . defenderBonuses - > hasBonus ( isAdvancedAirShield ) )
2012-08-26 12:07:48 +03:00
{
multBonus * = 0.5 ;
}
if ( obstaclePenalty )
{
multBonus * = 0.5 ; //cumulative
}
}
2012-09-20 19:55:21 +03:00
if ( ! info . shooting & & info . attackerBonuses - > hasBonusOfType ( Bonus : : SHOOTER ) & & ! info . attackerBonuses - > hasBonusOfType ( Bonus : : NO_MELEE_PENALTY ) )
2012-08-26 12:07:48 +03:00
{
multBonus * = 0.5 ;
}
2016-02-01 19:03:57 +02:00
// psychic elementals versus mind immune units 50%
if ( attackerType - > idNumber = = CreatureID : : PSYCHIC_ELEMENTAL
& & info . defenderBonuses - > hasBonusOfType ( Bonus : : MIND_IMMUNITY ) )
{
multBonus * = 0.5 ;
}
2013-06-17 18:45:55 +03:00
// TODO attack on petrified unit 50%
// blinded unit retaliates
2012-08-26 12:07:48 +03:00
minDmg * = additiveBonus * multBonus ;
maxDmg * = additiveBonus * multBonus ;
TDmgRange returnedVal ;
if ( curseEffects - > size ( ) ) //curse handling (rest)
{
minDmg + = curseBlessAdditiveModifier ;
returnedVal = std : : make_pair ( int ( minDmg ) , int ( minDmg ) ) ;
}
else if ( blessEffects - > size ( ) ) //bless handling
{
maxDmg + = curseBlessAdditiveModifier ;
returnedVal = std : : make_pair ( int ( maxDmg ) , int ( maxDmg ) ) ;
}
else
{
returnedVal = std : : make_pair ( int ( minDmg ) , int ( maxDmg ) ) ;
}
//damage cannot be less than 1
vstd : : amax ( returnedVal . first , 1 ) ;
vstd : : amax ( returnedVal . second , 1 ) ;
return returnedVal ;
}
2012-09-20 19:55:21 +03:00
TDmgRange CBattleInfoCallback : : calculateDmgRange ( const CStack * attacker , const CStack * defender , TQuantity attackerCount ,
2013-05-04 16:14:23 +03:00
bool shooting , ui8 charge , bool lucky , bool unlucky , bool deathBlow , bool ballistaDoubleDmg ) const
2012-09-20 19:55:21 +03:00
{
BattleAttackInfo bai ( attacker , defender , shooting ) ;
bai . attackerCount = attackerCount ;
bai . chargedFields = charge ;
bai . luckyHit = lucky ;
2013-05-04 16:14:23 +03:00
bai . unluckyHit = unlucky ;
2012-09-20 19:55:21 +03:00
bai . deathBlow = deathBlow ;
bai . ballistaDoubleDamage = ballistaDoubleDmg ;
return calculateDmgRange ( bai ) ;
}
2012-08-26 12:07:48 +03:00
TDmgRange CBattleInfoCallback : : battleEstimateDamage ( const CStack * attacker , const CStack * defender , TDmgRange * retaliationDmg ) const
{
RETURN_IF_NOT_BATTLE ( std : : make_pair ( 0 , 0 ) ) ;
const bool shooting = battleCanShoot ( attacker , defender - > position ) ;
2012-09-20 19:55:21 +03:00
const BattleAttackInfo bai ( attacker , defender , shooting ) ;
return battleEstimateDamage ( bai , retaliationDmg ) ;
}
2013-06-26 14:18:27 +03:00
std : : pair < ui32 , ui32 > CBattleInfoCallback : : battleEstimateDamage ( const BattleAttackInfo & bai , std : : pair < ui32 , ui32 > * retaliationDmg /*= nullptr*/ ) const
2012-09-20 19:55:21 +03:00
{
RETURN_IF_NOT_BATTLE ( std : : make_pair ( 0 , 0 ) ) ;
2012-09-21 00:28:18 +03:00
//const bool shooting = battleCanShoot(bai.attacker, bai.defenderPosition); //TODO handle bonus bearer
2012-08-26 12:59:07 +03:00
//const ui8 mySide = !attacker->attackerOwned;
2012-09-20 19:55:21 +03:00
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
{
retaliationDmg - > first = retaliationDmg - > second = 0 ;
}
else
{
ui32 TDmgRange : : * pairElems [ ] = { & TDmgRange : : first , & TDmgRange : : second } ;
for ( int i = 0 ; i < 2 ; + + i )
{
BattleStackAttacked bsa ;
bsa . damageAmount = ret . * pairElems [ i ] ;
2014-03-17 22:51:07 +03:00
bai . defender - > prepareAttacked ( bsa , gs - > getRandomGenerator ( ) , bai . defenderCount ) ;
2012-09-20 19:55:21 +03:00
auto retaliationAttack = bai . reverse ( ) ;
retaliationAttack . attackerCount = bsa . newAmount ;
retaliationDmg - > * pairElems [ ! i ] = calculateDmgRange ( retaliationAttack ) . * pairElems [ ! i ] ;
2012-08-26 12:07:48 +03:00
}
}
}
return ret ;
}
2015-12-29 04:43:33 +02:00
std : : shared_ptr < const CObstacleInstance > CBattleInfoCallback : : battleGetObstacleOnPos ( BattleHex tile , bool onlyBlocking /*= true*/ ) const
2012-08-26 12:07:48 +03:00
{
2015-12-29 04:43:33 +02:00
RETURN_IF_NOT_BATTLE ( std : : shared_ptr < const CObstacleInstance > ( ) ) ;
2012-08-26 12:07:48 +03:00
2013-06-29 16:05:48 +03:00
for ( auto & obs : battleGetAllObstacles ( ) )
2012-08-26 12:07:48 +03:00
{
if ( vstd : : contains ( obs - > getBlockedTiles ( ) , tile )
| | ( ! onlyBlocking & & vstd : : contains ( obs - > getAffectedTiles ( ) , tile ) ) )
{
return obs ;
}
}
2015-12-29 04:43:33 +02:00
return std : : shared_ptr < const CObstacleInstance > ( ) ;
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 ;
}
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
{
2016-01-29 21:43:35 +02:00
EAccessibility : : 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
2013-06-29 16:05:48 +03:00
for ( auto stack : battleAliveStacks ( ) )
2012-08-26 12:07:48 +03:00
{
2013-06-29 16:05:48 +03:00
for ( auto hex : stack - > 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
2016-02-09 16:38:59 +02:00
static const std : : pair < int , BattleHex > lockedIfNotDestroyed [ ] =
{
//which part of wall, which hex is blocked if this part of wall is not destroyed
std : : make_pair ( 2 , BattleHex ( ESiegeHex : : DESTRUCTIBLE_WALL_4 ) ) ,
std : : make_pair ( 3 , BattleHex ( ESiegeHex : : DESTRUCTIBLE_WALL_3 ) ) ,
std : : make_pair ( 4 , BattleHex ( ESiegeHex : : DESTRUCTIBLE_WALL_2 ) ) ,
std : : make_pair ( 5 , BattleHex ( ESiegeHex : : DESTRUCTIBLE_WALL_1 ) )
} ;
2012-08-26 12:07:48 +03:00
2013-06-29 16:05:48 +03:00
for ( 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 ;
}
2012-08-28 15:28:13 +03:00
AccessibilityInfo CBattleInfoCallback : : getAccesibility ( const CStack * stack ) const
{
return getAccesibility ( stack - > getHexes ( ) ) ;
}
AccessibilityInfo CBattleInfoCallback : : getAccesibility ( const std : : vector < BattleHex > & accessibleHexes ) const
{
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 ;
}
2013-11-07 15:48:41 +03: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 ;
2012-08-26 12:07:48 +03:00
const std : : set < BattleHex > quicksands = getStoppers ( params . perspective ) ;
//const bool twoHexCreature = params.doubleWide;
std : : queue < BattleHex > hexq ; //bfs queue
//first element
hexq . push ( params . startPosition ) ;
ret . distances [ params . startPosition ] = 0 ;
while ( ! hexq . empty ( ) ) //bfs loop
{
const BattleHex curHex = hexq . front ( ) ;
hexq . pop ( ) ;
//walking stack can't step past the quicksands
//TODO what if second hex of two-hex creature enters quicksand
2012-08-26 12:59:07 +03:00
if ( curHex ! = params . startPosition & & vstd : : contains ( quicksands , curHex ) )
2012-08-26 12:07:48 +03:00
continue ;
const int costToNeighbour = ret . distances [ curHex ] + 1 ;
2013-06-29 16:05:48 +03:00
for ( BattleHex neighbour : curHex . neighbouringTiles ( ) )
2012-08-26 12:07:48 +03:00
{
const bool accessible = accessibility . accessible ( neighbour , params . doubleWide , params . attackerOwned ) ;
const int costFoundSoFar = ret . distances [ neighbour ] ;
if ( accessible & & costToNeighbour < costFoundSoFar )
{
hexq . push ( neighbour ) ;
ret . distances [ neighbour ] = costToNeighbour ;
ret . predecessors [ neighbour ] = curHex ;
}
}
}
return ret ;
}
ReachabilityInfo CBattleInfoCallback : : makeBFS ( const CStack * stack ) const
{
2012-08-28 15:28:13 +03:00
return makeBFS ( getAccesibility ( stack ) , ReachabilityInfo : : Parameters ( stack ) ) ;
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 ;
}
2013-05-25 10:06:00 +03:00
std : : pair < const CStack * , BattleHex > CBattleInfoCallback : : getNearestStack ( const CStack * closest , boost : : logic : : tribool attackerOwned ) const
2012-08-26 12:07:48 +03:00
{
auto reachability = getReachability ( closest ) ;
2015-04-03 04:08:13 +02:00
auto avHexes = battleGetAvailableHexes ( closest , false ) ;
2012-08-26 12:07:48 +03:00
// I hate std::pairs with their undescriptive member names first / second
struct DistStack
{
2016-02-01 19:03:57 +02:00
int distanceToPred ;
BattleHex destination ;
2012-08-26 12:07:48 +03:00
const CStack * stack ;
} ;
2015-04-03 04:08:13 +02:00
std : : vector < DistStack > stackPairs ;
2012-08-26 12:07:48 +03:00
2015-04-03 04:08:13 +02:00
std : : vector < const CStack * > possibleStacks = battleGetStacksIf ( [ = ] ( const CStack * s )
{
2016-02-28 04:10:20 +02:00
return s - > isValidTarget ( false ) & & s ! = closest & & ( boost : : logic : : indeterminate ( attackerOwned ) | | s - > attackerOwned = = attackerOwned ) ;
} ) ;
2016-02-01 19:03:57 +02:00
2015-04-03 04:08:13 +02:00
for ( const CStack * st : possibleStacks )
for ( BattleHex hex : avHexes )
if ( CStack : : isMeleeAttackPossible ( closest , st , hex ) )
2013-05-25 10:06:00 +03:00
{
2015-04-03 04:08:13 +02:00
DistStack hlp = { reachability . distances [ st - > position ] , hex , st } ;
2013-05-25 10:06:00 +03:00
stackPairs . push_back ( hlp ) ;
}
2012-08-26 12:07:48 +03:00
2013-05-25 10:06:00 +03:00
if ( stackPairs . size ( ) )
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
2013-06-26 14:18:27 +03:00
return std : : make_pair < const CStack * , BattleHex > ( nullptr , BattleHex : : INVALID ) ;
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 )
| | ( side & & dest . getX ( ) < GameConstants : : BFIELD_WIDTH - 1 & & dest . getX ( ) > = GameConstants : : BFIELD_WIDTH - dist - 1 ) ) ;
}
ReachabilityInfo CBattleInfoCallback : : getReachability ( const CStack * stack ) const
{
ReachabilityInfo : : Parameters params ( stack ) ;
if ( ! battleDoWeKnowAbout ( ! stack - > attackerOwned ) )
{
//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 + + )
{
if ( ret . accessibility . accessible ( i , params . doubleWide , params . attackerOwned ) )
{
ret . predecessors [ i ] = params . startPosition ;
ret . distances [ i ] = BattleHex : : getDistance ( params . startPosition , i ) ;
}
}
return ret ;
}
2012-11-29 18:02:55 +03:00
AttackableTiles CBattleInfoCallback : : getPotentiallyAttackableHexes ( const CStack * 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-11-29 18:02:55 +03:00
//TODO: apply rotation to two-hex attackers
2012-11-30 17:06:22 +03:00
bool isAttacker = attacker - > attackerOwned ;
2012-11-29 18:02:55 +03:00
2012-08-26 12:07:48 +03:00
AttackableTiles at ;
RETURN_IF_NOT_BATTLE ( at ) ;
const int WN = GameConstants : : BFIELD_WIDTH ;
ui16 hex = ( attackerPos ! = BattleHex : : INVALID ) ? attackerPos . hex : attacker - > position . hex ; //real or hypothetical (cursor) position
2012-11-29 18:02:55 +03:00
2012-11-21 12:13:33 +03:00
//FIXME: dragons or cerbers can rotate before attack, making their base hex different (#1124)
2012-11-30 17:06:22 +03:00
bool reverse = isToReverse ( hex , destinationTile , isAttacker , attacker - > doubleWide ( ) , isAttacker ) ;
if ( reverse )
2012-11-29 18:02:55 +03:00
{
2012-11-30 17:06:22 +03:00
hex = attacker - > occupiedHex ( hex ) ; //the other hex stack stands on
2012-11-29 18:02:55 +03:00
}
2012-08-26 12:07:48 +03:00
if ( attacker - > hasBonusOfType ( Bonus : : ATTACKS_ALL_ADJACENT ) )
{
2012-11-30 17:06:22 +03:00
boost : : copy ( attacker - > getSurroundingHexes ( attackerPos ) , vstd : : set_inserter ( at . hostileCreaturePositions ) ) ;
2012-08-26 12:07:48 +03:00
}
if ( attacker - > hasBonusOfType ( Bonus : : THREE_HEADED_ATTACK ) )
{
std : : vector < BattleHex > hexes = attacker - > getSurroundingHexes ( attackerPos ) ;
2013-06-29 16:05:48 +03:00
for ( BattleHex tile : hexes )
2012-08-26 12:07:48 +03:00
{
2012-11-30 17:06:22 +03:00
if ( ( BattleHex : : mutualPosition ( tile , destinationTile ) > - 1 & & BattleHex : : mutualPosition ( tile , hex ) > - 1 ) ) //adjacent both to attacker's head and attacked tile
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?
{
at . hostileCreaturePositions . insert ( tile ) ;
}
}
}
}
2012-11-29 18:02:55 +03:00
if ( attacker - > hasBonusOfType ( Bonus : : TWO_HEX_ATTACK_BREATH ) & & BattleHex : : mutualPosition ( destinationTile . hex , hex ) > - 1 ) //only adjacent hexes are subject of dragon breath calculation
2012-08-26 12:07:48 +03:00
{
std : : vector < BattleHex > hexes ; //only one, in fact
int pseudoVector = destinationTile . hex - hex ;
switch ( pseudoVector )
{
case 1 :
case - 1 :
2012-11-29 18:02:55 +03:00
BattleHex : : checkAndPush ( destinationTile . hex + pseudoVector , hexes ) ;
2012-08-26 12:07:48 +03:00
break ;
2012-11-29 18:02:55 +03:00
case WN : //17 //left-down or right-down
case - WN : //-17 //left-up or right-up
case WN + 1 : //18 //right-down
case - WN + 1 : //-16 //right-up
2014-11-16 19:48:29 +02:00
BattleHex : : checkAndPush ( destinationTile . hex + pseudoVector + ( ( ( hex / WN ) % 2 ) ? 1 : - 1 ) , hexes ) ;
2012-08-26 12:07:48 +03:00
break ;
2012-11-29 18:02:55 +03:00
case WN - 1 : //16 //left-down
case - WN - 1 : //-18 //left-up
2014-11-16 19:48:29 +02:00
BattleHex : : checkAndPush ( destinationTile . hex + pseudoVector + ( ( ( hex / WN ) % 2 ) ? 1 : 0 ) , hexes ) ;
2012-08-26 12:07:48 +03:00
break ;
}
2013-06-29 16:05:48 +03:00
for ( BattleHex tile : hexes )
2012-08-26 12:07:48 +03:00
{
//friendly stacks can also be damaged by Dragon Breath
2012-11-29 18:02:55 +03:00
if ( battleGetStackByPos ( tile , true ) )
at . friendlyCreaturePositions . insert ( tile ) ;
2012-08-26 12:07:48 +03:00
}
}
return at ;
}
std : : set < const CStack * > CBattleInfoCallback : : getAttackedCreatures ( const CStack * attacker , BattleHex destinationTile , BattleHex attackerPos /*= BattleHex::INVALID*/ ) const
{
std : : set < const CStack * > attackedCres ;
RETURN_IF_NOT_BATTLE ( attackedCres ) ;
AttackableTiles 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 ;
}
2013-08-06 18:25:51 +03:00
//TODO: this should apply also to mechanics and cursor interface
bool CBattleInfoCallback : : isToReverseHlp ( BattleHex hexFrom , BattleHex hexTo , bool curDir ) const
2012-11-29 18:02:55 +03:00
{
2013-08-06 18:25:51 +03:00
int fromX = hexFrom . getX ( ) ;
int fromY = hexFrom . getY ( ) ;
int toX = hexTo . getX ( ) ;
int toY = hexTo . getY ( ) ;
2012-11-29 18:02:55 +03:00
2013-08-06 18:25:51 +03:00
if ( curDir ) // attacker, facing right
2012-11-29 18:02:55 +03:00
{
2013-08-06 18:25:51 +03:00
if ( fromX < toX )
return false ;
if ( fromX > toX )
return true ;
if ( fromY % 2 = = 0 & & toY % 2 = = 1 )
return true ;
2012-11-29 18:02:55 +03:00
return false ;
2013-08-06 18:25:51 +03:00
}
else // defender, facing left
2012-11-29 18:02:55 +03:00
{
2013-08-06 18:25:51 +03:00
if ( fromX < toX )
return true ;
if ( fromX > toX )
return false ;
if ( fromY % 2 = = 1 & & toY % 2 = = 0 )
return true ;
return false ;
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
bool CBattleInfoCallback : : isToReverse ( BattleHex hexFrom , BattleHex hexTo , bool curDir , bool toDoubleWide , bool toDir ) const
2012-11-29 18:02:55 +03:00
{
2013-02-27 10:40:21 +03:00
if ( hexTo < 0 | | hexFrom < 0 ) //turret
2012-11-29 18:02:55 +03:00
return false ;
if ( toDoubleWide )
{
2014-03-23 19:36:16 +03:00
if ( isToReverseHlp ( hexFrom , hexTo , curDir ) )
{
if ( toDir )
return isToReverseHlp ( hexFrom , hexTo - 1 , curDir ) ;
else
return isToReverseHlp ( hexFrom , hexTo + 1 , curDir ) ;
}
return false ;
2012-11-29 18:02:55 +03:00
}
else
{
return isToReverseHlp ( hexFrom , hexTo , curDir ) ;
}
}
2013-06-26 14:18:27 +03:00
ReachabilityInfo : : TDistances CBattleInfoCallback : : battleGetDistances ( const CStack * stack , BattleHex hex /*= BattleHex::INVALID*/ , BattleHex * predecessors /*= nullptr*/ ) const
2012-08-26 12:07:48 +03:00
{
ReachabilityInfo : : TDistances ret ;
ret . fill ( - 1 ) ;
RETURN_IF_NOT_BATTLE ( ret ) ;
ReachabilityInfo : : Parameters params ( stack ) ;
2012-09-20 19:55:21 +03:00
params . perspective = battleGetMySide ( ) ;
2012-08-26 12:07:48 +03:00
params . startPosition = hex . isValid ( ) ? hex : stack - > position ;
auto reachability = getReachability ( params ) ;
boost : : copy ( reachability . distances , ret . begin ( ) ) ;
if ( predecessors )
for ( int i = 0 ; i < GameConstants : : BFIELD_SIZE ; i + + )
predecessors [ i ] = reachability . predecessors [ i ] ;
return ret ;
}
si8 CBattleInfoCallback : : battleHasDistancePenalty ( const CStack * stack , BattleHex destHex ) const
2012-09-20 19:55:21 +03:00
{
return battleHasDistancePenalty ( stack , stack - > position , destHex ) ;
}
si8 CBattleInfoCallback : : battleHasDistancePenalty ( const IBonusBearer * bonusBearer , BattleHex shooterPosition , BattleHex destHex ) const
2012-08-26 12:07:48 +03:00
{
RETURN_IF_NOT_BATTLE ( false ) ;
2012-09-20 19:55:21 +03:00
if ( bonusBearer - > hasBonusOfType ( Bonus : : NO_DISTANCE_PENALTY ) )
2012-08-26 12:07:48 +03:00
return false ;
2012-09-20 19:55:21 +03:00
if ( const CStack * dstStack = battleGetStackByPos ( destHex , false ) )
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
2013-06-29 16:05:48 +03:00
for ( auto hex : dstStack - > getHexes ( ) )
2012-09-20 19:55:21 +03:00
if ( BattleHex : : getDistance ( shooterPosition , hex ) < = GameConstants : : BATTLE_PENALTY_DISTANCE )
2012-08-26 12:07:48 +03:00
return false ;
2012-09-20 19:55:21 +03:00
//TODO what about two-hex shooters?
2012-08-26 12:07:48 +03:00
}
2012-11-29 18:02:55 +03:00
else
{
if ( BattleHex : : getDistance ( shooterPosition , destHex ) < = GameConstants : : BATTLE_PENALTY_DISTANCE )
return false ;
}
2012-08-26 12:07:48 +03:00
return true ;
}
2013-12-08 20:54:13 +03:00
BattleHex CBattleInfoCallback : : wallPartToBattleHex ( EWallPart : : EWallPart part ) const
2013-08-06 14:20:28 +03:00
{
RETURN_IF_NOT_BATTLE ( BattleHex : : INVALID ) ;
return WallPartToHex ( part ) ;
}
2013-12-08 20:54:13 +03:00
EWallPart : : 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 ) ;
}
2013-12-08 20:54:13 +03:00
bool CBattleInfoCallback : : isWallPartPotentiallyAttackable ( EWallPart : : EWallPart wallPart ) const
{
RETURN_IF_NOT_BATTLE ( false ) ;
return wallPart ! = EWallPart : : INDESTRUCTIBLE_PART & & wallPart ! = EWallPart : : INDESTRUCTIBLE_PART_OF_GATE & &
wallPart ! = EWallPart : : INVALID ;
}
std : : vector < BattleHex > CBattleInfoCallback : : getAttackableBattleHexes ( ) const
{
std : : vector < BattleHex > attackableBattleHexes ;
RETURN_IF_NOT_BATTLE ( attackableBattleHexes ) ;
for ( auto & wallPartPair : wallParts )
{
if ( isWallPartPotentiallyAttackable ( wallPartPair . second ) )
{
auto wallState = static_cast < EWallState : : EWallState > ( battleGetWallState ( static_cast < int > ( wallPartPair . second ) ) ) ;
if ( wallState = = EWallState : : INTACT | | wallState = = EWallState : : DAMAGED )
{
attackableBattleHexes . push_back ( BattleHex ( wallPartPair . first ) ) ;
}
}
}
return attackableBattleHexes ;
}
2015-09-28 15:06:26 +02:00
ESpellCastProblem : : ESpellCastProblem CBattleInfoCallback : : battleCanCastThisSpell ( const ISpellCaster * caster , const CSpell * spell , ECastingMode : : ECastingMode mode ) const
2012-08-26 12:07:48 +03:00
{
RETURN_IF_NOT_BATTLE ( ESpellCastProblem : : INVALID ) ;
2015-09-28 15:06:26 +02:00
if ( caster = = nullptr )
{
logGlobal - > errorStream ( ) < < " CBattleInfoCallback::battleCanCastThisSpell: no spellcaster. " ;
return ESpellCastProblem : : INVALID ;
}
const PlayerColor player = caster - > getOwner ( ) ;
2012-08-26 12:07:48 +03:00
const ui8 side = playerToSide ( player ) ;
if ( ! battleDoWeKnowAbout ( side ) )
2012-08-26 12:59:07 +03:00
return ESpellCastProblem : : INVALID ;
2012-08-26 12:07:48 +03:00
ESpellCastProblem : : ESpellCastProblem genProblem = battleCanCastSpell ( player , mode ) ;
if ( genProblem ! = ESpellCastProblem : : OK )
return genProblem ;
switch ( mode )
{
case ECastingMode : : HERO_CASTING :
{
2015-09-28 15:06:26 +02:00
const CGHeroInstance * castingHero = dynamic_cast < const CGHeroInstance * > ( caster ) ; //todo: unify hero|creature spell cost
2012-08-26 12:07:48 +03:00
assert ( castingHero ) ;
if ( ! castingHero - > canCastThisSpell ( spell ) )
return ESpellCastProblem : : HERO_DOESNT_KNOW_SPELL ;
if ( castingHero - > mana < battleGetSpellCost ( spell , castingHero ) ) //not enough mana
return ESpellCastProblem : : NOT_ENOUGH_MANA ;
}
break ;
}
2013-01-16 14:19:04 +03:00
if ( ! spell - > combatSpell )
2012-08-26 12:07:48 +03:00
return ESpellCastProblem : : ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL ;
2015-09-21 11:19:35 +02:00
const ESpellCastProblem : : ESpellCastProblem specificProblem = spell - > canBeCast ( this , player ) ;
2016-02-01 19:03:57 +02:00
2015-03-18 15:39:07 +02:00
if ( specificProblem ! = ESpellCastProblem : : OK )
2016-02-01 19:03:57 +02:00
return specificProblem ;
2015-03-18 15:39:07 +02:00
2013-07-28 13:32:01 +03:00
if ( spell - > isNegative ( ) | | spell - > hasEffects ( ) )
2012-08-26 12:07:48 +03:00
{
2013-07-28 13:32:01 +03:00
bool allStacksImmune = true ;
//we are interested only in enemy stacks when casting offensive spells
2014-03-17 16:11:10 +03:00
//TODO: should hero be able to cast non-smart negative spell if all enemy stacks are immune?
2013-07-28 13:32:01 +03:00
auto stacks = spell - > isNegative ( ) ? battleAliveStacks ( ! side ) : battleAliveStacks ( ) ;
2014-03-07 16:21:09 +03:00
for ( auto stack : stacks )
2012-08-26 12:07:48 +03:00
{
2015-09-28 15:06:26 +02:00
if ( ESpellCastProblem : : OK = = spell - > isImmuneByStack ( caster , stack ) )
2012-08-26 12:07:48 +03:00
{
2013-07-28 13:32:01 +03:00
allStacksImmune = false ;
2012-08-26 12:07:48 +03:00
break ;
}
}
2013-07-28 13:32:01 +03:00
if ( allStacksImmune )
2012-08-26 12:07:48 +03:00
return ESpellCastProblem : : NO_APPROPRIATE_TARGET ;
}
2015-09-16 17:28:14 +02:00
if ( battleMaxSpellLevel ( side ) < spell - > level ) //effect like Recanter's Cloak or Orb of Inhibition
2012-08-26 12:07:48 +03:00
return ESpellCastProblem : : SPELL_LEVEL_LIMIT_EXCEEDED ;
//checking if there exists an appropriate target
switch ( spell - > getTargetType ( ) )
{
case CSpell : : CREATURE :
if ( mode = = ECastingMode : : HERO_CASTING )
{
2015-09-19 15:51:06 +02:00
const CSpell : : TargetInfo ti ( spell , caster - > getSpellSchoolLevel ( spell ) ) ;
2012-08-26 12:07:48 +03:00
bool targetExists = false ;
2014-08-05 20:12:56 +03:00
2016-03-12 03:41:27 +02:00
for ( const CStack * stack : battleGetAllStacks ( ) ) //dead stacks will be immune anyway
2012-08-26 12:07:48 +03:00
{
2014-11-13 03:53:25 +02:00
bool immune = ESpellCastProblem : : OK ! = spell - > isImmuneByStack ( caster , stack ) ;
2014-03-17 16:11:10 +03:00
bool casterStack = stack - > owner = = caster - > getOwner ( ) ;
2016-02-01 19:03:57 +02:00
2016-03-12 03:41:27 +02:00
if ( ! immune )
{
2014-03-17 16:11:10 +03:00
switch ( spell - > positiveness )
2012-08-26 12:07:48 +03:00
{
2014-03-17 16:11:10 +03:00
case CSpell : : POSITIVE :
if ( casterStack | | ! ti . smart )
2012-08-26 12:07:48 +03:00
{
targetExists = true ;
break ;
}
break ;
2014-03-17 16:11:10 +03:00
case CSpell : : NEUTRAL :
targetExists = true ;
break ;
case CSpell : : NEGATIVE :
if ( ! casterStack | | ! ti . smart )
2012-08-26 12:07:48 +03:00
{
targetExists = true ;
break ;
}
2014-03-17 16:11:10 +03:00
break ;
2012-08-26 12:07:48 +03:00
}
2016-03-12 03:41:27 +02:00
}
2012-08-26 12:07:48 +03:00
}
2016-03-12 03:41:27 +02:00
if ( ! targetExists )
2012-08-26 12:07:48 +03:00
{
return ESpellCastProblem : : NO_APPROPRIATE_TARGET ;
}
}
break ;
case CSpell : : OBSTACLE :
break ;
}
return ESpellCastProblem : : OK ;
}
2013-03-03 20:06:03 +03:00
std : : vector < BattleHex > CBattleInfoCallback : : battleGetPossibleTargets ( PlayerColor player , const CSpell * spell ) const
2012-09-20 19:55:21 +03:00
{
std : : vector < BattleHex > ret ;
RETURN_IF_NOT_BATTLE ( ret ) ;
switch ( spell - > getTargetType ( ) )
{
case CSpell : : CREATURE :
{
const CGHeroInstance * caster = battleGetFightingHero ( playerToSide ( player ) ) ; //TODO
2015-09-19 15:51:06 +02:00
const CSpell : : TargetInfo ti ( spell , caster - > getSpellSchoolLevel ( spell ) ) ;
2016-02-01 19:03:57 +02:00
2013-06-29 16:05:48 +03:00
for ( const CStack * stack : battleAliveStacks ( ) )
2012-09-20 19:55:21 +03:00
{
2014-11-13 03:53:25 +02:00
bool immune = ESpellCastProblem : : OK ! = spell - > isImmuneByStack ( caster , stack ) ;
2014-03-17 16:11:10 +03:00
bool casterStack = stack - > owner = = caster - > getOwner ( ) ;
2016-02-01 19:03:57 +02:00
2014-03-17 16:11:10 +03:00
if ( ! immune )
switch ( spell - > positiveness )
{
case CSpell : : POSITIVE :
if ( casterStack | | ti . smart )
2012-09-20 19:55:21 +03:00
ret . push_back ( stack - > position ) ;
2014-03-17 16:11:10 +03:00
break ;
2012-09-20 19:55:21 +03:00
2014-03-17 16:11:10 +03:00
case CSpell : : NEUTRAL :
2012-09-20 19:55:21 +03:00
ret . push_back ( stack - > position ) ;
2014-03-17 16:11:10 +03:00
break ;
2012-09-20 19:55:21 +03:00
2014-03-17 16:11:10 +03:00
case CSpell : : NEGATIVE :
if ( ! casterStack | | ti . smart )
2012-09-20 19:55:21 +03:00
ret . push_back ( stack - > position ) ;
2014-03-17 16:11:10 +03:00
break ;
}
2012-09-20 19:55:21 +03:00
}
}
break ;
default :
2016-03-12 03:41:27 +02:00
logGlobal - > errorStream ( ) < < " FIXME " < < __FUNCTION__ < < " doesn't work with target type " < < spell - > getTargetType ( ) ;
2012-09-20 19:55:21 +03:00
}
return ret ;
}
2012-08-26 12:07:48 +03:00
ui32 CBattleInfoCallback : : battleGetSpellCost ( const CSpell * sp , const CGHeroInstance * caster ) const
{
RETURN_IF_NOT_BATTLE ( - 1 ) ;
//TODO should be replaced using bonus system facilities (propagation onto battle node)
ui32 ret = caster - > getSpellCost ( sp ) ;
//checking for friendly stacks reducing cost of the spell and
//enemy stacks increasing it
si32 manaReduction = 0 ;
si32 manaIncrease = 0 ;
2013-06-29 16:05:48 +03:00
for ( auto stack : battleAliveStacks ( ) )
2012-08-26 12:07:48 +03:00
{
if ( stack - > owner = = caster - > tempOwner & & stack - > hasBonusOfType ( Bonus : : CHANGES_SPELL_COST_FOR_ALLY ) )
{
vstd : : amax ( manaReduction , stack - > valOfBonuses ( Bonus : : CHANGES_SPELL_COST_FOR_ALLY ) ) ;
}
if ( stack - > owner ! = caster - > tempOwner & & stack - > hasBonusOfType ( Bonus : : CHANGES_SPELL_COST_FOR_ENEMY ) )
{
vstd : : amax ( manaIncrease , stack - > valOfBonuses ( Bonus : : CHANGES_SPELL_COST_FOR_ENEMY ) ) ;
}
}
return ret - manaReduction + manaIncrease ;
}
2015-09-28 15:06:26 +02:00
ESpellCastProblem : : ESpellCastProblem CBattleInfoCallback : : battleCanCastThisSpellHere ( const ISpellCaster * caster , const CSpell * spell , ECastingMode : : ECastingMode mode , BattleHex dest ) const
2012-08-26 12:07:48 +03:00
{
RETURN_IF_NOT_BATTLE ( ESpellCastProblem : : INVALID ) ;
2015-09-28 15:06:26 +02:00
if ( caster = = nullptr )
{
logGlobal - > errorStream ( ) < < " CBattleInfoCallback::battleCanCastThisSpellHere: no spellcaster. " ;
return ESpellCastProblem : : INVALID ;
2016-02-01 19:03:57 +02:00
}
2015-09-28 15:06:26 +02:00
const PlayerColor player = caster - > getOwner ( ) ;
ESpellCastProblem : : ESpellCastProblem moreGeneralProblem = battleCanCastThisSpell ( caster , spell , mode ) ;
2012-08-26 12:07:48 +03:00
if ( moreGeneralProblem ! = ESpellCastProblem : : OK )
return moreGeneralProblem ;
if ( spell - > getTargetType ( ) = = CSpell : : OBSTACLE )
{
2013-07-21 13:25:12 +03:00
if ( spell - > id = = SpellID : : REMOVE_OBSTACLE )
{
if ( auto obstacle = battleGetObstacleOnPos ( dest , false ) )
{
switch ( obstacle - > obstacleType )
{
2014-03-23 15:59:03 +03:00
case CObstacleInstance : : ABSOLUTE_OBSTACLE : //cliff-like obstacles can't be removed
2014-03-07 16:21:09 +03:00
case CObstacleInstance : : MOAT :
2013-07-21 13:25:12 +03:00
return ESpellCastProblem : : NO_APPROPRIATE_TARGET ;
case CObstacleInstance : : USUAL :
return ESpellCastProblem : : OK ;
// //TODO FIRE_WALL only for ADVANCED level casters
// case CObstacleInstance::FIRE_WALL:
2014-03-07 16:21:09 +03:00
// return
2013-07-21 13:25:12 +03:00
// //TODO other magic obstacles for EXPERT
// case CObstacleInstance::QUICKSAND:
// case CObstacleInstance::LAND_MINE:
// case CObstacleInstance::FORCE_FIELD:
2014-03-07 16:21:09 +03:00
// return
2013-07-21 13:25:12 +03:00
default :
// assert(0);
return ESpellCastProblem : : OK ;
}
}
}
2012-08-26 12:07:48 +03:00
//isObstacleOnTile(dest)
2012-08-26 12:59:07 +03:00
//
//
2012-08-26 12:07:48 +03:00
//TODO
//assert that it's remove obstacle
//rules whether we can remove spell-created obstacle
return ESpellCastProblem : : NO_APPROPRIATE_TARGET ;
}
//get dead stack if we cast resurrection or animate dead
2012-08-28 18:38:00 +03:00
const CStack * deadStack = getStackIf ( [ dest ] ( const CStack * s ) { return ! s - > alive ( ) & & s - > coversPos ( dest ) ; } ) ;
const CStack * aliveStack = getStackIf ( [ dest ] ( const CStack * s ) { return s - > alive ( ) & & s - > coversPos ( dest ) ; } ) ;
2012-08-26 12:07:48 +03:00
if ( spell - > isRisingSpell ( ) )
{
if ( ! deadStack & & ! aliveStack )
return ESpellCastProblem : : NO_APPROPRIATE_TARGET ;
if ( deadStack & & deadStack - > owner ! = player ) //you can resurrect only your own stacks //FIXME: it includes alive stacks as well
return ESpellCastProblem : : NO_APPROPRIATE_TARGET ;
}
2014-03-17 16:11:10 +03:00
else if ( spell - > getTargetType ( ) = = CSpell : : CREATURE )
2012-08-26 12:07:48 +03:00
{
if ( ! aliveStack )
return ESpellCastProblem : : NO_APPROPRIATE_TARGET ;
if ( spell - > isNegative ( ) & & aliveStack - > owner = = player )
return ESpellCastProblem : : NO_APPROPRIATE_TARGET ;
if ( spell - > isPositive ( ) & & aliveStack - > owner ! = player )
return ESpellCastProblem : : NO_APPROPRIATE_TARGET ;
}
2014-11-29 03:25:51 +02:00
return spell - > isImmuneAt ( this , caster , mode , dest ) ;
2012-08-26 12:07:48 +03:00
}
2013-06-26 14:18:27 +03:00
const CStack * CBattleInfoCallback : : getStackIf ( std : : function < bool ( const CStack * ) > pred ) const
2012-08-26 12:07:48 +03:00
{
RETURN_IF_NOT_BATTLE ( nullptr ) ;
auto stacks = battleGetAllStacks ( ) ;
auto stackItr = range : : find_if ( stacks , pred ) ;
2012-08-26 12:59:07 +03:00
return stackItr = = stacks . end ( )
2013-06-26 14:18:27 +03:00
? nullptr
2012-08-26 12:07:48 +03:00
: * stackItr ;
}
bool CBattleInfoCallback : : battleIsStackBlocked ( const CStack * stack ) const
{
RETURN_IF_NOT_BATTLE ( false ) ;
if ( stack - > hasBonusOfType ( Bonus : : SIEGE_WEAPON ) ) //siege weapons cannot be blocked
return false ;
2013-06-29 16:05:48 +03:00
for ( const CStack * s : batteAdjacentCreatures ( stack ) )
2012-08-26 12:07:48 +03:00
{
if ( s - > owner ! = stack - > owner ) //blocked by enemy stack
return true ;
}
return false ;
}
std : : set < const CStack * > CBattleInfoCallback : : batteAdjacentCreatures ( const CStack * stack ) const
{
std : : set < const CStack * > stacks ;
RETURN_IF_NOT_BATTLE ( stacks ) ;
2013-06-29 16:05:48 +03:00
for ( BattleHex hex : stack - > getSurroundingHexes ( ) )
2012-08-26 12:07:48 +03:00
if ( const CStack * neighbour = battleGetStackByPos ( hex , true ) )
stacks . insert ( neighbour ) ;
return stacks ;
}
2013-02-11 02:24:57 +03:00
SpellID CBattleInfoCallback : : getRandomBeneficialSpell ( 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
2014-12-01 23:24:36 +02:00
auto getAliveEnemy = [ = ] ( const std : : function < bool ( const CStack * ) > & pred )
2012-08-26 12:07:48 +03:00
{
2014-12-01 23:24:36 +02:00
return getStackIf ( [ = ] ( const CStack * stack )
2012-08-26 12:07:48 +03:00
{
2014-12-01 23:24:36 +02:00
return pred ( stack ) & & stack - > owner ! = subject - > owner & & stack - > alive ( ) ;
} ) ;
} ;
2012-08-26 12:07:48 +03:00
2014-12-01 23:24:36 +02:00
for ( const SpellID spellID : allPossibleSpells )
{
if ( subject - > hasBonusFrom ( Bonus : : SPELL_EFFECT , spellID )
//TODO: this ability has special limitations
2015-09-28 15:06:26 +02:00
| | battleCanCastThisSpellHere ( subject , spellID . toSpell ( ) , ECastingMode : : CREATURE_ACTIVE_CASTING , subject - > position ) ! = ESpellCastProblem : : OK )
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
2016-02-01 19:03:57 +02:00
{
2014-12-01 23:24:36 +02:00
auto walker = getAliveEnemy ( [ & ] ( const CStack * stack ) //look for enemy, non-shooting stack
2012-08-26 12:07:48 +03:00
{
2014-12-01 23:24:36 +02:00
return ! stack - > shots ;
} ) ;
2012-08-26 12:07:48 +03:00
2014-12-01 23:24:36 +02:00
if ( ! walker )
continue ;
}
break ;
case SpellID : : AIR_SHIELD : //only against active shooters
{
auto shooter = getAliveEnemy ( [ & ] ( const CStack * stack ) //look for enemy, non-shooting stack
2012-08-26 12:07:48 +03:00
{
2014-12-01 23:24:36 +02:00
return stack - > hasBonusOfType ( Bonus : : SHOOTER ) & & stack - > shots ;
} ) ;
if ( ! shooter )
continue ;
}
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 :
2014-12-01 23:24:36 +02:00
{
const ui8 enemySide = ( ui8 ) subject - > attackerOwned ;
//todo: only if enemy has spellbook
if ( ! battleHasHero ( enemySide ) ) //only if there is enemy hero
continue ;
}
break ;
case SpellID : : CURE : //only damaged units
{
//do not cast on affected by debuffs
if ( subject - > firstHPleft > = subject - > MaxHealth ( ) )
continue ;
}
break ;
case SpellID : : BLOODLUST :
{
if ( subject - > shots ) //if can shoot - only if enemy uits are adjacent
continue ;
}
break ;
case SpellID : : PRECISION :
{
if ( ! ( subject - > hasBonusOfType ( Bonus : : SHOOTER ) & & subject - > shots ) )
continue ;
}
break ;
case SpellID : : SLAYER : //only if monsters are present
{
auto kingMonster = getAliveEnemy ( [ & ] ( const CStack * stack ) - > bool //look for enemy, non-shooting stack
2012-08-26 12:07:48 +03:00
{
2014-12-01 23:24:36 +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
2014-12-01 23:24:36 +02:00
return stack - > hasBonus ( isKing ) ;
} ) ;
2012-08-26 12:07:48 +03:00
2014-12-01 23:24:36 +02:00
if ( ! kingMonster )
continue ;
2012-08-26 12:07:48 +03:00
}
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
{
2014-12-01 23:24:36 +02:00
return * RandomGeneratorUtil : : nextItem ( beneficialSpells , gs - > getRandomGenerator ( ) ) ;
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
}
2013-02-11 02:24:57 +03:00
SpellID CBattleInfoCallback : : getRandomCastedSpell ( 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
TBonusListPtr bl = caster - > getBonuses ( Selector : : type ( Bonus : : SPELLCASTER ) ) ;
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 ;
2013-06-29 16:05:48 +03:00
for ( Bonus * b : * bl )
2012-08-26 12:07:48 +03:00
{
totalWeight + = std : : max ( b - > additionalInfo , 1 ) ; //minimal chance to cast is 1
}
2014-04-10 20:11:09 +03:00
int randomPos = gs - > getRandomGenerator ( ) . nextInt ( totalWeight - 1 ) ;
2013-06-29 16:05:48 +03:00
for ( Bonus * b : * bl )
2012-08-26 12:07:48 +03:00
{
randomPos - = std : : max ( b - > additionalInfo , 1 ) ;
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
}
2013-03-03 20:06:03 +03:00
int CBattleInfoCallback : : battleGetSurrenderCost ( PlayerColor Player ) const
2012-08-26 12:07:48 +03:00
{
RETURN_IF_NOT_BATTLE ( - 3 ) ;
if ( ! battleCanSurrender ( Player ) )
return - 1 ;
int ret = 0 ;
double discount = 0 ;
2013-06-29 16:05:48 +03:00
for ( const CStack * s : battleAliveStacks ( playerToSide ( Player ) ) )
2012-08-26 12:07:48 +03:00
if ( s - > base ) //we pay for our stack that comes from our army slots - condition eliminates summoned cres and war machines
ret + = s - > getCreature ( ) - > cost [ Res : : GOLD ] * s - > count ;
if ( const CGHeroInstance * h = battleGetFightingHero ( playerToSide ( Player ) ) )
discount + = h - > valOfBonuses ( Bonus : : SURRENDER_DISCOUNT ) ;
ret * = ( 100.0 - discount ) / 100.0 ;
vstd : : amax ( ret , 0 ) ; //no negative costs for >100% discounts (impossible in original H3 mechanics, but some day...)
return ret ;
}
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)
auto b = node - > getBonuses ( Selector : : type ( Bonus : : BLOCK_MAGIC_ABOVE ) ) ;
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
{
2013-09-30 11:45:26 +03:00
auto stacks = battleGetAllStacks ( ) ;
2013-09-29 23:54:29 +03:00
//checking winning condition
bool hasStack [ 2 ] ; //hasStack[0] - true if attacker has a living stack; defender similarly
hasStack [ 0 ] = hasStack [ 1 ] = false ;
for ( auto & stack : stacks )
{
if ( stack - > alive ( ) & & ! stack - > hasBonusOfType ( Bonus : : SIEGE_WEAPON ) )
{
hasStack [ 1 - stack - > attackerOwned ] = true ;
}
}
if ( ! hasStack [ 0 ] & & ! hasStack [ 1 ] )
return 2 ;
if ( ! hasStack [ 1 ] )
return 0 ;
2013-09-30 00:31:14 +03:00
if ( ! hasStack [ 0 ] )
return 1 ;
2013-09-29 23:54:29 +03:00
return boost : : none ;
}
2012-08-26 12:07:48 +03:00
bool AccessibilityInfo : : accessible ( BattleHex tile , const CStack * stack ) const
{
return accessible ( tile , stack - > doubleWide ( ) , stack - > attackerOwned ) ;
}
bool AccessibilityInfo : : accessible ( BattleHex tile , bool doubleWide , bool attackerOwned ) const
{
// All hexes that stack would cover if standing on tile have to be accessible.
2013-06-29 16:05:48 +03:00
for ( auto hex : CStack : : getHexes ( tile , doubleWide , attackerOwned ) )
2012-09-29 23:25:54 +03:00
{
2016-03-12 03:41:27 +02:00
// If the hex is out of range then the tile isn't accessible
if ( ! hex . isValid ( ) )
return false ;
// If we're no defender which step on gate and the hex isn't accessible, then the tile
// isn't accessible
else if ( at ( hex ) ! = EAccessibility : : ACCESSIBLE & &
! ( at ( hex ) = = EAccessibility : : GATE & & ! attackerOwned ) )
{
return false ;
}
2012-09-29 23:25:54 +03:00
}
2012-08-26 12:07:48 +03:00
return true ;
}
bool AccessibilityInfo : : occupiable ( const CStack * stack , BattleHex tile ) const
{
//obviously, we can occupy tile by standing on it
if ( accessible ( tile , stack ) )
return true ;
if ( stack - > doubleWide ( ) )
{
//Check the tile next to -> if stack stands there, it'll also occupy considered hex
const BattleHex anotherTile = tile + ( stack - > attackerOwned ? BattleHex : : RIGHT : BattleHex : : LEFT ) ;
if ( accessible ( anotherTile , stack ) )
return true ;
}
return false ;
}
ReachabilityInfo : : Parameters : : Parameters ( )
{
stack = nullptr ;
perspective = BattlePerspective : : ALL_KNOWING ;
attackerOwned = doubleWide = flying = false ;
}
ReachabilityInfo : : Parameters : : Parameters ( const CStack * Stack )
{
stack = Stack ;
perspective = ( BattlePerspective : : BattlePerspective ) ( ! Stack - > attackerOwned ) ;
startPosition = Stack - > position ;
doubleWide = stack - > doubleWide ( ) ;
attackerOwned = stack - > attackerOwned ;
flying = stack - > hasBonusOfType ( Bonus : : FLYING ) ;
2012-08-28 15:28:13 +03:00
knownAccessible = stack - > getHexes ( ) ;
2012-08-26 12:07:48 +03:00
}
ESpellCastProblem : : ESpellCastProblem CPlayerBattleCallback : : battleCanCastThisSpell ( const CSpell * spell ) const
{
RETURN_IF_NOT_BATTLE ( ESpellCastProblem : : INVALID ) ;
2013-02-11 17:42:09 +03:00
ASSERT_IF_CALLED_WITH_PLAYER
2012-08-26 12:07:48 +03:00
2015-09-28 15:06:26 +02:00
const ISpellCaster * hero = battleGetMyHero ( ) ;
if ( hero = = nullptr )
return ESpellCastProblem : : INVALID ;
else
return CBattleInfoCallback : : battleCanCastThisSpell ( hero , spell , ECastingMode : : HERO_CASTING ) ;
2012-08-26 12:07:48 +03:00
}
bool CPlayerBattleCallback : : battleCanFlee ( ) const
{
RETURN_IF_NOT_BATTLE ( false ) ;
2013-02-11 17:42:09 +03:00
ASSERT_IF_CALLED_WITH_PLAYER
return CBattleInfoEssentials : : battleCanFlee ( * player ) ;
2012-08-26 12:07:48 +03:00
}
TStacks CPlayerBattleCallback : : battleGetStacks ( EStackOwnership whose /*= MINE_AND_ENEMY*/ , bool onlyAlive /*= true*/ ) const
{
2013-06-17 18:45:55 +03:00
if ( whose ! = MINE_AND_ENEMY )
{
ASSERT_IF_CALLED_WITH_PLAYER
}
2016-02-01 19:03:57 +02:00
2014-05-17 13:36:49 +03:00
return battleGetStacksIf ( [ = ] ( const CStack * s ) {
2012-08-26 12:59:07 +03:00
const bool ownerMatches = ( whose = = MINE_AND_ENEMY )
| | ( whose = = ONLY_MINE & & s - > owner = = player )
2012-08-26 12:07:48 +03:00
| | ( whose = = ONLY_ENEMY & & s - > owner ! = player ) ;
2016-02-28 04:10:20 +02:00
return ownerMatches & & s - > isValidTarget ( ! onlyAlive ) ;
2012-08-26 12:07:48 +03:00
} ) ;
}
int CPlayerBattleCallback : : battleGetSurrenderCost ( ) const
{
RETURN_IF_NOT_BATTLE ( - 3 )
2013-02-11 17:42:09 +03:00
ASSERT_IF_CALLED_WITH_PLAYER
return CBattleInfoCallback : : battleGetSurrenderCost ( * player ) ;
2012-08-26 12:07:48 +03:00
}
bool CPlayerBattleCallback : : battleCanCastSpell ( ESpellCastProblem : : ESpellCastProblem * outProblem /*= nullptr*/ ) const
{
RETURN_IF_NOT_BATTLE ( false ) ;
2013-02-11 17:42:09 +03:00
ASSERT_IF_CALLED_WITH_PLAYER
auto problem = CBattleInfoCallback : : battleCanCastSpell ( * player , ECastingMode : : HERO_CASTING ) ;
2012-08-26 12:07:48 +03:00
if ( outProblem )
* outProblem = problem ;
return problem = = ESpellCastProblem : : OK ;
}
2012-09-20 19:55:21 +03:00
2012-09-24 02:10:56 +03:00
const CGHeroInstance * CPlayerBattleCallback : : battleGetMyHero ( ) const
{
return CBattleInfoEssentials : : battleGetFightingHero ( battleGetMySide ( ) ) ;
}
InfoAboutHero CPlayerBattleCallback : : battleGetEnemyHero ( ) const
{
2013-01-20 23:29:35 +03:00
return battleGetHeroInfo ( ! battleGetMySide ( ) ) ;
2012-09-24 02:10:56 +03:00
}
2012-09-20 19:55:21 +03:00
BattleAttackInfo : : BattleAttackInfo ( const CStack * Attacker , const CStack * Defender , bool Shooting )
{
attacker = Attacker ;
defender = Defender ;
attackerBonuses = Attacker ;
defenderBonuses = Defender ;
attackerPosition = Attacker - > position ;
defenderPosition = Defender - > position ;
attackerCount = Attacker - > count ;
2013-06-21 23:59:32 +03:00
defenderCount = Defender - > count ;
2012-09-20 19:55:21 +03:00
shooting = Shooting ;
chargedFields = 0 ;
luckyHit = false ;
2015-12-04 00:11:44 +02:00
unluckyHit = false ;
2012-09-20 19:55:21 +03:00
deathBlow = false ;
ballistaDoubleDamage = false ;
}
BattleAttackInfo BattleAttackInfo : : reverse ( ) const
{
BattleAttackInfo ret = * this ;
std : : swap ( ret . attacker , ret . defender ) ;
std : : swap ( ret . attackerBonuses , ret . defenderBonuses ) ;
std : : swap ( ret . attackerPosition , ret . defenderPosition ) ;
2013-06-21 23:59:32 +03:00
std : : swap ( ret . attackerCount , ret . defenderCount ) ;
2012-09-20 19:55:21 +03:00
ret . shooting = false ;
ret . chargedFields = 0 ;
ret . luckyHit = ret . ballistaDoubleDamage = ret . deathBlow = false ;
return ret ;
2012-10-19 16:16:14 +03:00
}