2017-07-13 10:26:03 +02:00
/*
* CBattleInterface . 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
*
*/
2011-12-14 00:35:28 +03:00
# include "StdInc.h"
2009-01-15 19:01:08 +02:00
# include "CBattleInterface.h"
2011-12-14 00:35:28 +03:00
2014-07-13 20:53:37 +03:00
# include "CBattleAnimations.h"
# include "CBattleInterfaceClasses.h"
# include "CCreatureAnimation.h"
2013-07-06 19:10:20 +03:00
# include "../CBitmapHandler.h"
2014-07-13 20:53:37 +03:00
# include "../CGameInfo.h"
2011-12-14 00:35:28 +03:00
# include "../CMessage.h"
2014-07-13 20:53:37 +03:00
# include "../CMT.h"
# include "../CMusicHandler.h"
# include "../CPlayerInterface.h"
# include "../CVideoHandler.h"
# include "../Graphics.h"
2017-09-05 15:44:27 +02:00
# include "../gui/CAnimation.h"
2014-07-13 20:53:37 +03:00
# include "../gui/CCursorHandler.h"
# include "../gui/CGuiHandler.h"
# include "../gui/SDL_Extensions.h"
# include "../windows/CAdvmapInterface.h"
# include "../windows/CCreatureWindow.h"
# include "../windows/CSpellWindow.h"
2011-12-14 00:35:28 +03:00
# include "../../CCallback.h"
2017-03-17 17:48:44 +02:00
# include "../../lib/CStack.h"
2012-09-29 13:59:43 +03:00
# include "../../lib/CConfigHandler.h"
2014-07-13 20:53:37 +03:00
# include "../../lib/CGeneralTextHandler.h"
# include "../../lib/CHeroHandler.h"
2011-12-14 00:35:28 +03:00
# include "../../lib/CondSh.h"
2014-07-13 20:53:37 +03:00
# include "../../lib/CRandomGenerator.h"
2015-02-02 10:25:26 +02:00
# include "../../lib/spells/CSpellHandler.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 "../../lib/spells/ISpellMechanics.h"
# include "../../lib/spells/Problem.h"
2011-12-14 00:35:28 +03:00
# include "../../lib/CTownHandler.h"
2022-06-28 10:05:30 +02:00
# include "../../lib/BattleFieldHandler.h"
2022-09-15 10:06:54 +02:00
# include "../../lib/ObstacleHandler.h"
2014-07-15 10:14:49 +03:00
# include "../../lib/CGameState.h"
2013-04-07 13:48:07 +03:00
# include "../../lib/mapping/CMap.h"
2014-07-13 20:53:37 +03:00
# include "../../lib/NetPacks.h"
2012-02-20 00:03:43 +03:00
# include "../../lib/UnlockGuard.h"
2009-01-15 19:01:08 +02:00
2016-11-27 18:13:40 +02:00
CondSh < bool > CBattleInterface : : animsAreDisplayed ( false ) ;
2017-06-14 03:53:26 +02:00
CondSh < BattleAction * > CBattleInterface : : givenCommand ( nullptr ) ;
2009-03-28 20:46:20 +02:00
2018-11-15 00:10:55 +02:00
static void onAnimationFinished ( const CStack * stack , std : : weak_ptr < CCreatureAnimation > anim )
2009-01-15 19:01:08 +02:00
{
2020-10-25 08:38:39 +02:00
std : : shared_ptr < CCreatureAnimation > animation = anim . lock ( ) ;
if ( ! animation )
2018-11-15 00:10:55 +02:00
return ;
if ( animation - > isIdle ( ) )
2009-01-15 19:01:08 +02:00
{
2013-07-06 19:10:20 +03:00
const CCreature * creature = stack - > getCreature ( ) ;
2018-11-15 00:10:55 +02:00
if ( animation - > framesInGroup ( CCreatureAnim : : MOUSEON ) > 0 )
2013-07-06 19:10:20 +03:00
{
2016-10-28 23:37:20 +02:00
if ( CRandomGenerator : : getDefault ( ) . nextDouble ( 99.0 ) < creature - > animation . timeBetweenFidgets * 10 )
2018-11-15 00:10:55 +02:00
animation - > playOnce ( CCreatureAnim : : MOUSEON ) ;
2013-07-06 19:10:20 +03:00
else
2018-11-15 00:10:55 +02:00
animation - > setType ( CCreatureAnim : : HOLDING ) ;
2013-07-06 19:10:20 +03:00
}
else
{
2018-11-15 00:10:55 +02:00
animation - > setType ( CCreatureAnim : : HOLDING ) ;
2013-07-06 19:10:20 +03:00
}
2009-01-15 19:01:08 +02:00
}
2013-07-06 19:10:20 +03:00
// always reset callback
2018-11-15 00:10:55 +02:00
animation - > onAnimationReset + = std : : bind ( & onAnimationFinished , stack , anim ) ;
2013-07-06 19:10:20 +03:00
}
2009-01-15 19:01:08 +02:00
2016-10-28 23:37:20 +02:00
static void transformPalette ( SDL_Surface * surf , double rCor , double gCor , double bCor )
2009-08-01 14:55:40 +03:00
{
2016-10-28 23:37:20 +02:00
SDL_Color * colorsToChange = surf - > format - > palette - > colors ;
for ( int g = 0 ; g < surf - > format - > palette - > ncolors ; + + g )
2009-08-01 14:55:40 +03:00
{
2020-10-01 10:38:06 +02:00
SDL_Color * color = & colorsToChange [ g ] ;
if ( color - > b ! = 132 & &
color - > g ! = 231 & &
color - > r ! = 255 ) //it's not yellow border
2009-08-01 14:55:40 +03:00
{
2020-10-06 01:27:04 +02:00
color - > r = static_cast < Uint8 > ( color - > r * rCor ) ;
color - > g = static_cast < Uint8 > ( color - > g * gCor ) ;
color - > b = static_cast < Uint8 > ( color - > b * bCor ) ;
2009-08-01 14:55:40 +03:00
}
}
}
2009-09-10 14:28:34 +03:00
2016-10-28 23:37:20 +02:00
void CBattleInterface : : addNewAnim ( CBattleAnimation * anim )
2009-09-10 14:28:34 +03:00
{
2011-12-14 00:35:28 +03:00
pendingAnims . push_back ( std : : make_pair ( anim , false ) ) ;
animsAreDisplayed . setn ( true ) ;
2009-09-10 14:28:34 +03:00
}
2016-10-28 23:37:20 +02:00
CBattleInterface : : CBattleInterface ( const CCreatureSet * army1 , const CCreatureSet * army2 ,
2018-07-25 00:36:48 +02:00
const CGHeroInstance * hero1 , const CGHeroInstance * hero2 ,
const SDL_Rect & myRect ,
std : : shared_ptr < CPlayerInterface > att , std : : shared_ptr < CPlayerInterface > defen , std : : shared_ptr < CPlayerInterface > spectatorInt )
: background ( nullptr ) , attackingHeroInstance ( hero1 ) , defendingHeroInstance ( hero2 ) , animCount ( 0 ) ,
activeStack ( nullptr ) , mouseHoveredStack ( nullptr ) , stackToActivate ( nullptr ) , selectedStack ( nullptr ) , previouslyHoveredHex ( - 1 ) ,
currentlyHoveredHex ( - 1 ) , attackingHex ( - 1 ) , stackCanCastSpell ( false ) , creatureCasting ( false ) , spellDestSelectMode ( false ) , spellToCast ( nullptr ) , sp ( nullptr ) ,
creatureSpellToCast ( - 1 ) ,
siegeH ( nullptr ) , attackerInt ( att ) , defenderInt ( defen ) , curInt ( att ) , animIDhelper ( 0 ) ,
2019-03-22 22:39:53 +02:00
myTurn ( false ) , moveStarted ( false ) , moveSoundHander ( - 1 ) , bresult ( nullptr ) , battleActionsStarted ( false )
2009-09-10 14:28:34 +03:00
{
2011-12-17 21:59:59 +03:00
OBJ_CONSTRUCTION ;
2009-09-10 14:28:34 +03:00
2017-06-03 07:25:10 +02:00
if ( spectatorInt )
2018-07-25 00:36:48 +02:00
{
2017-06-03 07:25:10 +02:00
curInt = spectatorInt ;
2018-07-25 00:36:48 +02:00
}
2017-06-03 07:25:10 +02:00
else if ( ! curInt )
2013-06-23 00:47:51 +03:00
{
//May happen when we are defending during network MP game -> attacker interface is just not present
2014-03-07 16:21:09 +03:00
curInt = defenderInt ;
2013-06-23 00:47:51 +03:00
}
2010-01-28 19:23:01 +02:00
2011-12-14 00:35:28 +03:00
animsAreDisplayed . setn ( false ) ;
pos = myRect ;
strongInterest = true ;
2017-06-14 03:53:26 +02:00
givenCommand . setn ( nullptr ) ;
2010-01-28 19:23:01 +02:00
2013-11-09 19:25:20 +03:00
//hot-seat -> check tactics for both players (defender may be local human)
2018-07-25 00:36:48 +02:00
if ( attackerInt & & attackerInt - > cb - > battleGetTacticDist ( ) )
2011-12-14 00:35:28 +03:00
tacticianInterface = attackerInt ;
2018-07-25 00:36:48 +02:00
else if ( defenderInt & & defenderInt - > cb - > battleGetTacticDist ( ) )
2011-12-14 00:35:28 +03:00
tacticianInterface = defenderInt ;
2010-01-28 19:23:01 +02:00
2013-11-09 19:25:20 +03:00
//if we found interface of player with tactics, then enter tactics mode
tacticsMode = static_cast < bool > ( tacticianInterface ) ;
2010-01-28 19:23:01 +02:00
2011-12-14 00:35:28 +03:00
//create stack queue
2017-07-20 06:08:49 +02:00
bool embedQueue ;
std : : string queueSize = settings [ " battle " ] [ " queueSize " ] . String ( ) ;
if ( queueSize = = " auto " )
embedQueue = screen - > h < 700 ;
else
embedQueue = screen - > h < 700 | | queueSize = = " small " ;
2018-07-25 00:36:48 +02:00
queue = std : : make_shared < CStackQueue > ( embedQueue , this ) ;
2017-07-20 06:08:49 +02:00
if ( ! embedQueue )
2011-12-14 00:35:28 +03:00
{
2016-10-28 23:37:20 +02:00
if ( settings [ " battle " ] [ " showQueue " ] . Bool ( ) )
2011-12-14 00:35:28 +03:00
pos . y + = queue - > pos . h / 2 ; //center whole window
2010-01-28 19:23:01 +02:00
2011-12-22 16:05:19 +03:00
queue - > moveTo ( Point ( pos . x , pos . y - queue - > pos . h ) ) ;
2011-12-14 00:35:28 +03:00
}
queue - > update ( ) ;
2010-01-28 19:23:01 +02:00
2011-12-14 00:35:28 +03:00
//preparing siege info
2016-10-28 23:37:20 +02:00
const CGTownInstance * town = curInt - > cb - > battleGetDefendedTown ( ) ;
2018-07-25 00:36:48 +02:00
if ( town & & town - > hasFort ( ) )
2011-12-14 00:35:28 +03:00
{
siegeH = new SiegeHelper ( town , this ) ;
}
2010-01-28 19:23:01 +02:00
2018-07-25 00:36:48 +02:00
CPlayerInterface : : battleInt = this ;
2010-01-28 19:23:01 +02:00
2011-12-14 00:35:28 +03:00
//initializing armies
this - > army1 = army1 ;
this - > army2 = army2 ;
2013-09-27 22:42:17 +03:00
std : : vector < const CStack * > stacks = curInt - > cb - > battleGetAllStacks ( true ) ;
2018-07-25 00:36:48 +02:00
for ( const CStack * s : stacks )
2011-12-14 00:35:28 +03:00
{
2017-07-20 06:08:49 +02:00
unitAdded ( s ) ;
2011-12-14 00:35:28 +03:00
}
2009-09-17 15:59:04 +03:00
2011-12-14 00:35:28 +03:00
//preparing menu background and terrain
2018-07-25 00:36:48 +02:00
if ( siegeH )
2009-09-17 15:59:04 +03:00
{
2011-12-14 00:35:28 +03:00
background = BitmapHandler : : loadBitmap ( siegeH - > getSiegeName ( 0 ) , false ) ;
ui8 siegeLevel = curInt - > cb - > battleGetSiegeLevel ( ) ;
2016-10-28 23:37:20 +02:00
if ( siegeLevel > = 2 ) //citadel or castle
2009-09-17 15:59:04 +03:00
{
2011-12-14 00:35:28 +03:00
//print moat/mlip
2016-10-28 23:37:20 +02:00
SDL_Surface * moat = BitmapHandler : : loadBitmap ( siegeH - > getSiegeName ( 13 ) ) ,
2011-12-14 00:35:28 +03:00
* mlip = BitmapHandler : : loadBitmap ( siegeH - > getSiegeName ( 14 ) ) ;
2009-09-17 15:59:04 +03:00
2012-10-05 21:03:49 +03:00
auto & info = siegeH - > town - > town - > clientInfo ;
Point moatPos ( info . siegePositions [ 13 ] . x , info . siegePositions [ 13 ] . y ) ;
Point mlipPos ( info . siegePositions [ 14 ] . x , info . siegePositions [ 14 ] . y ) ;
2010-07-15 20:13:17 +03:00
2016-10-28 23:37:20 +02:00
if ( moat ) //eg. tower has no moat
2011-12-14 00:35:28 +03:00
blitAt ( moat , moatPos . x , moatPos . y , background ) ;
2016-10-28 23:37:20 +02:00
if ( mlip ) //eg. tower has no mlip
2011-12-14 00:35:28 +03:00
blitAt ( mlip , mlipPos . x , mlipPos . y , background ) ;
2009-09-17 15:59:04 +03:00
2011-12-14 00:35:28 +03:00
SDL_FreeSurface ( moat ) ;
SDL_FreeSurface ( mlip ) ;
2009-09-23 16:22:40 +03:00
}
2009-09-17 15:59:04 +03:00
}
2011-12-14 00:35:28 +03:00
else
2009-09-17 15:59:04 +03:00
{
2022-06-22 10:41:02 +02:00
auto bfieldType = curInt - > cb - > battleGetBattlefieldType ( ) ;
2022-06-28 10:05:30 +02:00
if ( bfieldType = = BattleField : : NONE )
2022-06-22 10:41:02 +02:00
{
2022-06-28 10:05:30 +02:00
logGlobal - > error ( " Invalid battlefield returned for current battle " ) ;
2022-06-22 10:41:02 +02:00
}
2013-06-22 17:47:20 +03:00
else
{
2022-06-28 10:05:30 +02:00
background = BitmapHandler : : loadBitmap ( bfieldType . getInfo ( ) - > graphics , false ) ;
2013-06-22 17:47:20 +03:00
}
2009-09-17 15:59:04 +03:00
}
2012-02-16 20:10:58 +03:00
2011-12-14 00:35:28 +03:00
//preparing graphics for displaying amounts of creatures
amountNormal = BitmapHandler : : loadBitmap ( " CMNUMWIN.BMP " ) ;
CSDL_Ext : : alphaTransform ( amountNormal ) ;
transformPalette ( amountNormal , 0.59 , 0.19 , 0.93 ) ;
2012-02-16 20:10:58 +03:00
2011-12-14 00:35:28 +03:00
amountPositive = BitmapHandler : : loadBitmap ( " CMNUMWIN.BMP " ) ;
CSDL_Ext : : alphaTransform ( amountPositive ) ;
transformPalette ( amountPositive , 0.18 , 1.00 , 0.18 ) ;
2009-09-23 16:22:40 +03:00
2011-12-14 00:35:28 +03:00
amountNegative = BitmapHandler : : loadBitmap ( " CMNUMWIN.BMP " ) ;
CSDL_Ext : : alphaTransform ( amountNegative ) ;
transformPalette ( amountNegative , 1.00 , 0.18 , 0.18 ) ;
2009-09-17 15:59:04 +03:00
2011-12-14 00:35:28 +03:00
amountEffNeutral = BitmapHandler : : loadBitmap ( " CMNUMWIN.BMP " ) ;
CSDL_Ext : : alphaTransform ( amountEffNeutral ) ;
transformPalette ( amountEffNeutral , 1.00 , 1.00 , 0.18 ) ;
2009-09-17 15:59:04 +03:00
2011-12-14 00:35:28 +03:00
//preparing buttons and console
2018-07-25 00:36:48 +02:00
bOptions = std : : make_shared < CButton > ( Point ( 3 , 561 ) , " icm003.def " , CGI - > generaltexth - > zelp [ 381 ] , std : : bind ( & CBattleInterface : : bOptionsf , this ) , SDLK_o ) ;
bSurrender = std : : make_shared < CButton > ( Point ( 54 , 561 ) , " icm001.def " , CGI - > generaltexth - > zelp [ 379 ] , std : : bind ( & CBattleInterface : : bSurrenderf , this ) , SDLK_s ) ;
bFlee = std : : make_shared < CButton > ( Point ( 105 , 561 ) , " icm002.def " , CGI - > generaltexth - > zelp [ 380 ] , std : : bind ( & CBattleInterface : : bFleef , this ) , SDLK_r ) ;
bAutofight = std : : make_shared < CButton > ( Point ( 157 , 561 ) , " icm004.def " , CGI - > generaltexth - > zelp [ 382 ] , std : : bind ( & CBattleInterface : : bAutofightf , this ) , SDLK_a ) ;
bSpell = std : : make_shared < CButton > ( Point ( 645 , 561 ) , " icm005.def " , CGI - > generaltexth - > zelp [ 385 ] , std : : bind ( & CBattleInterface : : bSpellf , this ) , SDLK_c ) ;
bWait = std : : make_shared < CButton > ( Point ( 696 , 561 ) , " icm006.def " , CGI - > generaltexth - > zelp [ 386 ] , std : : bind ( & CBattleInterface : : bWaitf , this ) , SDLK_w ) ;
bDefence = std : : make_shared < CButton > ( Point ( 747 , 561 ) , " icm007.def " , CGI - > generaltexth - > zelp [ 387 ] , std : : bind ( & CBattleInterface : : bDefencef , this ) , SDLK_d ) ;
2011-12-14 00:35:28 +03:00
bDefence - > assignedKeys . insert ( SDLK_SPACE ) ;
2018-07-25 00:36:48 +02:00
bConsoleUp = std : : make_shared < CButton > ( Point ( 624 , 561 ) , " ComSlide.def " , std : : make_pair ( " " , " " ) , std : : bind ( & CBattleInterface : : bConsoleUpf , this ) , SDLK_UP ) ;
bConsoleDown = std : : make_shared < CButton > ( Point ( 624 , 580 ) , " ComSlide.def " , std : : make_pair ( " " , " " ) , std : : bind ( & CBattleInterface : : bConsoleDownf , this ) , SDLK_DOWN ) ;
2019-05-19 19:58:53 +02:00
bConsoleUp - > setImageOrder ( 0 , 1 , 0 , 0 ) ;
bConsoleDown - > setImageOrder ( 2 , 3 , 2 , 2 ) ;
2018-07-25 00:36:48 +02:00
console = std : : make_shared < CBattleConsole > ( ) ;
2012-06-02 18:16:54 +03:00
console - > pos . x + = 211 ;
console - > pos . y + = 560 ;
2011-12-14 00:35:28 +03:00
console - > pos . w = 406 ;
console - > pos . h = 38 ;
2018-07-25 00:36:48 +02:00
if ( tacticsMode )
2009-09-17 15:59:04 +03:00
{
2018-07-25 00:36:48 +02:00
btactNext = std : : make_shared < CButton > ( Point ( 213 , 560 ) , " icm011.def " , std : : make_pair ( " " , " " ) , [ & ] ( ) { bTacticNextStack ( nullptr ) ; } , SDLK_SPACE ) ;
btactEnd = std : : make_shared < CButton > ( Point ( 419 , 560 ) , " icm012.def " , std : : make_pair ( " " , " " ) , [ & ] ( ) { bEndTacticPhase ( ) ; } , SDLK_RETURN ) ;
2011-12-14 00:35:28 +03:00
menu = BitmapHandler : : loadBitmap ( " COPLACBR.BMP " ) ;
2009-09-17 15:59:04 +03:00
}
2011-12-14 00:35:28 +03:00
else
2009-09-17 15:59:04 +03:00
{
2011-12-14 00:35:28 +03:00
menu = BitmapHandler : : loadBitmap ( " CBAR.BMP " ) ;
2009-09-17 15:59:04 +03:00
}
2011-12-14 00:35:28 +03:00
graphics - > blueToPlayersAdv ( menu , curInt - > playerID ) ;
2009-09-17 15:59:04 +03:00
2011-12-14 00:35:28 +03:00
//loading hero animations
2018-07-25 00:36:48 +02:00
if ( hero1 ) // attacking hero
2009-09-16 13:09:08 +03:00
{
2012-12-16 16:47:53 +03:00
std : : string battleImage ;
2018-08-27 08:42:36 +02:00
if ( ! hero1 - > type - > battleImage . empty ( ) )
{
battleImage = hero1 - > type - > battleImage ;
}
2012-12-16 16:47:53 +03:00
else
2018-08-27 08:42:36 +02:00
{
if ( hero1 - > sex )
battleImage = hero1 - > type - > heroClass - > imageBattleFemale ;
else
battleImage = hero1 - > type - > heroClass - > imageBattleMale ;
}
2012-12-16 16:47:53 +03:00
2018-07-25 00:36:48 +02:00
attackingHero = std : : make_shared < CBattleHero > ( battleImage , false , hero1 - > tempOwner , hero1 - > tempOwner = = curInt - > playerID ? hero1 : nullptr , this ) ;
2017-09-05 15:44:27 +02:00
2018-03-30 13:02:04 +02:00
auto img = attackingHero - > animation - > getImage ( 0 , 0 , true ) ;
2017-09-05 15:44:27 +02:00
if ( img )
attackingHero - > pos = genRect ( img - > height ( ) , img - > width ( ) , pos . x - 43 , pos . y - 19 ) ;
2009-09-16 13:09:08 +03:00
}
2018-07-25 00:36:48 +02:00
if ( hero2 ) // defending hero
2009-09-16 13:09:08 +03:00
{
2012-12-16 16:47:53 +03:00
std : : string battleImage ;
2018-08-27 08:42:36 +02:00
if ( ! hero2 - > type - > battleImage . empty ( ) )
{
battleImage = hero2 - > type - > battleImage ;
}
2012-12-16 16:47:53 +03:00
else
2018-08-27 08:42:36 +02:00
{
if ( hero2 - > sex )
battleImage = hero2 - > type - > heroClass - > imageBattleFemale ;
else
battleImage = hero2 - > type - > heroClass - > imageBattleMale ;
}
2012-12-16 16:47:53 +03:00
2018-07-25 00:36:48 +02:00
defendingHero = std : : make_shared < CBattleHero > ( battleImage , true , hero2 - > tempOwner , hero2 - > tempOwner = = curInt - > playerID ? hero2 : nullptr , this ) ;
2017-09-05 15:44:27 +02:00
2018-03-30 13:02:04 +02:00
auto img = defendingHero - > animation - > getImage ( 0 , 0 , true ) ;
2017-09-05 15:44:27 +02:00
if ( img )
defendingHero - > pos = genRect ( img - > height ( ) , img - > width ( ) , pos . x + 693 , pos . y - 19 ) ;
2009-09-16 13:09:08 +03:00
}
2018-07-25 00:36:48 +02:00
2011-09-01 06:12:54 +03:00
2011-12-14 00:35:28 +03:00
//preparing cells and hexes
cellBorder = BitmapHandler : : loadBitmap ( " CCELLGRD.BMP " ) ;
CSDL_Ext : : alphaTransform ( cellBorder ) ;
cellShade = BitmapHandler : : loadBitmap ( " CCELLSHD.BMP " ) ;
CSDL_Ext : : alphaTransform ( cellShade ) ;
2016-10-28 23:37:20 +02:00
for ( int h = 0 ; h < GameConstants : : BFIELD_SIZE ; + + h )
2009-09-10 14:28:34 +03:00
{
2018-07-25 00:36:48 +02:00
auto hex = std : : make_shared < CClickableHex > ( ) ;
2012-06-09 19:45:45 +03:00
hex - > myNumber = h ;
hex - > pos = hexPosition ( h ) ;
hex - > accessible = true ;
hex - > myInterface = this ;
bfield . push_back ( hex ) ;
2009-09-10 14:28:34 +03:00
}
2011-12-14 00:35:28 +03:00
//locking occupied positions on batlefield
2017-07-20 06:08:49 +02:00
for ( const CStack * s : stacks ) //stacks gained at top of this function
if ( s - > initialPosition > = 0 ) //turrets have position < 0
bfield [ s - > getPosition ( ) ] - > accessible = false ;
2009-09-14 15:00:23 +03:00
2011-12-14 00:35:28 +03:00
//preparing graphic with cell borders
cellBorders = CSDL_Ext : : newSurface ( background - > w , background - > h , cellBorder ) ;
//copying palette
2016-10-28 23:37:20 +02:00
for ( int g = 0 ; g < cellBorder - > format - > palette - > ncolors ; + + g ) //we assume that cellBorders->format->palette->ncolors == 256
2011-09-01 06:12:54 +03:00
{
2011-12-14 00:35:28 +03:00
cellBorders - > format - > palette - > colors [ g ] = cellBorder - > format - > palette - > colors [ g ] ;
2011-09-01 06:12:54 +03:00
}
2011-12-14 00:35:28 +03:00
//palette copied
2016-10-28 23:37:20 +02:00
for ( int i = 0 ; i < GameConstants : : BFIELD_HEIGHT ; + + i ) //rows
2011-09-01 06:12:54 +03:00
{
2016-10-28 23:37:20 +02:00
for ( int j = 0 ; j < GameConstants : : BFIELD_WIDTH - 2 ; + + j ) //columns
2011-09-01 06:12:54 +03:00
{
2011-12-14 00:35:28 +03:00
int x = 58 + ( i % 2 = = 0 ? 22 : 0 ) + 44 * j ;
2016-10-28 23:37:20 +02:00
int y = 86 + 42 * i ;
for ( int cellX = 0 ; cellX < cellBorder - > w ; + + cellX )
2011-12-14 00:35:28 +03:00
{
2016-10-28 23:37:20 +02:00
for ( int cellY = 0 ; cellY < cellBorder - > h ; + + cellY )
2011-12-14 00:35:28 +03:00
{
2016-10-28 23:37:20 +02:00
if ( y + cellY < cellBorders - > h & & x + cellX < cellBorders - > w )
* ( ( Uint8 * ) cellBorders - > pixels + ( y + cellY ) * cellBorders - > pitch + ( x + cellX ) ) | = * ( ( Uint8 * ) cellBorder - > pixels + cellY * cellBorder - > pitch + cellX ) ;
2011-12-14 00:35:28 +03:00
}
}
2011-09-01 06:12:54 +03:00
}
}
2011-12-14 00:35:28 +03:00
backgroundWithHexes = CSDL_Ext : : newSurface ( background - > w , background - > h , screen ) ;
2009-09-17 15:59:04 +03:00
2011-12-14 00:35:28 +03:00
//preparing obstacle defs
2012-05-18 23:50:16 +03:00
auto obst = curInt - > cb - > battleGetAllObstacles ( ) ;
2017-07-20 06:08:49 +02:00
for ( auto & elem : obst )
2009-09-10 14:28:34 +03:00
{
2017-07-20 06:08:49 +02:00
if ( elem - > obstacleType = = CObstacleInstance : : USUAL )
2009-09-17 15:59:04 +03:00
{
2022-09-15 10:06:54 +02:00
std : : string animationName = elem - > getInfo ( ) . animation ;
2017-07-20 06:08:49 +02:00
auto cached = animationsCache . find ( animationName ) ;
if ( cached = = animationsCache . end ( ) )
2015-03-22 11:18:58 +02:00
{
2017-07-20 06:08:49 +02:00
auto animation = std : : make_shared < CAnimation > ( animationName ) ;
animationsCache [ animationName ] = animation ;
obstacleAnimations [ elem - > uniqueID ] = animation ;
animation - > preload ( ) ;
}
else
{
obstacleAnimations [ elem - > uniqueID ] = cached - > second ;
2012-04-23 22:56:37 +03:00
}
}
2016-10-28 23:37:20 +02:00
else if ( elem - > obstacleType = = CObstacleInstance : : ABSOLUTE_OBSTACLE )
2012-04-23 22:56:37 +03:00
{
2022-09-15 10:06:54 +02:00
std : : string animationName = elem - > getInfo ( ) . animation ;
2017-07-20 06:08:49 +02:00
auto cached = animationsCache . find ( animationName ) ;
if ( cached = = animationsCache . end ( ) )
{
auto animation = std : : make_shared < CAnimation > ( ) ;
animation - > setCustom ( animationName , 0 , 0 ) ;
animationsCache [ animationName ] = animation ;
obstacleAnimations [ elem - > uniqueID ] = animation ;
animation - > preload ( ) ;
}
else
{
obstacleAnimations [ elem - > uniqueID ] = cached - > second ;
}
2009-09-17 15:59:04 +03:00
}
2009-09-10 14:28:34 +03:00
}
2018-07-25 00:36:48 +02:00
for ( auto hex : bfield )
addChild ( hex . get ( ) ) ;
2009-09-10 14:28:34 +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
if ( tacticsMode )
2011-12-14 00:35:28 +03:00
bTacticNextStack ( ) ;
2009-09-10 14:28:34 +03:00
2011-12-14 00:35:28 +03:00
CCS - > musich - > stopMusic ( ) ;
2019-03-22 22:39:53 +02:00
battleIntroSoundChannel = CCS - > soundh - > playSoundFromSet ( CCS - > soundh - > battleIntroSounds ) ;
auto onIntroPlayed = [ & ] ( )
2012-08-06 10:34:37 +03:00
{
2019-03-22 22:39:53 +02:00
if ( LOCPLINT - > battleInt )
{
2012-08-06 10:34:37 +03:00
CCS - > musich - > playMusicFromSet ( " battle " , true ) ;
2019-03-22 22:39:53 +02:00
battleActionsStarted = true ;
blockUI ( settings [ " session " ] [ " spectate " ] . Bool ( ) ) ;
battleIntroSoundChannel = - 1 ;
}
2012-08-06 10:34:37 +03:00
} ;
2019-03-22 22:39:53 +02:00
CCS - > soundh - > setCallback ( battleIntroSoundChannel , onIntroPlayed ) ;
2012-04-04 11:03:52 +03:00
2019-05-04 05:42:55 +02:00
currentAction = PossiblePlayerBattleAction : : INVALID ;
selectedAction = PossiblePlayerBattleAction : : INVALID ;
2012-06-02 18:16:54 +03:00
addUsedEvents ( RCLICK | MOVE | KEYBOARD ) ;
2019-03-22 22:39:53 +02:00
blockUI ( true ) ;
2009-09-10 14:28:34 +03:00
}
2011-12-14 00:35:28 +03:00
CBattleInterface : : ~ CBattleInterface ( )
2009-09-10 14:28:34 +03:00
{
2018-07-25 00:36:48 +02:00
CPlayerInterface : : battleInt = nullptr ;
2017-06-14 03:53:26 +02:00
givenCommand . cond . notify_all ( ) ; //that two lines should make any activeStack waiting thread to finish
2012-02-20 00:03:43 +03:00
2011-12-14 00:35:28 +03:00
if ( active ) //dirty fix for #485
2009-09-10 14:28:34 +03:00
{
2011-12-14 00:35:28 +03:00
deactivate ( ) ;
2009-09-10 14:28:34 +03:00
}
2011-12-14 00:35:28 +03:00
SDL_FreeSurface ( background ) ;
SDL_FreeSurface ( menu ) ;
SDL_FreeSurface ( amountNormal ) ;
SDL_FreeSurface ( amountNegative ) ;
SDL_FreeSurface ( amountPositive ) ;
SDL_FreeSurface ( amountEffNeutral ) ;
SDL_FreeSurface ( cellBorders ) ;
SDL_FreeSurface ( backgroundWithHexes ) ;
2012-06-09 19:45:45 +03:00
2009-09-10 14:28:34 +03:00
2011-12-14 00:35:28 +03:00
SDL_FreeSurface ( cellBorder ) ;
SDL_FreeSurface ( cellShade ) ;
2009-09-10 14:28:34 +03:00
2011-12-14 00:35:28 +03:00
delete siegeH ;
2014-03-07 16:21:09 +03:00
2011-12-14 00:35:28 +03:00
//TODO: play AI tracks if battle was during AI turn
2012-02-16 20:10:58 +03:00
//if (!curInt->makingTurn)
2011-12-14 00:35:28 +03:00
//CCS->musich->playMusicFromSet(CCS->musich->aiMusics, -1);
2012-02-16 20:10:58 +03:00
2016-10-28 23:37:20 +02:00
if ( adventureInt & & adventureInt - > selection )
2011-12-14 00:35:28 +03:00
{
2022-06-20 16:39:50 +02:00
auto & terrain = LOCPLINT - > cb - > getTile ( adventureInt - > selection - > visitablePos ( ) ) - > terType ;
2012-08-06 10:34:37 +03:00
CCS - > musich - > playMusicFromSet ( " terrain " , terrain , true ) ;
2011-12-14 00:35:28 +03:00
}
2017-06-05 17:43:02 +02:00
animsAreDisplayed . setn ( false ) ;
2009-09-10 14:28:34 +03:00
}
2011-12-14 00:35:28 +03:00
void CBattleInterface : : setPrintCellBorders ( bool set )
2009-09-10 14:28:34 +03:00
{
2012-01-12 18:23:00 +03:00
Settings cellBorders = settings . write [ " battle " ] [ " cellBorders " ] ;
cellBorders - > Bool ( ) = set ;
2011-12-14 00:35:28 +03:00
redrawBackgroundWithHexes ( activeStack ) ;
GH . totalRedraw ( ) ;
2009-09-10 14:28:34 +03:00
}
2011-12-14 00:35:28 +03:00
void CBattleInterface : : setPrintStackRange ( bool set )
{
2012-01-12 18:23:00 +03:00
Settings stackRange = settings . write [ " battle " ] [ " stackRange " ] ;
stackRange - > Bool ( ) = set ;
2011-12-14 00:35:28 +03:00
redrawBackgroundWithHexes ( activeStack ) ;
GH . totalRedraw ( ) ;
}
2009-09-10 14:28:34 +03:00
2011-12-14 00:35:28 +03:00
void CBattleInterface : : setPrintMouseShadow ( bool set )
2009-09-10 14:28:34 +03:00
{
2012-01-12 18:23:00 +03:00
Settings shadow = settings . write [ " battle " ] [ " mouseShadow " ] ;
shadow - > Bool ( ) = set ;
2011-12-14 00:35:28 +03:00
}
2009-09-10 14:28:34 +03:00
2011-12-14 00:35:28 +03:00
void CBattleInterface : : activate ( )
{
2016-10-28 23:37:20 +02:00
if ( curInt - > isAutoFightOn )
2013-06-23 00:47:51 +03:00
{
bAutofight - > activate ( ) ;
return ;
}
2012-06-02 18:16:54 +03:00
CIntObject : : activate ( ) ;
2011-12-14 00:35:28 +03:00
bOptions - > activate ( ) ;
bSurrender - > activate ( ) ;
bFlee - > activate ( ) ;
bAutofight - > activate ( ) ;
bSpell - > activate ( ) ;
bWait - > activate ( ) ;
bDefence - > activate ( ) ;
2012-06-09 19:45:45 +03:00
2016-10-28 23:37:20 +02:00
if ( attackingHero )
2011-12-14 00:35:28 +03:00
attackingHero - > activate ( ) ;
2016-10-28 23:37:20 +02:00
if ( defendingHero )
2011-12-14 00:35:28 +03:00
defendingHero - > activate ( ) ;
2022-09-11 10:31:24 +02:00
for ( auto hex : bfield )
hex - > activate ( ) ;
2016-10-28 23:37:20 +02:00
if ( settings [ " battle " ] [ " showQueue " ] . Bool ( ) )
2011-12-14 00:35:28 +03:00
queue - > activate ( ) ;
2009-09-10 14:28:34 +03:00
2016-10-28 23:37:20 +02:00
if ( tacticsMode )
2009-09-10 14:28:34 +03:00
{
2011-12-14 00:35:28 +03:00
btactNext - > activate ( ) ;
btactEnd - > activate ( ) ;
2009-09-10 14:28:34 +03:00
}
2011-12-14 00:35:28 +03:00
else
2009-09-10 14:28:34 +03:00
{
2011-12-14 00:35:28 +03:00
bConsoleUp - > activate ( ) ;
bConsoleDown - > activate ( ) ;
2009-09-10 14:28:34 +03:00
}
2009-09-11 15:46:26 +03:00
2011-12-14 00:35:28 +03:00
LOCPLINT - > cingconsole - > activate ( ) ;
}
void CBattleInterface : : deactivate ( )
{
2012-06-02 18:16:54 +03:00
CIntObject : : deactivate ( ) ;
2011-12-14 00:35:28 +03:00
bOptions - > deactivate ( ) ;
bSurrender - > deactivate ( ) ;
bFlee - > deactivate ( ) ;
bAutofight - > deactivate ( ) ;
bSpell - > deactivate ( ) ;
bWait - > deactivate ( ) ;
bDefence - > deactivate ( ) ;
2012-06-09 19:45:45 +03:00
2016-10-28 23:37:20 +02:00
for ( auto hex : bfield )
2012-06-09 19:45:45 +03:00
hex - > deactivate ( ) ;
2016-10-28 23:37:20 +02:00
if ( attackingHero )
2011-12-14 00:35:28 +03:00
attackingHero - > deactivate ( ) ;
2016-10-28 23:37:20 +02:00
if ( defendingHero )
2011-12-14 00:35:28 +03:00
defendingHero - > deactivate ( ) ;
2016-10-28 23:37:20 +02:00
if ( settings [ " battle " ] [ " showQueue " ] . Bool ( ) )
2011-12-14 00:35:28 +03:00
queue - > deactivate ( ) ;
2009-09-11 15:46:26 +03:00
2016-10-28 23:37:20 +02:00
if ( tacticsMode )
2009-09-10 14:28:34 +03:00
{
2011-12-14 00:35:28 +03:00
btactNext - > deactivate ( ) ;
btactEnd - > deactivate ( ) ;
2009-09-10 14:28:34 +03:00
}
else
{
2011-12-14 00:35:28 +03:00
bConsoleUp - > deactivate ( ) ;
bConsoleDown - > deactivate ( ) ;
2009-09-20 15:47:40 +03:00
}
2011-12-14 00:35:28 +03:00
LOCPLINT - > cingconsole - > deactivate ( ) ;
2009-09-10 14:28:34 +03:00
}
2011-12-14 00:35:28 +03:00
void CBattleInterface : : keyPressed ( const SDL_KeyboardEvent & key )
{
2021-02-20 03:57:50 +02:00
if ( key . keysym . sym = = SDLK_q & & key . state = = SDL_PRESSED )
2009-09-04 17:11:42 +03:00
{
2021-02-20 03:57:50 +02:00
if ( settings [ " battle " ] [ " showQueue " ] . Bool ( ) ) //hide queue
2011-12-14 00:35:28 +03:00
hideQueue ( ) ;
else
showQueue ( ) ;
2009-09-21 11:29:41 +03:00
2011-12-14 00:35:28 +03:00
}
2021-02-20 03:57:50 +02:00
else if ( key . keysym . sym = = SDLK_f & & key . state = = SDL_PRESSED )
2016-10-15 22:50:12 +02:00
{
enterCreatureCastingMode ( ) ;
}
2021-02-20 03:57:50 +02:00
else if ( key . keysym . sym = = SDLK_ESCAPE )
2009-01-15 19:01:08 +02:00
{
2019-03-22 22:39:53 +02:00
if ( ! battleActionsStarted )
CCS - > soundh - > stopSound ( battleIntroSoundChannel ) ;
else
2021-02-20 03:57:50 +02:00
endCastingSpell ( ) ;
2009-01-15 19:01:08 +02:00
}
2011-12-14 00:35:28 +03:00
}
void CBattleInterface : : mouseMoved ( const SDL_MouseMotionEvent & sEvent )
{
2018-07-25 00:36:48 +02:00
auto hexItr = std : : find_if ( bfield . begin ( ) , bfield . end ( ) , [ ] ( std : : shared_ptr < CClickableHex > hex )
2011-12-14 00:35:28 +03:00
{
2012-06-09 19:45:45 +03:00
return hex - > hovered & & hex - > strictHovered ;
2012-03-31 00:36:07 +03:00
} ) ;
2011-12-14 00:35:28 +03:00
2012-06-09 19:45:45 +03:00
handleHex ( hexItr = = bfield . end ( ) ? - 1 : ( * hexItr ) - > myNumber , MOVE ) ;
2011-12-14 00:35:28 +03:00
}
void CBattleInterface : : setBattleCursor ( const int myNumber )
{
2012-06-09 19:45:45 +03:00
const CClickableHex & hoveredHex = * bfield [ myNumber ] ;
2011-12-14 00:35:28 +03:00
CCursorHandler * cursor = CCS - > curh ;
const double subdividingAngle = 2.0 * M_PI / 6.0 ; // Divide a hex into six sectors.
2017-10-29 17:23:30 +02:00
const double hexMidX = hoveredHex . pos . x + hoveredHex . pos . w / 2.0 ;
const double hexMidY = hoveredHex . pos . y + hoveredHex . pos . h / 2.0 ;
2011-12-14 00:35:28 +03:00
const double cursorHexAngle = M_PI - atan2 ( hexMidY - cursor - > ypos , cursor - > xpos - hexMidX ) + subdividingAngle / 2 ; //TODO: refactor this nightmare
const double sector = fmod ( cursorHexAngle / subdividingAngle , 6.0 ) ;
const int zigzagCorrection = ! ( ( myNumber / GameConstants : : BFIELD_WIDTH ) % 2 ) ; // Off-by-one correction needed to deal with the odd battlefield rows.
std : : vector < int > sectorCursor ; // From left to bottom left.
sectorCursor . push_back ( 8 ) ;
sectorCursor . push_back ( 9 ) ;
sectorCursor . push_back ( 10 ) ;
sectorCursor . push_back ( 11 ) ;
sectorCursor . push_back ( 12 ) ;
sectorCursor . push_back ( 7 ) ;
2011-10-01 22:56:54 +03:00
2011-12-14 00:35:28 +03:00
const bool doubleWide = activeStack - > doubleWide ( ) ;
bool aboveAttackable = true , belowAttackable = true ;
2011-10-01 22:56:54 +03:00
2011-12-14 00:35:28 +03:00
// Exclude directions which cannot be attacked from.
// Check to the left.
2012-02-16 20:10:58 +03:00
if ( myNumber % GameConstants : : BFIELD_WIDTH < = 1 | | ! vstd : : contains ( occupyableHexes , myNumber - 1 ) )
2011-12-14 00:35:28 +03:00
{
sectorCursor [ 0 ] = - 1 ;
}
// Check top left, top right as well as above for 2-hex creatures.
2012-02-16 20:10:58 +03:00
if ( myNumber / GameConstants : : BFIELD_WIDTH = = 0 )
2011-12-14 00:35:28 +03:00
{
sectorCursor [ 1 ] = - 1 ;
sectorCursor [ 2 ] = - 1 ;
aboveAttackable = false ;
2012-02-16 20:10:58 +03:00
}
else
2011-12-14 00:35:28 +03:00
{
2012-02-16 20:10:58 +03:00
if ( doubleWide )
2011-12-14 00:35:28 +03:00
{
bool attackRow [ 4 ] = { true , true , true , true } ;
2011-10-01 22:56:54 +03:00
2011-12-14 00:35:28 +03:00
if ( myNumber % GameConstants : : BFIELD_WIDTH < = 1 | | ! vstd : : contains ( occupyableHexes , myNumber - GameConstants : : BFIELD_WIDTH - 2 + zigzagCorrection ) )
attackRow [ 0 ] = false ;
if ( ! vstd : : contains ( occupyableHexes , myNumber - GameConstants : : BFIELD_WIDTH - 1 + zigzagCorrection ) )
attackRow [ 1 ] = false ;
if ( ! vstd : : contains ( occupyableHexes , myNumber - GameConstants : : BFIELD_WIDTH + zigzagCorrection ) )
attackRow [ 2 ] = false ;
if ( myNumber % GameConstants : : BFIELD_WIDTH > = GameConstants : : BFIELD_WIDTH - 2 | | ! vstd : : contains ( occupyableHexes , myNumber - GameConstants : : BFIELD_WIDTH + 1 + zigzagCorrection ) )
attackRow [ 3 ] = false ;
2011-10-01 22:56:54 +03:00
2011-12-14 00:35:28 +03:00
if ( ! ( attackRow [ 0 ] & & attackRow [ 1 ] ) )
sectorCursor [ 1 ] = - 1 ;
if ( ! ( attackRow [ 1 ] & & attackRow [ 2 ] ) )
aboveAttackable = false ;
if ( ! ( attackRow [ 2 ] & & attackRow [ 3 ] ) )
sectorCursor [ 2 ] = - 1 ;
}
else
{
if ( ! vstd : : contains ( occupyableHexes , myNumber - GameConstants : : BFIELD_WIDTH - 1 + zigzagCorrection ) )
sectorCursor [ 1 ] = - 1 ;
if ( ! vstd : : contains ( occupyableHexes , myNumber - GameConstants : : BFIELD_WIDTH + zigzagCorrection ) )
sectorCursor [ 2 ] = - 1 ;
}
}
// Check to the right.
if ( myNumber % GameConstants : : BFIELD_WIDTH > = GameConstants : : BFIELD_WIDTH - 2 | | ! vstd : : contains ( occupyableHexes , myNumber + 1 ) )
{
sectorCursor [ 3 ] = - 1 ;
}
// Check bottom right, bottom left as well as below for 2-hex creatures.
if ( myNumber / GameConstants : : BFIELD_WIDTH = = GameConstants : : BFIELD_HEIGHT - 1 )
{
sectorCursor [ 4 ] = - 1 ;
sectorCursor [ 5 ] = - 1 ;
belowAttackable = false ;
2012-02-16 20:10:58 +03:00
}
else
2011-12-14 00:35:28 +03:00
{
if ( doubleWide )
{
bool attackRow [ 4 ] = { true , true , true , true } ;
2011-10-01 22:56:54 +03:00
2011-12-14 00:35:28 +03:00
if ( myNumber % GameConstants : : BFIELD_WIDTH < = 1 | | ! vstd : : contains ( occupyableHexes , myNumber + GameConstants : : BFIELD_WIDTH - 2 + zigzagCorrection ) )
attackRow [ 0 ] = false ;
if ( ! vstd : : contains ( occupyableHexes , myNumber + GameConstants : : BFIELD_WIDTH - 1 + zigzagCorrection ) )
attackRow [ 1 ] = false ;
if ( ! vstd : : contains ( occupyableHexes , myNumber + GameConstants : : BFIELD_WIDTH + zigzagCorrection ) )
attackRow [ 2 ] = false ;
if ( myNumber % GameConstants : : BFIELD_WIDTH > = GameConstants : : BFIELD_WIDTH - 2 | | ! vstd : : contains ( occupyableHexes , myNumber + GameConstants : : BFIELD_WIDTH + 1 + zigzagCorrection ) )
attackRow [ 3 ] = false ;
2011-10-01 22:56:54 +03:00
2011-12-14 00:35:28 +03:00
if ( ! ( attackRow [ 0 ] & & attackRow [ 1 ] ) )
sectorCursor [ 5 ] = - 1 ;
if ( ! ( attackRow [ 1 ] & & attackRow [ 2 ] ) )
belowAttackable = false ;
if ( ! ( attackRow [ 2 ] & & attackRow [ 3 ] ) )
sectorCursor [ 4 ] = - 1 ;
2012-02-16 20:10:58 +03:00
}
else
2011-12-14 00:35:28 +03:00
{
if ( ! vstd : : contains ( occupyableHexes , myNumber + GameConstants : : BFIELD_WIDTH + zigzagCorrection ) )
sectorCursor [ 4 ] = - 1 ;
if ( ! vstd : : contains ( occupyableHexes , myNumber + GameConstants : : BFIELD_WIDTH - 1 + zigzagCorrection ) )
sectorCursor [ 5 ] = - 1 ;
2009-01-15 19:01:08 +02:00
}
}
2011-12-14 00:35:28 +03:00
// Determine index from sector.
int cursorIndex ;
2012-02-16 20:10:58 +03:00
if ( doubleWide )
2011-12-14 00:35:28 +03:00
{
sectorCursor . insert ( sectorCursor . begin ( ) + 5 , belowAttackable ? 13 : - 1 ) ;
sectorCursor . insert ( sectorCursor . begin ( ) + 2 , aboveAttackable ? 14 : - 1 ) ;
if ( sector < 1.5 )
2020-10-01 10:38:06 +02:00
cursorIndex = static_cast < int > ( sector ) ;
2011-12-14 00:35:28 +03:00
else if ( sector > = 1.5 & & sector < 2.5 )
cursorIndex = 2 ;
else if ( sector > = 2.5 & & sector < 4.5 )
cursorIndex = ( int ) sector + 1 ;
else if ( sector > = 4.5 & & sector < 5.5 )
cursorIndex = 6 ;
else
cursorIndex = ( int ) sector + 2 ;
2012-02-16 20:10:58 +03:00
}
else
2011-12-14 00:35:28 +03:00
{
2020-10-01 10:38:06 +02:00
cursorIndex = static_cast < int > ( sector ) ;
2011-12-14 00:35:28 +03:00
}
2012-09-28 22:18:08 +03:00
// Generally should NEVER happen, but to avoid the possibility of having endless loop below... [#1016]
2016-10-28 23:37:20 +02:00
if ( ! vstd : : contains_if ( sectorCursor , [ ] ( int sc ) { return sc ! = - 1 ; } ) )
2012-09-28 22:18:08 +03:00
{
2017-08-11 13:38:10 +02:00
logGlobal - > error ( " Error: for hex %d cannot find a hex to attack from! " , myNumber ) ;
2012-09-28 22:18:08 +03:00
attackingHex = - 1 ;
return ;
}
2011-12-14 00:35:28 +03:00
// Find the closest direction attackable, starting with the right one.
// FIXME: Is this really how the original H3 client does it?
int i = 0 ;
while ( sectorCursor [ ( cursorIndex + i ) % sectorCursor . size ( ) ] = = - 1 ) //Why hast thou forsaken me?
i = i < = 0 ? 1 - i : - i ; // 0, 1, -1, 2, -2, 3, -3 etc..
int index = ( cursorIndex + i ) % sectorCursor . size ( ) ; //hopefully we get elements from sectorCursor
2012-12-14 18:32:53 +03:00
cursor - > changeGraphic ( ECursor : : COMBAT , sectorCursor [ index ] ) ;
2011-12-14 00:35:28 +03:00
switch ( index )
{
case 0 :
attackingHex = myNumber - 1 ; //left
break ;
case 1 :
attackingHex = myNumber - GameConstants : : BFIELD_WIDTH - 1 + zigzagCorrection ; //top left
break ;
case 2 :
attackingHex = myNumber - GameConstants : : BFIELD_WIDTH + zigzagCorrection ; //top right
break ;
case 3 :
attackingHex = myNumber + 1 ; //right
2016-03-12 03:41:27 +02:00
break ;
2011-12-14 00:35:28 +03:00
case 4 :
attackingHex = myNumber + GameConstants : : BFIELD_WIDTH + zigzagCorrection ; //bottom right
2016-03-12 03:41:27 +02:00
break ;
case 5 :
2011-12-14 00:35:28 +03:00
attackingHex = myNumber + GameConstants : : BFIELD_WIDTH - 1 + zigzagCorrection ; //bottom left
break ;
}
2011-12-22 16:05:19 +03:00
BattleHex hex ( attackingHex ) ;
2011-12-14 00:35:28 +03:00
if ( ! hex . isValid ( ) )
attackingHex = - 1 ;
2009-01-15 19:01:08 +02:00
}
2011-12-14 00:35:28 +03:00
void CBattleInterface : : clickRight ( tribool down , bool previousState )
2009-09-24 16:44:55 +03:00
{
2016-10-28 23:37:20 +02:00
if ( ! down )
2009-09-24 16:44:55 +03:00
{
2011-12-14 00:35:28 +03:00
endCastingSpell ( ) ;
}
}
2009-09-24 16:44:55 +03:00
2011-12-14 00:35:28 +03:00
void CBattleInterface : : bOptionsf ( )
{
2016-10-28 23:37:20 +02:00
if ( spellDestSelectMode ) //we are casting a spell
2011-12-14 00:35:28 +03:00
return ;
2012-12-14 18:32:53 +03:00
CCS - > curh - > changeGraphic ( ECursor : : ADVENTURE , 0 ) ;
2011-12-14 00:35:28 +03:00
2011-12-22 16:05:19 +03:00
Rect tempRect = genRect ( 431 , 481 , 160 , 84 ) ;
2011-12-14 00:35:28 +03:00
tempRect + = pos . topLeft ( ) ;
2018-07-25 00:36:48 +02:00
GH . pushIntT < CBattleOptionsWindow > ( tempRect , this ) ;
2011-12-14 00:35:28 +03:00
}
void CBattleInterface : : bSurrenderf ( )
{
2021-02-20 03:57:50 +02:00
if ( spellDestSelectMode ) //we are casting a spell
2011-12-14 00:35:28 +03:00
return ;
int cost = curInt - > cb - > battleGetSurrenderCost ( ) ;
2021-02-20 03:57:50 +02:00
if ( cost > = 0 )
2011-12-14 00:35:28 +03:00
{
2013-01-20 23:29:35 +03:00
std : : string enemyHeroName = curInt - > cb - > battleGetEnemyHero ( ) . name ;
2021-02-20 03:57:50 +02:00
if ( enemyHeroName . empty ( ) )
2019-05-19 12:22:41 +02:00
{
logGlobal - > warn ( " Surrender performed without enemy hero, should not happen! " ) ;
enemyHeroName = " #ENEMY# " ;
}
2013-01-20 23:29:35 +03:00
2011-12-14 00:35:28 +03:00
std : : string surrenderMessage = boost : : str ( boost : : format ( CGI - > generaltexth - > allTexts [ 32 ] ) % enemyHeroName % cost ) ; //%s states: "I will accept your surrender and grant you and your troops safe passage for the price of %d gold."
2018-04-07 13:34:11 +02:00
curInt - > showYesNoDialog ( surrenderMessage , [ this ] ( ) { reallySurrender ( ) ; } , nullptr ) ;
2011-12-14 00:35:28 +03:00
}
}
void CBattleInterface : : bFleef ( )
{
2016-10-28 23:37:20 +02:00
if ( spellDestSelectMode ) //we are casting a spell
2011-12-14 00:35:28 +03:00
return ;
2016-10-28 23:37:20 +02:00
if ( curInt - > cb - > battleCanFlee ( ) )
2011-12-14 00:35:28 +03:00
{
2014-08-04 21:33:59 +03:00
CFunctionList < void ( ) > ony = std : : bind ( & CBattleInterface : : reallyFlee , this ) ;
2018-04-07 13:34:11 +02:00
curInt - > showYesNoDialog ( CGI - > generaltexth - > allTexts [ 28 ] , ony , nullptr ) ; //Are you sure you want to retreat?
2011-12-14 00:35:28 +03:00
}
else
{
2018-04-07 13:34:11 +02:00
std : : vector < std : : shared_ptr < CComponent > > comps ;
2011-12-14 00:35:28 +03:00
std : : string heroName ;
//calculating fleeing hero's name
2016-10-28 23:37:20 +02:00
if ( attackingHeroInstance )
if ( attackingHeroInstance - > tempOwner = = curInt - > cb - > getMyColor ( ) )
2011-12-14 00:35:28 +03:00
heroName = attackingHeroInstance - > name ;
2016-10-28 23:37:20 +02:00
if ( defendingHeroInstance )
if ( defendingHeroInstance - > tempOwner = = curInt - > cb - > getMyColor ( ) )
2011-12-14 00:35:28 +03:00
heroName = defendingHeroInstance - > name ;
//calculating text
2014-11-22 13:45:13 +02:00
auto txt = boost : : format ( CGI - > generaltexth - > allTexts [ 340 ] ) % heroName ; //The Shackles of War are present. %s can not retreat!
2011-12-14 00:35:28 +03:00
//printing message
2014-11-22 13:45:13 +02:00
curInt - > showInfoDialog ( boost : : to_string ( txt ) , comps ) ;
2011-12-14 00:35:28 +03:00
}
}
void CBattleInterface : : reallyFlee ( )
{
2017-07-20 06:08:49 +02:00
giveCommand ( EActionType : : RETREAT ) ;
2012-12-14 18:32:53 +03:00
CCS - > curh - > changeGraphic ( ECursor : : ADVENTURE , 0 ) ;
2011-12-14 00:35:28 +03:00
}
void CBattleInterface : : reallySurrender ( )
{
2016-10-28 23:37:20 +02:00
if ( curInt - > cb - > getResourceAmount ( Res : : GOLD ) < curInt - > cb - > battleGetSurrenderCost ( ) )
2011-12-14 00:35:28 +03:00
{
curInt - > showInfoDialog ( CGI - > generaltexth - > allTexts [ 29 ] ) ; //You don't have enough gold!
}
else
{
2017-07-20 06:08:49 +02:00
giveCommand ( EActionType : : SURRENDER ) ;
2012-12-14 18:32:53 +03:00
CCS - > curh - > changeGraphic ( ECursor : : ADVENTURE , 0 ) ;
2009-09-24 16:44:55 +03:00
}
}
2011-12-14 00:35:28 +03:00
void CBattleInterface : : bAutofightf ( )
2009-09-15 15:20:11 +03:00
{
2016-10-28 23:37:20 +02:00
if ( spellDestSelectMode ) //we are casting a spell
2011-12-14 00:35:28 +03:00
return ;
2014-03-07 16:21:09 +03:00
2013-06-23 00:47:51 +03:00
//Stop auto-fight mode
2016-10-28 23:37:20 +02:00
if ( curInt - > isAutoFightOn )
2013-06-22 17:47:20 +03:00
{
2013-06-23 14:25:48 +03:00
assert ( curInt - > autofightingAI ) ;
curInt - > isAutoFightOn = false ;
2017-08-10 18:39:27 +02:00
logGlobal - > trace ( " Stopping the autofight... " ) ;
2013-06-22 17:47:20 +03:00
}
else
{
2014-03-07 16:21:09 +03:00
curInt - > isAutoFightOn = true ;
2013-07-21 13:10:38 +03:00
blockUI ( true ) ;
2013-06-23 14:25:48 +03:00
2017-01-17 13:17:37 +02:00
auto ai = CDynLibHandler : : getNewBattleAI ( settings [ " server " ] [ " friendlyAI " ] . String ( ) ) ;
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
ai - > init ( curInt - > env , curInt - > cb ) ;
2013-06-23 14:25:48 +03:00
ai - > battleStart ( army1 , army2 , int3 ( 0 , 0 , 0 ) , attackingHeroInstance , defendingHeroInstance , curInt - > cb - > battleGetMySide ( ) ) ;
curInt - > autofightingAI = ai ;
curInt - > cb - > registerBattleInterface ( ai ) ;
2013-06-22 17:47:20 +03:00
2013-06-23 00:47:51 +03:00
requestAutofightingAIToTakeAction ( ) ;
2013-06-22 17:47:20 +03:00
}
2009-09-15 15:20:11 +03:00
}
2011-12-14 00:35:28 +03:00
void CBattleInterface : : bSpellf ( )
2009-01-15 19:01:08 +02:00
{
2016-10-28 23:37:20 +02:00
if ( spellDestSelectMode ) //we are casting a spell
2011-12-14 00:35:28 +03:00
return ;
2016-10-28 23:37:20 +02:00
if ( ! myTurn )
2012-08-26 12:07:48 +03:00
return ;
auto myHero = currentHero ( ) ;
2017-06-05 21:16:12 +02:00
if ( ! myHero )
return ;
CCS - > curh - > changeGraphic ( ECursor : : ADVENTURE , 0 ) ;
2017-07-20 06:08:49 +02:00
ESpellCastProblem : : ESpellCastProblem spellCastProblem = curInt - > cb - > battleCanCastSpell ( myHero , spells : : Mode : : HERO ) ;
2017-06-05 21:16:12 +02:00
if ( spellCastProblem = = ESpellCastProblem : : OK )
2012-01-19 17:33:22 +03:00
{
2018-07-25 00:36:48 +02:00
GH . pushIntT < CSpellWindow > ( myHero , curInt . get ( ) ) ;
2012-01-19 17:33:22 +03:00
}
2016-10-28 23:37:20 +02:00
else if ( spellCastProblem = = ESpellCastProblem : : MAGIC_IS_BLOCKED )
2012-08-26 12:07:48 +03:00
{
2017-06-05 21:16:12 +02:00
//TODO: move to spell mechanics, add more information to spell cast problem
2012-08-26 12:07:48 +03:00
//Handle Orb of Inhibition-like effects -> we want to display dialog with info, why casting is impossible
2020-11-11 21:43:40 +02:00
auto blockingBonus = currentHero ( ) - > getBonusLocalFirst ( Selector : : type ( ) ( Bonus : : BLOCK_ALL_MAGIC ) ) ;
2016-10-28 23:37:20 +02:00
if ( ! blockingBonus )
2017-07-12 21:01:10 +02:00
return ;
2014-03-07 16:21:09 +03:00
2016-10-28 23:37:20 +02:00
if ( blockingBonus - > source = = Bonus : : ARTIFACT )
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
const int32_t artID = blockingBonus - > sid ;
2012-08-26 12:07:48 +03:00
//If we have artifact, put name of our hero. Otherwise assume it's the enemy.
//TODO check who *really* is source of bonus
std : : string heroName = myHero - > hasArt ( artID ) ? myHero - > name : enemyHero ( ) . name ;
//%s wields the %s, an ancient artifact which creates a p dead to all magic.
2014-03-07 16:21:09 +03:00
LOCPLINT - > showInfoDialog ( boost : : str ( boost : : format ( CGI - > generaltexth - > allTexts [ 683 ] )
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
% heroName % CGI - > artifacts ( ) - > getByIndex ( artID ) - > getName ( ) ) ) ;
2012-08-26 12:07:48 +03:00
}
}
2009-01-15 19:01:08 +02:00
}
2011-12-14 00:35:28 +03:00
void CBattleInterface : : bWaitf ( )
2009-01-15 19:01:08 +02:00
{
2016-10-28 23:37:20 +02:00
if ( spellDestSelectMode ) //we are casting a spell
2011-12-14 00:35:28 +03:00
return ;
2009-01-15 19:01:08 +02:00
2016-10-28 23:37:20 +02:00
if ( activeStack ! = nullptr )
2017-07-20 06:08:49 +02:00
giveCommand ( EActionType : : WAIT ) ;
2011-12-14 00:35:28 +03:00
}
2009-01-15 19:01:08 +02:00
2011-12-14 00:35:28 +03:00
void CBattleInterface : : bDefencef ( )
{
2016-10-28 23:37:20 +02:00
if ( spellDestSelectMode ) //we are casting a spell
2011-12-14 00:35:28 +03:00
return ;
2009-01-15 19:01:08 +02:00
2016-10-28 23:37:20 +02:00
if ( activeStack ! = nullptr )
2017-07-20 06:08:49 +02:00
giveCommand ( EActionType : : DEFEND ) ;
2011-12-14 00:35:28 +03:00
}
2009-09-17 15:59:04 +03:00
2011-12-14 00:35:28 +03:00
void CBattleInterface : : bConsoleUpf ( )
{
2016-10-28 23:37:20 +02:00
if ( spellDestSelectMode ) //we are casting a spell
2011-12-14 00:35:28 +03:00
return ;
2009-07-26 15:15:38 +03:00
2011-12-14 00:35:28 +03:00
console - > scrollUp ( ) ;
}
2009-11-08 16:44:58 +02:00
2011-12-14 00:35:28 +03:00
void CBattleInterface : : bConsoleDownf ( )
{
2016-10-28 23:37:20 +02:00
if ( spellDestSelectMode ) //we are casting a spell
2011-12-14 00:35:28 +03:00
return ;
console - > scrollDown ( ) ;
}
2017-07-20 06:08:49 +02:00
void CBattleInterface : : unitAdded ( const CStack * stack )
2011-12-14 00:35:28 +03:00
{
2017-07-01 10:34:00 +02:00
creDir [ stack - > ID ] = stack - > side = = BattleSide : : ATTACKER ; // must be set before getting stack position
2013-07-07 22:44:08 +03:00
2017-07-20 06:08:49 +02:00
Point coords = CClickableHex : : getXYUnitAnim ( stack - > getPosition ( ) , stack , this ) ;
2011-12-14 00:35:28 +03:00
2017-07-20 06:08:49 +02:00
if ( stack - > initialPosition < 0 ) //turret
2009-11-08 16:44:58 +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
const CCreature * turretCreature = CGI - > creh - > objects [ siegeH - > town - > town - > clientInfo . siegeShooter ] ;
2013-07-06 19:10:20 +03:00
creAnims [ stack - > ID ] = AnimationControls : : getAnimation ( turretCreature ) ;
2011-12-14 00:35:28 +03:00
2013-07-28 17:49:50 +03:00
// Turret positions are read out of the config/wall_pos.txt
2011-12-14 00:35:28 +03:00
int posID = 0 ;
2017-07-20 06:08:49 +02:00
switch ( stack - > initialPosition )
2010-03-02 13:40:29 +02:00
{
2011-12-14 00:35:28 +03:00
case - 2 : // keep creature
posID = 18 ;
break ;
case - 3 : // bottom creature
posID = 19 ;
break ;
case - 4 : // upper creature
posID = 20 ;
break ;
2010-03-02 13:40:29 +02:00
}
2011-12-14 00:35:28 +03:00
if ( posID ! = 0 )
2010-03-02 13:40:29 +02:00
{
2012-10-05 21:03:49 +03:00
coords . x = siegeH - > town - > town - > clientInfo . siegePositions [ posID ] . x + this - > pos . x ;
coords . y = siegeH - > town - > town - > clientInfo . siegePositions [ posID ] . y + this - > pos . y ;
2010-03-02 13:40:29 +02:00
}
2013-08-01 01:24:44 +03:00
creAnims [ stack - > ID ] - > pos . h = 225 ;
2011-12-14 00:35:28 +03:00
}
else
{
2013-07-06 19:10:20 +03:00
creAnims [ stack - > ID ] = AnimationControls : : getAnimation ( stack - > getCreature ( ) ) ;
creAnims [ stack - > ID ] - > onAnimationReset + = std : : bind ( & onAnimationFinished , stack , creAnims [ stack - > ID ] ) ;
creAnims [ stack - > ID ] - > pos . h = creAnims [ stack - > ID ] - > getHeight ( ) ;
2011-12-14 00:35:28 +03:00
}
2013-07-06 19:10:20 +03:00
creAnims [ stack - > ID ] - > pos . x = coords . x ;
creAnims [ stack - > ID ] - > pos . y = coords . y ;
creAnims [ stack - > ID ] - > pos . w = creAnims [ stack - > ID ] - > getWidth ( ) ;
2011-12-14 00:35:28 +03:00
creAnims [ stack - > ID ] - > setType ( CCreatureAnim : : HOLDING ) ;
2014-03-07 16:21:09 +03:00
2017-01-26 21:53:28 +02:00
//loading projectiles for units
2017-07-20 06:08:49 +02:00
if ( stack - > isShooter ( ) )
2017-01-26 21:53:28 +02:00
{
2017-02-02 21:27:51 +02:00
initStackProjectile ( stack ) ;
}
}
2017-01-26 21:53:28 +02:00
2017-02-02 21:27:51 +02:00
void CBattleInterface : : initStackProjectile ( const CStack * stack )
{
2017-09-05 19:04:17 +02:00
const CCreature * creature ; //creature whose shots should be loaded
if ( stack - > getCreature ( ) - > idNumber = = CreatureID : : ARROW_TOWERS )
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
creature = CGI - > creh - > objects [ siegeH - > town - > town - > clientInfo . siegeShooter ] ;
2017-02-02 21:27:51 +02:00
else
creature = stack - > getCreature ( ) ;
2017-01-26 21:53:28 +02:00
2017-09-05 19:04:17 +02:00
std : : shared_ptr < CAnimation > projectile = std : : make_shared < CAnimation > ( creature - > animation . projectileImageName ) ;
projectile - > preload ( ) ;
2017-02-02 21:27:51 +02:00
2017-09-05 19:04:17 +02:00
if ( projectile - > size ( 1 ) ! = 0 )
logAnim - > error ( " Expected empty group 1 in stack projectile " ) ;
else
projectile - > createFlippedGroup ( 0 , 1 ) ;
idToProjectile [ stack - > getCreature ( ) - > idNumber ] = projectile ;
2011-12-14 00:35:28 +03:00
}
2017-07-20 06:08:49 +02:00
void CBattleInterface : : stackRemoved ( uint32_t stackID )
2011-12-14 00:35:28 +03:00
{
2016-10-28 23:37:20 +02:00
if ( activeStack ! = nullptr )
2015-10-06 02:46:35 +02:00
{
2016-10-28 23:37:20 +02:00
if ( activeStack - > ID = = stackID )
2015-10-06 02:46:35 +02:00
{
2016-10-28 23:37:20 +02:00
BattleAction * action = new BattleAction ( ) ;
2015-10-08 07:15:29 +02:00
action - > side = defendingHeroInstance ? ( curInt - > playerID = = defendingHeroInstance - > tempOwner ) : false ;
2017-07-20 06:08:49 +02:00
action - > actionType = EActionType : : CANCEL ;
2015-10-08 07:15:29 +02:00
action - > stackNumber = activeStack - > ID ;
2017-06-14 03:53:26 +02:00
givenCommand . setn ( action ) ;
2015-10-06 02:46:35 +02:00
setActiveStack ( nullptr ) ;
}
}
2016-01-26 08:37:55 +02:00
2016-03-01 04:39:04 +02:00
//todo: ensure that ghost stack animation has fadeout effect
2012-09-18 18:17:32 +03:00
redrawBackgroundWithHexes ( activeStack ) ;
2012-02-22 20:43:59 +03:00
queue - > update ( ) ;
2011-12-14 00:35:28 +03:00
}
2016-10-28 23:37:20 +02:00
void CBattleInterface : : stackActivated ( const CStack * stack ) //TODO: check it all before game state is changed due to abilities
2011-12-14 00:35:28 +03:00
{
stackToActivate = stack ;
2012-02-18 20:39:47 +03:00
waitForAnims ( ) ;
2016-10-28 23:37:20 +02:00
if ( stackToActivate ) //during waiting stack may have gotten activated through show
2011-12-14 00:35:28 +03:00
activateStack ( ) ;
}
2016-10-28 23:37:20 +02:00
void CBattleInterface : : stackMoved ( const CStack * stack , std : : vector < BattleHex > destHex , int distance )
2011-12-14 00:35:28 +03:00
{
addNewAnim ( new CMovementAnimation ( this , stack , destHex , distance ) ) ;
waitForAnims ( ) ;
}
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
void CBattleInterface : : stacksAreAttacked ( std : : vector < StackAttackedInfo > attackedInfos )
2011-12-14 00:35:28 +03:00
{
2017-07-20 06:08:49 +02:00
for ( auto & attackedInfo : attackedInfos )
2011-12-14 00:35:28 +03:00
{
2014-09-23 16:02:30 +03:00
//if (!attackedInfo.cloneKilled) //FIXME: play dead animation for cloned creature before it vanishes
2013-06-29 16:05:48 +03:00
addNewAnim ( new CDefenceAnimation ( attackedInfo , this ) ) ;
2014-09-23 16:02:30 +03:00
2017-07-20 06:08:49 +02:00
if ( attackedInfo . rebirth )
2011-06-01 21:26:44 +03:00
{
2017-07-20 06:08:49 +02:00
displayEffect ( 50 , attackedInfo . defender - > getPosition ( ) ) ; //TODO: play reverse death animation
2011-12-14 00:35:28 +03:00
CCS - > soundh - > playSound ( soundBase : : RESURECT ) ;
2011-06-01 21:26:44 +03:00
}
2009-11-08 16:44:58 +02:00
}
2011-12-14 00:35:28 +03:00
waitForAnims ( ) ;
2017-09-27 16:35:30 +02:00
std : : array < int , 2 > killedBySide = { 0 , 0 } ;
int targets = 0 , damage = 0 ;
for ( const StackAttackedInfo & attackedInfo : attackedInfos )
2009-11-08 16:44:58 +02:00
{
2011-12-14 00:35:28 +03:00
+ + targets ;
2020-10-01 10:38:06 +02:00
damage + = ( int ) attackedInfo . dmg ;
2017-09-27 16:35:30 +02:00
ui8 side = attackedInfo . defender - > side ;
killedBySide . at ( side ) + = attackedInfo . amountKilled ;
}
for ( ui8 side = 0 ; side < 2 ; side + + )
{
if ( killedBySide . at ( side ) > killedBySide . at ( 1 - side ) )
setHeroAnimation ( side , 2 ) ;
else if ( killedBySide . at ( side ) < killedBySide . at ( 1 - side ) )
setHeroAnimation ( side , 3 ) ;
2009-11-08 16:44:58 +02:00
}
2011-12-14 00:35:28 +03:00
2016-10-28 23:37:20 +02:00
for ( auto & attackedInfo : attackedInfos )
2011-10-09 14:23:24 +03:00
{
2013-06-29 16:05:48 +03:00
if ( attackedInfo . rebirth )
creAnims [ attackedInfo . defender - > ID ] - > setType ( CCreatureAnim : : HOLDING ) ;
if ( attackedInfo . cloneKilled )
stackRemoved ( attackedInfo . defender - > ID ) ;
2011-10-09 14:23:24 +03:00
}
2009-03-21 18:03:07 +02:00
}
2016-10-28 23:37:20 +02:00
void CBattleInterface : : stackAttacking ( const CStack * attacker , BattleHex dest , const CStack * attacked , bool shooting )
2009-03-21 18:03:07 +02:00
{
2011-12-14 00:35:28 +03:00
if ( shooting )
2009-03-21 18:03:07 +02:00
{
2011-12-14 00:35:28 +03:00
addNewAnim ( new CShootingAnimation ( this , attacker , dest , attacked ) ) ;
2011-01-18 19:23:31 +02:00
}
2011-12-14 00:35:28 +03:00
else
2011-01-18 19:23:31 +02:00
{
2011-12-14 00:35:28 +03:00
addNewAnim ( new CMeleeAttackAnimation ( this , attacker , dest , attacked ) ) ;
}
2013-07-16 21:12:47 +03:00
//waitForAnims();
2011-12-14 00:35:28 +03:00
}
2011-01-18 19:23:31 +02:00
2011-12-14 00:35:28 +03:00
void CBattleInterface : : newRoundFirst ( int round )
{
waitForAnims ( ) ;
}
2011-01-18 19:23:31 +02:00
2011-12-14 00:35:28 +03:00
void CBattleInterface : : newRound ( int number )
{
console - > addText ( CGI - > generaltexth - > allTexts [ 412 ] ) ;
}
2017-07-20 06:08:49 +02:00
void CBattleInterface : : giveCommand ( EActionType action , BattleHex tile , si32 additional )
2011-12-14 00:35:28 +03:00
{
2017-07-20 06:08:49 +02:00
const CStack * actor = nullptr ;
if ( action ! = EActionType : : HERO_SPELL & & action ! = EActionType : : RETREAT & & action ! = EActionType : : SURRENDER )
2011-12-14 00:35:28 +03:00
{
2017-07-20 06:08:49 +02:00
actor = activeStack ;
2009-01-15 19:01:08 +02:00
}
2012-04-03 02:23:14 +03:00
2017-07-20 06:08:49 +02:00
auto side = curInt - > cb - > playerToSide ( curInt - > playerID ) ;
if ( ! side )
{
logGlobal - > error ( " Player %s is not in battle " , curInt - > playerID . getStr ( ) ) ;
return ;
}
2012-04-03 02:23:14 +03:00
2017-07-20 06:08:49 +02:00
auto ba = new BattleAction ( ) ; //is deleted in CPlayerInterface::activeStack()
ba - > side = side . get ( ) ;
2011-12-14 00:35:28 +03:00
ba - > actionType = action ;
2017-07-20 06:08:49 +02:00
ba - > aimToHex ( tile ) ;
ba - > actionSubtype = additional ;
sendCommand ( ba , actor ) ;
}
2011-01-18 19:23:31 +02:00
2017-07-20 06:08:49 +02:00
void CBattleInterface : : sendCommand ( BattleAction * & command , const CStack * actor )
{
command - > stackNumber = actor ? actor - > unitId ( ) : ( ( command - > side = = BattleSide : : ATTACKER ) ? - 1 : - 2 ) ;
if ( ! tacticsMode )
2010-03-02 13:40:29 +02:00
{
2017-07-20 06:08:49 +02:00
logGlobal - > trace ( " Setting command for %s " , ( actor ? actor - > nodeName ( ) : " hero " ) ) ;
2011-12-14 00:35:28 +03:00
myTurn = false ;
2013-07-06 19:10:20 +03:00
setActiveStack ( nullptr ) ;
2017-07-20 06:08:49 +02:00
givenCommand . setn ( command ) ;
2011-12-14 00:35:28 +03:00
}
else
{
2017-07-20 06:08:49 +02:00
curInt - > cb - > battleMakeTacticAction ( command ) ;
vstd : : clear_pointer ( command ) ;
2013-07-06 19:10:20 +03:00
setActiveStack ( nullptr ) ;
2012-04-03 02:23:14 +03:00
//next stack will be activated when action ends
2010-03-02 13:40:29 +02:00
}
2009-01-15 19:01:08 +02:00
}
2011-12-22 16:05:19 +03:00
bool CBattleInterface : : isTileAttackable ( const BattleHex & number ) const
2009-01-15 19:01:08 +02:00
{
2016-10-28 23:37:20 +02:00
for ( auto & elem : occupyableHexes )
2011-12-14 00:35:28 +03:00
{
2016-10-28 23:37:20 +02:00
if ( BattleHex : : mutualPosition ( elem , number ) ! = - 1 | | elem = = number )
2011-12-14 00:35:28 +03:00
return true ;
}
return false ;
}
2009-01-15 19:01:08 +02:00
2011-12-22 16:05:19 +03:00
bool CBattleInterface : : isCatapultAttackable ( BattleHex hex ) const
2011-12-14 00:35:28 +03:00
{
2016-10-28 23:37:20 +02:00
if ( ! siegeH | | tacticsMode ) return false ;
2011-12-14 00:35:28 +03:00
2013-12-08 20:54:13 +03:00
auto wallPart = curInt - > cb - > battleHexToWallPart ( hex ) ;
2016-10-28 23:37:20 +02:00
if ( ! curInt - > cb - > isWallPartPotentiallyAttackable ( wallPart ) ) return false ;
2011-12-14 00:35:28 +03:00
2013-12-08 20:54:13 +03:00
auto state = curInt - > cb - > battleGetWallState ( static_cast < int > ( wallPart ) ) ;
2013-08-06 18:25:51 +03:00
return state ! = EWallState : : DESTROYED & & state ! = EWallState : : NONE ;
2011-12-14 00:35:28 +03:00
}
2017-07-01 10:34:00 +02:00
const CGHeroInstance * CBattleInterface : : getActiveHero ( )
2011-12-14 00:35:28 +03:00
{
2016-10-28 23:37:20 +02:00
const CStack * attacker = activeStack ;
2017-07-01 10:34:00 +02:00
if ( ! attacker )
2009-01-15 19:01:08 +02:00
{
2013-06-26 14:18:27 +03:00
return nullptr ;
2011-12-14 00:35:28 +03:00
}
2017-07-01 10:34:00 +02:00
if ( attacker - > side = = BattleSide : : ATTACKER )
2011-12-14 00:35:28 +03:00
{
return attackingHeroInstance ;
}
2012-02-16 20:10:58 +03:00
2011-12-14 00:35:28 +03:00
return defendingHeroInstance ;
}
void CBattleInterface : : hexLclicked ( int whichOne )
{
2012-03-31 00:36:07 +03:00
handleHex ( whichOne , LCLICK ) ;
2009-01-15 19:01:08 +02:00
}
2011-12-14 00:35:28 +03:00
void CBattleInterface : : stackIsCatapulting ( const CatapultAttack & ca )
2009-09-15 15:20:11 +03:00
{
2016-10-28 23:37:20 +02:00
if ( ca . attacker ! = - 1 )
2011-10-08 09:59:36 +03:00
{
2016-10-28 23:37:20 +02:00
const CStack * stack = curInt - > cb - > battleGetStackByID ( ca . attacker ) ;
for ( auto attackInfo : ca . attackedParts )
2015-03-18 12:27:07 +02:00
{
addNewAnim ( new CShootingAnimation ( this , stack , attackInfo . destinationTile , nullptr , true , attackInfo . damageDealt ) ) ;
2016-01-26 08:37:55 +02:00
}
2013-07-21 13:10:38 +03:00
}
2015-03-18 16:48:32 +02:00
else
{
2016-01-26 08:37:55 +02:00
//no attacker stack, assume spell-related (earthquake) - only hit animation
2016-10-28 23:37:20 +02:00
for ( auto attackInfo : ca . attackedParts )
2015-03-18 16:48:32 +02:00
{
Point destPos = CClickableHex : : getXYUnitAnim ( attackInfo . destinationTile , nullptr , this ) + Point ( 99 , 120 ) ;
2016-01-26 08:37:55 +02:00
2017-09-05 19:45:29 +02:00
addNewAnim ( new CEffectAnimation ( this , " SGEXPL.DEF " , destPos . x , destPos . y ) ) ;
2015-03-18 16:48:32 +02:00
}
2016-01-26 08:37:55 +02:00
}
2009-11-13 23:01:33 +02:00
2013-07-21 13:10:38 +03:00
waitForAnims ( ) ;
2016-10-28 23:37:20 +02:00
for ( auto attackInfo : ca . attackedParts )
2013-07-21 13:10:38 +03:00
{
2016-02-10 06:10:32 +02:00
int wallId = attackInfo . attackedPart + 2 ;
//gate state changing handled separately
2016-10-28 23:37:20 +02:00
if ( wallId = = SiegeHelper : : GATE )
2016-02-10 06:10:32 +02:00
continue ;
SDL_FreeSurface ( siegeH - > walls [ wallId ] ) ;
siegeH - > walls [ wallId ] = BitmapHandler : : loadBitmap (
siegeH - > getSiegeName ( wallId , curInt - > cb - > battleGetWallState ( attackInfo . attackedPart ) ) ) ;
2011-12-14 00:35:28 +03:00
}
2009-09-15 15:20:11 +03:00
}
2011-12-14 00:35:28 +03:00
void CBattleInterface : : battleFinished ( const BattleResult & br )
2009-01-15 19:01:08 +02:00
{
2011-12-14 00:35:28 +03:00
bresult = & br ;
2012-02-20 00:03:43 +03:00
{
2016-11-25 21:12:22 +02:00
auto unlockPim = vstd : : makeUnlockGuard ( * CPlayerInterface : : pim ) ;
2012-02-20 00:03:43 +03:00
animsAreDisplayed . waitUntil ( false ) ;
}
2013-07-06 19:10:20 +03:00
setActiveStack ( nullptr ) ;
2017-05-13 09:54:03 +02:00
displayBattleFinished ( ) ;
2009-01-15 19:01:08 +02:00
}
2011-12-14 00:35:28 +03:00
void CBattleInterface : : displayBattleFinished ( )
2009-04-16 17:01:27 +03:00
{
2012-12-14 18:32:53 +03:00
CCS - > curh - > changeGraphic ( ECursor : : ADVENTURE , 0 ) ;
2017-06-03 07:25:10 +02:00
if ( settings [ " session " ] [ " spectate " ] . Bool ( ) & & settings [ " session " ] [ " spectate-skip-battle-result " ] . Bool ( ) )
{
2018-07-25 00:36:48 +02:00
close ( ) ;
2017-06-03 07:25:10 +02:00
return ;
}
2012-02-16 20:10:58 +03:00
2018-07-25 00:36:48 +02:00
GH . pushInt ( std : : make_shared < CBattleResultWindow > ( * bresult , * ( this - > curInt ) ) ) ;
2017-03-19 04:15:31 +02:00
curInt - > waitWhileDialog ( ) ; // Avoid freeze when AI end turn after battle. Check bug #1897
2018-07-25 00:36:48 +02:00
CPlayerInterface : : battleInt = nullptr ;
2009-04-16 17:01:27 +03:00
}
2017-09-08 13:25:12 +02:00
void CBattleInterface : : spellCast ( const BattleSpellCast * sc )
2009-08-26 17:09:55 +03:00
{
2017-07-20 06:08:49 +02:00
const SpellID spellID = sc - > spellID ;
const CSpell * spell = spellID . toSpell ( ) ;
if ( ! spell )
return ;
2009-08-26 17:09:55 +03:00
2017-07-20 06:08:49 +02:00
const std : : string & castSoundPath = spell - > getCastSound ( ) ;
2015-02-07 10:13:24 +02:00
2016-10-28 23:37:20 +02:00
if ( ! castSoundPath . empty ( ) )
2014-03-10 19:00:58 +03:00
CCS - > soundh - > playSound ( castSoundPath ) ;
2014-11-27 20:06:11 +02:00
2017-09-08 13:25:12 +02:00
const auto casterStackID = sc - > casterStack ;
const CStack * casterStack = nullptr ;
if ( casterStackID > = 0 )
2014-11-27 20:06:11 +02:00
{
2017-09-08 13:25:12 +02:00
casterStack = curInt - > cb - > battleGetStackByID ( casterStackID ) ;
}
2014-11-27 20:33:57 +02:00
2017-09-08 13:25:12 +02:00
Point srccoord = ( sc - > side ? Point ( 770 , 60 ) : Point ( 30 , 60 ) ) + pos ; //hero position by default
{
if ( casterStack ! = nullptr )
2014-11-27 20:06:11 +02:00
{
2017-07-20 06:08:49 +02:00
srccoord = CClickableHex : : getXYUnitAnim ( casterStack - > getPosition ( ) , casterStack , this ) ;
2017-09-08 13:25:12 +02:00
srccoord . x + = 250 ;
srccoord . y + = 240 ;
2014-11-27 20:06:11 +02:00
}
2014-11-27 20:33:57 +02:00
}
2017-09-08 13:25:12 +02:00
if ( casterStack ! = nullptr & & sc - > activeCast )
{
//todo: custom cast animation for hero
2017-07-20 06:08:49 +02:00
displaySpellCast ( spellID , casterStack - > getPosition ( ) ) ;
2017-09-08 13:25:12 +02:00
addNewAnim ( new CCastAnimation ( this , casterStack , sc - > tile , curInt - > cb - > battleGetStackByPos ( sc - > tile ) ) ) ;
}
waitForAnims ( ) ; //wait for cast animation
2015-03-22 11:18:58 +02:00
2014-11-26 23:27:38 +02:00
//playing projectile animation
2016-10-28 23:37:20 +02:00
if ( sc - > tile . isValid ( ) )
2014-11-27 20:06:11 +02:00
{
2014-11-26 23:27:38 +02:00
Point destcoord = CClickableHex : : getXYUnitAnim ( sc - > tile , curInt - > cb - > battleGetStackByPos ( sc - > tile ) , this ) ; //position attacked by projectile
destcoord . x + = 250 ; destcoord . y + = 240 ;
//animation angle
double angle = atan2 ( static_cast < double > ( destcoord . x - srccoord . x ) , static_cast < double > ( destcoord . y - srccoord . y ) ) ;
bool Vflip = ( angle < 0 ) ;
2016-10-28 23:37:20 +02:00
if ( Vflip )
2014-11-26 23:27:38 +02:00
angle = - angle ;
2015-02-07 10:13:24 +02:00
2017-07-20 06:08:49 +02:00
std : : string animToDisplay = spell - > animationInfo . selectProjectile ( angle ) ;
2015-02-07 10:13:24 +02:00
2017-09-05 19:45:29 +02:00
if ( ! animToDisplay . empty ( ) )
2009-08-26 17:09:55 +03:00
{
2017-09-05 19:45:29 +02:00
//TODO: calculate inside CEffectAnimation
std : : shared_ptr < CAnimation > tmp = std : : make_shared < CAnimation > ( animToDisplay ) ;
tmp - > load ( 0 , 0 ) ;
2018-03-30 13:02:04 +02:00
auto first = tmp - > getImage ( 0 , 0 ) ;
2017-09-05 19:45:29 +02:00
2011-12-14 00:35:28 +03:00
//displaying animation
2013-07-16 21:12:47 +03:00
double diffX = ( destcoord . x - srccoord . x ) * ( destcoord . x - srccoord . x ) ;
double diffY = ( destcoord . y - srccoord . y ) * ( destcoord . y - srccoord . y ) ;
double distance = sqrt ( diffX + diffY ) ;
2012-02-16 20:10:58 +03:00
2020-10-01 10:38:06 +02:00
int steps = static_cast < int > ( distance / AnimationControls : : getSpellEffectSpeed ( ) + 1 ) ;
2017-09-05 19:45:29 +02:00
int dx = ( destcoord . x - srccoord . x - first - > width ( ) ) / steps ;
int dy = ( destcoord . y - srccoord . y - first - > height ( ) ) / steps ;
2011-12-14 00:35:28 +03:00
2017-09-05 19:45:29 +02:00
addNewAnim ( new CEffectAnimation ( this , animToDisplay , srccoord . x , srccoord . y , dx , dy , Vflip ) ) ;
2014-11-26 23:27:38 +02:00
}
2015-03-22 11:18:58 +02:00
}
2017-09-08 13:25:12 +02:00
waitForAnims ( ) ; //wait for projectile animation
2015-03-22 11:18:58 +02:00
2014-11-27 23:36:14 +02:00
displaySpellHit ( spellID , sc - > tile ) ;
2015-03-22 11:18:58 +02:00
2015-09-14 04:45:05 +02:00
//queuing affect animation
2017-07-20 06:08:49 +02:00
for ( auto & elem : sc - > affectedCres )
2014-11-26 23:27:38 +02:00
{
2017-07-20 06:08:49 +02:00
auto stack = curInt - > cb - > battleGetStackByID ( elem , false ) ;
if ( stack )
displaySpellEffect ( spellID , stack - > getPosition ( ) ) ;
2015-09-14 04:45:05 +02:00
}
2015-02-07 10:13:24 +02:00
2015-09-14 05:21:49 +02:00
//queuing additional animation
2017-07-20 06:08:49 +02:00
for ( auto & elem : sc - > customEffects )
2015-09-14 04:45:05 +02:00
{
2017-07-20 06:08:49 +02:00
auto stack = curInt - > cb - > battleGetStackByID ( elem . stack , false ) ;
if ( stack )
displayEffect ( elem . effect , stack - > getPosition ( ) ) ;
2014-11-26 23:27:38 +02:00
}
2011-12-14 00:35:28 +03:00
waitForAnims ( ) ;
//mana absorption
2016-10-28 23:37:20 +02:00
if ( sc - > manaGained > 0 )
2011-12-14 00:35:28 +03:00
{
2011-12-22 16:05:19 +03:00
Point leftHero = Point ( 15 , 30 ) + pos ;
Point rightHero = Point ( 755 , 30 ) + pos ;
2017-09-05 19:45:29 +02:00
addNewAnim ( new CEffectAnimation ( this , sc - > side ? " SP07_A.DEF " : " SP07_B.DEF " , leftHero . x , leftHero . y , 0 , 0 , false ) ) ;
addNewAnim ( new CEffectAnimation ( this , sc - > side ? " SP07_B.DEF " : " SP07_A.DEF " , rightHero . x , rightHero . y , 0 , 0 , false ) ) ;
2009-01-15 19:01:08 +02:00
}
}
2011-12-14 00:35:28 +03:00
void CBattleInterface : : battleStacksEffectsSet ( const SetStackEffect & sse )
2009-01-15 19:01:08 +02:00
{
2016-11-02 19:11:01 +02:00
if ( activeStack ! = nullptr )
2011-12-14 00:35:28 +03:00
redrawBackgroundWithHexes ( activeStack ) ;
}
2011-05-26 17:47:45 +03:00
2017-09-27 16:35:30 +02:00
void CBattleInterface : : setHeroAnimation ( ui8 side , int phase )
{
if ( side = = BattleSide : : ATTACKER )
2017-10-07 23:04:43 +02:00
{
if ( attackingHero )
attackingHero - > setPhase ( phase ) ;
}
2017-09-27 16:35:30 +02:00
else
2017-10-07 23:04:43 +02:00
{
if ( defendingHero )
defendingHero - > setPhase ( phase ) ;
}
2017-09-27 16:35:30 +02:00
}
2016-09-10 18:26:55 +02:00
void CBattleInterface : : castThisSpell ( SpellID spellID )
{
2018-07-25 00:36:48 +02:00
spellToCast = std : : make_shared < BattleAction > ( ) ;
2017-07-20 06:08:49 +02:00
spellToCast - > actionType = EActionType : : HERO_SPELL ;
spellToCast - > actionSubtype = spellID ; //spell number
spellToCast - > stackNumber = ( attackingHeroInstance - > tempOwner = = curInt - > playerID ) ? - 1 : - 2 ;
spellToCast - > side = defendingHeroInstance ? ( curInt - > playerID = = defendingHeroInstance - > tempOwner ) : false ;
2016-09-10 18:26:55 +02:00
spellDestSelectMode = true ;
creatureCasting = false ;
2016-09-10 18:38:14 +02:00
//choosing possible targets
2016-10-28 23:37:20 +02:00
const CGHeroInstance * castingHero = ( attackingHeroInstance - > tempOwner = = curInt - > playerID ) ? attackingHeroInstance : defendingHeroInstance ;
2016-09-10 18:26:55 +02:00
assert ( castingHero ) ; // code below assumes non-null hero
sp = spellID . toSpell ( ) ;
2019-05-04 05:42:55 +02:00
PossiblePlayerBattleAction spellSelMode = curInt - > cb - > getCasterAction ( sp , castingHero , spells : : Mode : : HERO ) ;
2016-09-10 18:26:55 +02:00
2019-05-04 05:42:55 +02:00
if ( spellSelMode = = PossiblePlayerBattleAction : : NO_LOCATION ) //user does not have to select location
2011-12-14 00:35:28 +03:00
{
2017-07-20 06:08:49 +02:00
spellToCast - > aimToHex ( BattleHex : : INVALID ) ;
2018-07-25 00:36:48 +02:00
curInt - > cb - > battleMakeAction ( spellToCast . get ( ) ) ;
2011-12-14 00:35:28 +03:00
endCastingSpell ( ) ;
}
else
2009-09-24 16:44:55 +03:00
{
2012-04-16 20:12:39 +03:00
possibleActions . clear ( ) ;
2012-05-07 19:04:43 +03:00
possibleActions . push_back ( spellSelMode ) ; //only this one action can be performed at the moment
2012-02-16 20:10:58 +03:00
GH . fakeMouseMove ( ) ; //update cursor
2009-09-24 16:44:55 +03:00
}
2011-12-14 00:35:28 +03:00
}
2011-01-14 20:08:01 +02:00
2017-07-20 06:08:49 +02:00
void CBattleInterface : : displayBattleLog ( const std : : vector < MetaString > & battleLog )
{
for ( const auto & line : battleLog )
{
std : : string formatted = line . toString ( ) ;
boost : : algorithm : : trim ( formatted ) ;
if ( ! console - > addText ( formatted ) )
logGlobal - > warn ( " Too long battle log line " ) ;
}
}
void CBattleInterface : : displayCustomEffects ( const std : : vector < CustomEffectInfo > & customEffects )
{
for ( const CustomEffectInfo & one : customEffects )
{
if ( one . sound ! = 0 )
CCS - > soundh - > playSound ( soundBase : : soundID ( one . sound ) ) ;
const CStack * s = curInt - > cb - > battleGetStackByID ( one . stack , false ) ;
if ( s & & one . effect ! = 0 )
displayEffect ( one . effect , s - > getPosition ( ) ) ;
}
}
2017-09-05 19:45:29 +02:00
void CBattleInterface : : displayEffect ( ui32 effect , BattleHex destTile )
2011-12-14 00:35:28 +03:00
{
2017-09-05 19:45:29 +02:00
std : : string customAnim = graphics - > battleACToDef [ effect ] [ 0 ] ;
addNewAnim ( new CEffectAnimation ( this , customAnim , destTile ) ) ;
2011-12-14 00:35:28 +03:00
}
2011-01-14 20:08:01 +02:00
2021-02-20 03:57:50 +02:00
void CBattleInterface : : displaySpellAnimationQueue ( const CSpell : : TAnimationQueue & q , BattleHex destinationTile )
2015-03-22 15:47:20 +02:00
{
2021-02-20 03:57:50 +02:00
for ( const CSpell : : TAnimation & animation : q )
2015-03-22 15:47:20 +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
if ( animation . pause > 0 )
addNewAnim ( new CDummyAnimation ( this , animation . pause ) ) ;
else
addNewAnim ( new CEffectAnimation ( this , animation . resourceName , destinationTile , false , animation . verticalPosition = = VerticalPosition : : BOTTOM ) ) ;
2016-01-26 08:37:55 +02:00
}
2015-03-22 15:47:20 +02:00
}
2021-02-20 03:57:50 +02:00
void CBattleInterface : : displaySpellCast ( SpellID spellID , BattleHex destinationTile )
2014-11-27 23:36:14 +02:00
{
2021-02-20 03:57:50 +02:00
const CSpell * spell = spellID . toSpell ( ) ;
2015-03-22 11:18:58 +02:00
2021-02-20 03:57:50 +02:00
if ( spell )
displaySpellAnimationQueue ( spell - > animationInfo . cast , destinationTile ) ;
}
2014-11-27 23:36:14 +02:00
2021-02-20 03:57:50 +02:00
void CBattleInterface : : displaySpellEffect ( SpellID spellID , BattleHex destinationTile )
{
const CSpell * spell = spellID . toSpell ( ) ;
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
2021-02-20 03:57:50 +02:00
if ( spell )
displaySpellAnimationQueue ( spell - > animationInfo . affect , destinationTile ) ;
2014-11-27 23:36:14 +02:00
}
2016-09-10 17:56:38 +02:00
void CBattleInterface : : displaySpellHit ( SpellID spellID , BattleHex destinationTile )
2014-11-27 23:36:14 +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
const CSpell * spell = spellID . toSpell ( ) ;
2015-03-22 11:18:58 +02:00
2021-02-20 03:57:50 +02:00
if ( spell )
displaySpellAnimationQueue ( spell - > animationInfo . hit , destinationTile ) ;
2014-11-27 23:36:14 +02:00
}
2011-12-14 00:35:28 +03:00
void CBattleInterface : : battleTriggerEffect ( const BattleTriggerEffect & bte )
{
2017-07-20 06:08:49 +02:00
const CStack * stack = curInt - > cb - > battleGetStackByID ( bte . stackID ) ;
if ( ! stack )
{
logGlobal - > error ( " Invalid stack ID %d " , bte . stackID ) ;
return ;
}
2011-12-14 00:35:28 +03:00
//don't show animation when no HP is regenerated
2017-07-20 06:08:49 +02:00
switch ( bte . effect )
2011-01-14 20:08:01 +02:00
{
2017-07-20 06:08:49 +02:00
//TODO: move to bonus type handler
2011-12-14 00:35:28 +03:00
case Bonus : : HP_REGENERATION :
2017-07-20 06:08:49 +02:00
displayEffect ( 74 , stack - > getPosition ( ) ) ;
2012-04-17 11:46:09 +03:00
CCS - > soundh - > playSound ( soundBase : : REGENER ) ;
2011-12-14 00:35:28 +03:00
break ;
case Bonus : : MANA_DRAIN :
2017-07-20 06:08:49 +02:00
displayEffect ( 77 , stack - > getPosition ( ) ) ;
2011-12-14 00:35:28 +03:00
CCS - > soundh - > playSound ( soundBase : : MANADRAI ) ;
break ;
case Bonus : : POISON :
2017-07-20 06:08:49 +02:00
displayEffect ( 67 , stack - > getPosition ( ) ) ;
2011-12-14 00:35:28 +03:00
CCS - > soundh - > playSound ( soundBase : : POISON ) ;
break ;
case Bonus : : FEAR :
2017-07-20 06:08:49 +02:00
displayEffect ( 15 , stack - > getPosition ( ) ) ;
2011-12-14 00:35:28 +03:00
CCS - > soundh - > playSound ( soundBase : : FEAR ) ;
break ;
2012-01-26 19:48:53 +03:00
case Bonus : : MORALE :
{
std : : string hlp = CGI - > generaltexth - > allTexts [ 33 ] ;
boost : : algorithm : : replace_first ( hlp , " %s " , ( stack - > getName ( ) ) ) ;
2017-07-20 06:08:49 +02:00
displayEffect ( 20 , stack - > getPosition ( ) ) ;
2013-05-04 16:14:23 +03:00
CCS - > soundh - > playSound ( soundBase : : GOODMRLE ) ;
2012-01-26 19:48:53 +03:00
console - > addText ( hlp ) ;
break ;
}
2011-12-14 00:35:28 +03:00
default :
return ;
2011-01-14 20:08:01 +02:00
}
2011-12-14 00:35:28 +03:00
//waitForAnims(); //fixme: freezes game :?
}
2011-01-14 20:08:01 +02:00
2011-12-14 00:35:28 +03:00
void CBattleInterface : : setAnimSpeed ( int set )
{
2012-01-12 18:23:00 +03:00
Settings speed = settings . write [ " battle " ] [ " animationSpeed " ] ;
2013-07-06 19:10:20 +03:00
speed - > Float ( ) = float ( set ) / 100 ;
2011-12-14 00:35:28 +03:00
}
2011-02-23 05:57:45 +02:00
2011-12-14 00:35:28 +03:00
int CBattleInterface : : getAnimSpeed ( ) const
{
2017-06-03 07:25:10 +02:00
if ( settings [ " session " ] [ " spectate " ] . Bool ( ) & & ! settings [ " session " ] [ " spectate-battle-speed " ] . isNull ( ) )
2020-10-01 10:38:06 +02:00
return static_cast < int > ( vstd : : round ( settings [ " session " ] [ " spectate-battle-speed " ] . Float ( ) * 100 ) ) ;
2017-06-03 07:25:10 +02:00
2020-10-01 10:38:06 +02:00
return static_cast < int > ( vstd : : round ( settings [ " battle " ] [ " animationSpeed " ] . Float ( ) * 100 ) ) ;
2013-07-06 19:10:20 +03:00
}
2016-10-28 23:37:20 +02:00
CPlayerInterface * CBattleInterface : : getCurrentPlayerInterface ( ) const
2013-11-03 19:44:47 +03:00
{
return curInt . get ( ) ;
}
2022-08-12 10:38:09 +02:00
bool CBattleInterface : : shouldRotate ( const CStack * stack , const BattleHex & oldPos , const BattleHex & nextHex )
{
Point begPosition = CClickableHex : : getXYUnitAnim ( oldPos , stack , this ) ;
Point endPosition = CClickableHex : : getXYUnitAnim ( nextHex , stack , this ) ;
if ( ( begPosition . x > endPosition . x ) & & creDir [ stack - > ID ] )
return true ;
else if ( ( begPosition . x < endPosition . x ) & & ! creDir [ stack - > ID ] )
return true ;
return false ;
}
2016-10-28 23:37:20 +02:00
void CBattleInterface : : setActiveStack ( const CStack * stack )
2013-07-06 19:10:20 +03:00
{
if ( activeStack ) // update UI
creAnims [ activeStack - > ID ] - > setBorderColor ( AnimationControls : : getNoBorder ( ) ) ;
activeStack = stack ;
if ( activeStack ) // update UI
creAnims [ activeStack - > ID ] - > setBorderColor ( AnimationControls : : getGoldBorder ( ) ) ;
2013-07-21 13:10:38 +03:00
blockUI ( activeStack = = nullptr ) ;
2013-07-06 19:10:20 +03:00
}
2016-10-28 23:37:20 +02:00
void CBattleInterface : : setHoveredStack ( const CStack * stack )
2013-07-06 19:10:20 +03:00
{
if ( mouseHoveredStack )
creAnims [ mouseHoveredStack - > ID ] - > setBorderColor ( AnimationControls : : getNoBorder ( ) ) ;
// stack must be alive and not active (which uses gold border instead)
if ( stack & & stack - > alive ( ) & & stack ! = activeStack )
{
mouseHoveredStack = stack ;
if ( mouseHoveredStack )
{
creAnims [ mouseHoveredStack - > ID ] - > setBorderColor ( AnimationControls : : getBlueBorder ( ) ) ;
if ( creAnims [ mouseHoveredStack - > ID ] - > framesInGroup ( CCreatureAnim : : MOUSEON ) > 0 )
creAnims [ mouseHoveredStack - > ID ] - > playOnce ( CCreatureAnim : : MOUSEON ) ;
}
}
else
mouseHoveredStack = nullptr ;
2009-09-20 15:47:40 +03:00
}
2011-12-14 00:35:28 +03:00
void CBattleInterface : : activateStack ( )
2009-09-20 15:47:40 +03:00
{
2019-03-22 22:39:53 +02:00
if ( ! battleActionsStarted )
return ; //"show" function should re-call this function
2013-11-02 23:07:45 +03:00
myTurn = true ;
2016-10-28 23:37:20 +02:00
if ( ! ! attackerInt & & defenderInt ) //hotseat -> need to pick which interface "takes over" as active
2013-11-02 23:07:45 +03:00
curInt = attackerInt - > playerID = = stackToActivate - > owner ? attackerInt : defenderInt ;
2013-07-06 19:10:20 +03:00
setActiveStack ( stackToActivate ) ;
2013-06-26 14:18:27 +03:00
stackToActivate = nullptr ;
2011-12-14 00:35:28 +03:00
const CStack * s = activeStack ;
2009-09-24 16:23:52 +03:00
2011-12-14 00:35:28 +03:00
queue - > update ( ) ;
redrawBackgroundWithHexes ( activeStack ) ;
2012-03-31 00:36:07 +03:00
2011-12-14 00:35:28 +03:00
//set casting flag to true if creature can use it to not check it every time
2020-11-11 21:43:40 +02:00
const auto spellcaster = s - > getBonusLocalFirst ( Selector : : type ( ) ( Bonus : : SPELLCASTER ) ) ,
randomSpellcaster = s - > getBonusLocalFirst ( Selector : : type ( ) ( Bonus : : RANDOM_SPELLCASTER ) ) ;
2017-07-04 13:24:46 +02:00
if ( s - > canCast ( ) & & ( spellcaster | | randomSpellcaster ) )
2009-09-20 15:47:40 +03:00
{
2011-12-14 00:35:28 +03:00
stackCanCastSpell = true ;
2017-07-04 13:24:46 +02:00
if ( randomSpellcaster )
2013-01-22 12:47:25 +03:00
creatureSpellToCast = - 1 ; //spell will be set later on cast
2016-09-10 19:21:51 +02:00
else
2016-09-12 09:13:40 +02:00
creatureSpellToCast = curInt - > cb - > battleGetRandomStackSpell ( CRandomGenerator : : getDefault ( ) , s , CBattleInfoCallback : : RANDOM_AIMED ) ; //faerie dragon can cast only one spell until their next move
2013-01-22 12:47:25 +03:00
//TODO: what if creature can cast BOTH random genie spell and aimed spell?
2016-09-10 20:07:36 +02:00
//TODO: faerie dragon type spell should be selected by server
2011-12-14 00:35:28 +03:00
}
else
{
stackCanCastSpell = false ;
creatureSpellToCast = - 1 ;
2009-09-20 15:47:40 +03:00
}
2019-05-04 05:42:55 +02:00
possibleActions = getPossibleActionsForStack ( s ) ;
2009-09-24 16:23:52 +03:00
2012-05-05 00:16:39 +03:00
GH . fakeMouseMove ( ) ;
2011-12-14 00:35:28 +03:00
}
2009-09-20 15:47:40 +03:00
2011-12-14 00:35:28 +03:00
void CBattleInterface : : endCastingSpell ( )
2009-09-24 16:23:52 +03:00
{
2018-07-25 00:36:48 +02:00
if ( spellDestSelectMode )
2016-10-15 22:50:12 +02:00
{
2018-07-25 00:36:48 +02:00
spellToCast . reset ( ) ;
2016-10-15 22:50:12 +02:00
sp = nullptr ;
spellDestSelectMode = false ;
CCS - > curh - > changeGraphic ( ECursor : : COMBAT , ECursor : : COMBAT_POINTER ) ;
2018-07-25 00:36:48 +02:00
if ( activeStack )
2016-10-15 22:50:12 +02:00
{
2019-05-04 05:42:55 +02:00
possibleActions = getPossibleActionsForStack ( activeStack ) ; //restore actions after they were cleared
2016-10-15 22:50:12 +02:00
myTurn = true ;
}
}
else
{
2018-07-25 00:36:48 +02:00
if ( activeStack )
2016-10-15 22:50:12 +02:00
{
2019-05-04 05:42:55 +02:00
possibleActions = getPossibleActionsForStack ( activeStack ) ;
2016-10-15 22:50:12 +02:00
GH . fakeMouseMove ( ) ;
}
}
}
void CBattleInterface : : enterCreatureCastingMode ( )
{
//silently check for possible errors
2016-10-28 23:37:20 +02:00
if ( ! myTurn )
2016-10-15 22:50:12 +02:00
return ;
2016-10-28 23:37:20 +02:00
if ( tacticsMode )
2016-10-15 22:50:12 +02:00
return ;
2011-08-25 18:24:37 +03:00
2020-10-06 01:27:04 +02:00
//hero is casting a spell
2016-10-28 23:37:20 +02:00
if ( spellDestSelectMode )
2016-10-15 22:50:12 +02:00
return ;
2016-10-28 23:37:20 +02:00
if ( ! activeStack )
2016-10-15 22:50:12 +02:00
return ;
2015-03-18 23:10:41 +02:00
2016-10-28 23:37:20 +02:00
if ( ! stackCanCastSpell )
2016-10-15 22:50:12 +02:00
return ;
//random spellcaster
2016-10-28 23:37:20 +02:00
if ( creatureSpellToCast = = - 1 )
2016-10-15 22:50:12 +02:00
return ;
2012-04-04 11:03:52 +03:00
2019-05-04 05:42:55 +02:00
if ( vstd : : contains ( possibleActions , PossiblePlayerBattleAction : : NO_LOCATION ) )
2012-05-07 19:04:43 +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
const spells : : Caster * caster = activeStack ;
const CSpell * spell = SpellID ( creatureSpellToCast ) . toSpell ( ) ;
spells : : Target target ;
target . emplace_back ( ) ;
spells : : BattleCast cast ( curInt - > cb . get ( ) , caster , spells : : Mode : : CREATURE_ACTIVE , spell ) ;
auto m = spell - > battleMechanics ( & cast ) ;
spells : : detail : : ProblemImpl ignored ;
2021-02-20 03:57:50 +02:00
const bool isCastingPossible = m - > canBeCastAt ( target , ignored ) ;
2016-10-15 22:50:12 +02:00
2016-10-28 23:37:20 +02:00
if ( isCastingPossible )
2016-10-15 22:50:12 +02:00
{
myTurn = false ;
2017-07-20 06:08:49 +02:00
giveCommand ( EActionType : : MONSTER_SPELL , BattleHex : : INVALID , creatureSpellToCast ) ;
2016-10-15 22:50:12 +02:00
selectedStack = nullptr ;
CCS - > curh - > changeGraphic ( ECursor : : COMBAT , ECursor : : COMBAT_POINTER ) ;
}
}
else
{
2019-05-04 05:42:55 +02:00
possibleActions = getPossibleActionsForStack ( activeStack ) ;
auto actionFilterPredicate = [ ] ( const PossiblePlayerBattleAction x )
{
return ( x ! = PossiblePlayerBattleAction : : ANY_LOCATION ) & & ( x ! = PossiblePlayerBattleAction : : NO_LOCATION ) & &
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
( x ! = PossiblePlayerBattleAction : : FREE_LOCATION ) & & ( x ! = PossiblePlayerBattleAction : : AIMED_SPELL_CREATURE ) & &
2019-05-04 05:42:55 +02:00
( x ! = PossiblePlayerBattleAction : : OBSTACLE ) ;
} ;
vstd : : erase_if ( possibleActions , actionFilterPredicate ) ;
2016-10-15 22:50:12 +02:00
GH . fakeMouseMove ( ) ;
2012-05-07 19:04:43 +03:00
}
2012-04-04 11:03:52 +03:00
}
2019-05-04 05:42:55 +02:00
std : : vector < PossiblePlayerBattleAction > CBattleInterface : : getPossibleActionsForStack ( const CStack * stack )
2012-04-04 11:03:52 +03:00
{
2019-05-04 05:42:55 +02:00
BattleClientInterfaceData data ; //hard to get rid of these things so for now they're required data to pass
data . creatureSpellToCast = creatureSpellToCast ;
data . tacticsMode = tacticsMode ;
auto allActions = curInt - > cb - > getClientActionsForStack ( stack , data ) ;
2016-10-15 22:50:12 +02:00
2019-05-04 05:42:55 +02:00
return std : : vector < PossiblePlayerBattleAction > ( allActions ) ;
}
2012-04-04 11:03:52 +03:00
2019-05-04 05:42:55 +02:00
void CBattleInterface : : reorderPossibleActionsPriority ( const CStack * stack , MouseHoveredHexContext context )
{
if ( tacticsMode | | possibleActions . empty ( ) ) return ; //this function is not supposed to be called in tactics mode or before getPossibleActionsForStack
2012-04-04 11:03:52 +03:00
2019-05-04 05:42:55 +02:00
auto assignPriority = [ & ] ( PossiblePlayerBattleAction const & item ) - > uint8_t //large lambda assigning priority which would have to be part of possibleActions without it
{
switch ( item )
{
case PossiblePlayerBattleAction : : AIMED_SPELL_CREATURE :
case PossiblePlayerBattleAction : : ANY_LOCATION :
case PossiblePlayerBattleAction : : NO_LOCATION :
case PossiblePlayerBattleAction : : FREE_LOCATION :
case PossiblePlayerBattleAction : : OBSTACLE :
if ( ! stack - > hasBonusOfType ( Bonus : : NO_SPELLCAST_BY_DEFAULT ) & & context = = MouseHoveredHexContext : : OCCUPIED_HEX )
return 1 ;
2021-02-20 03:57:50 +02:00
else
2019-05-04 05:42:55 +02:00
return 100 ; //bottom priority
break ;
case PossiblePlayerBattleAction : : RANDOM_GENIE_SPELL :
return 2 ; break ;
case PossiblePlayerBattleAction : : RISE_DEMONS :
return 3 ; break ;
case PossiblePlayerBattleAction : : SHOOT :
return 4 ; break ;
case PossiblePlayerBattleAction : : ATTACK_AND_RETURN :
return 5 ; break ;
case PossiblePlayerBattleAction : : ATTACK :
return 6 ; break ;
case PossiblePlayerBattleAction : : WALK_AND_ATTACK :
return 7 ; break ;
case PossiblePlayerBattleAction : : MOVE_STACK :
return 8 ; break ;
case PossiblePlayerBattleAction : : CATAPULT :
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
return 9 ; break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : HEAL :
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
return 10 ; break ;
2019-05-04 05:42:55 +02:00
default :
return 200 ; break ;
}
} ;
2012-04-04 11:03:52 +03:00
2019-05-04 05:42:55 +02:00
auto comparer = [ & ] ( PossiblePlayerBattleAction const & lhs , PossiblePlayerBattleAction const & rhs )
{
return assignPriority ( lhs ) > assignPriority ( rhs ) ;
} ;
2016-10-15 22:50:12 +02:00
2019-05-04 05:42:55 +02:00
std : : make_heap ( possibleActions . begin ( ) , possibleActions . end ( ) , comparer ) ;
2011-12-14 00:35:28 +03:00
}
2011-02-14 22:31:53 +02:00
2011-12-14 00:35:28 +03:00
void CBattleInterface : : endAction ( const BattleAction * action )
2012-02-16 20:10:58 +03:00
{
2016-10-28 23:37:20 +02:00
const CStack * stack = curInt - > cb - > battleGetStackByID ( action - > stackNumber ) ;
2013-05-19 18:14:23 +03:00
2017-07-20 06:08:49 +02:00
if ( action - > actionType = = EActionType : : HERO_SPELL )
2017-09-27 16:35:30 +02:00
setHeroAnimation ( action - > side , 0 ) ;
2012-05-05 00:16:39 +03:00
2011-12-14 00:35:28 +03:00
//check if we should reverse stacks
//for some strange reason, it's not enough
TStacks stacks = curInt - > cb - > battleGetStacks ( CBattleCallback : : MINE_AND_ENEMY ) ;
2016-10-28 23:37:20 +02:00
for ( const CStack * s : stacks )
2009-01-15 19:01:08 +02:00
{
2017-07-01 10:34:00 +02:00
if ( s & & creDir [ s - > ID ] ! = ( s - > side = = BattleSide : : ATTACKER ) & & s - > alive ( )
2013-07-06 19:10:20 +03:00
& & creAnims [ s - > ID ] - > isIdle ( ) )
2009-01-15 19:01:08 +02:00
{
2017-07-20 06:08:49 +02:00
addNewAnim ( new CReverseAnimation ( this , s , s - > getPosition ( ) , false ) ) ;
2009-01-15 19:01:08 +02:00
}
}
2011-12-14 00:35:28 +03:00
queue - > update ( ) ;
2016-10-28 23:37:20 +02:00
if ( tacticsMode ) //stack ended movement in tactics phase -> select the next one
2014-03-07 16:21:09 +03:00
bTacticNextStack ( stack ) ;
2012-04-03 02:23:14 +03:00
2017-07-20 06:08:49 +02:00
if ( action - > actionType = = EActionType : : HERO_SPELL ) //we have activated next stack after sending request that has been just realized -> blockmap due to movement has changed
2011-12-14 00:35:28 +03:00
redrawBackgroundWithHexes ( activeStack ) ;
2013-05-19 18:14:23 +03:00
2016-10-28 23:37:20 +02:00
if ( activeStack & & ! animsAreDisplayed . get ( ) & & pendingAnims . empty ( ) & & ! active )
2013-05-19 18:14:23 +03:00
{
2017-08-10 18:39:27 +02:00
logGlobal - > warn ( " Something wrong... interface was deactivated but there is no animation. Reactivating... " ) ;
2013-07-21 13:10:38 +03:00
blockUI ( false ) ;
}
else
{
// block UI if no active stack (e.g. enemy turn);
blockUI ( activeStack = = nullptr ) ;
2013-05-19 18:14:23 +03:00
}
2009-01-15 19:01:08 +02:00
}
2011-12-14 00:35:28 +03:00
void CBattleInterface : : hideQueue ( )
2009-01-15 19:01:08 +02:00
{
2012-01-12 18:23:00 +03:00
Settings showQueue = settings . write [ " battle " ] [ " showQueue " ] ;
showQueue - > Bool ( ) = false ;
2011-12-14 00:35:28 +03:00
queue - > deactivate ( ) ;
2016-10-28 23:37:20 +02:00
if ( ! queue - > embedded )
2009-01-15 19:01:08 +02:00
{
2011-12-22 16:05:19 +03:00
moveBy ( Point ( 0 , - queue - > pos . h / 2 ) ) ;
2011-12-14 00:35:28 +03:00
GH . totalRedraw ( ) ;
2009-01-15 19:01:08 +02:00
}
}
2011-12-14 00:35:28 +03:00
void CBattleInterface : : showQueue ( )
2009-01-15 19:01:08 +02:00
{
2012-01-12 18:23:00 +03:00
Settings showQueue = settings . write [ " battle " ] [ " showQueue " ] ;
showQueue - > Bool ( ) = true ;
2009-01-15 19:01:08 +02:00
2011-12-14 00:35:28 +03:00
queue - > activate ( ) ;
2009-01-15 19:01:08 +02:00
2016-10-28 23:37:20 +02:00
if ( ! queue - > embedded )
2011-12-14 00:35:28 +03:00
{
2011-12-22 16:05:19 +03:00
moveBy ( Point ( 0 , + queue - > pos . h / 2 ) ) ;
2011-12-14 00:35:28 +03:00
GH . totalRedraw ( ) ;
}
2009-01-15 19:01:08 +02:00
}
2013-07-21 13:10:38 +03:00
void CBattleInterface : : blockUI ( bool on )
{
2017-06-05 21:16:12 +02:00
bool canCastSpells = false ;
auto hero = curInt - > cb - > battleGetMyHero ( ) ;
if ( hero )
{
2017-07-20 06:08:49 +02:00
ESpellCastProblem : : ESpellCastProblem spellcastingProblem = curInt - > cb - > battleCanCastSpell ( hero , spells : : Mode : : HERO ) ;
2017-06-05 21:16:12 +02:00
//if magic is blocked, we leave button active, so the message can be displayed after button click
canCastSpells = spellcastingProblem = = ESpellCastProblem : : OK | | spellcastingProblem = = ESpellCastProblem : : MAGIC_IS_BLOCKED ;
}
2019-03-24 00:51:50 +02:00
bool canWait = activeStack ? ! activeStack - > waitedThisTurn : false ;
2013-07-21 13:10:38 +03:00
bOptions - > block ( on ) ;
bFlee - > block ( on | | ! curInt - > cb - > battleCanFlee ( ) ) ;
bSurrender - > block ( on | | curInt - > cb - > battleGetSurrenderCost ( ) < 0 ) ;
2014-01-13 20:44:21 +03:00
// block only if during enemy turn and auto-fight is off
// othervice - crash on accessing non-exisiting active stack
2014-01-14 17:34:08 +03:00
bAutofight - > block ( ! curInt - > isAutoFightOn & & ! activeStack ) ;
2013-07-21 13:10:38 +03:00
2016-10-28 23:37:20 +02:00
if ( tacticsMode & & btactEnd & & btactNext )
2013-07-21 13:10:38 +03:00
{
btactNext - > block ( on ) ;
btactEnd - > block ( on ) ;
}
else
{
bConsoleUp - > block ( on ) ;
bConsoleDown - > block ( on ) ;
}
2016-10-01 17:18:46 +02:00
bSpell - > block ( on | | tacticsMode | | ! canCastSpells ) ;
2013-07-21 13:10:38 +03:00
bWait - > block ( on | | tacticsMode | | ! canWait ) ;
bDefence - > block ( on | | tacticsMode ) ;
}
2011-12-14 00:35:28 +03:00
void CBattleInterface : : startAction ( const BattleAction * action )
2009-01-15 19:01:08 +02:00
{
2013-07-16 21:12:47 +03:00
//setActiveStack(nullptr);
2013-07-06 19:10:20 +03:00
setHoveredStack ( nullptr ) ;
2013-07-21 13:10:38 +03:00
blockUI ( true ) ;
2013-07-06 19:10:20 +03:00
2017-07-20 06:08:49 +02:00
if ( action - > actionType = = EActionType : : END_TACTIC_PHASE )
2009-01-15 19:01:08 +02:00
{
2011-12-14 00:35:28 +03:00
SDL_FreeSurface ( menu ) ;
menu = BitmapHandler : : loadBitmap ( " CBAR.bmp " ) ;
2009-01-15 19:01:08 +02:00
2011-12-14 00:35:28 +03:00
graphics - > blueToPlayersAdv ( menu , curInt - > playerID ) ;
return ;
2009-01-15 19:01:08 +02:00
}
2011-12-14 00:35:28 +03:00
const CStack * stack = curInt - > cb - > battleGetStackByID ( action - > stackNumber ) ;
2016-10-28 23:37:20 +02:00
if ( stack )
2009-01-15 19:01:08 +02:00
{
2011-12-14 00:35:28 +03:00
queue - > update ( ) ;
2009-01-15 19:01:08 +02:00
}
2011-12-14 00:35:28 +03:00
else
2009-01-15 19:01:08 +02:00
{
2017-07-20 06:08:49 +02:00
assert ( action - > actionType = = EActionType : : HERO_SPELL ) ; //only cast spell is valid action without acting stack number
2009-01-15 19:01:08 +02:00
}
2017-07-20 06:08:49 +02:00
auto actionTarget = action - > getTarget ( curInt - > cb . get ( ) ) ;
if ( action - > actionType = = EActionType : : WALK
| | ( action - > actionType = = EActionType : : WALK_AND_ATTACK & & actionTarget . at ( 0 ) . hexValue ! = stack - > getPosition ( ) ) )
2009-01-15 19:01:08 +02:00
{
2013-11-07 15:48:41 +03:00
assert ( stack ) ;
2011-12-14 00:35:28 +03:00
moveStarted = true ;
2016-10-28 23:37:20 +02:00
if ( creAnims [ action - > stackNumber ] - > framesInGroup ( CCreatureAnim : : MOVE_START ) )
2009-01-15 19:01:08 +02:00
{
2011-12-14 00:35:28 +03:00
pendingAnims . push_back ( std : : make_pair ( new CMovementStartAnimation ( this , stack ) , false ) ) ;
2009-01-15 19:01:08 +02:00
}
2022-08-12 10:38:09 +02:00
if ( shouldRotate ( stack , stack - > getPosition ( ) , actionTarget . at ( 0 ) . hexValue ) )
pendingAnims . push_back ( std : : make_pair ( new CReverseAnimation ( this , stack , stack - > getPosition ( ) , true ) , false ) ) ;
2009-01-15 19:01:08 +02:00
}
2010-08-30 02:12:34 +03:00
2013-07-15 11:51:48 +03:00
redraw ( ) ; // redraw after deactivation, including proper handling of hovered hexes
2011-12-14 00:35:28 +03:00
2017-07-20 06:08:49 +02:00
if ( action - > actionType = = EActionType : : HERO_SPELL ) //when hero casts spell
2011-12-14 00:35:28 +03:00
{
2017-09-27 16:35:30 +02:00
setHeroAnimation ( action - > side , 4 ) ;
2011-12-14 00:35:28 +03:00
return ;
2010-08-30 02:12:34 +03:00
}
2017-09-27 16:35:30 +02:00
2016-10-28 23:37:20 +02:00
if ( ! stack )
2010-08-30 02:12:34 +03:00
{
2017-08-11 13:38:10 +02:00
logGlobal - > error ( " Something wrong with stackNumber in actionStarted. Stack number: %d " , action - > stackNumber ) ;
2011-12-14 00:35:28 +03:00
return ;
2009-01-15 19:01:08 +02:00
}
2011-12-14 00:35:28 +03:00
int txtid = 0 ;
switch ( action - > actionType )
{
2017-07-20 06:08:49 +02:00
case EActionType : : WAIT :
2011-12-14 00:35:28 +03:00
txtid = 136 ;
break ;
2017-07-20 06:08:49 +02:00
case EActionType : : BAD_MORALE :
2011-12-14 00:35:28 +03:00
txtid = - 34 ; //negative -> no separate singular/plural form
2017-07-20 06:08:49 +02:00
displayEffect ( 30 , stack - > getPosition ( ) ) ;
2013-05-04 16:14:23 +03:00
CCS - > soundh - > playSound ( soundBase : : BADMRLE ) ;
2011-12-14 00:35:28 +03:00
break ;
}
2009-01-15 19:01:08 +02:00
2017-07-04 13:24:46 +02:00
if ( txtid ! = 0 )
console - > addText ( stack - > formatGeneralMessage ( txtid ) ) ;
2009-01-15 19:01:08 +02:00
2011-12-14 00:35:28 +03:00
//displaying special abilities
2017-07-04 13:24:46 +02:00
switch ( action - > actionType )
2011-01-08 21:38:42 +02:00
{
2017-07-20 06:08:49 +02:00
case EActionType : : STACK_HEAL :
displayEffect ( 74 , actionTarget . at ( 0 ) . hexValue ) ;
2011-12-14 00:35:28 +03:00
CCS - > soundh - > playSound ( soundBase : : REGENER ) ;
break ;
2011-01-08 21:38:42 +02:00
}
2011-12-14 00:35:28 +03:00
}
2011-01-08 21:38:42 +02:00
2011-12-14 00:35:28 +03:00
void CBattleInterface : : waitForAnims ( )
{
2016-11-25 21:12:22 +02:00
auto unlockPim = vstd : : makeUnlockGuard ( * CPlayerInterface : : pim ) ;
2011-12-14 00:35:28 +03:00
animsAreDisplayed . waitWhileTrue ( ) ;
2009-01-15 19:01:08 +02:00
}
2011-12-14 00:35:28 +03:00
void CBattleInterface : : bEndTacticPhase ( )
2009-01-15 19:01:08 +02:00
{
2013-07-06 19:10:20 +03:00
setActiveStack ( nullptr ) ;
2013-07-21 13:10:38 +03:00
blockUI ( true ) ;
2011-12-14 00:35:28 +03:00
tacticsMode = false ;
2009-01-15 19:01:08 +02:00
}
2011-12-14 00:35:28 +03:00
static bool immobile ( const CStack * s )
2009-01-15 19:01:08 +02:00
{
2011-12-14 00:35:28 +03:00
return ! s - > Speed ( 0 , true ) ; //should bound stacks be immobile?
2009-01-15 19:01:08 +02:00
}
2017-07-15 13:08:20 +02:00
void CBattleInterface : : bTacticNextStack ( const CStack * current )
2009-01-15 19:01:08 +02:00
{
2016-10-28 23:37:20 +02:00
if ( ! current )
2012-04-03 02:23:14 +03:00
current = activeStack ;
2012-03-01 14:57:38 +03:00
//no switching stacks when the current one is moving
2012-04-03 02:23:14 +03:00
waitForAnims ( ) ;
2012-03-01 14:57:38 +03:00
2011-12-14 00:35:28 +03:00
TStacks stacksOfMine = tacticianInterface - > cb - > battleGetStacks ( CBattleCallback : : ONLY_MINE ) ;
2016-10-28 23:37:20 +02:00
vstd : : erase_if ( stacksOfMine , & immobile ) ;
if ( stacksOfMine . empty ( ) )
2016-08-09 17:15:58 +02:00
{
bEndTacticPhase ( ) ;
return ;
}
2013-06-29 16:05:48 +03:00
auto it = vstd : : find ( stacksOfMine , current ) ;
2016-10-28 23:37:20 +02:00
if ( it ! = stacksOfMine . end ( ) & & + + it ! = stacksOfMine . end ( ) )
2011-12-14 00:35:28 +03:00
stackActivated ( * it ) ;
else
stackActivated ( stacksOfMine . front ( ) ) ;
2012-04-04 11:03:52 +03:00
2009-01-15 19:01:08 +02:00
}
2009-08-24 15:55:05 +03:00
2012-03-31 00:36:07 +03:00
std : : string formatDmgRange ( std : : pair < ui32 , ui32 > dmgRange )
{
2016-10-28 23:37:20 +02:00
if ( dmgRange . first ! = dmgRange . second )
2012-03-31 00:36:07 +03:00
return ( boost : : format ( " %d - %d " ) % dmgRange . first % dmgRange . second ) . str ( ) ;
else
return ( boost : : format ( " %d " ) % dmgRange . first ) . str ( ) ;
}
2017-07-01 10:34:00 +02:00
bool CBattleInterface : : canStackMoveHere ( const CStack * activeStack , BattleHex myNumber )
2012-04-18 12:01:08 +03:00
{
2017-07-20 06:08:49 +02:00
std : : vector < BattleHex > acc = curInt - > cb - > battleGetAvailableHexes ( activeStack ) ;
2017-07-03 11:59:33 +02:00
BattleHex shiftedDest = myNumber . cloneInDirection ( activeStack - > destShiftDir ( ) , false ) ;
2012-04-18 12:01:08 +03:00
if ( vstd : : contains ( acc , myNumber ) )
return true ;
else if ( activeStack - > doubleWide ( ) & & vstd : : contains ( acc , shiftedDest ) )
return true ;
else
return false ;
}
2012-03-31 00:36:07 +03:00
void CBattleInterface : : handleHex ( BattleHex myNumber , int eventType )
{
2019-03-22 22:39:53 +02:00
if ( ! myTurn | | ! battleActionsStarted ) //we are not permit to do anything
2012-04-04 11:03:52 +03:00
return ;
2012-03-31 00:36:07 +03:00
2014-03-07 16:21:09 +03:00
// This function handles mouse move over hexes and l-clicking on them.
2012-03-31 00:36:07 +03:00
// First we decide what happens if player clicks on this hex and set appropriately
// consoleMsg, cursorFrame/Type and prepare lambda realizeAction.
2014-03-07 16:21:09 +03:00
//
2012-03-31 00:36:07 +03:00
// Then, depending whether it was hover/click we either call the action or set tooltip/cursor.
//used when hovering -> tooltip message and cursor to be set
std : : string consoleMsg ;
bool setCursor = true ; //if we want to suppress setting cursor
2012-12-14 18:32:53 +03:00
ECursor : : ECursorTypes cursorType = ECursor : : COMBAT ;
int cursorFrame = ECursor : : COMBAT_POINTER ; //TODO: is this line used?
2014-03-07 16:21:09 +03:00
2012-03-31 00:36:07 +03:00
//used when l-clicking -> action to be called upon the click
std : : function < void ( ) > realizeAction ;
2012-08-28 18:38:00 +03:00
//Get stack on the hex - first try to grab the alive one, if not found -> allow dead stacks.
2017-07-20 06:08:49 +02:00
const CStack * shere = curInt - > cb - > battleGetStackByPos ( myNumber , true ) ;
if ( ! shere )
2012-08-28 18:38:00 +03:00
shere = curInt - > cb - > battleGetStackByPos ( myNumber , false ) ;
2012-03-31 00:36:07 +03:00
2017-07-20 06:08:49 +02:00
if ( ! activeStack )
2012-03-31 00:36:07 +03:00
return ;
2012-04-16 20:12:39 +03:00
bool ourStack = false ;
if ( shere )
ourStack = shere - > owner = = curInt - > playerID ;
2014-03-07 16:21:09 +03:00
2013-07-06 19:10:20 +03:00
//stack changed, update selection border
if ( shere ! = mouseHoveredStack )
{
setHoveredStack ( shere ) ;
}
2012-04-04 11:03:52 +03:00
localActions . clear ( ) ;
2012-04-18 18:57:49 +03:00
illegalActions . clear ( ) ;
2019-05-04 05:42:55 +02:00
reorderPossibleActionsPriority ( activeStack , shere ? MouseHoveredHexContext : : OCCUPIED_HEX : MouseHoveredHexContext : : UNOCCUPIED_HEX ) ;
2016-10-15 22:50:12 +02:00
const bool forcedAction = possibleActions . size ( ) = = 1 ;
2019-05-04 05:42:55 +02:00
for ( PossiblePlayerBattleAction action : possibleActions )
2012-04-04 11:03:52 +03:00
{
2013-07-25 14:53:36 +03:00
bool legalAction = false ; //this action is legal and can be performed
2012-04-18 16:24:18 +03:00
bool notLegal = false ; //this action is not legal and should display message
2014-03-07 16:21:09 +03:00
2012-04-16 20:12:39 +03:00
switch ( action )
2014-03-07 16:21:09 +03:00
{
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : CHOOSE_TACTICS_STACK :
2012-04-16 20:12:39 +03:00
if ( shere & & ourStack )
legalAction = true ;
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : MOVE_TACTICS :
case PossiblePlayerBattleAction : : MOVE_STACK :
2012-08-14 09:31:14 +03:00
{
if ( ! ( shere & & shere - > alive ( ) ) ) //we can walk on dead stacks
{
2017-07-20 06:08:49 +02:00
if ( canStackMoveHere ( activeStack , myNumber ) )
2012-08-14 09:31:14 +03:00
legalAction = true ;
}
2012-04-16 20:12:39 +03:00
break ;
2012-08-14 09:31:14 +03:00
}
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : ATTACK :
case PossiblePlayerBattleAction : : WALK_AND_ATTACK :
case PossiblePlayerBattleAction : : ATTACK_AND_RETURN :
2012-03-31 00:36:07 +03:00
{
2017-07-20 06:08:49 +02:00
if ( curInt - > cb - > battleCanAttack ( activeStack , shere , myNumber ) )
2012-04-17 17:50:23 +03:00
{
2015-04-09 21:49:11 +02:00
if ( isTileAttackable ( myNumber ) ) // move isTileAttackable to be part of battleCanAttack?
2012-04-18 12:01:08 +03:00
{
setBattleCursor ( myNumber ) ; // temporary - needed for following function :(
BattleHex attackFromHex = fromWhichHexAttack ( myNumber ) ;
2012-03-31 00:36:07 +03:00
2012-04-18 12:01:08 +03:00
if ( attackFromHex > = 0 ) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
legalAction = true ;
}
2012-04-17 17:50:23 +03:00
}
2012-04-16 20:12:39 +03:00
}
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : SHOOT :
2017-07-20 06:08:49 +02:00
if ( curInt - > cb - > battleCanShoot ( activeStack , myNumber ) )
2012-04-16 20:12:39 +03:00
legalAction = true ;
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : ANY_LOCATION :
2012-05-07 19:04:43 +03:00
if ( myNumber > - 1 ) //TODO: this should be checked for all actions
{
2017-07-20 06:08:49 +02:00
if ( isCastingPossibleHere ( activeStack , shere , myNumber ) )
2016-10-02 00:32:28 +02:00
legalAction = true ;
2012-05-07 19:04:43 +03:00
}
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : AIMED_SPELL_CREATURE :
2017-07-20 06:08:49 +02:00
if ( shere & & isCastingPossibleHere ( activeStack , shere , myNumber ) )
2012-04-16 20:12:39 +03:00
legalAction = true ;
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : RANDOM_GENIE_SPELL :
2012-03-31 00:36:07 +03:00
{
2017-07-20 06:08:49 +02:00
if ( shere & & ourStack & & shere ! = activeStack & & shere - > alive ( ) ) //only positive spells for other allied creatures
2012-04-16 20:12:39 +03:00
{
2016-09-09 19:30:36 +02:00
int spellID = curInt - > cb - > battleGetRandomStackSpell ( CRandomGenerator : : getDefault ( ) , shere , CBattleInfoCallback : : RANDOM_GENIE ) ;
2017-07-20 06:08:49 +02:00
if ( spellID > - 1 )
2012-04-16 20:12:39 +03:00
{
legalAction = true ;
}
}
2012-03-31 00:36:07 +03:00
}
2012-04-16 20:12:39 +03:00
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : OBSTACLE :
2017-07-20 06:08:49 +02:00
if ( isCastingPossibleHere ( activeStack , shere , myNumber ) )
2012-04-28 18:38:34 +03:00
legalAction = true ;
2012-04-16 20:12:39 +03:00
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : TELEPORT :
2012-03-31 00:36:07 +03:00
{
2016-09-24 08:27:58 +02:00
//todo: move to mechanics
2012-04-18 12:01:08 +03:00
ui8 skill = 0 ;
if ( creatureCasting )
2017-07-20 06:08:49 +02:00
skill = activeStack - > getEffectLevel ( SpellID ( SpellID : : TELEPORT ) . toSpell ( ) ) ;
2012-04-18 12:01:08 +03:00
else
2015-09-17 07:42:30 +02:00
skill = getActiveHero ( ) - > getEffectLevel ( SpellID ( SpellID : : TELEPORT ) . toSpell ( ) ) ;
2012-04-17 11:46:09 +03:00
//TODO: explicitely save power, skill
2012-04-28 22:40:27 +03:00
if ( curInt - > cb - > battleCanTeleportTo ( selectedStack , myNumber , skill ) )
2012-04-16 20:12:39 +03:00
legalAction = true ;
2012-03-31 00:36:07 +03:00
else
2012-04-18 16:24:18 +03:00
notLegal = true ;
2012-03-31 00:36:07 +03:00
}
2012-04-16 20:12:39 +03:00
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : SACRIFICE : //choose our living stack to sacrifice
2012-04-28 22:40:27 +03:00
if ( shere & & shere ! = selectedStack & & ourStack & & shere - > alive ( ) )
legalAction = true ;
else
notLegal = true ;
2012-04-16 20:12:39 +03:00
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : FREE_LOCATION :
2016-03-04 18:11:42 +02:00
legalAction = true ;
2017-07-20 06:08:49 +02:00
if ( ! isCastingPossibleHere ( activeStack , shere , myNumber ) )
2012-05-18 23:50:16 +03:00
{
2016-03-04 18:11:42 +02:00
legalAction = false ;
notLegal = true ;
2012-05-18 23:50:16 +03:00
}
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : CATAPULT :
2012-04-16 20:12:39 +03:00
if ( isCatapultAttackable ( myNumber ) )
legalAction = true ;
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : HEAL :
2012-04-16 20:12:39 +03:00
if ( shere & & ourStack & & shere - > canBeHealed ( ) )
legalAction = true ;
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : RISE_DEMONS :
2012-04-16 20:12:39 +03:00
if ( shere & & ourStack & & ! shere - > alive ( ) )
2015-11-07 10:42:06 +02:00
{
2016-10-28 23:37:20 +02:00
if ( ! ( shere - > hasBonusOfType ( Bonus : : UNDEAD )
2016-02-28 00:08:56 +02:00
| | shere - > hasBonusOfType ( Bonus : : NON_LIVING )
2020-01-18 11:02:17 +02:00
| | shere - > hasBonusOfType ( Bonus : : GARGOYLE )
2017-07-20 06:08:49 +02:00
| | shere - > summoned
2017-07-04 13:24:46 +02:00
| | shere - > isClone ( )
2015-11-07 10:42:06 +02:00
| | shere - > hasBonusOfType ( Bonus : : SIEGE_WEAPON )
) )
legalAction = true ;
2016-02-28 00:08:56 +02:00
}
2012-04-16 20:12:39 +03:00
break ;
2012-03-31 00:36:07 +03:00
}
2012-04-16 20:12:39 +03:00
if ( legalAction )
localActions . push_back ( action ) ;
2016-10-15 22:50:12 +02:00
else if ( notLegal | | forcedAction )
2012-04-16 20:12:39 +03:00
illegalActions . push_back ( action ) ;
}
2019-05-04 05:42:55 +02:00
illegalAction = PossiblePlayerBattleAction : : INVALID ; //clear it in first place
2012-03-31 00:36:07 +03:00
2012-04-16 20:12:39 +03:00
if ( vstd : : contains ( localActions , selectedAction ) ) //try to use last selected action by default
currentAction = selectedAction ;
2015-03-18 23:10:41 +02:00
else if ( localActions . size ( ) ) //if not possible, select first available action (they are sorted by suggested priority)
2012-04-16 20:12:39 +03:00
currentAction = localActions . front ( ) ;
else //no legal action possible
{
2019-05-04 05:42:55 +02:00
currentAction = PossiblePlayerBattleAction : : INVALID ; //don't allow to do anything
2012-03-31 00:36:07 +03:00
2012-04-16 20:12:39 +03:00
if ( vstd : : contains ( illegalActions , selectedAction ) )
illegalAction = selectedAction ;
else if ( illegalActions . size ( ) )
illegalAction = illegalActions . front ( ) ;
2012-04-17 11:46:09 +03:00
else if ( shere & & ourStack & & shere - > alive ( ) ) //last possibility - display info about our creature
{
2019-05-04 05:42:55 +02:00
currentAction = PossiblePlayerBattleAction : : CREATURE_INFO ;
2012-04-17 11:46:09 +03:00
}
2012-04-16 20:12:39 +03:00
else
2019-05-04 05:42:55 +02:00
illegalAction = PossiblePlayerBattleAction : : INVALID ; //we should never be here
2012-03-31 00:36:07 +03:00
}
2012-04-16 20:12:39 +03:00
bool isCastingPossible = false ;
2012-04-18 16:24:18 +03:00
bool secondaryTarget = false ;
2012-03-31 00:36:07 +03:00
2019-05-04 05:42:55 +02:00
if ( currentAction > PossiblePlayerBattleAction : : INVALID )
2012-03-31 00:36:07 +03:00
{
2012-04-16 20:12:39 +03:00
switch ( currentAction ) //display console message, realize selected action
2012-03-31 00:36:07 +03:00
{
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : CHOOSE_TACTICS_STACK :
2012-04-16 20:12:39 +03:00
consoleMsg = ( boost : : format ( CGI - > generaltexth - > allTexts [ 481 ] ) % shere - > getName ( ) ) . str ( ) ; //Select %s
2017-07-17 14:35:57 +02:00
realizeAction = [ = ] ( ) { stackActivated ( shere ) ; } ;
2012-04-16 20:12:39 +03:00
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : MOVE_TACTICS :
case PossiblePlayerBattleAction : : MOVE_STACK :
2012-04-28 16:01:39 +03:00
if ( activeStack - > hasBonusOfType ( Bonus : : FLYING ) )
2012-04-16 20:12:39 +03:00
{
cursorFrame = ECursor : : COMBAT_FLY ;
consoleMsg = ( boost : : format ( CGI - > generaltexth - > allTexts [ 295 ] ) % activeStack - > getName ( ) ) . str ( ) ; //Fly %s here
}
else
{
cursorFrame = ECursor : : COMBAT_MOVE ;
consoleMsg = ( boost : : format ( CGI - > generaltexth - > allTexts [ 294 ] ) % activeStack - > getName ( ) ) . str ( ) ; //Move %s here
}
2012-03-31 00:36:07 +03:00
2017-07-19 01:06:05 +02:00
realizeAction = [ = ] ( )
2012-03-31 00:36:07 +03:00
{
2017-07-01 10:34:00 +02:00
if ( activeStack - > doubleWide ( ) )
2012-03-31 00:36:07 +03:00
{
2017-07-20 06:08:49 +02:00
std : : vector < BattleHex > acc = curInt - > cb - > battleGetAvailableHexes ( activeStack ) ;
2017-07-03 11:59:33 +02:00
BattleHex shiftedDest = myNumber . cloneInDirection ( activeStack - > destShiftDir ( ) , false ) ;
2017-07-01 10:34:00 +02:00
if ( vstd : : contains ( acc , myNumber ) )
2017-07-20 06:08:49 +02:00
giveCommand ( EActionType : : WALK , myNumber ) ;
2017-07-01 10:34:00 +02:00
else if ( vstd : : contains ( acc , shiftedDest ) )
2017-07-20 06:08:49 +02:00
giveCommand ( EActionType : : WALK , shiftedDest ) ;
2012-03-31 00:36:07 +03:00
}
2012-04-16 20:12:39 +03:00
else
2012-03-31 00:36:07 +03:00
{
2017-07-20 06:08:49 +02:00
giveCommand ( EActionType : : WALK , myNumber ) ;
2012-03-31 00:36:07 +03:00
}
2012-04-16 20:12:39 +03:00
} ;
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : ATTACK :
case PossiblePlayerBattleAction : : WALK_AND_ATTACK :
case PossiblePlayerBattleAction : : ATTACK_AND_RETURN : //TODO: allow to disable return
2012-03-31 00:36:07 +03:00
{
2017-07-20 06:08:49 +02:00
setBattleCursor ( myNumber ) ; //handle direction of cursor and attackable tile
setCursor = false ; //don't overwrite settings from the call above //TODO: what does it mean?
2012-03-31 00:36:07 +03:00
2019-05-04 05:42:55 +02:00
bool returnAfterAttack = currentAction = = PossiblePlayerBattleAction : : ATTACK_AND_RETURN ;
2017-07-20 06:08:49 +02:00
realizeAction = [ = ] ( )
{
BattleHex attackFromHex = fromWhichHexAttack ( myNumber ) ;
if ( attackFromHex . isValid ( ) ) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
{
auto command = new BattleAction ( BattleAction : : makeMeleeAttack ( activeStack , myNumber , attackFromHex , returnAfterAttack ) ) ;
sendCommand ( command , activeStack ) ;
}
} ;
2020-10-01 10:38:06 +02:00
TDmgRange damage = curInt - > cb - > battleEstimateDamage ( activeStack , shere ) ;
std : : string estDmgText = formatDmgRange ( std : : make_pair ( ( ui32 ) damage . first , ( ui32 ) damage . second ) ) ; //calculating estimated dmg
2017-07-20 06:08:49 +02:00
consoleMsg = ( boost : : format ( CGI - > generaltexth - > allTexts [ 36 ] ) % shere - > getName ( ) % estDmgText ) . str ( ) ; //Attack %s (%s damage)
}
2012-04-16 20:12:39 +03:00
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : SHOOT :
2012-03-31 00:36:07 +03:00
{
2016-10-28 23:37:20 +02:00
if ( curInt - > cb - > battleHasShootingPenalty ( activeStack , myNumber ) )
2012-04-16 20:12:39 +03:00
cursorFrame = ECursor : : COMBAT_SHOOT_PENALTY ;
else
cursorFrame = ECursor : : COMBAT_SHOOT ;
2017-07-20 06:08:49 +02:00
realizeAction = [ = ] ( ) { giveCommand ( EActionType : : SHOOT , myNumber ) ; } ;
2020-10-06 01:27:04 +02:00
TDmgRange damage = curInt - > cb - > battleEstimateDamage ( activeStack , shere ) ;
2020-10-01 10:38:06 +02:00
std : : string estDmgText = formatDmgRange ( std : : make_pair ( ( ui32 ) damage . first , ( ui32 ) damage . second ) ) ; //calculating estimated dmg
2012-04-16 20:12:39 +03:00
//printing - Shoot %s (%d shots left, %s damage)
2017-07-20 06:08:49 +02:00
consoleMsg = ( boost : : format ( CGI - > generaltexth - > allTexts [ 296 ] ) % shere - > getName ( ) % activeStack - > shots . available ( ) % estDmgText ) . str ( ) ;
2012-03-31 00:36:07 +03:00
}
2012-04-16 20:12:39 +03:00
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : AIMED_SPELL_CREATURE :
2017-07-20 06:08:49 +02:00
sp = CGI - > spellh - > objects [ creatureCasting ? creatureSpellToCast : spellToCast - > actionSubtype ] ; //necessary if creature has random Genie spell at same time
2012-04-18 18:57:49 +03:00
consoleMsg = boost : : str ( boost : : format ( CGI - > generaltexth - > allTexts [ 27 ] ) % sp - > name % shere - > getName ( ) ) ; //Cast %s on %s
switch ( sp - > id )
2012-04-18 12:01:08 +03:00
{
2013-02-11 02:24:57 +03:00
case SpellID : : SACRIFICE :
case SpellID : : TELEPORT :
2013-07-25 14:53:36 +03:00
selectedStack = shere ; //remember first target
2012-04-18 18:57:49 +03:00
secondaryTarget = true ;
break ;
2012-04-18 12:01:08 +03:00
}
2012-04-18 18:57:49 +03:00
isCastingPossible = true ;
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : ANY_LOCATION :
2017-07-20 06:08:49 +02:00
sp = CGI - > spellh - > objects [ creatureCasting ? creatureSpellToCast : spellToCast - > actionSubtype ] ; //necessary if creature has random Genie spell at same time
2012-05-18 23:50:16 +03:00
consoleMsg = boost : : str ( boost : : format ( CGI - > generaltexth - > allTexts [ 26 ] ) % sp - > name ) ; //Cast %s
2012-05-07 19:04:43 +03:00
isCastingPossible = true ;
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : RANDOM_GENIE_SPELL : //we assume that teleport / sacrifice will never be available as random spell
2013-06-26 14:18:27 +03:00
sp = nullptr ;
2012-04-18 18:57:49 +03:00
consoleMsg = boost : : str ( boost : : format ( CGI - > generaltexth - > allTexts [ 301 ] ) % shere - > getName ( ) ) ; //Cast a spell on %
2012-06-20 16:03:05 +03:00
creatureCasting = true ;
2012-04-18 18:57:49 +03:00
isCastingPossible = true ;
2012-04-16 20:12:39 +03:00
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : TELEPORT :
2012-04-16 20:12:39 +03:00
consoleMsg = CGI - > generaltexth - > allTexts [ 25 ] ; //Teleport Here
2013-07-25 14:53:36 +03:00
cursorFrame = ECursor : : COMBAT_TELEPORT ;
2012-04-16 20:12:39 +03:00
isCastingPossible = true ;
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : OBSTACLE :
2012-04-16 20:12:39 +03:00
consoleMsg = CGI - > generaltexth - > allTexts [ 550 ] ;
2013-07-25 14:53:36 +03:00
//TODO: remove obstacle cursor
2012-04-16 20:12:39 +03:00
isCastingPossible = true ;
2012-04-28 18:18:21 +03:00
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : SACRIFICE :
2012-04-28 22:40:27 +03:00
consoleMsg = ( boost : : format ( CGI - > generaltexth - > allTexts [ 549 ] ) % shere - > getName ( ) ) . str ( ) ; //sacrifice the %s
2014-03-07 16:21:09 +03:00
cursorFrame = ECursor : : COMBAT_SACRIFICE ;
2012-04-28 22:40:27 +03:00
isCastingPossible = true ;
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : FREE_LOCATION :
2012-05-18 23:50:16 +03:00
consoleMsg = boost : : str ( boost : : format ( CGI - > generaltexth - > allTexts [ 26 ] ) % sp - > name ) ; //Cast %s
isCastingPossible = true ;
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : HEAL :
2012-04-16 20:12:39 +03:00
cursorFrame = ECursor : : COMBAT_HEAL ;
consoleMsg = ( boost : : format ( CGI - > generaltexth - > allTexts [ 419 ] ) % shere - > getName ( ) ) . str ( ) ; //Apply first aid to the %s
2017-07-20 06:08:49 +02:00
realizeAction = [ = ] ( ) { giveCommand ( EActionType : : STACK_HEAL , myNumber ) ; } ; //command healing
2012-04-16 20:12:39 +03:00
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : RISE_DEMONS :
2012-04-16 20:12:39 +03:00
cursorType = ECursor : : SPELLBOOK ;
2017-07-19 01:06:05 +02:00
realizeAction = [ = ] ( )
2016-02-28 00:08:56 +02:00
{
2017-07-20 06:08:49 +02:00
giveCommand ( EActionType : : DAEMON_SUMMONING , myNumber ) ;
2015-11-07 10:42:06 +02:00
} ;
2012-04-16 20:12:39 +03:00
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : CATAPULT :
2012-04-16 20:12:39 +03:00
cursorFrame = ECursor : : COMBAT_SHOOT_CATAPULT ;
2017-07-20 06:08:49 +02:00
realizeAction = [ = ] ( ) { giveCommand ( EActionType : : CATAPULT , myNumber ) ; } ;
2012-04-18 16:24:18 +03:00
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : CREATURE_INFO :
2012-04-17 11:46:09 +03:00
{
cursorFrame = ECursor : : COMBAT_QUERY ;
consoleMsg = ( boost : : format ( CGI - > generaltexth - > allTexts [ 297 ] ) % shere - > getName ( ) ) . str ( ) ;
2018-07-25 00:36:48 +02:00
realizeAction = [ = ] ( ) { GH . pushIntT < CStackWindow > ( shere , false ) ; } ;
2012-04-16 20:12:39 +03:00
break ;
2013-07-06 19:10:20 +03:00
}
2012-03-31 00:36:07 +03:00
}
2012-04-16 20:12:39 +03:00
}
2012-04-17 11:46:09 +03:00
else //no possible valid action, display message
2012-04-16 20:12:39 +03:00
{
switch ( illegalAction )
2012-03-31 00:36:07 +03:00
{
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : AIMED_SPELL_CREATURE :
case PossiblePlayerBattleAction : : RANDOM_GENIE_SPELL :
2012-04-16 20:12:39 +03:00
cursorFrame = ECursor : : COMBAT_BLOCKED ;
consoleMsg = CGI - > generaltexth - > allTexts [ 23 ] ;
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : TELEPORT :
2013-07-25 14:53:36 +03:00
cursorFrame = ECursor : : COMBAT_BLOCKED ;
2012-04-16 20:12:39 +03:00
consoleMsg = CGI - > generaltexth - > allTexts [ 24 ] ; //Invalid Teleport Destination
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : SACRIFICE :
2012-04-28 22:40:27 +03:00
consoleMsg = CGI - > generaltexth - > allTexts [ 543 ] ; //choose army to sacrifice
break ;
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : FREE_LOCATION :
2012-05-18 23:50:16 +03:00
cursorFrame = ECursor : : COMBAT_BLOCKED ;
consoleMsg = boost : : str ( boost : : format ( CGI - > generaltexth - > allTexts [ 181 ] ) % sp - > name ) ; //No room to place %s here
break ;
2012-04-16 20:12:39 +03:00
default :
2012-08-06 09:01:02 +03:00
if ( myNumber = = - 1 )
CCS - > curh - > changeGraphic ( ECursor : : COMBAT , ECursor : : COMBAT_POINTER ) ; //set neutral cursor over menu etc.
else
cursorFrame = ECursor : : COMBAT_BLOCKED ;
2012-04-16 20:12:39 +03:00
break ;
2012-03-31 00:36:07 +03:00
}
}
2012-04-16 20:12:39 +03:00
if ( isCastingPossible ) //common part
2012-03-31 00:36:07 +03:00
{
2013-07-25 14:53:36 +03:00
switch ( currentAction ) //don't use that with teleport / sacrifice
{
2019-05-04 05:42:55 +02:00
case PossiblePlayerBattleAction : : TELEPORT : //FIXME: more generic solution?
case PossiblePlayerBattleAction : : SACRIFICE :
2013-07-25 14:53:36 +03:00
break ;
default :
cursorType = ECursor : : SPELLBOOK ;
cursorFrame = 0 ;
2016-10-28 23:37:20 +02:00
if ( consoleMsg . empty ( ) & & sp )
2013-07-25 14:53:36 +03:00
consoleMsg = boost : : str ( boost : : format ( CGI - > generaltexth - > allTexts [ 26 ] ) % sp - > name ) ; //Cast %s
break ;
}
2014-03-07 16:21:09 +03:00
2017-07-19 01:06:05 +02:00
realizeAction = [ = ] ( )
2012-03-31 00:36:07 +03:00
{
2017-07-20 06:08:49 +02:00
if ( secondaryTarget ) //select that target now
2012-03-31 00:36:07 +03:00
{
2017-07-20 06:08:49 +02:00
2012-04-18 16:24:18 +03:00
possibleActions . clear ( ) ;
2013-02-11 02:24:57 +03:00
switch ( sp - > id . toEnum ( ) )
2012-04-18 16:24:18 +03:00
{
2014-03-07 16:21:09 +03:00
case SpellID : : TELEPORT : //don't cast spell yet, only select target
2017-07-20 06:08:49 +02:00
spellToCast - > aimToUnit ( shere ) ;
2019-05-04 05:42:55 +02:00
possibleActions . push_back ( PossiblePlayerBattleAction : : TELEPORT ) ;
2012-04-18 16:24:18 +03:00
break ;
2013-02-11 02:24:57 +03:00
case SpellID : : SACRIFICE :
2017-07-20 06:08:49 +02:00
spellToCast - > aimToHex ( myNumber ) ;
2019-05-04 05:42:55 +02:00
possibleActions . push_back ( PossiblePlayerBattleAction : : SACRIFICE ) ;
2012-04-18 16:24:18 +03:00
break ;
}
2012-04-18 12:01:08 +03:00
}
else
{
2016-10-28 23:37:20 +02:00
if ( creatureCasting )
2012-04-18 16:24:18 +03:00
{
2012-04-18 18:57:49 +03:00
if ( sp )
{
2017-07-20 06:08:49 +02:00
giveCommand ( EActionType : : MONSTER_SPELL , myNumber , creatureSpellToCast ) ;
2012-04-18 18:57:49 +03:00
}
else //unknown random spell
{
2017-07-20 06:08:49 +02:00
giveCommand ( EActionType : : MONSTER_SPELL , myNumber ) ;
2012-04-18 18:57:49 +03:00
}
2012-04-18 16:24:18 +03:00
}
else
{
2017-07-20 06:08:49 +02:00
assert ( sp ) ;
2013-02-11 02:24:57 +03:00
switch ( sp - > id . toEnum ( ) )
2012-04-28 22:40:27 +03:00
{
2017-07-20 06:08:49 +02:00
case SpellID : : SACRIFICE :
spellToCast - > aimToUnit ( shere ) ; //victim
break ;
default :
spellToCast - > aimToHex ( myNumber ) ;
break ;
2012-04-28 22:40:27 +03:00
}
2018-07-25 00:36:48 +02:00
curInt - > cb - > battleMakeAction ( spellToCast . get ( ) ) ;
2012-04-18 16:24:18 +03:00
endCastingSpell ( ) ;
}
2013-06-26 14:18:27 +03:00
selectedStack = nullptr ;
2012-03-31 00:36:07 +03:00
}
2012-04-16 20:12:39 +03:00
} ;
2012-03-31 00:36:07 +03:00
}
2017-07-20 06:08:49 +02:00
2013-07-25 14:53:36 +03:00
{
2016-10-28 23:37:20 +02:00
if ( eventType = = MOVE )
2013-07-25 14:53:36 +03:00
{
2016-10-28 23:37:20 +02:00
if ( setCursor )
2013-07-25 14:53:36 +03:00
CCS - > curh - > changeGraphic ( cursorType , cursorFrame ) ;
this - > console - > alterText ( consoleMsg ) ;
this - > console - > whoSetAlter = 0 ;
}
2016-10-28 23:37:20 +02:00
if ( eventType = = LCLICK & & realizeAction )
2013-07-25 14:53:36 +03:00
{
2014-03-07 16:21:09 +03:00
//opening creature window shouldn't affect myTurn...
2019-05-04 05:42:55 +02:00
if ( ( currentAction ! = PossiblePlayerBattleAction : : CREATURE_INFO ) & & ! secondaryTarget )
2013-07-25 14:53:36 +03:00
{
myTurn = false ; //tends to crash with empty calls
}
realizeAction ( ) ;
if ( ! secondaryTarget ) //do not replace teleport or sacrifice cursor
2014-03-07 16:21:09 +03:00
CCS - > curh - > changeGraphic ( ECursor : : COMBAT , ECursor : : COMBAT_POINTER ) ;
2013-07-25 14:53:36 +03:00
this - > console - > alterText ( " " ) ;
}
2017-07-20 06:08:49 +02:00
}
2012-04-16 20:12:39 +03:00
}
2016-10-28 23:37:20 +02:00
bool CBattleInterface : : isCastingPossibleHere ( const CStack * sactive , const CStack * shere , BattleHex myNumber )
2012-04-16 20:12:39 +03:00
{
2012-04-22 16:28:46 +03:00
creatureCasting = stackCanCastSpell & & ! spellDestSelectMode ; //TODO: allow creatures to cast aimed spells
2014-03-07 16:21:09 +03:00
2012-04-16 20:12:39 +03:00
bool isCastingPossible = true ;
int spellID = - 1 ;
if ( creatureCasting )
{
2012-04-18 18:57:49 +03:00
if ( creatureSpellToCast > - 1 & & ( shere ! = sactive ) ) //can't cast on itself
2012-04-18 12:01:08 +03:00
spellID = creatureSpellToCast ; //TODO: merge with SpellTocast?
2012-04-16 20:12:39 +03:00
}
2012-04-18 18:57:49 +03:00
else //hero casting
2017-07-20 06:08:49 +02:00
{
spellID = spellToCast - > actionSubtype ;
}
2012-04-16 20:12:39 +03:00
2013-06-26 14:18:27 +03:00
sp = nullptr ;
2014-03-07 16:21:09 +03:00
if ( spellID > = 0 )
sp = CGI - > spellh - > objects [ spellID ] ;
2012-04-16 20:12:39 +03:00
2012-04-22 16:28:46 +03:00
if ( sp )
2012-04-16 20:12:39 +03:00
{
2017-07-20 06:08:49 +02:00
const spells : : Caster * caster = creatureCasting ? static_cast < const spells : : Caster * > ( sactive ) : static_cast < const spells : : Caster * > ( curInt - > cb - > battleGetMyHero ( ) ) ;
2016-10-28 23:37:20 +02:00
if ( caster = = nullptr )
2015-09-28 15:06:26 +02:00
{
isCastingPossible = false ; //just in case
}
2012-04-16 20:12:39 +03:00
else
2015-09-28 15:06:26 +02:00
{
2017-07-20 06:08:49 +02:00
const spells : : Mode mode = creatureCasting ? spells : : Mode : : CREATURE_ACTIVE : spells : : Mode : : HERO ;
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
spells : : Target target ;
target . emplace_back ( myNumber ) ;
spells : : BattleCast cast ( curInt - > cb . get ( ) , caster , mode , sp ) ;
auto m = sp - > battleMechanics ( & cast ) ;
spells : : detail : : ProblemImpl problem ; //todo: display problem in status bar
2021-02-20 03:57:50 +02:00
isCastingPossible = m - > canBeCastAt ( target , problem ) ;
2015-09-28 15:06:26 +02:00
}
2012-04-16 20:12:39 +03:00
}
2015-09-16 16:35:49 +02:00
else
isCastingPossible = false ;
2016-10-28 23:37:20 +02:00
if ( ! myNumber . isAvailable ( ) & & ! shere ) //empty tile outside battlefield (or in the unavailable border column)
2012-04-16 20:12:39 +03:00
isCastingPossible = false ;
return isCastingPossible ;
2012-03-31 00:36:07 +03:00
}
BattleHex CBattleInterface : : fromWhichHexAttack ( BattleHex myNumber )
{
//TODO far too much repeating code
2017-07-20 06:08:49 +02:00
BattleHex destHex ;
2012-12-14 18:32:53 +03:00
switch ( CCS - > curh - > frame )
2012-03-31 00:36:07 +03:00
{
case 12 : //from bottom right
{
bool doubleWide = activeStack - > doubleWide ( ) ;
destHex = myNumber + ( ( myNumber / GameConstants : : BFIELD_WIDTH ) % 2 ? GameConstants : : BFIELD_WIDTH : GameConstants : : BFIELD_WIDTH + 1 ) +
2017-07-01 10:34:00 +02:00
( activeStack - > side = = BattleSide : : ATTACKER & & doubleWide ? 1 : 0 ) ;
if ( vstd : : contains ( occupyableHexes , destHex ) )
2012-03-31 00:36:07 +03:00
return destHex ;
2017-07-01 10:34:00 +02:00
else if ( activeStack - > side = = BattleSide : : ATTACKER )
2012-03-31 00:36:07 +03:00
{
2016-10-28 23:37:20 +02:00
if ( vstd : : contains ( occupyableHexes , destHex + 1 ) )
2012-03-31 00:36:07 +03:00
return destHex + 1 ;
}
else //if we are defender
{
2017-07-01 10:34:00 +02:00
if ( vstd : : contains ( occupyableHexes , destHex - 1 ) )
2012-03-31 00:36:07 +03:00
return destHex - 1 ;
}
break ;
}
case 7 : //from bottom left
{
destHex = myNumber + ( ( myNumber / GameConstants : : BFIELD_WIDTH ) % 2 ? GameConstants : : BFIELD_WIDTH - 1 : GameConstants : : BFIELD_WIDTH ) ;
2016-10-28 23:37:20 +02:00
if ( vstd : : contains ( occupyableHexes , destHex ) )
2012-03-31 00:36:07 +03:00
return destHex ;
2017-07-01 10:34:00 +02:00
else if ( activeStack - > side = = BattleSide : : ATTACKER )
2012-03-31 00:36:07 +03:00
{
2017-07-01 10:34:00 +02:00
if ( vstd : : contains ( occupyableHexes , destHex + 1 ) )
2012-03-31 00:36:07 +03:00
return destHex + 1 ;
}
2017-07-01 10:34:00 +02:00
else //we are defender
2012-03-31 00:36:07 +03:00
{
2017-07-01 10:34:00 +02:00
if ( vstd : : contains ( occupyableHexes , destHex - 1 ) )
2012-03-31 00:36:07 +03:00
return destHex - 1 ;
}
break ;
}
case 8 : //from left
{
2017-07-07 01:53:38 +02:00
if ( activeStack - > doubleWide ( ) & & activeStack - > side = = BattleSide : : DEFENDER )
2012-03-31 00:36:07 +03:00
{
2017-07-20 06:08:49 +02:00
std : : vector < BattleHex > acc = curInt - > cb - > battleGetAvailableHexes ( activeStack ) ;
2016-10-28 23:37:20 +02:00
if ( vstd : : contains ( acc , myNumber ) )
2012-03-31 00:36:07 +03:00
return myNumber - 1 ;
else
return myNumber - 2 ;
}
else
{
return myNumber - 1 ;
}
break ;
}
case 9 : //from top left
{
2017-07-01 10:34:00 +02:00
destHex = myNumber - ( ( myNumber / GameConstants : : BFIELD_WIDTH ) % 2 ? GameConstants : : BFIELD_WIDTH + 1 : GameConstants : : BFIELD_WIDTH ) ;
if ( vstd : : contains ( occupyableHexes , destHex ) )
2012-03-31 00:36:07 +03:00
return destHex ;
2017-07-01 10:34:00 +02:00
else if ( activeStack - > side = = BattleSide : : ATTACKER )
2012-03-31 00:36:07 +03:00
{
2017-07-01 10:34:00 +02:00
if ( vstd : : contains ( occupyableHexes , destHex + 1 ) )
2012-03-31 00:36:07 +03:00
return destHex + 1 ;
}
else //if we are defender
{
2017-07-01 10:34:00 +02:00
if ( vstd : : contains ( occupyableHexes , destHex - 1 ) )
2012-03-31 00:36:07 +03:00
return destHex - 1 ;
}
break ;
}
case 10 : //from top right
{
bool doubleWide = activeStack - > doubleWide ( ) ;
destHex = myNumber - ( ( myNumber / GameConstants : : BFIELD_WIDTH ) % 2 ? GameConstants : : BFIELD_WIDTH : GameConstants : : BFIELD_WIDTH - 1 ) +
2017-07-01 10:34:00 +02:00
( activeStack - > side = = BattleSide : : ATTACKER & & doubleWide ? 1 : 0 ) ;
if ( vstd : : contains ( occupyableHexes , destHex ) )
2012-03-31 00:36:07 +03:00
return destHex ;
2017-07-01 10:34:00 +02:00
else if ( activeStack - > side = = BattleSide : : ATTACKER )
2012-03-31 00:36:07 +03:00
{
2017-07-01 10:34:00 +02:00
if ( vstd : : contains ( occupyableHexes , destHex + 1 ) )
2012-03-31 00:36:07 +03:00
return destHex + 1 ;
}
else //if we are defender
{
2017-07-01 10:34:00 +02:00
if ( vstd : : contains ( occupyableHexes , destHex - 1 ) )
2012-03-31 00:36:07 +03:00
return destHex - 1 ;
}
break ;
}
case 11 : //from right
{
2017-07-01 10:34:00 +02:00
if ( activeStack - > doubleWide ( ) & & activeStack - > side = = BattleSide : : ATTACKER )
2012-03-31 00:36:07 +03:00
{
2017-07-20 06:08:49 +02:00
std : : vector < BattleHex > acc = curInt - > cb - > battleGetAvailableHexes ( activeStack ) ;
2017-07-01 10:34:00 +02:00
if ( vstd : : contains ( acc , myNumber ) )
2012-03-31 00:36:07 +03:00
return myNumber + 1 ;
else
return myNumber + 2 ;
}
else
{
return myNumber + 1 ;
}
break ;
}
case 13 : //from bottom
{
destHex = myNumber + ( ( myNumber / GameConstants : : BFIELD_WIDTH ) % 2 ? GameConstants : : BFIELD_WIDTH : GameConstants : : BFIELD_WIDTH + 1 ) ;
2017-07-01 10:34:00 +02:00
if ( vstd : : contains ( occupyableHexes , destHex ) )
2012-03-31 00:36:07 +03:00
return destHex ;
2017-07-01 10:34:00 +02:00
else if ( activeStack - > side = = BattleSide : : ATTACKER )
2012-03-31 00:36:07 +03:00
{
2017-07-01 10:34:00 +02:00
if ( vstd : : contains ( occupyableHexes , destHex + 1 ) )
2012-03-31 00:36:07 +03:00
return destHex + 1 ;
}
else //if we are defender
{
2017-07-01 10:34:00 +02:00
if ( vstd : : contains ( occupyableHexes , destHex - 1 ) )
2012-03-31 00:36:07 +03:00
return destHex - 1 ;
}
break ;
}
case 14 : //from top
{
destHex = myNumber - ( ( myNumber / GameConstants : : BFIELD_WIDTH ) % 2 ? GameConstants : : BFIELD_WIDTH : GameConstants : : BFIELD_WIDTH - 1 ) ;
2016-10-28 23:37:20 +02:00
if ( vstd : : contains ( occupyableHexes , destHex ) )
2012-03-31 00:36:07 +03:00
return destHex ;
2017-07-01 10:34:00 +02:00
else if ( activeStack - > side = = BattleSide : : ATTACKER )
2012-03-31 00:36:07 +03:00
{
2017-07-01 10:34:00 +02:00
if ( vstd : : contains ( occupyableHexes , destHex + 1 ) )
2012-03-31 00:36:07 +03:00
return destHex + 1 ;
}
else //if we are defender
{
2017-07-01 10:34:00 +02:00
if ( vstd : : contains ( occupyableHexes , destHex - 1 ) )
2012-03-31 00:36:07 +03:00
return destHex - 1 ;
}
break ;
}
}
2012-04-08 13:34:23 +03:00
return - 1 ;
2012-03-31 00:36:07 +03:00
}
2012-04-23 22:56:37 +03:00
Rect CBattleInterface : : hexPosition ( BattleHex hex ) const
{
int x = 14 + ( ( hex . getY ( ) ) % 2 = = 0 ? 22 : 0 ) + 44 * hex . getX ( ) + pos . x ;
2016-10-28 23:37:20 +02:00
int y = 86 + 42 * hex . getY ( ) + pos . y ;
2012-04-23 22:56:37 +03:00
int w = cellShade - > w ;
int h = cellShade - > h ;
return Rect ( x , y , w , h ) ;
}
2012-05-05 00:16:39 +03:00
void CBattleInterface : : obstaclePlaced ( const CObstacleInstance & oi )
{
//so when multiple obstacles are added, they show up one after another
waitForAnims ( ) ;
2018-04-19 15:20:26 +02:00
//soundBase::soundID sound; // FIXME(v.markovtsev): soundh->playSound() is commented in the end => warning
2012-05-18 23:50:16 +03:00
std : : string defname ;
2012-05-05 00:16:39 +03:00
switch ( oi . obstacleType )
{
2017-07-20 06:08:49 +02:00
case CObstacleInstance : : SPELL_CREATED :
2012-05-18 23:50:16 +03:00
{
auto & spellObstacle = dynamic_cast < const SpellCreatedObstacle & > ( oi ) ;
2017-07-20 06:08:49 +02:00
defname = spellObstacle . appearAnimation ;
//TODO: sound
//soundBase::QUIKSAND
//soundBase::LANDMINE
//soundBase::FORCEFLD
//soundBase::fireWall
2012-05-18 23:50:16 +03:00
}
2012-05-05 00:16:39 +03:00
break ;
default :
2017-08-11 13:38:10 +02:00
logGlobal - > error ( " I don't know how to animate appearing obstacle of type %d " , ( int ) oi . obstacleType ) ;
2012-05-05 00:16:39 +03:00
return ;
}
2017-07-20 06:08:49 +02:00
auto animation = std : : make_shared < CAnimation > ( defname ) ;
animation - > preload ( ) ;
2012-05-05 00:16:39 +03:00
2018-03-30 13:02:04 +02:00
auto first = animation - > getImage ( 0 , 0 ) ;
2017-07-20 06:08:49 +02:00
if ( ! first )
return ;
2012-05-05 00:16:39 +03:00
//we assume here that effect graphics have the same size as the usual obstacle image
// -> if we know how to blit obstacle, let's blit the effect in the same place
2017-07-20 06:08:49 +02:00
Point whereTo = getObstaclePosition ( first , oi ) ;
addNewAnim ( new CEffectAnimation ( this , animation , whereTo . x , whereTo . y ) ) ;
2012-05-18 23:50:16 +03:00
//TODO we need to wait after playing sound till it's finished, otherwise it overlaps and sounds really bad
//CCS->soundh->playSound(sound);
2012-05-05 00:16:39 +03:00
}
2016-02-13 16:40:31 +02:00
void CBattleInterface : : gateStateChanged ( const EGateState state )
2016-02-10 06:10:32 +02:00
{
2016-02-13 16:40:31 +02:00
auto oldState = curInt - > cb - > battleGetGateState ( ) ;
2016-02-10 06:10:32 +02:00
bool playSound = false ;
int stateId = EWallState : : NONE ;
switch ( state )
{
2016-02-13 16:40:31 +02:00
case EGateState : : CLOSED :
2016-10-28 23:37:20 +02:00
if ( oldState ! = EGateState : : BLOCKED )
2016-02-10 06:10:32 +02:00
playSound = true ;
break ;
2016-02-13 16:40:31 +02:00
case EGateState : : BLOCKED :
2016-10-28 23:37:20 +02:00
if ( oldState ! = EGateState : : CLOSED )
2016-02-10 06:10:32 +02:00
playSound = true ;
break ;
2016-02-13 16:40:31 +02:00
case EGateState : : OPENED :
2016-02-10 06:10:32 +02:00
playSound = true ;
stateId = EWallState : : DAMAGED ;
break ;
2016-02-13 16:40:31 +02:00
case EGateState : : DESTROYED :
2016-02-10 06:10:32 +02:00
stateId = EWallState : : DESTROYED ;
break ;
}
2016-10-28 23:37:20 +02:00
if ( oldState ! = EGateState : : NONE & & oldState ! = EGateState : : CLOSED & & oldState ! = EGateState : : BLOCKED )
2016-03-14 09:37:43 +02:00
SDL_FreeSurface ( siegeH - > walls [ SiegeHelper : : GATE ] ) ;
2016-10-28 23:37:20 +02:00
if ( stateId ! = EWallState : : NONE )
2016-02-14 14:21:47 +02:00
siegeH - > walls [ SiegeHelper : : GATE ] = BitmapHandler : : loadBitmap ( siegeH - > getSiegeName ( SiegeHelper : : GATE , stateId ) ) ;
2016-10-28 23:37:20 +02:00
if ( playSound )
2016-02-10 06:10:32 +02:00
CCS - > soundh - > playSound ( soundBase : : DRAWBRG ) ;
}
2016-10-28 23:37:20 +02:00
const CGHeroInstance * CBattleInterface : : currentHero ( ) const
2012-08-26 12:07:48 +03:00
{
2016-10-28 23:37:20 +02:00
if ( attackingHeroInstance - > tempOwner = = curInt - > playerID )
2012-08-26 12:07:48 +03:00
return attackingHeroInstance ;
else
return defendingHeroInstance ;
}
InfoAboutHero CBattleInterface : : enemyHero ( ) const
{
InfoAboutHero ret ;
2016-10-28 23:37:20 +02:00
if ( attackingHeroInstance - > tempOwner = = curInt - > playerID )
2012-08-26 12:07:48 +03:00
curInt - > cb - > getHeroInfo ( defendingHeroInstance , ret ) ;
else
curInt - > cb - > getHeroInfo ( attackingHeroInstance , ret ) ;
return ret ;
}
2013-06-23 00:47:51 +03:00
void CBattleInterface : : requestAutofightingAIToTakeAction ( )
{
2013-06-23 14:25:48 +03:00
assert ( curInt - > isAutoFightOn ) ;
2013-06-23 00:47:51 +03:00
2017-07-19 01:06:05 +02:00
boost : : thread aiThread ( [ & ] ( )
2013-06-23 00:47:51 +03:00
{
2016-11-25 15:09:14 +02:00
auto ba = make_unique < BattleAction > ( curInt - > autofightingAI - > activeStack ( activeStack ) ) ;
2013-06-23 00:47:51 +03:00
2022-04-04 09:16:32 +02:00
if ( curInt - > cb - > battleIsFinished ( ) )
{
return ; // battle finished with spellcast
}
2016-10-28 23:37:20 +02:00
if ( curInt - > isAutoFightOn )
2013-06-23 00:47:51 +03:00
{
2016-10-28 23:37:20 +02:00
if ( tacticsMode )
2013-11-09 19:25:20 +03:00
{
// Always end tactics mode. Player interface is blocked currently, so it's not possible that
// the AI can take any action except end tactics phase (AI actions won't be triggered)
//TODO implement the possibility that the AI will be triggered for further actions
//TODO any solution to merge tactics phase & normal phase in the way it is handled by the player and battle interface?
setActiveStack ( nullptr ) ;
blockUI ( true ) ;
tacticsMode = false ;
}
else
{
2017-06-14 03:53:26 +02:00
givenCommand . setn ( ba . release ( ) ) ;
2013-11-09 19:25:20 +03:00
}
2013-06-23 00:47:51 +03:00
}
2013-06-23 14:25:48 +03:00
else
{
2016-11-25 21:12:22 +02:00
boost : : unique_lock < boost : : recursive_mutex > un ( * CPlayerInterface : : pim ) ;
2013-06-23 14:25:48 +03:00
activateStack ( ) ;
}
2013-06-23 00:47:51 +03:00
} ) ;
2014-03-07 16:21:09 +03:00
2013-06-24 17:35:27 +03:00
aiThread . detach ( ) ;
2013-06-23 00:47:51 +03:00
}
2016-10-28 23:37:20 +02:00
CBattleInterface : : SiegeHelper : : SiegeHelper ( const CGTownInstance * siegeTown , const CBattleInterface * _owner )
2016-02-13 16:10:14 +02:00
: owner ( _owner ) , town ( siegeTown )
2009-08-26 17:09:55 +03:00
{
2016-10-28 23:37:20 +02:00
for ( int g = 0 ; g < ARRAY_COUNT ( walls ) ; + + g )
2009-08-26 17:09:55 +03:00
{
2016-10-28 23:37:20 +02:00
if ( g ! = SiegeHelper : : GATE )
2016-03-14 09:37:43 +02:00
walls [ g ] = BitmapHandler : : loadBitmap ( getSiegeName ( g ) ) ;
2009-08-26 17:09:55 +03:00
}
}
CBattleInterface : : SiegeHelper : : ~ SiegeHelper ( )
{
2016-03-14 11:55:57 +02:00
auto gateState = owner - > curInt - > cb - > battleGetGateState ( ) ;
2016-10-28 23:37:20 +02:00
for ( int g = 0 ; g < ARRAY_COUNT ( walls ) ; + + g )
2009-08-26 17:09:55 +03:00
{
2016-10-28 23:37:20 +02:00
if ( g ! = SiegeHelper : : GATE | | ( gateState ! = EGateState : : NONE & & gateState ! = EGateState : : CLOSED & & gateState ! = EGateState : : BLOCKED ) )
2016-03-14 11:55:57 +02:00
SDL_FreeSurface ( walls [ g ] ) ;
2009-08-26 17:09:55 +03:00
}
}
2013-08-06 14:20:28 +03:00
std : : string CBattleInterface : : SiegeHelper : : getSiegeName ( ui16 what ) const
2009-08-26 17:09:55 +03:00
{
2013-08-06 14:20:28 +03:00
return getSiegeName ( what , EWallState : : INTACT ) ;
}
std : : string CBattleInterface : : SiegeHelper : : getSiegeName ( ui16 what , int state ) const
{
auto getImageIndex = [ & ] ( ) - > int
{
switch ( state )
{
case EWallState : : INTACT : return 1 ;
2018-03-02 16:23:07 +02:00
case EWallState : : DAMAGED :
if ( what = = 2 | | what = = 3 | | what = = 8 ) // towers don't have separate image here - INTACT and DAMAGED is 1, DESTROYED is 2
return 1 ;
else
return 2 ;
2013-08-06 14:20:28 +03:00
case EWallState : : DESTROYED :
2018-03-02 16:23:07 +02:00
if ( what = = 2 | | what = = 3 | | what = = 8 )
2013-08-06 14:20:28 +03:00
return 2 ;
else
return 3 ;
}
return 1 ;
} ;
2012-10-05 21:03:49 +03:00
2013-09-07 00:57:16 +03:00
const std : : string & prefix = town - > town - > clientInfo . siegePrefix ;
2013-08-06 14:20:28 +03:00
std : : string addit = boost : : lexical_cast < std : : string > ( getImageIndex ( ) ) ;
2012-10-05 21:03:49 +03:00
2009-08-26 17:09:55 +03:00
switch ( what )
{
2016-02-13 16:10:14 +02:00
case SiegeHelper : : BACKGROUND :
2012-10-05 21:03:49 +03:00
return prefix + " BACK.BMP " ;
2016-02-13 16:10:14 +02:00
case SiegeHelper : : BACKGROUND_WALL :
2009-08-28 12:03:58 +03:00
{
2013-04-21 15:49:26 +03:00
switch ( town - > town - > faction - > index )
2009-08-28 12:03:58 +03:00
{
2016-02-13 16:10:14 +02:00
case ETownType : : RAMPART :
case ETownType : : NECROPOLIS :
case ETownType : : DUNGEON :
case ETownType : : STRONGHOLD :
2012-10-05 21:03:49 +03:00
return prefix + " TPW1.BMP " ;
2009-08-28 12:03:58 +03:00
default :
2012-12-13 16:07:56 +03:00
return prefix + " TPWL.BMP " ;
2009-08-28 12:03:58 +03:00
}
}
2016-02-13 16:10:14 +02:00
case SiegeHelper : : KEEP :
2012-10-05 21:03:49 +03:00
return prefix + " MAN " + addit + " .BMP " ;
2016-02-13 16:10:14 +02:00
case SiegeHelper : : BOTTOM_TOWER :
2012-10-05 21:03:49 +03:00
return prefix + " TW1 " + addit + " .BMP " ;
2016-02-13 16:10:14 +02:00
case SiegeHelper : : BOTTOM_WALL :
2012-10-05 21:03:49 +03:00
return prefix + " WA1 " + addit + " .BMP " ;
2016-02-13 16:10:14 +02:00
case SiegeHelper : : WALL_BELLOW_GATE :
2012-10-05 21:03:49 +03:00
return prefix + " WA3 " + addit + " .BMP " ;
2016-02-13 16:10:14 +02:00
case SiegeHelper : : WALL_OVER_GATE :
2012-10-05 21:03:49 +03:00
return prefix + " WA4 " + addit + " .BMP " ;
2016-02-13 16:10:14 +02:00
case SiegeHelper : : UPPER_WALL :
2012-10-05 21:03:49 +03:00
return prefix + " WA6 " + addit + " .BMP " ;
2016-02-13 16:10:14 +02:00
case SiegeHelper : : UPPER_TOWER :
2012-10-05 21:03:49 +03:00
return prefix + " TW2 " + addit + " .BMP " ;
2016-02-13 16:10:14 +02:00
case SiegeHelper : : GATE :
2012-10-05 21:03:49 +03:00
return prefix + " DRW " + addit + " .BMP " ;
2016-02-13 16:10:14 +02:00
case SiegeHelper : : GATE_ARCH :
2012-10-05 21:03:49 +03:00
return prefix + " ARCH.BMP " ;
2016-02-13 16:10:14 +02:00
case SiegeHelper : : BOTTOM_STATIC_WALL :
2012-10-05 21:03:49 +03:00
return prefix + " WA2.BMP " ;
2016-02-13 16:10:14 +02:00
case SiegeHelper : : UPPER_STATIC_WALL :
2012-10-05 21:03:49 +03:00
return prefix + " WA5.BMP " ;
2016-02-13 16:10:14 +02:00
case SiegeHelper : : MOAT :
2012-10-05 21:03:49 +03:00
return prefix + " MOAT.BMP " ;
2016-02-13 16:10:14 +02:00
case SiegeHelper : : BACKGROUND_MOAT :
2012-10-05 21:03:49 +03:00
return prefix + " MLIP.BMP " ;
2016-02-13 16:10:14 +02:00
case SiegeHelper : : KEEP_BATTLEMENT :
2012-10-05 21:03:49 +03:00
return prefix + " MANC.BMP " ;
2016-02-13 16:10:14 +02:00
case SiegeHelper : : BOTTOM_BATTLEMENT :
2012-10-05 21:03:49 +03:00
return prefix + " TW1C.BMP " ;
2016-02-13 16:10:14 +02:00
case SiegeHelper : : UPPER_BATTLEMENT :
2012-10-05 21:03:49 +03:00
return prefix + " TW2C.BMP " ;
2009-08-26 17:09:55 +03:00
default :
return " " ;
}
}
2016-10-28 23:37:20 +02:00
void CBattleInterface : : SiegeHelper : : printPartOfWall ( SDL_Surface * to , int what )
2009-08-24 15:55:05 +03:00
{
2011-12-22 16:05:19 +03:00
Point pos = Point ( - 1 , - 1 ) ;
2016-02-14 14:21:47 +02:00
auto & ci = town - > town - > clientInfo ;
2012-02-16 20:10:58 +03:00
2016-02-13 16:10:14 +02:00
if ( vstd : : iswithin ( what , 1 , 17 ) )
2009-08-26 17:09:55 +03:00
{
2013-08-01 01:24:44 +03:00
pos . x = ci . siegePositions [ what ] . x + owner - > pos . x ;
pos . y = ci . siegePositions [ what ] . y + owner - > pos . y ;
2011-04-30 22:52:35 +03:00
}
2009-08-26 17:09:55 +03:00
2013-08-01 01:24:44 +03:00
if ( town - > town - > faction - > index = = ETownType : : TOWER
2016-02-13 16:10:14 +02:00
& & ( what = = SiegeHelper : : MOAT | | what = = SiegeHelper : : BACKGROUND_MOAT ) )
2013-08-01 01:24:44 +03:00
return ; // no moat in Tower. TODO: remove hardcode somehow?
2016-10-28 23:37:20 +02:00
if ( pos . x ! = - 1 )
2009-08-26 17:09:55 +03:00
{
2016-02-14 14:21:47 +02:00
//gate have no displayed bitmap when drawbridge is raised
2016-10-28 23:37:20 +02:00
if ( what = = SiegeHelper : : GATE )
2016-02-14 14:21:47 +02:00
{
auto gateState = owner - > curInt - > cb - > battleGetGateState ( ) ;
2016-10-28 23:37:20 +02:00
if ( gateState ! = EGateState : : OPENED & & gateState ! = EGateState : : DESTROYED )
2016-02-14 14:21:47 +02:00
return ;
}
2009-09-01 16:54:13 +03:00
blitAt ( walls [ what ] , pos . x , pos . y , to ) ;
2009-08-26 17:09:55 +03:00
}
2009-08-24 15:55:05 +03:00
}
2009-09-20 15:47:40 +03:00
2013-04-04 21:20:25 +03:00
CatapultProjectileInfo : : CatapultProjectileInfo ( Point from , Point dest )
{
facA = 0.005 ; // seems to be constant
// system of 2 linear equations, solutions of which are missing coefficients
// for quadratic equation a*x*x + b*x + c
double eq [ 2 ] [ 3 ] = {
{ static_cast < double > ( from . x ) , 1.0 , from . y - facA * from . x * from . x } ,
{ static_cast < double > ( dest . x ) , 1.0 , dest . y - facA * dest . x * dest . x }
} ;
// solve system via determinants
2016-10-28 23:37:20 +02:00
double det = eq [ 0 ] [ 0 ] * eq [ 1 ] [ 1 ] - eq [ 1 ] [ 0 ] * eq [ 0 ] [ 1 ] ;
double detB = eq [ 0 ] [ 2 ] * eq [ 1 ] [ 1 ] - eq [ 1 ] [ 2 ] * eq [ 0 ] [ 1 ] ;
double detC = eq [ 0 ] [ 0 ] * eq [ 1 ] [ 2 ] - eq [ 1 ] [ 0 ] * eq [ 0 ] [ 2 ] ;
2013-04-04 21:20:25 +03:00
facB = detB / det ;
facC = detC / det ;
// make sure that parabola is correct e.g. passes through from and dest
assert ( fabs ( calculateY ( from . x ) - from . y ) < 1.0 ) ;
assert ( fabs ( calculateY ( dest . x ) - dest . y ) < 1.0 ) ;
}
2011-12-22 16:05:19 +03:00
double CatapultProjectileInfo : : calculateY ( double x )
2011-05-30 17:16:34 +03:00
{
2016-10-28 23:37:20 +02:00
return facA * pow ( x , 2.0 ) + facB * x + facC ;
2011-05-30 17:16:34 +03:00
}
2013-07-19 19:35:16 +03:00
2016-10-28 23:37:20 +02:00
void CBattleInterface : : showAll ( SDL_Surface * to )
2013-07-19 19:35:16 +03:00
{
show ( to ) ;
}
2016-10-28 23:37:20 +02:00
void CBattleInterface : : show ( SDL_Surface * to )
2013-07-19 19:35:16 +03:00
{
assert ( to ) ;
SDL_Rect buf ;
SDL_GetClipRect ( to , & buf ) ;
SDL_SetClipRect ( to , & pos ) ;
+ + animCount ;
showBackground ( to ) ;
showBattlefieldObjects ( to ) ;
showProjectiles ( to ) ;
2019-03-22 22:39:53 +02:00
if ( battleActionsStarted )
2021-02-20 03:57:50 +02:00
updateBattleAnimations ( ) ;
2013-07-19 19:35:16 +03:00
SDL_SetClipRect ( to , & buf ) ; //restoring previous clip_rect
showInterface ( to ) ;
//activation of next stack
2019-03-22 22:39:53 +02:00
if ( pendingAnims . empty ( ) & & stackToActivate ! = nullptr & & battleActionsStarted ) //FIXME: watch for recursive infinite loop here when working with this file, this needs rework anyway...
2013-07-19 19:35:16 +03:00
{
activateStack ( ) ;
//we may have changed active interface (another side in hot-seat),
// so we can't continue drawing with old setting.
show ( to ) ;
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
}
2013-07-19 19:35:16 +03:00
}
2016-10-28 23:37:20 +02:00
void CBattleInterface : : showBackground ( SDL_Surface * to )
2013-07-19 19:35:16 +03:00
{
2016-10-28 23:37:20 +02:00
if ( activeStack ! = nullptr & & creAnims [ activeStack - > ID ] - > isIdle ( ) ) //show everything with range
2013-07-19 19:35:16 +03:00
{
// FIXME: any *real* reason to keep this separate? Speed difference can't be that big
blitAt ( backgroundWithHexes , pos . x , pos . y , to ) ;
}
else
{
showBackgroundImage ( to ) ;
showAbsoluteObstacles ( to ) ;
}
showHighlightedHexes ( to ) ;
}
2016-10-28 23:37:20 +02:00
void CBattleInterface : : showBackgroundImage ( SDL_Surface * to )
2013-07-19 19:35:16 +03:00
{
blitAt ( background , pos . x , pos . y , to ) ;
2016-10-28 23:37:20 +02:00
if ( settings [ " battle " ] [ " cellBorders " ] . Bool ( ) )
2013-07-19 19:35:16 +03:00
{
CSDL_Ext : : blit8bppAlphaTo24bpp ( cellBorders , nullptr , to , & pos ) ;
}
}
2017-07-20 06:08:49 +02:00
void CBattleInterface : : showAbsoluteObstacles ( SDL_Surface * to )
2013-07-19 19:35:16 +03:00
{
//Blit absolute obstacles
2017-07-20 06:08:49 +02:00
for ( auto & oi : curInt - > cb - > battleGetAllObstacles ( ) )
{
if ( oi - > obstacleType = = CObstacleInstance : : ABSOLUTE_OBSTACLE )
{
2018-03-30 13:02:04 +02:00
auto img = getObstacleImage ( * oi ) ;
2017-07-20 06:08:49 +02:00
if ( img )
img - > draw ( to , pos . x + oi - > getInfo ( ) . width , pos . y + oi - > getInfo ( ) . height ) ;
}
}
2013-07-19 19:35:16 +03:00
2013-08-01 01:24:44 +03:00
if ( siegeH & & siegeH - > town - > hasBuilt ( BuildingID : : CITADEL ) )
2016-02-13 16:10:14 +02:00
siegeH - > printPartOfWall ( to , SiegeHelper : : BACKGROUND_MOAT ) ;
2013-07-19 19:35:16 +03:00
}
2016-10-28 23:37:20 +02:00
void CBattleInterface : : showHighlightedHexes ( SDL_Surface * to )
2013-07-19 19:35:16 +03:00
{
2017-07-17 00:15:05 +02:00
bool delayedBlit = false ; //workaround for blitting enemy stack hex without mouse shadow with stack range on
if ( activeStack & & settings [ " battle " ] [ " stackRange " ] . Bool ( ) )
{
std : : set < BattleHex > set = curInt - > cb - > battleGetAttackedHexes ( activeStack , currentlyHoveredHex , attackingHex ) ;
for ( BattleHex hex : set )
if ( hex ! = currentlyHoveredHex )
showHighlightedHex ( to , hex ) ;
// display the movement shadow of the stack at b (i.e. stack under mouse)
const CStack * const shere = curInt - > cb - > battleGetStackByPos ( currentlyHoveredHex , false ) ;
if ( shere & & shere ! = activeStack & & shere - > alive ( ) )
{
2017-07-20 06:08:49 +02:00
std : : vector < BattleHex > v = curInt - > cb - > battleGetAvailableHexes ( shere , true , nullptr ) ;
2017-07-17 00:15:05 +02:00
for ( BattleHex hex : v )
{
if ( hex ! = currentlyHoveredHex )
showHighlightedHex ( to , hex ) ;
else if ( ! settings [ " battle " ] [ " mouseShadow " ] . Bool ( ) )
delayedBlit = true ; //blit at the end of method to avoid graphic artifacts
else
showHighlightedHex ( to , hex , true ) ; //blit now and blit 2nd time later for darker shadow - avoids graphic artifacts
}
}
}
2017-07-01 10:34:00 +02:00
for ( int b = 0 ; b < GameConstants : : BFIELD_SIZE ; + + b )
2013-07-19 19:35:16 +03:00
{
2017-07-01 10:34:00 +02:00
if ( bfield [ b ] - > strictHovered & & bfield [ b ] - > hovered )
2013-07-19 19:35:16 +03:00
{
2017-07-01 10:34:00 +02:00
if ( previouslyHoveredHex = = - 1 )
2013-07-19 19:35:16 +03:00
previouslyHoveredHex = b ; //something to start with
2017-07-01 10:34:00 +02:00
if ( currentlyHoveredHex = = - 1 )
2013-07-19 19:35:16 +03:00
currentlyHoveredHex = b ; //something to start with
2017-07-01 10:34:00 +02:00
if ( currentlyHoveredHex ! = b ) //repair hover info
2013-07-19 19:35:16 +03:00
{
previouslyHoveredHex = currentlyHoveredHex ;
currentlyHoveredHex = b ;
}
2017-07-17 00:15:05 +02:00
if ( settings [ " battle " ] [ " mouseShadow " ] . Bool ( ) | | delayedBlit )
2013-07-19 19:35:16 +03:00
{
2017-07-20 06:08:49 +02:00
const spells : : Caster * caster = nullptr ;
2016-10-28 23:37:20 +02:00
const CSpell * spell = nullptr ;
2016-09-22 17:44:57 +02:00
2017-07-20 06:08:49 +02:00
spells : : Mode mode = spells : : Mode : : HERO ;
2020-10-06 01:27:04 +02:00
if ( spellToCast ) //hero casts spell
2013-07-19 19:35:16 +03:00
{
2017-07-20 06:08:49 +02:00
spell = SpellID ( spellToCast - > actionSubtype ) . toSpell ( ) ;
2017-07-03 12:40:22 +02:00
caster = getActiveHero ( ) ;
2016-09-22 17:44:57 +02:00
}
2017-07-01 10:34:00 +02:00
else if ( creatureSpellToCast > = 0 & & stackCanCastSpell & & creatureCasting ) //stack casts spell
2016-09-22 17:44:57 +02:00
{
2017-07-03 12:40:22 +02:00
spell = SpellID ( creatureSpellToCast ) . toSpell ( ) ;
caster = activeStack ;
2017-07-20 06:08:49 +02:00
mode = spells : : Mode : : CREATURE_ACTIVE ;
2016-09-22 17:44:57 +02:00
}
2013-07-19 19:35:16 +03:00
2017-07-01 10:34:00 +02:00
if ( caster & & spell ) //when casting spell
2016-09-22 17:44:57 +02:00
{
2013-07-19 19:35:16 +03:00
// printing shaded hex(es)
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
spells : : BattleCast event ( curInt - > cb . get ( ) , caster , mode , spell ) ;
auto shaded = spell - > battleMechanics ( & event ) - > rangeInHexes ( currentlyHoveredHex ) ;
2017-07-01 10:34:00 +02:00
for ( BattleHex shadedHex : shaded )
2013-07-19 19:35:16 +03:00
{
2017-07-01 10:34:00 +02:00
if ( ( shadedHex . getX ( ) ! = 0 ) & & ( shadedHex . getX ( ) ! = GameConstants : : BFIELD_WIDTH - 1 ) )
2017-07-17 00:15:05 +02:00
showHighlightedHex ( to , shadedHex , true ) ;
2013-07-19 19:35:16 +03:00
}
}
2017-07-17 00:15:05 +02:00
else if ( active | | delayedBlit ) //always highlight pointed hex, keep this condition last in this method for correct behavior
2013-07-19 19:35:16 +03:00
{
2017-07-01 10:34:00 +02:00
if ( currentlyHoveredHex . getX ( ) ! = 0
2013-07-19 19:35:16 +03:00
& & currentlyHoveredHex . getX ( ) ! = GameConstants : : BFIELD_WIDTH - 1 )
2017-07-17 00:15:05 +02:00
showHighlightedHex ( to , currentlyHoveredHex , true ) ; //keep true for OH3 behavior: hovered hex frame "thinner"
2013-07-19 19:35:16 +03:00
}
}
}
}
}
2017-07-17 00:15:05 +02:00
void CBattleInterface : : showHighlightedHex ( SDL_Surface * to , BattleHex hex , bool darkBorder )
2013-07-19 19:35:16 +03:00
{
2016-10-28 23:37:20 +02:00
int x = 14 + ( hex . getY ( ) % 2 = = 0 ? 22 : 0 ) + 44 * ( hex . getX ( ) ) + pos . x ;
int y = 86 + 42 * hex . getY ( ) + pos . y ;
2013-07-19 19:35:16 +03:00
SDL_Rect temp_rect = genRect ( cellShade - > h , cellShade - > w , x , y ) ;
CSDL_Ext : : blit8bppAlphaTo24bpp ( cellShade , nullptr , to , & temp_rect ) ;
2017-07-17 00:15:05 +02:00
if ( ! darkBorder & & settings [ " battle " ] [ " cellBorders " ] . Bool ( ) )
CSDL_Ext : : blit8bppAlphaTo24bpp ( cellBorder , nullptr , to , & temp_rect ) ; //redraw border to make it light green instead of shaded
2013-07-19 19:35:16 +03:00
}
2016-10-28 23:37:20 +02:00
void CBattleInterface : : showProjectiles ( SDL_Surface * to )
2013-07-19 19:35:16 +03:00
{
assert ( to ) ;
std : : list < std : : list < ProjectileInfo > : : iterator > toBeDeleted ;
2016-10-28 23:37:20 +02:00
for ( auto it = projectiles . begin ( ) ; it ! = projectiles . end ( ) ; + + it )
2013-07-19 19:35:16 +03:00
{
// Check if projectile is already visible (shooter animation did the shot)
if ( ! it - > shotDone )
{
2013-07-21 13:10:38 +03:00
// frame we're waiting for is reached OR animation has already finished
if ( creAnims [ it - > stackID ] - > getCurrentFrame ( ) > = it - > animStartDelay | |
2020-10-06 01:27:04 +02:00
creAnims [ it - > stackID ] - > isShooting ( ) = = false )
2013-07-19 19:35:16 +03:00
{
//at this point projectile should become visible
creAnims [ it - > stackID ] - > pause ( ) ; // pause animation
it - > shotDone = true ;
}
else
continue ; // wait...
}
2017-09-05 19:04:17 +02:00
size_t group = it - > reverse ? 1 : 0 ;
2018-03-30 13:02:04 +02:00
auto image = idToProjectile [ it - > creID ] - > getImage ( it - > frameNum , group , true ) ;
2013-07-19 19:35:16 +03:00
2017-09-05 19:04:17 +02:00
if ( image )
2013-07-19 19:35:16 +03:00
{
2017-09-05 19:04:17 +02:00
SDL_Rect dst ;
dst . h = image - > height ( ) ;
dst . w = image - > width ( ) ;
2020-10-01 10:38:06 +02:00
dst . x = static_cast < int > ( it - > x - dst . w / 2 ) ;
dst . y = static_cast < int > ( it - > y - dst . h / 2 ) ;
2017-09-05 19:04:17 +02:00
image - > draw ( to , & dst , nullptr ) ;
2013-07-19 19:35:16 +03:00
}
// Update projectile
+ + it - > step ;
2016-10-28 23:37:20 +02:00
if ( it - > step = = it - > lastStep )
2013-07-19 19:35:16 +03:00
{
toBeDeleted . insert ( toBeDeleted . end ( ) , it ) ;
}
else
{
if ( it - > catapultInfo )
{
// Parabolic shot of the trajectory, as follows: f(x) = ax^2 + bx + c
it - > x + = it - > dx ;
it - > y = it - > catapultInfo - > calculateY ( it - > x ) ;
+ + ( it - > frameNum ) ;
2017-09-05 19:04:17 +02:00
it - > frameNum % = idToProjectile [ it - > creID ] - > size ( 0 ) ;
2013-07-19 19:35:16 +03:00
}
else
{
// Normal projectile, just add the calculated "deltas" to the x and y positions.
it - > x + = it - > dx ;
it - > y + = it - > dy ;
}
}
}
2016-10-28 23:37:20 +02:00
for ( auto & elem : toBeDeleted )
2013-07-19 19:35:16 +03:00
{
// resume animation
creAnims [ elem - > stackID ] - > play ( ) ;
projectiles . erase ( elem ) ;
}
}
2016-10-28 23:37:20 +02:00
void CBattleInterface : : showBattlefieldObjects ( SDL_Surface * to )
2013-07-19 19:35:16 +03:00
{
auto showHexEntry = [ & ] ( BattleObjectsByHex : : HexData & hex )
{
showPiecesOfWall ( to , hex . walls ) ;
showObstacles ( to , hex . obstacles ) ;
showAliveStacks ( to , hex . alive ) ;
showBattleEffects ( to , hex . effects ) ;
} ;
BattleObjectsByHex objects = sortObjectsByHex ( ) ;
2013-07-21 13:10:38 +03:00
// dead stacks should be blit first
showStacks ( to , objects . beforeAll . dead ) ;
2016-10-30 05:47:33 +02:00
for ( auto & data : objects . hex )
2013-07-21 13:10:38 +03:00
showStacks ( to , data . dead ) ;
showStacks ( to , objects . afterAll . dead ) ;
2013-07-19 19:35:16 +03:00
// display objects that must be blit before anything else (e.g. topmost walls)
showHexEntry ( objects . beforeAll ) ;
2013-09-30 11:45:26 +03:00
// show heroes after "beforeAll" - e.g. topmost wall in siege
2016-10-28 23:37:20 +02:00
if ( attackingHero )
2013-09-30 11:45:26 +03:00
attackingHero - > show ( to ) ;
2016-10-28 23:37:20 +02:00
if ( defendingHero )
2013-09-30 11:45:26 +03:00
defendingHero - > show ( to ) ;
2013-07-19 19:35:16 +03:00
// actual blit of most of objects, hex by hex
// NOTE: row-by-row blitting may be a better approach
2016-10-30 05:47:33 +02:00
for ( auto & data : objects . hex )
2013-07-19 19:35:16 +03:00
showHexEntry ( data ) ;
2016-10-30 05:47:33 +02:00
2013-07-19 19:35:16 +03:00
// objects that must be blit *after* everything else - e.g. bottom tower or some spell effects
showHexEntry ( objects . afterAll ) ;
}
2016-10-28 23:37:20 +02:00
void CBattleInterface : : showAliveStacks ( SDL_Surface * to , std : : vector < const CStack * > stacks )
2013-07-19 19:35:16 +03:00
{
2017-07-20 06:08:49 +02:00
BattleHex currentActionTarget ;
if ( curInt - > curAction )
{
2020-10-06 01:27:04 +02:00
auto target = curInt - > curAction - > getTarget ( curInt - > cb . get ( ) ) ;
if ( ! target . empty ( ) )
2017-07-20 06:08:49 +02:00
currentActionTarget = target . at ( 0 ) . hexValue ;
}
2016-10-28 23:37:20 +02:00
auto isAmountBoxVisible = [ & ] ( const CStack * stack ) - > bool
2013-07-19 19:35:16 +03:00
{
2017-07-04 13:24:46 +02:00
if ( stack - > hasBonusOfType ( Bonus : : SIEGE_WEAPON ) & & stack - > getCount ( ) = = 1 ) //do not show box for singular war machines, stacked war machines with box shown are supported as extension feature
2013-07-19 19:35:16 +03:00
return false ;
2017-07-04 13:24:46 +02:00
if ( stack - > getCount ( ) = = 0 ) //hide box when target is going to die anyway - do not display "0 creatures"
2017-07-07 16:02:16 +02:00
return false ;
for ( auto anim : pendingAnims ) //no matter what other conditions below are, hide box when creature is playing hit animation
2013-07-19 19:35:16 +03:00
{
2017-07-07 16:02:16 +02:00
auto hitAnimation = dynamic_cast < CDefenceAnimation * > ( anim . first ) ;
if ( hitAnimation & & ( hitAnimation - > stack - > ID = = stack - > ID ) ) //we process only "current creature" as other creatures will be processed reliably on their own iteration
2013-07-19 19:35:16 +03:00
return false ;
2017-07-07 16:02:16 +02:00
}
2013-07-19 19:35:16 +03:00
2017-07-07 16:02:16 +02:00
if ( curInt - > curAction )
{
if ( curInt - > curAction - > stackNumber = = stack - > ID ) //stack is currently taking action (is not a target of another creature's action etc)
{
2017-07-20 06:08:49 +02:00
if ( curInt - > curAction - > actionType = = EActionType : : WALK | | curInt - > curAction - > actionType = = EActionType : : SHOOT ) //hide when stack walks or shoots
2017-07-07 16:02:16 +02:00
return false ;
2017-07-20 06:08:49 +02:00
else if ( curInt - > curAction - > actionType = = EActionType : : WALK_AND_ATTACK & & currentActionTarget ! = stack - > getPosition ( ) ) //when attacking, hide until walk phase finished
2017-07-07 16:02:16 +02:00
return false ;
}
2013-07-19 19:35:16 +03:00
2017-07-20 06:08:49 +02:00
if ( curInt - > curAction - > actionType = = EActionType : : SHOOT & & currentActionTarget = = stack - > getPosition ( ) ) //hide if we are ranged attack target
2013-07-19 19:35:16 +03:00
return false ;
}
return true ;
} ;
2016-10-01 08:07:18 +02:00
auto getEffectsPositivness = [ & ] ( const std : : vector < si32 > & activeSpells ) - > int
2013-07-19 19:35:16 +03:00
{
int pos = 0 ;
2016-10-28 23:37:20 +02:00
for ( const auto & spellId : activeSpells )
2013-07-19 19:35:16 +03:00
{
2016-10-01 08:07:18 +02:00
pos + = CGI - > spellh - > objects . at ( spellId ) - > positiveness ;
2013-07-19 19:35:16 +03:00
}
return pos ;
} ;
auto getAmountBoxBackground = [ & ] ( int positivness ) - > SDL_Surface *
{
if ( positivness > 0 )
return amountPositive ;
if ( positivness < 0 )
return amountNegative ;
return amountEffNeutral ;
} ;
showStacks ( to , stacks ) ; // Actual display of all stacks
for ( auto & stack : stacks )
{
assert ( stack ) ;
//printing amount
if ( isAmountBoxVisible ( stack ) )
{
2017-07-01 10:34:00 +02:00
const int sideShift = stack - > side = = BattleSide : : ATTACKER ? 1 : - 1 ;
const int reverseSideShift = stack - > side = = BattleSide : : ATTACKER ? - 1 : 1 ;
2017-07-20 06:08:49 +02:00
const BattleHex nextPos = stack - > getPosition ( ) + sideShift ;
const bool edge = stack - > getPosition ( ) % GameConstants : : BFIELD_WIDTH = = ( stack - > side = = BattleSide : : ATTACKER ? GameConstants : : BFIELD_WIDTH - 2 : 1 ) ;
2013-07-19 19:35:16 +03:00
const bool moveInside = ! edge & & ! stackCountOutsideHexes [ nextPos ] ;
2017-07-01 10:34:00 +02:00
int xAdd = ( stack - > side = = BattleSide : : ATTACKER ? 220 : 202 ) +
( stack - > doubleWide ( ) ? 44 : 0 ) * sideShift +
( moveInside ? amountNormal - > w + 10 : 0 ) * reverseSideShift ;
int yAdd = 260 + ( ( stack - > side = = BattleSide : : ATTACKER | | moveInside ) ? 0 : - 15 ) ;
2013-07-19 19:35:16 +03:00
//blitting amount background box
SDL_Surface * amountBG = amountNormal ;
2016-10-01 08:07:18 +02:00
std : : vector < si32 > activeSpells = stack - > activeSpells ( ) ;
2016-10-28 23:37:20 +02:00
if ( ! activeSpells . empty ( ) )
2016-10-01 08:07:18 +02:00
amountBG = getAmountBoxBackground ( getEffectsPositivness ( activeSpells ) ) ;
2013-07-19 19:35:16 +03:00
SDL_Rect temp_rect = genRect ( amountBG - > h , amountBG - > w , creAnims [ stack - > ID ] - > pos . x + xAdd , creAnims [ stack - > ID ] - > pos . y + yAdd ) ;
SDL_BlitSurface ( amountBG , nullptr , to , & temp_rect ) ;
//blitting amount
Point textPos ( creAnims [ stack - > ID ] - > pos . x + xAdd + amountNormal - > w / 2 ,
2020-10-06 01:27:04 +02:00
creAnims [ stack - > ID ] - > pos . y + yAdd + amountNormal - > h / 2 ) ;
2017-07-04 13:24:46 +02:00
graphics - > fonts [ FONT_TINY ] - > renderTextCenter ( to , makeNumberShort ( stack - > getCount ( ) ) , Colors : : WHITE , textPos ) ;
2013-07-19 19:35:16 +03:00
}
}
}
2016-10-28 23:37:20 +02:00
void CBattleInterface : : showStacks ( SDL_Surface * to , std : : vector < const CStack * > stacks )
2013-07-19 19:35:16 +03:00
{
2016-10-28 23:37:20 +02:00
for ( const CStack * stack : stacks )
2013-07-19 19:35:16 +03:00
{
creAnims [ stack - > ID ] - > nextFrame ( to , creDir [ stack - > ID ] ) ; // do actual blit
creAnims [ stack - > ID ] - > incrementFrame ( float ( GH . mainFPSmng - > getElapsedMilliseconds ( ) ) / 1000 ) ;
}
}
2017-07-20 06:08:49 +02:00
void CBattleInterface : : showObstacles ( SDL_Surface * to , std : : vector < std : : shared_ptr < const CObstacleInstance > > & obstacles )
2013-07-19 19:35:16 +03:00
{
2017-07-20 06:08:49 +02:00
for ( auto & obstacle : obstacles )
2013-07-19 19:35:16 +03:00
{
2018-03-30 13:02:04 +02:00
auto img = getObstacleImage ( * obstacle ) ;
2017-07-20 06:08:49 +02:00
if ( img )
{
Point p = getObstaclePosition ( img , * obstacle ) ;
img - > draw ( to , p . x , p . y ) ;
}
2013-07-19 19:35:16 +03:00
}
}
void CBattleInterface : : showBattleEffects ( SDL_Surface * to , const std : : vector < const BattleEffect * > & battleEffects )
{
2016-10-28 23:37:20 +02:00
for ( auto & elem : battleEffects )
2013-07-19 19:35:16 +03:00
{
2020-10-01 10:38:06 +02:00
int currentFrame = static_cast < int > ( floor ( elem - > currentFrame ) ) ;
2017-09-05 16:21:44 +02:00
currentFrame % = elem - > animation - > size ( ) ;
2013-07-19 19:35:16 +03:00
2018-03-30 13:02:04 +02:00
auto img = elem - > animation - > getImage ( currentFrame ) ;
2017-09-05 16:21:44 +02:00
SDL_Rect temp_rect = genRect ( img - > height ( ) , img - > width ( ) , elem - > x , elem - > y ) ;
img - > draw ( to , & temp_rect , nullptr ) ;
2013-07-19 19:35:16 +03:00
}
}
2016-10-28 23:37:20 +02:00
void CBattleInterface : : showInterface ( SDL_Surface * to )
2013-07-19 19:35:16 +03:00
{
blitAt ( menu , pos . x , 556 + pos . y , to ) ;
2016-10-28 23:37:20 +02:00
if ( tacticsMode )
2013-07-19 19:35:16 +03:00
{
btactNext - > showAll ( to ) ;
btactEnd - > showAll ( to ) ;
}
else
{
console - > showAll ( to ) ;
bConsoleUp - > showAll ( to ) ;
bConsoleDown - > showAll ( to ) ;
}
//showing buttons
bOptions - > showAll ( to ) ;
bSurrender - > showAll ( to ) ;
bFlee - > showAll ( to ) ;
bAutofight - > showAll ( to ) ;
bSpell - > showAll ( to ) ;
bWait - > showAll ( to ) ;
bDefence - > showAll ( to ) ;
//showing in-game console
LOCPLINT - > cingconsole - > show ( to ) ;
Rect posWithQueue = Rect ( pos . x , pos . y , 800 , 600 ) ;
2016-10-28 23:37:20 +02:00
if ( settings [ " battle " ] [ " showQueue " ] . Bool ( ) )
2013-07-19 19:35:16 +03:00
{
2016-10-28 23:37:20 +02:00
if ( ! queue - > embedded )
2013-07-19 19:35:16 +03:00
{
posWithQueue . y - = queue - > pos . h ;
posWithQueue . h + = queue - > pos . h ;
}
2017-07-20 06:08:49 +02:00
queue - > showAll ( to ) ;
2013-07-19 19:35:16 +03:00
}
//printing border around interface
2016-10-28 23:37:20 +02:00
if ( screen - > w ! = 800 | | screen - > h ! = 600 )
2013-07-19 19:35:16 +03:00
{
CMessage : : drawBorder ( curInt - > playerID , to , posWithQueue . w + 28 , posWithQueue . h + 28 , posWithQueue . x - 14 , posWithQueue . y - 15 ) ;
}
}
BattleObjectsByHex CBattleInterface : : sortObjectsByHex ( )
{
2016-10-28 23:37:20 +02:00
auto getCurrentPosition = [ & ] ( const CStack * stack ) - > BattleHex
2013-07-19 19:35:16 +03:00
{
for ( auto & anim : pendingAnims )
{
// certainly ugly workaround but fixes quite annoying bug
// stack position will be updated only *after* movement is finished
// before this - stack is always at its initial position. Thus we need to find
// its current position. Which can be found only in this class
2016-10-28 23:37:20 +02:00
if ( CMovementAnimation * move = dynamic_cast < CMovementAnimation * > ( anim . first ) )
2013-07-19 19:35:16 +03:00
{
if ( move - > stack = = stack )
return move - > nextHex ;
}
}
2017-07-20 06:08:49 +02:00
return stack - > getPosition ( ) ;
2013-07-19 19:35:16 +03:00
} ;
BattleObjectsByHex sorted ;
2016-10-28 23:37:20 +02:00
auto stacks = curInt - > cb - > battleGetStacksIf ( [ ] ( const CStack * s )
2016-02-29 03:42:15 +02:00
{
return ! s - > isTurret ( ) ;
} ) ;
2013-07-19 19:35:16 +03:00
// Sort creatures
2016-02-29 03:42:15 +02:00
for ( auto & stack : stacks )
2013-07-19 19:35:16 +03:00
{
2016-10-28 23:37:20 +02:00
if ( creAnims . find ( stack - > ID ) = = creAnims . end ( ) ) //e.g. for summoned but not yet handled stacks
2013-07-19 19:35:16 +03:00
continue ;
2017-07-20 06:08:49 +02:00
if ( stack - > initialPosition < 0 ) // turret shooters are handled separately
2013-07-19 19:35:16 +03:00
continue ;
2016-03-01 03:41:51 +02:00
//FIXME: hack to ignore ghost stacks
2016-10-28 23:37:20 +02:00
if ( ( creAnims [ stack - > ID ] - > getType ( ) = = CCreatureAnim : : DEAD | | creAnims [ stack - > ID ] - > getType ( ) = = CCreatureAnim : : HOLDING ) & & stack - > isGhost ( ) )
2016-03-01 03:41:51 +02:00
; //ignore
2016-10-28 23:37:20 +02:00
else if ( ! creAnims [ stack - > ID ] - > isDead ( ) )
2013-07-19 19:35:16 +03:00
{
2013-07-21 13:10:38 +03:00
if ( ! creAnims [ stack - > ID ] - > isMoving ( ) )
2017-07-20 06:08:49 +02:00
sorted . hex [ stack - > getPosition ( ) ] . alive . push_back ( stack ) ;
2013-07-19 19:35:16 +03:00
else
{
// flying creature - just blit them over everyone else
if ( stack - > hasBonusOfType ( Bonus : : FLYING ) )
sorted . afterAll . alive . push_back ( stack ) ;
else //try to find current location
sorted . hex [ getCurrentPosition ( stack ) ] . alive . push_back ( stack ) ;
}
}
2016-03-01 03:41:51 +02:00
else
2017-07-20 06:08:49 +02:00
sorted . hex [ stack - > getPosition ( ) ] . dead . push_back ( stack ) ;
2013-07-19 19:35:16 +03:00
}
// Sort battle effects (spells)
for ( auto & battleEffect : battleEffects )
{
2016-10-28 23:37:20 +02:00
if ( battleEffect . position . isValid ( ) )
2013-07-19 19:35:16 +03:00
sorted . hex [ battleEffect . position ] . effects . push_back ( & battleEffect ) ;
else
sorted . afterAll . effects . push_back ( & battleEffect ) ;
}
// Sort obstacles
{
2016-10-30 05:47:33 +02:00
std : : map < BattleHex , std : : shared_ptr < const CObstacleInstance > > backgroundObstacles ;
for ( auto & obstacle : curInt - > cb - > battleGetAllObstacles ( ) ) {
if ( obstacle - > obstacleType ! = CObstacleInstance : : ABSOLUTE_OBSTACLE
& & obstacle - > obstacleType ! = CObstacleInstance : : MOAT ) {
backgroundObstacles [ obstacle - > pos ] = obstacle ;
}
}
for ( auto & op : backgroundObstacles )
2013-07-19 19:35:16 +03:00
{
2016-10-30 05:47:33 +02:00
sorted . beforeAll . obstacles . push_back ( op . second ) ;
2013-07-19 19:35:16 +03:00
}
}
// Sort wall parts
if ( siegeH )
{
2016-02-13 16:10:14 +02:00
sorted . beforeAll . walls . push_back ( SiegeHelper : : BACKGROUND_WALL ) ;
sorted . hex [ 135 ] . walls . push_back ( SiegeHelper : : KEEP ) ;
sorted . afterAll . walls . push_back ( SiegeHelper : : BOTTOM_TOWER ) ;
sorted . hex [ 182 ] . walls . push_back ( SiegeHelper : : BOTTOM_WALL ) ;
sorted . hex [ 130 ] . walls . push_back ( SiegeHelper : : WALL_BELLOW_GATE ) ;
2016-02-14 13:38:24 +02:00
sorted . hex [ 78 ] . walls . push_back ( SiegeHelper : : WALL_OVER_GATE ) ;
2016-02-13 16:10:14 +02:00
sorted . hex [ 12 ] . walls . push_back ( SiegeHelper : : UPPER_WALL ) ;
sorted . beforeAll . walls . push_back ( SiegeHelper : : UPPER_TOWER ) ;
sorted . hex [ 94 ] . walls . push_back ( SiegeHelper : : GATE ) ;
sorted . hex [ 112 ] . walls . push_back ( SiegeHelper : : GATE_ARCH ) ;
sorted . hex [ 165 ] . walls . push_back ( SiegeHelper : : BOTTOM_STATIC_WALL ) ;
sorted . hex [ 45 ] . walls . push_back ( SiegeHelper : : UPPER_STATIC_WALL ) ;
2013-10-27 16:05:01 +03:00
2013-08-01 01:24:44 +03:00
if ( siegeH & & siegeH - > town - > hasBuilt ( BuildingID : : CITADEL ) )
{
2016-02-13 16:10:14 +02:00
sorted . beforeAll . walls . push_back ( SiegeHelper : : MOAT ) ;
//sorted.beforeAll.walls.push_back(SiegeHelper::BACKGROUND_MOAT); // blit as absolute obstacle
sorted . hex [ 135 ] . walls . push_back ( SiegeHelper : : KEEP_BATTLEMENT ) ;
2013-08-01 01:24:44 +03:00
}
if ( siegeH & & siegeH - > town - > hasBuilt ( BuildingID : : CASTLE ) )
{
2016-02-13 16:10:14 +02:00
sorted . afterAll . walls . push_back ( SiegeHelper : : BOTTOM_BATTLEMENT ) ;
sorted . beforeAll . walls . push_back ( SiegeHelper : : UPPER_BATTLEMENT ) ;
2013-08-01 01:24:44 +03:00
}
2013-07-19 19:35:16 +03:00
}
return sorted ;
}
void CBattleInterface : : updateBattleAnimations ( )
{
//handle animations
2016-10-28 23:37:20 +02:00
for ( auto & elem : pendingAnims )
2013-07-19 19:35:16 +03:00
{
2016-10-28 23:37:20 +02:00
if ( ! elem . first ) //this animation should be deleted
2013-07-19 19:35:16 +03:00
continue ;
2016-10-28 23:37:20 +02:00
if ( ! elem . second )
2013-07-19 19:35:16 +03:00
{
elem . second = elem . first - > init ( ) ;
}
2016-10-28 23:37:20 +02:00
if ( elem . second & & elem . first )
2013-07-19 19:35:16 +03:00
elem . first - > nextFrame ( ) ;
}
//delete anims
2020-10-01 10:38:06 +02:00
int preSize = static_cast < int > ( pendingAnims . size ( ) ) ;
2016-10-28 23:37:20 +02:00
for ( auto it = pendingAnims . begin ( ) ; it ! = pendingAnims . end ( ) ; + + it )
2013-07-19 19:35:16 +03:00
{
2016-10-28 23:37:20 +02:00
if ( it - > first = = nullptr )
2013-07-19 19:35:16 +03:00
{
pendingAnims . erase ( it ) ;
it = pendingAnims . begin ( ) ;
break ;
}
}
2016-10-28 23:37:20 +02:00
if ( preSize > 0 & & pendingAnims . empty ( ) )
2013-07-19 19:35:16 +03:00
{
//anims ended
2013-07-21 13:10:38 +03:00
blockUI ( activeStack = = nullptr ) ;
2013-07-19 19:35:16 +03:00
animsAreDisplayed . setn ( false ) ;
}
}
2018-04-07 13:34:11 +02:00
std : : shared_ptr < IImage > CBattleInterface : : getObstacleImage ( const CObstacleInstance & oi )
2013-07-19 19:35:16 +03:00
{
2016-10-28 23:37:20 +02:00
int frameIndex = ( animCount + 1 ) * 25 / getAnimSpeed ( ) ;
2017-07-20 06:08:49 +02:00
std : : shared_ptr < CAnimation > animation ;
if ( oi . obstacleType = = CObstacleInstance : : USUAL | | oi . obstacleType = = CObstacleInstance : : ABSOLUTE_OBSTACLE )
2013-07-19 19:35:16 +03:00
{
2017-07-20 06:08:49 +02:00
animation = obstacleAnimations [ oi . uniqueID ] ;
}
else if ( oi . obstacleType = = CObstacleInstance : : SPELL_CREATED )
{
const SpellCreatedObstacle * spellObstacle = dynamic_cast < const SpellCreatedObstacle * > ( & oi ) ;
if ( ! spellObstacle )
2018-04-07 13:34:11 +02:00
return std : : shared_ptr < IImage > ( ) ;
2017-07-20 06:08:49 +02:00
std : : string animationName = spellObstacle - > animation ;
auto cacheIter = animationsCache . find ( animationName ) ;
if ( cacheIter = = animationsCache . end ( ) )
{
2019-01-15 05:00:00 +02:00
logAnim - > trace ( " Creating obstacle animation %s " , animationName ) ;
2017-07-20 06:08:49 +02:00
animation = std : : make_shared < CAnimation > ( animationName ) ;
animation - > preload ( ) ;
animationsCache [ animationName ] = animation ;
}
else
{
animation = cacheIter - > second ;
2013-07-19 19:35:16 +03:00
}
2017-07-20 06:08:49 +02:00
}
2013-07-19 19:35:16 +03:00
2017-07-20 06:08:49 +02:00
if ( animation )
{
frameIndex % = animation - > size ( 0 ) ;
return animation - > getImage ( frameIndex , 0 ) ;
2013-07-19 19:35:16 +03:00
}
2017-07-20 06:08:49 +02:00
return nullptr ;
2013-07-19 19:35:16 +03:00
}
2018-04-07 13:34:11 +02:00
Point CBattleInterface : : getObstaclePosition ( std : : shared_ptr < IImage > image , const CObstacleInstance & obstacle )
2013-07-19 19:35:16 +03:00
{
2017-07-20 06:08:49 +02:00
int offset = obstacle . getAnimationYOffset ( image - > height ( ) ) ;
2013-07-19 19:35:16 +03:00
Rect r = hexPosition ( obstacle . pos ) ;
2017-07-20 06:08:49 +02:00
r . y + = 42 - image - > height ( ) + offset ;
2013-07-19 19:35:16 +03:00
return r . topLeft ( ) ;
}
2016-10-28 23:37:20 +02:00
void CBattleInterface : : redrawBackgroundWithHexes ( const CStack * activeStack )
2013-07-19 19:35:16 +03:00
{
attackableHexes . clear ( ) ;
if ( activeStack )
occupyableHexes = curInt - > cb - > battleGetAvailableHexes ( activeStack , true , & attackableHexes ) ;
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
auto fillStackCountOutsideHexes = [ & ] ( )
{
auto accessibility = curInt - > cb - > getAccesibility ( ) ;
for ( int i = 0 ; i < accessibility . size ( ) ; i + + )
stackCountOutsideHexes . at ( i ) = ( accessibility [ i ] = = EAccessibility : : ACCESSIBLE ) ;
} ;
fillStackCountOutsideHexes ( ) ;
2016-10-28 23:37:20 +02:00
//prepare background graphic with hexes and shaded hexes
2013-07-19 19:35:16 +03:00
blitAt ( background , 0 , 0 , backgroundWithHexes ) ;
//draw absolute obstacles (cliffs and so on)
2017-07-20 06:08:49 +02:00
for ( auto & oi : curInt - > cb - > battleGetAllObstacles ( ) )
2013-07-19 19:35:16 +03:00
{
2017-07-20 06:08:49 +02:00
if ( oi - > obstacleType = = CObstacleInstance : : ABSOLUTE_OBSTACLE )
{
2018-03-30 13:02:04 +02:00
auto img = getObstacleImage ( * oi ) ;
2017-07-20 06:08:49 +02:00
if ( img )
img - > draw ( backgroundWithHexes , oi - > getInfo ( ) . width , oi - > getInfo ( ) . height ) ;
}
2013-07-19 19:35:16 +03:00
}
2016-10-28 23:37:20 +02:00
if ( settings [ " battle " ] [ " stackRange " ] . Bool ( ) )
2013-07-19 19:35:16 +03:00
{
std : : vector < BattleHex > hexesToShade = occupyableHexes ;
hexesToShade . insert ( hexesToShade . end ( ) , attackableHexes . begin ( ) , attackableHexes . end ( ) ) ;
2016-10-28 23:37:20 +02:00
for ( BattleHex hex : hexesToShade )
2013-07-19 19:35:16 +03:00
{
int i = hex . getY ( ) ; //row
int j = hex . getX ( ) - 1 ; //column
int x = 58 + ( i % 2 = = 0 ? 22 : 0 ) + 44 * j ;
2016-10-28 23:37:20 +02:00
int y = 86 + 42 * i ;
2013-07-19 19:35:16 +03:00
SDL_Rect temp_rect = genRect ( cellShade - > h , cellShade - > w , x , y ) ;
CSDL_Ext : : blit8bppAlphaTo24bpp ( cellShade , nullptr , backgroundWithHexes , & temp_rect ) ;
}
}
2017-07-17 00:15:05 +02:00
if ( settings [ " battle " ] [ " cellBorders " ] . Bool ( ) )
CSDL_Ext : : blit8bppAlphaTo24bpp ( cellBorders , nullptr , backgroundWithHexes , nullptr ) ;
2013-07-19 19:35:16 +03:00
}
2016-10-28 23:37:20 +02:00
void CBattleInterface : : showPiecesOfWall ( SDL_Surface * to , std : : vector < int > pieces )
2013-07-19 19:35:16 +03:00
{
2016-10-28 23:37:20 +02:00
if ( ! siegeH )
2013-07-19 19:35:16 +03:00
return ;
for ( auto piece : pieces )
{
if ( piece < 15 ) // not a tower - just print
siegeH - > printPartOfWall ( to , piece ) ;
else // tower. find if tower is built and not destroyed - stack is present
{
// PieceID StackID
// 15 = keep, -2
// 16 = lower, -3
// 17 = upper, -4
// tower. check if tower is alive - stack is found
int stackPos = 13 - piece ;
const CStack * turret = nullptr ;
2016-10-28 23:37:20 +02:00
for ( auto & stack : curInt - > cb - > battleGetAllStacks ( true ) )
2013-07-19 19:35:16 +03:00
{
2017-07-20 06:08:49 +02:00
if ( stack - > initialPosition = = stackPos )
2013-07-19 19:35:16 +03:00
{
turret = stack ;
break ;
}
}
if ( turret )
{
std : : vector < const CStack * > stackList ( 1 , turret ) ;
showStacks ( to , stackList ) ;
siegeH - > printPartOfWall ( to , piece ) ;
}
}
}
2013-11-02 23:07:45 +03:00
}