2022-11-20 22:56:42 +02:00
/*
2022-12-11 23:16:23 +02:00
* BattleActionsController . cpp , part of VCMI engine
2022-11-20 22:56:42 +02:00
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
# include "StdInc.h"
2022-12-09 13:38:46 +02:00
# include "BattleActionsController.h"
2022-11-20 22:56:42 +02:00
2022-12-09 13:38:46 +02:00
# include "BattleControlPanel.h"
# include "BattleStacksController.h"
# include "BattleInterface.h"
# include "BattleFieldController.h"
# include "BattleSiegeController.h"
# include "BattleInterfaceClasses.h"
2022-11-20 22:56:42 +02:00
2022-11-28 16:43:38 +02:00
# include "../CGameInfo.h"
# include "../CPlayerInterface.h"
2022-11-20 22:56:42 +02:00
# include "../gui/CCursorHandler.h"
# include "../gui/CGuiHandler.h"
# include "../gui/CIntObject.h"
# include "../windows/CCreatureWindow.h"
2022-11-28 16:43:38 +02:00
2022-11-20 22:56:42 +02:00
# include "../../CCallback.h"
# include "../../lib/CStack.h"
# include "../../lib/battle/BattleAction.h"
# include "../../lib/spells/CSpellHandler.h"
# include "../../lib/spells/ISpellMechanics.h"
# include "../../lib/spells/Problem.h"
# include "../../lib/CGeneralTextHandler.h"
static std : : string formatDmgRange ( std : : pair < ui32 , ui32 > dmgRange )
{
if ( dmgRange . first ! = dmgRange . second )
return ( boost : : format ( " %d - %d " ) % dmgRange . first % dmgRange . second ) . str ( ) ;
else
return ( boost : : format ( " %d " ) % dmgRange . first ) . str ( ) ;
}
2022-12-13 13:58:16 +02:00
BattleActionsController : : BattleActionsController ( BattleInterface & owner ) :
2022-11-20 22:56:42 +02:00
owner ( owner ) ,
creatureCasting ( false ) ,
spellDestSelectMode ( false ) ,
spellToCast ( nullptr ) ,
2022-11-28 16:43:38 +02:00
currentSpell ( nullptr )
2022-11-20 22:56:42 +02:00
{
currentAction = PossiblePlayerBattleAction : : INVALID ;
selectedAction = PossiblePlayerBattleAction : : INVALID ;
}
2022-12-09 13:26:17 +02:00
void BattleActionsController : : endCastingSpell ( )
2022-11-20 22:56:42 +02:00
{
if ( spellDestSelectMode )
{
spellToCast . reset ( ) ;
2022-11-28 16:43:38 +02:00
currentSpell = nullptr ;
2022-11-20 22:56:42 +02:00
spellDestSelectMode = false ;
CCS - > curh - > changeGraphic ( ECursor : : COMBAT , ECursor : : COMBAT_POINTER ) ;
2022-12-13 13:58:16 +02:00
if ( owner . stacksController - > getActiveStack ( ) )
2022-11-20 22:56:42 +02:00
{
2022-12-13 13:58:16 +02:00
possibleActions = getPossibleActionsForStack ( owner . stacksController - > getActiveStack ( ) ) ; //restore actions after they were cleared
owner . myTurn = true ;
2022-11-20 22:56:42 +02:00
}
}
else
{
2022-12-13 13:58:16 +02:00
if ( owner . stacksController - > getActiveStack ( ) )
2022-11-20 22:56:42 +02:00
{
2022-12-13 13:58:16 +02:00
possibleActions = getPossibleActionsForStack ( owner . stacksController - > getActiveStack ( ) ) ;
2022-11-20 22:56:42 +02:00
GH . fakeMouseMove ( ) ;
}
}
}
2022-12-09 13:26:17 +02:00
void BattleActionsController : : enterCreatureCastingMode ( )
2022-11-20 22:56:42 +02:00
{
//silently check for possible errors
2022-12-13 13:58:16 +02:00
if ( ! owner . myTurn )
2022-11-20 22:56:42 +02:00
return ;
2022-12-13 13:58:16 +02:00
if ( owner . tacticsMode )
2022-11-20 22:56:42 +02:00
return ;
//hero is casting a spell
if ( spellDestSelectMode )
return ;
2022-12-13 13:58:16 +02:00
if ( ! owner . stacksController - > getActiveStack ( ) )
2022-11-20 22:56:42 +02:00
return ;
2022-12-13 13:58:16 +02:00
if ( ! owner . stacksController - > activeStackSpellcaster ( ) )
2022-11-20 22:56:42 +02:00
return ;
//random spellcaster
2022-12-13 13:58:16 +02:00
if ( owner . stacksController - > activeStackSpellToCast ( ) = = SpellID : : NONE )
2022-11-20 22:56:42 +02:00
return ;
if ( vstd : : contains ( possibleActions , PossiblePlayerBattleAction : : NO_LOCATION ) )
{
2022-12-13 13:58:16 +02:00
const spells : : Caster * caster = owner . stacksController - > getActiveStack ( ) ;
const CSpell * spell = owner . stacksController - > activeStackSpellToCast ( ) . toSpell ( ) ;
2022-11-20 22:56:42 +02:00
spells : : Target target ;
target . emplace_back ( ) ;
2022-12-13 13:58:16 +02:00
spells : : BattleCast cast ( owner . curInt - > cb . get ( ) , caster , spells : : Mode : : CREATURE_ACTIVE , spell ) ;
2022-11-20 22:56:42 +02:00
auto m = spell - > battleMechanics ( & cast ) ;
spells : : detail : : ProblemImpl ignored ;
const bool isCastingPossible = m - > canBeCastAt ( target , ignored ) ;
if ( isCastingPossible )
{
2022-12-13 13:58:16 +02:00
owner . myTurn = false ;
owner . giveCommand ( EActionType : : MONSTER_SPELL , BattleHex : : INVALID , owner . stacksController - > activeStackSpellToCast ( ) ) ;
owner . stacksController - > setSelectedStack ( nullptr ) ;
2022-11-20 22:56:42 +02:00
CCS - > curh - > changeGraphic ( ECursor : : COMBAT , ECursor : : COMBAT_POINTER ) ;
}
}
else
{
2022-12-13 13:58:16 +02:00
possibleActions = getPossibleActionsForStack ( owner . stacksController - > getActiveStack ( ) ) ;
2022-11-20 22:56:42 +02:00
auto actionFilterPredicate = [ ] ( const PossiblePlayerBattleAction x )
{
return ( x ! = PossiblePlayerBattleAction : : ANY_LOCATION ) & & ( x ! = PossiblePlayerBattleAction : : NO_LOCATION ) & &
( x ! = PossiblePlayerBattleAction : : FREE_LOCATION ) & & ( x ! = PossiblePlayerBattleAction : : AIMED_SPELL_CREATURE ) & &
( x ! = PossiblePlayerBattleAction : : OBSTACLE ) ;
} ;
vstd : : erase_if ( possibleActions , actionFilterPredicate ) ;
GH . fakeMouseMove ( ) ;
}
}
2022-12-11 23:16:23 +02:00
std : : vector < PossiblePlayerBattleAction > BattleActionsController : : getPossibleActionsForStack ( const CStack * stack ) const
2022-11-20 22:56:42 +02:00
{
BattleClientInterfaceData data ; //hard to get rid of these things so for now they're required data to pass
2022-12-13 13:58:16 +02:00
data . creatureSpellToCast = owner . stacksController - > activeStackSpellToCast ( ) ;
data . tacticsMode = owner . tacticsMode ;
auto allActions = owner . curInt - > cb - > getClientActionsForStack ( stack , data ) ;
2022-11-20 22:56:42 +02:00
return std : : vector < PossiblePlayerBattleAction > ( allActions ) ;
}
2022-12-09 13:26:17 +02:00
void BattleActionsController : : reorderPossibleActionsPriority ( const CStack * stack , MouseHoveredHexContext context )
2022-11-20 22:56:42 +02:00
{
2022-12-13 13:58:16 +02:00
if ( owner . tacticsMode | | possibleActions . empty ( ) ) return ; //this function is not supposed to be called in tactics mode or before getPossibleActionsForStack
2022-11-20 22:56:42 +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 ;
else
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 :
return 9 ; break ;
case PossiblePlayerBattleAction : : HEAL :
return 10 ; break ;
default :
return 200 ; break ;
}
} ;
auto comparer = [ & ] ( PossiblePlayerBattleAction const & lhs , PossiblePlayerBattleAction const & rhs )
{
return assignPriority ( lhs ) > assignPriority ( rhs ) ;
} ;
std : : make_heap ( possibleActions . begin ( ) , possibleActions . end ( ) , comparer ) ;
}
2022-12-09 13:26:17 +02:00
void BattleActionsController : : castThisSpell ( SpellID spellID )
2022-11-20 22:56:42 +02:00
{
spellToCast = std : : make_shared < BattleAction > ( ) ;
spellToCast - > actionType = EActionType : : HERO_SPELL ;
spellToCast - > actionSubtype = spellID ; //spell number
2022-12-13 13:58:16 +02:00
spellToCast - > stackNumber = ( owner . attackingHeroInstance - > tempOwner = = owner . curInt - > playerID ) ? - 1 : - 2 ;
spellToCast - > side = owner . defendingHeroInstance ? ( owner . curInt - > playerID = = owner . defendingHeroInstance - > tempOwner ) : false ;
2022-11-20 22:56:42 +02:00
spellDestSelectMode = true ;
creatureCasting = false ;
//choosing possible targets
2022-12-13 13:58:16 +02:00
const CGHeroInstance * castingHero = ( owner . attackingHeroInstance - > tempOwner = = owner . curInt - > playerID ) ? owner . attackingHeroInstance : owner . defendingHeroInstance ;
2022-11-20 22:56:42 +02:00
assert ( castingHero ) ; // code below assumes non-null hero
2022-11-28 16:43:38 +02:00
currentSpell = spellID . toSpell ( ) ;
2022-12-13 13:58:16 +02:00
PossiblePlayerBattleAction spellSelMode = owner . curInt - > cb - > getCasterAction ( currentSpell , castingHero , spells : : Mode : : HERO ) ;
2022-11-20 22:56:42 +02:00
if ( spellSelMode = = PossiblePlayerBattleAction : : NO_LOCATION ) //user does not have to select location
{
spellToCast - > aimToHex ( BattleHex : : INVALID ) ;
2022-12-13 13:58:16 +02:00
owner . curInt - > cb - > battleMakeAction ( spellToCast . get ( ) ) ;
2022-11-20 22:56:42 +02:00
endCastingSpell ( ) ;
}
else
{
possibleActions . clear ( ) ;
possibleActions . push_back ( spellSelMode ) ; //only this one action can be performed at the moment
GH . fakeMouseMove ( ) ; //update cursor
}
}
2022-12-09 13:26:17 +02:00
void BattleActionsController : : handleHex ( BattleHex myNumber , int eventType )
2022-11-20 22:56:42 +02:00
{
2022-12-13 13:58:16 +02:00
if ( ! owner . myTurn | | ! owner . battleActionsStarted ) //we are not permit to do anything
2022-11-20 22:56:42 +02:00
return ;
// This function handles mouse move over hexes and l-clicking on them.
// First we decide what happens if player clicks on this hex and set appropriately
// consoleMsg, cursorFrame/Type and prepare lambda realizeAction.
//
// Then, depending whether it was hover/click we either call the action or set tooltip/cursor.
2022-12-01 23:36:41 +02:00
std : : string newConsoleMsg ;
2022-11-20 22:56:42 +02:00
//used when hovering -> tooltip message and cursor to be set
bool setCursor = true ; //if we want to suppress setting cursor
ECursor : : ECursorTypes cursorType = ECursor : : COMBAT ;
int cursorFrame = ECursor : : COMBAT_POINTER ; //TODO: is this line used?
//used when l-clicking -> action to be called upon the click
std : : function < void ( ) > realizeAction ;
//Get stack on the hex - first try to grab the alive one, if not found -> allow dead stacks.
2022-12-13 13:58:16 +02:00
const CStack * shere = owner . curInt - > cb - > battleGetStackByPos ( myNumber , true ) ;
2022-11-20 22:56:42 +02:00
if ( ! shere )
2022-12-13 13:58:16 +02:00
shere = owner . curInt - > cb - > battleGetStackByPos ( myNumber , false ) ;
2022-11-20 22:56:42 +02:00
2022-12-13 13:58:16 +02:00
if ( ! owner . stacksController - > getActiveStack ( ) )
2022-11-20 22:56:42 +02:00
return ;
bool ourStack = false ;
if ( shere )
2022-12-13 13:58:16 +02:00
ourStack = shere - > owner = = owner . curInt - > playerID ;
2022-11-20 22:56:42 +02:00
//stack may have changed, update selection border
2022-12-13 13:58:16 +02:00
owner . stacksController - > setHoveredStack ( shere ) ;
2022-11-20 22:56:42 +02:00
localActions . clear ( ) ;
illegalActions . clear ( ) ;
2022-12-13 13:58:16 +02:00
reorderPossibleActionsPriority ( owner . stacksController - > getActiveStack ( ) , shere ? MouseHoveredHexContext : : OCCUPIED_HEX : MouseHoveredHexContext : : UNOCCUPIED_HEX ) ;
2022-11-20 22:56:42 +02:00
const bool forcedAction = possibleActions . size ( ) = = 1 ;
for ( PossiblePlayerBattleAction action : possibleActions )
{
bool legalAction = false ; //this action is legal and can be performed
bool notLegal = false ; //this action is not legal and should display message
switch ( action )
{
case PossiblePlayerBattleAction : : CHOOSE_TACTICS_STACK :
if ( shere & & ourStack )
legalAction = true ;
break ;
case PossiblePlayerBattleAction : : MOVE_TACTICS :
case PossiblePlayerBattleAction : : MOVE_STACK :
{
if ( ! ( shere & & shere - > alive ( ) ) ) //we can walk on dead stacks
{
2022-12-13 13:58:16 +02:00
if ( canStackMoveHere ( owner . stacksController - > getActiveStack ( ) , myNumber ) )
2022-11-20 22:56:42 +02:00
legalAction = true ;
}
break ;
}
case PossiblePlayerBattleAction : : ATTACK :
case PossiblePlayerBattleAction : : WALK_AND_ATTACK :
case PossiblePlayerBattleAction : : ATTACK_AND_RETURN :
{
2022-12-13 13:58:16 +02:00
if ( owner . curInt - > cb - > battleCanAttack ( owner . stacksController - > getActiveStack ( ) , shere , myNumber ) )
2022-11-20 22:56:42 +02:00
{
2022-12-13 13:58:16 +02:00
if ( owner . fieldController - > isTileAttackable ( myNumber ) ) // move isTileAttackable to be part of battleCanAttack?
2022-11-20 22:56:42 +02:00
{
2022-12-13 13:58:16 +02:00
owner . fieldController - > setBattleCursor ( myNumber ) ; // temporary - needed for following function :(
BattleHex attackFromHex = owner . fieldController - > fromWhichHexAttack ( myNumber ) ;
2022-11-20 22:56:42 +02:00
if ( attackFromHex > = 0 ) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
legalAction = true ;
}
}
}
break ;
case PossiblePlayerBattleAction : : SHOOT :
2022-12-13 13:58:16 +02:00
if ( owner . curInt - > cb - > battleCanShoot ( owner . stacksController - > getActiveStack ( ) , myNumber ) )
2022-11-20 22:56:42 +02:00
legalAction = true ;
break ;
case PossiblePlayerBattleAction : : ANY_LOCATION :
if ( myNumber > - 1 ) //TODO: this should be checked for all actions
{
2022-12-13 13:58:16 +02:00
if ( isCastingPossibleHere ( owner . stacksController - > getActiveStack ( ) , shere , myNumber ) )
2022-11-20 22:56:42 +02:00
legalAction = true ;
}
break ;
case PossiblePlayerBattleAction : : AIMED_SPELL_CREATURE :
2022-12-13 13:58:16 +02:00
if ( shere & & isCastingPossibleHere ( owner . stacksController - > getActiveStack ( ) , shere , myNumber ) )
2022-11-20 22:56:42 +02:00
legalAction = true ;
break ;
case PossiblePlayerBattleAction : : RANDOM_GENIE_SPELL :
{
2022-12-13 13:58:16 +02:00
if ( shere & & ourStack & & shere ! = owner . stacksController - > getActiveStack ( ) & & shere - > alive ( ) ) //only positive spells for other allied creatures
2022-11-20 22:56:42 +02:00
{
2022-12-13 13:58:16 +02:00
int spellID = owner . curInt - > cb - > battleGetRandomStackSpell ( CRandomGenerator : : getDefault ( ) , shere , CBattleInfoCallback : : RANDOM_GENIE ) ;
2022-11-20 22:56:42 +02:00
if ( spellID > - 1 )
{
legalAction = true ;
}
}
}
break ;
case PossiblePlayerBattleAction : : OBSTACLE :
2022-12-13 13:58:16 +02:00
if ( isCastingPossibleHere ( owner . stacksController - > getActiveStack ( ) , shere , myNumber ) )
2022-11-20 22:56:42 +02:00
legalAction = true ;
break ;
case PossiblePlayerBattleAction : : TELEPORT :
{
//todo: move to mechanics
ui8 skill = 0 ;
if ( creatureCasting )
2022-12-13 13:58:16 +02:00
skill = owner . stacksController - > getActiveStack ( ) - > getEffectLevel ( SpellID ( SpellID : : TELEPORT ) . toSpell ( ) ) ;
2022-11-20 22:56:42 +02:00
else
2022-12-13 13:58:16 +02:00
skill = owner . getActiveHero ( ) - > getEffectLevel ( SpellID ( SpellID : : TELEPORT ) . toSpell ( ) ) ;
2022-11-20 22:56:42 +02:00
//TODO: explicitely save power, skill
2022-12-13 13:58:16 +02:00
if ( owner . curInt - > cb - > battleCanTeleportTo ( owner . stacksController - > getSelectedStack ( ) , myNumber , skill ) )
2022-11-20 22:56:42 +02:00
legalAction = true ;
else
notLegal = true ;
}
break ;
case PossiblePlayerBattleAction : : SACRIFICE : //choose our living stack to sacrifice
2022-12-13 13:58:16 +02:00
if ( shere & & shere ! = owner . stacksController - > getSelectedStack ( ) & & ourStack & & shere - > alive ( ) )
2022-11-20 22:56:42 +02:00
legalAction = true ;
else
notLegal = true ;
break ;
case PossiblePlayerBattleAction : : FREE_LOCATION :
legalAction = true ;
2022-12-13 13:58:16 +02:00
if ( ! isCastingPossibleHere ( owner . stacksController - > getActiveStack ( ) , shere , myNumber ) )
2022-11-20 22:56:42 +02:00
{
legalAction = false ;
notLegal = true ;
}
break ;
case PossiblePlayerBattleAction : : CATAPULT :
2022-12-13 13:58:16 +02:00
if ( owner . siegeController & & owner . siegeController - > isAttackableByCatapult ( myNumber ) )
2022-11-20 22:56:42 +02:00
legalAction = true ;
break ;
case PossiblePlayerBattleAction : : HEAL :
if ( shere & & ourStack & & shere - > canBeHealed ( ) )
legalAction = true ;
break ;
case PossiblePlayerBattleAction : : RISE_DEMONS :
if ( shere & & ourStack & & ! shere - > alive ( ) )
{
if ( ! ( shere - > hasBonusOfType ( Bonus : : UNDEAD )
| | shere - > hasBonusOfType ( Bonus : : NON_LIVING )
| | shere - > hasBonusOfType ( Bonus : : GARGOYLE )
| | shere - > summoned
| | shere - > isClone ( )
| | shere - > hasBonusOfType ( Bonus : : SIEGE_WEAPON )
) )
legalAction = true ;
}
break ;
}
if ( legalAction )
localActions . push_back ( action ) ;
else if ( notLegal | | forcedAction )
illegalActions . push_back ( action ) ;
}
illegalAction = PossiblePlayerBattleAction : : INVALID ; //clear it in first place
if ( vstd : : contains ( localActions , selectedAction ) ) //try to use last selected action by default
currentAction = selectedAction ;
else if ( localActions . size ( ) ) //if not possible, select first available action (they are sorted by suggested priority)
currentAction = localActions . front ( ) ;
else //no legal action possible
{
currentAction = PossiblePlayerBattleAction : : INVALID ; //don't allow to do anything
if ( vstd : : contains ( illegalActions , selectedAction ) )
illegalAction = selectedAction ;
else if ( illegalActions . size ( ) )
illegalAction = illegalActions . front ( ) ;
else if ( shere & & ourStack & & shere - > alive ( ) ) //last possibility - display info about our creature
{
currentAction = PossiblePlayerBattleAction : : CREATURE_INFO ;
}
else
illegalAction = PossiblePlayerBattleAction : : INVALID ; //we should never be here
}
bool isCastingPossible = false ;
bool secondaryTarget = false ;
if ( currentAction > PossiblePlayerBattleAction : : INVALID )
{
switch ( currentAction ) //display console message, realize selected action
{
case PossiblePlayerBattleAction : : CHOOSE_TACTICS_STACK :
2022-12-01 23:36:41 +02:00
newConsoleMsg = ( boost : : format ( CGI - > generaltexth - > allTexts [ 481 ] ) % shere - > getName ( ) ) . str ( ) ; //Select %s
2022-12-13 13:58:16 +02:00
realizeAction = [ = ] ( ) { owner . stackActivated ( shere ) ; } ;
2022-11-20 22:56:42 +02:00
break ;
case PossiblePlayerBattleAction : : MOVE_TACTICS :
case PossiblePlayerBattleAction : : MOVE_STACK :
2022-12-13 13:58:16 +02:00
if ( owner . stacksController - > getActiveStack ( ) - > hasBonusOfType ( Bonus : : FLYING ) )
2022-11-20 22:56:42 +02:00
{
cursorFrame = ECursor : : COMBAT_FLY ;
2022-12-13 13:58:16 +02:00
newConsoleMsg = ( boost : : format ( CGI - > generaltexth - > allTexts [ 295 ] ) % owner . stacksController - > getActiveStack ( ) - > getName ( ) ) . str ( ) ; //Fly %s here
2022-11-20 22:56:42 +02:00
}
else
{
cursorFrame = ECursor : : COMBAT_MOVE ;
2022-12-13 13:58:16 +02:00
newConsoleMsg = ( boost : : format ( CGI - > generaltexth - > allTexts [ 294 ] ) % owner . stacksController - > getActiveStack ( ) - > getName ( ) ) . str ( ) ; //Move %s here
2022-11-20 22:56:42 +02:00
}
realizeAction = [ = ] ( )
{
2022-12-13 13:58:16 +02:00
if ( owner . stacksController - > getActiveStack ( ) - > doubleWide ( ) )
2022-11-20 22:56:42 +02:00
{
2022-12-13 13:58:16 +02:00
std : : vector < BattleHex > acc = owner . curInt - > cb - > battleGetAvailableHexes ( owner . stacksController - > getActiveStack ( ) ) ;
BattleHex shiftedDest = myNumber . cloneInDirection ( owner . stacksController - > getActiveStack ( ) - > destShiftDir ( ) , false ) ;
2022-11-20 22:56:42 +02:00
if ( vstd : : contains ( acc , myNumber ) )
2022-12-13 13:58:16 +02:00
owner . giveCommand ( EActionType : : WALK , myNumber ) ;
2022-11-20 22:56:42 +02:00
else if ( vstd : : contains ( acc , shiftedDest ) )
2022-12-13 13:58:16 +02:00
owner . giveCommand ( EActionType : : WALK , shiftedDest ) ;
2022-11-20 22:56:42 +02:00
}
else
{
2022-12-13 13:58:16 +02:00
owner . giveCommand ( EActionType : : WALK , myNumber ) ;
2022-11-20 22:56:42 +02:00
}
} ;
break ;
case PossiblePlayerBattleAction : : ATTACK :
case PossiblePlayerBattleAction : : WALK_AND_ATTACK :
case PossiblePlayerBattleAction : : ATTACK_AND_RETURN : //TODO: allow to disable return
{
2022-12-13 13:58:16 +02:00
owner . fieldController - > setBattleCursor ( myNumber ) ; //handle direction of cursor and attackable tile
2022-11-20 22:56:42 +02:00
setCursor = false ; //don't overwrite settings from the call above //TODO: what does it mean?
bool returnAfterAttack = currentAction = = PossiblePlayerBattleAction : : ATTACK_AND_RETURN ;
realizeAction = [ = ] ( )
{
2022-12-13 13:58:16 +02:00
BattleHex attackFromHex = owner . fieldController - > fromWhichHexAttack ( myNumber ) ;
2022-11-20 22:56:42 +02:00
if ( attackFromHex . isValid ( ) ) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
{
2022-12-13 13:58:16 +02:00
auto command = new BattleAction ( BattleAction : : makeMeleeAttack ( owner . stacksController - > getActiveStack ( ) , myNumber , attackFromHex , returnAfterAttack ) ) ;
owner . sendCommand ( command , owner . stacksController - > getActiveStack ( ) ) ;
2022-11-20 22:56:42 +02:00
}
} ;
2022-12-13 13:58:16 +02:00
TDmgRange damage = owner . curInt - > cb - > battleEstimateDamage ( owner . stacksController - > getActiveStack ( ) , shere ) ;
2022-11-20 22:56:42 +02:00
std : : string estDmgText = formatDmgRange ( std : : make_pair ( ( ui32 ) damage . first , ( ui32 ) damage . second ) ) ; //calculating estimated dmg
2022-12-01 23:36:41 +02:00
newConsoleMsg = ( boost : : format ( CGI - > generaltexth - > allTexts [ 36 ] ) % shere - > getName ( ) % estDmgText ) . str ( ) ; //Attack %s (%s damage)
2022-11-20 22:56:42 +02:00
}
break ;
case PossiblePlayerBattleAction : : SHOOT :
{
2022-12-13 13:58:16 +02:00
if ( owner . curInt - > cb - > battleHasShootingPenalty ( owner . stacksController - > getActiveStack ( ) , myNumber ) )
2022-11-20 22:56:42 +02:00
cursorFrame = ECursor : : COMBAT_SHOOT_PENALTY ;
else
cursorFrame = ECursor : : COMBAT_SHOOT ;
2022-12-13 13:58:16 +02:00
realizeAction = [ = ] ( ) { owner . giveCommand ( EActionType : : SHOOT , myNumber ) ; } ;
TDmgRange damage = owner . curInt - > cb - > battleEstimateDamage ( owner . stacksController - > getActiveStack ( ) , shere ) ;
2022-11-20 22:56:42 +02:00
std : : string estDmgText = formatDmgRange ( std : : make_pair ( ( ui32 ) damage . first , ( ui32 ) damage . second ) ) ; //calculating estimated dmg
//printing - Shoot %s (%d shots left, %s damage)
2022-12-13 13:58:16 +02:00
newConsoleMsg = ( boost : : format ( CGI - > generaltexth - > allTexts [ 296 ] ) % shere - > getName ( ) % owner . stacksController - > getActiveStack ( ) - > shots . available ( ) % estDmgText ) . str ( ) ;
2022-11-20 22:56:42 +02:00
}
break ;
case PossiblePlayerBattleAction : : AIMED_SPELL_CREATURE :
2022-12-13 13:58:16 +02:00
currentSpell = CGI - > spellh - > objects [ creatureCasting ? owner . stacksController - > activeStackSpellToCast ( ) : spellToCast - > actionSubtype ] ; //necessary if creature has random Genie spell at same time
2022-12-01 23:36:41 +02:00
newConsoleMsg = boost : : str ( boost : : format ( CGI - > generaltexth - > allTexts [ 27 ] ) % currentSpell - > name % shere - > getName ( ) ) ; //Cast %s on %s
2022-11-28 16:43:38 +02:00
switch ( currentSpell - > id )
2022-11-20 22:56:42 +02:00
{
case SpellID : : SACRIFICE :
case SpellID : : TELEPORT :
2022-12-13 13:58:16 +02:00
owner . stacksController - > setSelectedStack ( shere ) ; //remember first target
2022-11-20 22:56:42 +02:00
secondaryTarget = true ;
break ;
}
isCastingPossible = true ;
break ;
case PossiblePlayerBattleAction : : ANY_LOCATION :
2022-12-13 13:58:16 +02:00
currentSpell = CGI - > spellh - > objects [ creatureCasting ? owner . stacksController - > activeStackSpellToCast ( ) : spellToCast - > actionSubtype ] ; //necessary if creature has random Genie spell at same time
2022-12-01 23:36:41 +02:00
newConsoleMsg = boost : : str ( boost : : format ( CGI - > generaltexth - > allTexts [ 26 ] ) % currentSpell - > name ) ; //Cast %s
2022-11-20 22:56:42 +02:00
isCastingPossible = true ;
break ;
case PossiblePlayerBattleAction : : RANDOM_GENIE_SPELL : //we assume that teleport / sacrifice will never be available as random spell
2022-11-28 16:43:38 +02:00
currentSpell = nullptr ;
2022-12-01 23:36:41 +02:00
newConsoleMsg = boost : : str ( boost : : format ( CGI - > generaltexth - > allTexts [ 301 ] ) % shere - > getName ( ) ) ; //Cast a spell on %
2022-11-20 22:56:42 +02:00
creatureCasting = true ;
isCastingPossible = true ;
break ;
case PossiblePlayerBattleAction : : TELEPORT :
2022-12-01 23:36:41 +02:00
newConsoleMsg = CGI - > generaltexth - > allTexts [ 25 ] ; //Teleport Here
2022-11-20 22:56:42 +02:00
cursorFrame = ECursor : : COMBAT_TELEPORT ;
isCastingPossible = true ;
break ;
case PossiblePlayerBattleAction : : OBSTACLE :
2022-12-01 23:36:41 +02:00
newConsoleMsg = CGI - > generaltexth - > allTexts [ 550 ] ;
2022-11-20 22:56:42 +02:00
//TODO: remove obstacle cursor
isCastingPossible = true ;
break ;
case PossiblePlayerBattleAction : : SACRIFICE :
2022-12-01 23:36:41 +02:00
newConsoleMsg = ( boost : : format ( CGI - > generaltexth - > allTexts [ 549 ] ) % shere - > getName ( ) ) . str ( ) ; //sacrifice the %s
2022-11-20 22:56:42 +02:00
cursorFrame = ECursor : : COMBAT_SACRIFICE ;
isCastingPossible = true ;
break ;
case PossiblePlayerBattleAction : : FREE_LOCATION :
2022-12-01 23:36:41 +02:00
newConsoleMsg = boost : : str ( boost : : format ( CGI - > generaltexth - > allTexts [ 26 ] ) % currentSpell - > name ) ; //Cast %s
2022-11-20 22:56:42 +02:00
isCastingPossible = true ;
break ;
case PossiblePlayerBattleAction : : HEAL :
cursorFrame = ECursor : : COMBAT_HEAL ;
2022-12-01 23:36:41 +02:00
newConsoleMsg = ( boost : : format ( CGI - > generaltexth - > allTexts [ 419 ] ) % shere - > getName ( ) ) . str ( ) ; //Apply first aid to the %s
2022-12-13 13:58:16 +02:00
realizeAction = [ = ] ( ) { owner . giveCommand ( EActionType : : STACK_HEAL , myNumber ) ; } ; //command healing
2022-11-20 22:56:42 +02:00
break ;
case PossiblePlayerBattleAction : : RISE_DEMONS :
cursorType = ECursor : : SPELLBOOK ;
realizeAction = [ = ] ( )
{
2022-12-13 13:58:16 +02:00
owner . giveCommand ( EActionType : : DAEMON_SUMMONING , myNumber ) ;
2022-11-20 22:56:42 +02:00
} ;
break ;
case PossiblePlayerBattleAction : : CATAPULT :
cursorFrame = ECursor : : COMBAT_SHOOT_CATAPULT ;
2022-12-13 13:58:16 +02:00
realizeAction = [ = ] ( ) { owner . giveCommand ( EActionType : : CATAPULT , myNumber ) ; } ;
2022-11-20 22:56:42 +02:00
break ;
case PossiblePlayerBattleAction : : CREATURE_INFO :
{
cursorFrame = ECursor : : COMBAT_QUERY ;
2022-12-01 23:36:41 +02:00
newConsoleMsg = ( boost : : format ( CGI - > generaltexth - > allTexts [ 297 ] ) % shere - > getName ( ) ) . str ( ) ;
2022-11-20 22:56:42 +02:00
realizeAction = [ = ] ( ) { GH . pushIntT < CStackWindow > ( shere , false ) ; } ;
break ;
}
}
}
else //no possible valid action, display message
{
switch ( illegalAction )
{
case PossiblePlayerBattleAction : : AIMED_SPELL_CREATURE :
case PossiblePlayerBattleAction : : RANDOM_GENIE_SPELL :
cursorFrame = ECursor : : COMBAT_BLOCKED ;
2022-12-01 23:36:41 +02:00
newConsoleMsg = CGI - > generaltexth - > allTexts [ 23 ] ;
2022-11-20 22:56:42 +02:00
break ;
case PossiblePlayerBattleAction : : TELEPORT :
cursorFrame = ECursor : : COMBAT_BLOCKED ;
2022-12-01 23:36:41 +02:00
newConsoleMsg = CGI - > generaltexth - > allTexts [ 24 ] ; //Invalid Teleport Destination
2022-11-20 22:56:42 +02:00
break ;
case PossiblePlayerBattleAction : : SACRIFICE :
2022-12-01 23:36:41 +02:00
newConsoleMsg = CGI - > generaltexth - > allTexts [ 543 ] ; //choose army to sacrifice
2022-11-20 22:56:42 +02:00
break ;
case PossiblePlayerBattleAction : : FREE_LOCATION :
cursorFrame = ECursor : : COMBAT_BLOCKED ;
2022-12-01 23:36:41 +02:00
newConsoleMsg = boost : : str ( boost : : format ( CGI - > generaltexth - > allTexts [ 181 ] ) % currentSpell - > name ) ; //No room to place %s here
2022-11-20 22:56:42 +02:00
break ;
default :
if ( myNumber = = - 1 )
CCS - > curh - > changeGraphic ( ECursor : : COMBAT , ECursor : : COMBAT_POINTER ) ; //set neutral cursor over menu etc.
else
cursorFrame = ECursor : : COMBAT_BLOCKED ;
break ;
}
}
if ( isCastingPossible ) //common part
{
switch ( currentAction ) //don't use that with teleport / sacrifice
{
case PossiblePlayerBattleAction : : TELEPORT : //FIXME: more generic solution?
case PossiblePlayerBattleAction : : SACRIFICE :
break ;
default :
cursorType = ECursor : : SPELLBOOK ;
cursorFrame = 0 ;
2022-12-01 23:36:41 +02:00
if ( newConsoleMsg . empty ( ) & & currentSpell )
newConsoleMsg = boost : : str ( boost : : format ( CGI - > generaltexth - > allTexts [ 26 ] ) % currentSpell - > name ) ; //Cast %s
2022-11-20 22:56:42 +02:00
break ;
}
realizeAction = [ = ] ( )
{
if ( secondaryTarget ) //select that target now
{
possibleActions . clear ( ) ;
2022-11-28 16:43:38 +02:00
switch ( currentSpell - > id . toEnum ( ) )
2022-11-20 22:56:42 +02:00
{
case SpellID : : TELEPORT : //don't cast spell yet, only select target
spellToCast - > aimToUnit ( shere ) ;
possibleActions . push_back ( PossiblePlayerBattleAction : : TELEPORT ) ;
break ;
case SpellID : : SACRIFICE :
spellToCast - > aimToHex ( myNumber ) ;
possibleActions . push_back ( PossiblePlayerBattleAction : : SACRIFICE ) ;
break ;
}
}
else
{
if ( creatureCasting )
{
2022-11-28 16:43:38 +02:00
if ( currentSpell )
2022-11-20 22:56:42 +02:00
{
2022-12-13 13:58:16 +02:00
owner . giveCommand ( EActionType : : MONSTER_SPELL , myNumber , owner . stacksController - > activeStackSpellToCast ( ) ) ;
2022-11-20 22:56:42 +02:00
}
else //unknown random spell
{
2022-12-13 13:58:16 +02:00
owner . giveCommand ( EActionType : : MONSTER_SPELL , myNumber ) ;
2022-11-20 22:56:42 +02:00
}
}
else
{
2022-11-28 16:43:38 +02:00
assert ( currentSpell ) ;
switch ( currentSpell - > id . toEnum ( ) )
2022-11-20 22:56:42 +02:00
{
case SpellID : : SACRIFICE :
spellToCast - > aimToUnit ( shere ) ; //victim
break ;
default :
spellToCast - > aimToHex ( myNumber ) ;
break ;
}
2022-12-13 13:58:16 +02:00
owner . curInt - > cb - > battleMakeAction ( spellToCast . get ( ) ) ;
2022-11-20 22:56:42 +02:00
endCastingSpell ( ) ;
}
2022-12-13 13:58:16 +02:00
owner . stacksController - > setSelectedStack ( nullptr ) ;
2022-11-20 22:56:42 +02:00
}
} ;
}
{
if ( eventType = = CIntObject : : MOVE )
{
if ( setCursor )
CCS - > curh - > changeGraphic ( cursorType , cursorFrame ) ;
2022-12-01 23:36:41 +02:00
if ( ! currentConsoleMsg . empty ( ) )
2022-12-13 13:58:16 +02:00
owner . controlPanel - > console - > clearIfMatching ( currentConsoleMsg ) ;
2022-12-01 23:36:41 +02:00
if ( ! newConsoleMsg . empty ( ) )
2022-12-13 13:58:16 +02:00
owner . controlPanel - > console - > write ( newConsoleMsg ) ;
2022-12-01 23:36:41 +02:00
currentConsoleMsg = newConsoleMsg ;
2022-11-20 22:56:42 +02:00
}
if ( eventType = = CIntObject : : LCLICK & & realizeAction )
{
//opening creature window shouldn't affect myTurn...
if ( ( currentAction ! = PossiblePlayerBattleAction : : CREATURE_INFO ) & & ! secondaryTarget )
{
2022-12-13 13:58:16 +02:00
owner . myTurn = false ; //tends to crash with empty calls
2022-11-20 22:56:42 +02:00
}
realizeAction ( ) ;
if ( ! secondaryTarget ) //do not replace teleport or sacrifice cursor
CCS - > curh - > changeGraphic ( ECursor : : COMBAT , ECursor : : COMBAT_POINTER ) ;
2022-12-13 13:58:16 +02:00
owner . controlPanel - > console - > clear ( ) ;
2022-11-20 22:56:42 +02:00
}
}
}
2022-12-09 13:26:17 +02:00
bool BattleActionsController : : isCastingPossibleHere ( const CStack * sactive , const CStack * shere , BattleHex myNumber )
2022-11-20 22:56:42 +02:00
{
2022-12-13 13:58:16 +02:00
creatureCasting = owner . stacksController - > activeStackSpellcaster ( ) & & ! spellDestSelectMode ; //TODO: allow creatures to cast aimed spells
2022-11-20 22:56:42 +02:00
bool isCastingPossible = true ;
int spellID = - 1 ;
if ( creatureCasting )
{
2022-12-13 13:58:16 +02:00
if ( owner . stacksController - > activeStackSpellToCast ( ) ! = SpellID : : NONE & & ( shere ! = sactive ) ) //can't cast on itself
spellID = owner . stacksController - > activeStackSpellToCast ( ) ; //TODO: merge with SpellTocast?
2022-11-20 22:56:42 +02:00
}
else //hero casting
{
spellID = spellToCast - > actionSubtype ;
}
2022-11-28 16:43:38 +02:00
currentSpell = nullptr ;
2022-11-20 22:56:42 +02:00
if ( spellID > = 0 )
2022-11-28 16:43:38 +02:00
currentSpell = CGI - > spellh - > objects [ spellID ] ;
2022-11-20 22:56:42 +02:00
2022-11-28 16:43:38 +02:00
if ( currentSpell )
2022-11-20 22:56:42 +02:00
{
2022-12-13 13:58:16 +02:00
const spells : : Caster * caster = creatureCasting ? static_cast < const spells : : Caster * > ( sactive ) : static_cast < const spells : : Caster * > ( owner . curInt - > cb - > battleGetMyHero ( ) ) ;
2022-11-20 22:56:42 +02:00
if ( caster = = nullptr )
{
isCastingPossible = false ; //just in case
}
else
{
const spells : : Mode mode = creatureCasting ? spells : : Mode : : CREATURE_ACTIVE : spells : : Mode : : HERO ;
spells : : Target target ;
target . emplace_back ( myNumber ) ;
2022-12-13 13:58:16 +02:00
spells : : BattleCast cast ( owner . curInt - > cb . get ( ) , caster , mode , currentSpell ) ;
2022-11-20 22:56:42 +02:00
2022-11-28 16:43:38 +02:00
auto m = currentSpell - > battleMechanics ( & cast ) ;
2022-11-20 22:56:42 +02:00
spells : : detail : : ProblemImpl problem ; //todo: display problem in status bar
isCastingPossible = m - > canBeCastAt ( target , problem ) ;
}
}
else
isCastingPossible = false ;
if ( ! myNumber . isAvailable ( ) & & ! shere ) //empty tile outside battlefield (or in the unavailable border column)
isCastingPossible = false ;
return isCastingPossible ;
}
2022-12-11 23:16:23 +02:00
bool BattleActionsController : : canStackMoveHere ( const CStack * stackToMove , BattleHex myNumber ) const
2022-11-20 22:56:42 +02:00
{
2022-12-13 13:58:16 +02:00
std : : vector < BattleHex > acc = owner . curInt - > cb - > battleGetAvailableHexes ( stackToMove ) ;
2022-11-20 22:56:42 +02:00
BattleHex shiftedDest = myNumber . cloneInDirection ( stackToMove - > destShiftDir ( ) , false ) ;
if ( vstd : : contains ( acc , myNumber ) )
return true ;
else if ( stackToMove - > doubleWide ( ) & & vstd : : contains ( acc , shiftedDest ) )
return true ;
else
return false ;
}
2022-12-09 13:26:17 +02:00
void BattleActionsController : : activateStack ( )
2022-11-20 22:56:42 +02:00
{
2022-12-13 13:58:16 +02:00
const CStack * s = owner . stacksController - > getActiveStack ( ) ;
2022-11-20 22:56:42 +02:00
if ( s )
possibleActions = getPossibleActionsForStack ( s ) ;
}
2022-12-11 23:16:23 +02:00
bool BattleActionsController : : spellcastingModeActive ( ) const
2022-11-20 22:56:42 +02:00
{
return spellDestSelectMode ;
}
2022-12-11 23:16:23 +02:00
SpellID BattleActionsController : : selectedSpell ( ) const
2022-11-20 22:56:42 +02:00
{
if ( ! spellToCast )
return SpellID : : NONE ;
return SpellID ( spellToCast - > actionSubtype ) ;
}