mirror of
https://github.com/vcmi/vcmi.git
synced 2024-11-24 08:32:34 +02:00
Creature spellcast refactor (#569)
* Move some logic to lib * Mouse action priority queue enhancement * Get rid of siegehandler dependency * Improve AI offensive spellcasting * CBattleInterface cleanup
This commit is contained in:
parent
14e3bb29f1
commit
e50efdc279
@ -14,7 +14,6 @@
|
||||
|
||||
#include "StackWithBonuses.h"
|
||||
#include "EnemyInfo.h"
|
||||
#include "PossibleSpellcast.h"
|
||||
#include "../../lib/CStopWatch.h"
|
||||
#include "../../lib/CThreadHelper.h"
|
||||
#include "../../lib/spells/CSpellHandler.h"
|
||||
@ -122,16 +121,54 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
|
||||
return *action;
|
||||
//best action is from effective owner point if view, we are effective owner as we received "activeStack"
|
||||
|
||||
|
||||
//evaluate casting spell for spellcasting stack
|
||||
boost::optional<PossibleSpellcast> bestSpellcast(boost::none);
|
||||
//TODO: faerie dragon type spell should be selected by server
|
||||
SpellID creatureSpellToCast = cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), stack, CBattleInfoCallback::RANDOM_AIMED);
|
||||
if(stack->hasBonusOfType(Bonus::SPELLCASTER) && stack->canCast() && creatureSpellToCast != SpellID::NONE)
|
||||
{
|
||||
const CSpell * spell = creatureSpellToCast.toSpell();
|
||||
|
||||
if(spell->canBeCast(getCbc().get(), spells::Mode::CREATURE_ACTIVE, stack))
|
||||
{
|
||||
std::vector<PossibleSpellcast> possibleCasts;
|
||||
spells::BattleCast temp(getCbc().get(), stack, spells::Mode::CREATURE_ACTIVE, spell);
|
||||
for(auto & target : temp.findPotentialTargets())
|
||||
{
|
||||
PossibleSpellcast ps;
|
||||
ps.dest = target;
|
||||
ps.spell = spell;
|
||||
evaluateCreatureSpellcast(stack, ps);
|
||||
possibleCasts.push_back(ps);
|
||||
}
|
||||
|
||||
std::sort(possibleCasts.begin(), possibleCasts.end(), [&](const PossibleSpellcast & lhs, const PossibleSpellcast & rhs) { return lhs.value > rhs.value; });
|
||||
if(!possibleCasts.empty() && possibleCasts.front().value > 0)
|
||||
{
|
||||
bestSpellcast = boost::optional<PossibleSpellcast>(possibleCasts.front());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HypotheticBattle hb(getCbc());
|
||||
|
||||
PotentialTargets targets(stack, &hb);
|
||||
if(targets.possibleAttacks.size())
|
||||
{
|
||||
auto hlp = targets.bestAction();
|
||||
if(hlp.attack.shooting)
|
||||
return BattleAction::makeShotAttack(stack, hlp.attack.defender);
|
||||
AttackPossibility bestAttack = targets.bestAction();
|
||||
|
||||
//TODO: consider more complex spellcast evaluation, f.e. because "re-retaliation" during enemy move in same turn for melee attack etc.
|
||||
if(bestSpellcast.is_initialized() && bestSpellcast->value > bestAttack.damageDiff())
|
||||
return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id);
|
||||
else if(bestAttack.attack.shooting)
|
||||
return BattleAction::makeShotAttack(stack, bestAttack.attack.defender);
|
||||
else
|
||||
return BattleAction::makeMeleeAttack(stack, hlp.attack.defender->getPosition(), hlp.tile);
|
||||
return BattleAction::makeMeleeAttack(stack, bestAttack.attack.defender->getPosition(), bestAttack.tile);
|
||||
}
|
||||
else if(bestSpellcast.is_initialized())
|
||||
{
|
||||
return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -521,6 +558,58 @@ void CBattleAI::attemptCastingSpell()
|
||||
}
|
||||
}
|
||||
|
||||
//Below method works only for offensive spells
|
||||
void CBattleAI::evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps)
|
||||
{
|
||||
using ValueMap = PossibleSpellcast::ValueMap;
|
||||
|
||||
RNGStub rngStub;
|
||||
HypotheticBattle state(getCbc());
|
||||
TStacks all = getCbc()->battleGetAllStacks(false);
|
||||
|
||||
ValueMap healthOfStack;
|
||||
ValueMap newHealthOfStack;
|
||||
|
||||
for(auto unit : all)
|
||||
{
|
||||
healthOfStack[unit->unitId()] = unit->getAvailableHealth();
|
||||
}
|
||||
|
||||
spells::BattleCast cast(&state, stack, spells::Mode::CREATURE_ACTIVE, ps.spell);
|
||||
cast.target = ps.dest;
|
||||
cast.cast(&state, rngStub);
|
||||
|
||||
for(auto unit : all)
|
||||
{
|
||||
auto unitId = unit->unitId();
|
||||
auto localUnit = state.battleGetUnitByID(unitId);
|
||||
newHealthOfStack[unitId] = localUnit->getAvailableHealth();
|
||||
}
|
||||
|
||||
int64_t totalGain = 0;
|
||||
|
||||
for(auto unit : all)
|
||||
{
|
||||
auto unitId = unit->unitId();
|
||||
auto localUnit = state.battleGetUnitByID(unitId);
|
||||
|
||||
auto healthDiff = newHealthOfStack[unitId] - healthOfStack[unitId];
|
||||
|
||||
if(localUnit->unitOwner() != getCbc()->getPlayerID())
|
||||
healthDiff = -healthDiff;
|
||||
|
||||
if(healthDiff < 0)
|
||||
{
|
||||
ps.value = -1;
|
||||
return; //do not damage own units at all
|
||||
}
|
||||
|
||||
totalGain += healthDiff;
|
||||
}
|
||||
|
||||
ps.value = totalGain;
|
||||
};
|
||||
|
||||
int CBattleAI::distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances &dists, BattleHex *chosenHex)
|
||||
{
|
||||
int ret = 1000000;
|
||||
|
@ -9,6 +9,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
#include "../../lib/AI_Base.h"
|
||||
#include "PossibleSpellcast.h"
|
||||
#include "PotentialTargets.h"
|
||||
|
||||
class CSpell;
|
||||
@ -60,6 +61,8 @@ public:
|
||||
void init(std::shared_ptr<CBattleCallback> CB) override;
|
||||
void attemptCastingSpell();
|
||||
|
||||
void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only
|
||||
|
||||
BattleAction activeStack(const CStack * stack) override; //called when it's turn of that stack
|
||||
BattleAction goTowards(const CStack * stack, BattleHex hex );
|
||||
|
||||
|
@ -415,8 +415,8 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
|
||||
CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed);
|
||||
memset(stackCountOutsideHexes, 1, GameConstants::BFIELD_SIZE *sizeof(bool)); //initialize array with trues
|
||||
|
||||
currentAction = INVALID;
|
||||
selectedAction = INVALID;
|
||||
currentAction = PossiblePlayerBattleAction::INVALID;
|
||||
selectedAction = PossiblePlayerBattleAction::INVALID;
|
||||
addUsedEvents(RCLICK | MOVE | KEYBOARD);
|
||||
blockUI(true);
|
||||
}
|
||||
@ -1395,24 +1395,6 @@ void CBattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
|
||||
redrawBackgroundWithHexes(activeStack);
|
||||
}
|
||||
|
||||
CBattleInterface::PossibleActions CBattleInterface::getCasterAction(const CSpell * spell, const spells::Caster * caster, spells::Mode mode) const
|
||||
{
|
||||
PossibleActions spellSelMode = ANY_LOCATION;
|
||||
|
||||
const CSpell::TargetInfo ti(spell, caster->getSpellSchoolLevel(spell), mode);
|
||||
|
||||
if(ti.massive || ti.type == spells::AimType::NO_TARGET)
|
||||
spellSelMode = NO_LOCATION;
|
||||
else if(ti.type == spells::AimType::LOCATION && ti.clearAffected)
|
||||
spellSelMode = FREE_LOCATION;
|
||||
else if(ti.type == spells::AimType::CREATURE)
|
||||
spellSelMode = AIMED_SPELL_CREATURE;
|
||||
else if(ti.type == spells::AimType::OBSTACLE)
|
||||
spellSelMode = OBSTACLE;
|
||||
|
||||
return spellSelMode;
|
||||
}
|
||||
|
||||
void CBattleInterface::setHeroAnimation(ui8 side, int phase)
|
||||
{
|
||||
if(side == BattleSide::ATTACKER)
|
||||
@ -1441,9 +1423,9 @@ void CBattleInterface::castThisSpell(SpellID spellID)
|
||||
const CGHeroInstance *castingHero = (attackingHeroInstance->tempOwner == curInt->playerID) ? attackingHeroInstance : defendingHeroInstance;
|
||||
assert(castingHero); // code below assumes non-null hero
|
||||
sp = spellID.toSpell();
|
||||
PossibleActions spellSelMode = getCasterAction(sp, castingHero, spells::Mode::HERO);
|
||||
PossiblePlayerBattleAction spellSelMode = curInt->cb->getCasterAction(sp, castingHero, spells::Mode::HERO);
|
||||
|
||||
if (spellSelMode == NO_LOCATION) //user does not have to select location
|
||||
if (spellSelMode == PossiblePlayerBattleAction::NO_LOCATION) //user does not have to select location
|
||||
{
|
||||
spellToCast->aimToHex(BattleHex::INVALID);
|
||||
curInt->cb->battleMakeAction(spellToCast.get());
|
||||
@ -1669,7 +1651,7 @@ void CBattleInterface::activateStack()
|
||||
creatureSpellToCast = -1;
|
||||
}
|
||||
|
||||
getPossibleActionsForStack(s, false);
|
||||
possibleActions = getPossibleActionsForStack(s);
|
||||
|
||||
GH.fakeMouseMove();
|
||||
}
|
||||
@ -1686,7 +1668,7 @@ void CBattleInterface::endCastingSpell()
|
||||
|
||||
if(activeStack)
|
||||
{
|
||||
getPossibleActionsForStack(activeStack, false); //restore actions after they were cleared
|
||||
possibleActions = getPossibleActionsForStack(activeStack); //restore actions after they were cleared
|
||||
myTurn = true;
|
||||
}
|
||||
}
|
||||
@ -1694,7 +1676,7 @@ void CBattleInterface::endCastingSpell()
|
||||
{
|
||||
if(activeStack)
|
||||
{
|
||||
getPossibleActionsForStack(activeStack, false);
|
||||
possibleActions = getPossibleActionsForStack(activeStack);
|
||||
GH.fakeMouseMove();
|
||||
}
|
||||
}
|
||||
@ -1723,7 +1705,7 @@ void CBattleInterface::enterCreatureCastingMode()
|
||||
if (creatureSpellToCast == -1)
|
||||
return;
|
||||
|
||||
if (vstd::contains(possibleActions, NO_LOCATION))
|
||||
if (vstd::contains(possibleActions, PossiblePlayerBattleAction::NO_LOCATION))
|
||||
{
|
||||
const spells::Caster *caster = activeStack;
|
||||
const CSpell *spell = SpellID(creatureSpellToCast).toSpell();
|
||||
@ -1740,67 +1722,77 @@ void CBattleInterface::enterCreatureCastingMode()
|
||||
}
|
||||
else
|
||||
{
|
||||
getPossibleActionsForStack(activeStack, true);
|
||||
possibleActions = getPossibleActionsForStack(activeStack);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
void CBattleInterface::getPossibleActionsForStack(const CStack *stack, const bool forceCast)
|
||||
std::vector<PossiblePlayerBattleAction> CBattleInterface::getPossibleActionsForStack(const CStack *stack)
|
||||
{
|
||||
possibleActions.clear();
|
||||
if (tacticsMode)
|
||||
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);
|
||||
|
||||
return std::vector<PossiblePlayerBattleAction>(allActions);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
auto assignPriority = [&](PossiblePlayerBattleAction const & item) -> uint8_t //large lambda assigning priority which would have to be part of possibleActions without it
|
||||
{
|
||||
possibleActions.push_back(MOVE_TACTICS);
|
||||
possibleActions.push_back(CHOOSE_TACTICS_STACK);
|
||||
}
|
||||
else
|
||||
{
|
||||
PossibleActions notPriority = INVALID;
|
||||
//first action will be prioritized over later ones
|
||||
if(stack->canCast()) //TODO: check for battlefield effects that prevent casting?
|
||||
switch(item)
|
||||
{
|
||||
if(stack->hasBonusOfType (Bonus::SPELLCASTER))
|
||||
{
|
||||
if(creatureSpellToCast != -1)
|
||||
{
|
||||
const CSpell *spell = SpellID(creatureSpellToCast).toSpell();
|
||||
PossibleActions act = getCasterAction(spell, stack, spells::Mode::CREATURE_ACTIVE);
|
||||
|
||||
if(forceCast)
|
||||
{
|
||||
//forced action to be only one possible
|
||||
possibleActions.push_back(act);
|
||||
return;
|
||||
}
|
||||
else
|
||||
//if cast is not forced, cast action will have lowest priority
|
||||
notPriority = act;
|
||||
}
|
||||
}
|
||||
if (stack->hasBonusOfType (Bonus::RANDOM_SPELLCASTER))
|
||||
possibleActions.push_back (RANDOM_GENIE_SPELL);
|
||||
if (stack->hasBonusOfType (Bonus::DAEMON_SUMMONING))
|
||||
possibleActions.push_back (RISE_DEMONS);
|
||||
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;
|
||||
}
|
||||
if(stack->canShoot())
|
||||
possibleActions.push_back(SHOOT);
|
||||
if(stack->hasBonusOfType(Bonus::RETURN_AFTER_STRIKE))
|
||||
possibleActions.push_back(ATTACK_AND_RETURN);
|
||||
};
|
||||
|
||||
possibleActions.push_back(ATTACK); //all active stacks can attack
|
||||
possibleActions.push_back(WALK_AND_ATTACK); //not all stacks can always walk, but we will check this elsewhere
|
||||
auto comparer = [&](PossiblePlayerBattleAction const & lhs, PossiblePlayerBattleAction const & rhs)
|
||||
{
|
||||
return assignPriority(lhs) > assignPriority(rhs);
|
||||
};
|
||||
|
||||
if (stack->canMove() && stack->Speed(0, true)) //probably no reason to try move war machines or bound stacks
|
||||
possibleActions.push_back (MOVE_STACK); //all active stacks can attack
|
||||
|
||||
if (siegeH && stack->hasBonusOfType (Bonus::CATAPULT)) //TODO: check shots
|
||||
possibleActions.push_back (CATAPULT);
|
||||
if (stack->hasBonusOfType (Bonus::HEALER))
|
||||
possibleActions.push_back (HEAL);
|
||||
|
||||
if (notPriority != INVALID)
|
||||
possibleActions.push_back(notPriority);
|
||||
}
|
||||
std::make_heap(possibleActions.begin(), possibleActions.end(), comparer);
|
||||
}
|
||||
|
||||
void CBattleInterface::printConsoleAttacked(const CStack * defender, int dmg, int killed, const CStack * attacker, bool multiple)
|
||||
@ -2127,21 +2119,22 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
||||
localActions.clear();
|
||||
illegalActions.clear();
|
||||
|
||||
reorderPossibleActionsPriority(activeStack, shere ? MouseHoveredHexContext::OCCUPIED_HEX : MouseHoveredHexContext::UNOCCUPIED_HEX);
|
||||
const bool forcedAction = possibleActions.size() == 1;
|
||||
|
||||
for (PossibleActions action : possibleActions)
|
||||
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 CHOOSE_TACTICS_STACK:
|
||||
case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
|
||||
if (shere && ourStack)
|
||||
legalAction = true;
|
||||
break;
|
||||
case MOVE_TACTICS:
|
||||
case MOVE_STACK:
|
||||
case PossiblePlayerBattleAction::MOVE_TACTICS:
|
||||
case PossiblePlayerBattleAction::MOVE_STACK:
|
||||
{
|
||||
if (!(shere && shere->alive())) //we can walk on dead stacks
|
||||
{
|
||||
@ -2150,9 +2143,9 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ATTACK:
|
||||
case WALK_AND_ATTACK:
|
||||
case ATTACK_AND_RETURN:
|
||||
case PossiblePlayerBattleAction::ATTACK:
|
||||
case PossiblePlayerBattleAction::WALK_AND_ATTACK:
|
||||
case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
|
||||
{
|
||||
if(curInt->cb->battleCanAttack(activeStack, shere, myNumber))
|
||||
{
|
||||
@ -2167,22 +2160,22 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SHOOT:
|
||||
case PossiblePlayerBattleAction::SHOOT:
|
||||
if(curInt->cb->battleCanShoot(activeStack, myNumber))
|
||||
legalAction = true;
|
||||
break;
|
||||
case ANY_LOCATION:
|
||||
case PossiblePlayerBattleAction::ANY_LOCATION:
|
||||
if (myNumber > -1) //TODO: this should be checked for all actions
|
||||
{
|
||||
if(isCastingPossibleHere(activeStack, shere, myNumber))
|
||||
legalAction = true;
|
||||
}
|
||||
break;
|
||||
case AIMED_SPELL_CREATURE:
|
||||
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
|
||||
if(shere && isCastingPossibleHere(activeStack, shere, myNumber))
|
||||
legalAction = true;
|
||||
break;
|
||||
case RANDOM_GENIE_SPELL:
|
||||
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
|
||||
{
|
||||
if(shere && ourStack && shere != activeStack && shere->alive()) //only positive spells for other allied creatures
|
||||
{
|
||||
@ -2194,11 +2187,11 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OBSTACLE:
|
||||
case PossiblePlayerBattleAction::OBSTACLE:
|
||||
if(isCastingPossibleHere(activeStack, shere, myNumber))
|
||||
legalAction = true;
|
||||
break;
|
||||
case TELEPORT:
|
||||
case PossiblePlayerBattleAction::TELEPORT:
|
||||
{
|
||||
//todo: move to mechanics
|
||||
ui8 skill = 0;
|
||||
@ -2213,13 +2206,13 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
||||
notLegal = true;
|
||||
}
|
||||
break;
|
||||
case SACRIFICE: //choose our living stack to sacrifice
|
||||
case PossiblePlayerBattleAction::SACRIFICE: //choose our living stack to sacrifice
|
||||
if (shere && shere != selectedStack && ourStack && shere->alive())
|
||||
legalAction = true;
|
||||
else
|
||||
notLegal = true;
|
||||
break;
|
||||
case FREE_LOCATION:
|
||||
case PossiblePlayerBattleAction::FREE_LOCATION:
|
||||
legalAction = true;
|
||||
if(!isCastingPossibleHere(activeStack, shere, myNumber))
|
||||
{
|
||||
@ -2227,15 +2220,15 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
||||
notLegal = true;
|
||||
}
|
||||
break;
|
||||
case CATAPULT:
|
||||
case PossiblePlayerBattleAction::CATAPULT:
|
||||
if (isCatapultAttackable(myNumber))
|
||||
legalAction = true;
|
||||
break;
|
||||
case HEAL:
|
||||
case PossiblePlayerBattleAction::HEAL:
|
||||
if (shere && ourStack && shere->canBeHealed())
|
||||
legalAction = true;
|
||||
break;
|
||||
case RISE_DEMONS:
|
||||
case PossiblePlayerBattleAction::RISE_DEMONS:
|
||||
if (shere && ourStack && !shere->alive())
|
||||
{
|
||||
if (!(shere->hasBonusOfType(Bonus::UNDEAD)
|
||||
@ -2253,7 +2246,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
||||
else if (notLegal || forcedAction)
|
||||
illegalActions.push_back (action);
|
||||
}
|
||||
illegalAction = INVALID; //clear it in first place
|
||||
illegalAction = PossiblePlayerBattleAction::INVALID; //clear it in first place
|
||||
|
||||
if (vstd::contains(localActions, selectedAction)) //try to use last selected action by default
|
||||
currentAction = selectedAction;
|
||||
@ -2261,7 +2254,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
||||
currentAction = localActions.front();
|
||||
else //no legal action possible
|
||||
{
|
||||
currentAction = INVALID; //don't allow to do anything
|
||||
currentAction = PossiblePlayerBattleAction::INVALID; //don't allow to do anything
|
||||
|
||||
if (vstd::contains(illegalActions, selectedAction))
|
||||
illegalAction = selectedAction;
|
||||
@ -2269,25 +2262,25 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
||||
illegalAction = illegalActions.front();
|
||||
else if (shere && ourStack && shere->alive()) //last possibility - display info about our creature
|
||||
{
|
||||
currentAction = CREATURE_INFO;
|
||||
currentAction = PossiblePlayerBattleAction::CREATURE_INFO;
|
||||
}
|
||||
else
|
||||
illegalAction = INVALID; //we should never be here
|
||||
illegalAction = PossiblePlayerBattleAction::INVALID; //we should never be here
|
||||
}
|
||||
|
||||
bool isCastingPossible = false;
|
||||
bool secondaryTarget = false;
|
||||
|
||||
if (currentAction > INVALID)
|
||||
if (currentAction > PossiblePlayerBattleAction::INVALID)
|
||||
{
|
||||
switch (currentAction) //display console message, realize selected action
|
||||
{
|
||||
case CHOOSE_TACTICS_STACK:
|
||||
case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
|
||||
consoleMsg = (boost::format(CGI->generaltexth->allTexts[481]) % shere->getName()).str(); //Select %s
|
||||
realizeAction = [=](){ stackActivated(shere); };
|
||||
break;
|
||||
case MOVE_TACTICS:
|
||||
case MOVE_STACK:
|
||||
case PossiblePlayerBattleAction::MOVE_TACTICS:
|
||||
case PossiblePlayerBattleAction::MOVE_STACK:
|
||||
if (activeStack->hasBonusOfType(Bonus::FLYING))
|
||||
{
|
||||
cursorFrame = ECursor::COMBAT_FLY;
|
||||
@ -2316,14 +2309,14 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
||||
}
|
||||
};
|
||||
break;
|
||||
case ATTACK:
|
||||
case WALK_AND_ATTACK:
|
||||
case ATTACK_AND_RETURN: //TODO: allow to disable return
|
||||
case PossiblePlayerBattleAction::ATTACK:
|
||||
case PossiblePlayerBattleAction::WALK_AND_ATTACK:
|
||||
case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
|
||||
{
|
||||
setBattleCursor(myNumber); //handle direction of cursor and attackable tile
|
||||
setCursor = false; //don't overwrite settings from the call above //TODO: what does it mean?
|
||||
|
||||
bool returnAfterAttack = currentAction == ATTACK_AND_RETURN;
|
||||
bool returnAfterAttack = currentAction == PossiblePlayerBattleAction::ATTACK_AND_RETURN;
|
||||
|
||||
realizeAction = [=]()
|
||||
{
|
||||
@ -2339,7 +2332,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
||||
consoleMsg = (boost::format(CGI->generaltexth->allTexts[36]) % shere->getName() % estDmgText).str(); //Attack %s (%s damage)
|
||||
}
|
||||
break;
|
||||
case SHOOT:
|
||||
case PossiblePlayerBattleAction::SHOOT:
|
||||
{
|
||||
if (curInt->cb->battleHasShootingPenalty(activeStack, myNumber))
|
||||
cursorFrame = ECursor::COMBAT_SHOOT_PENALTY;
|
||||
@ -2352,7 +2345,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
||||
consoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % activeStack->shots.available() % estDmgText).str();
|
||||
}
|
||||
break;
|
||||
case AIMED_SPELL_CREATURE:
|
||||
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
|
||||
sp = CGI->spellh->objects[creatureCasting ? creatureSpellToCast : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
|
||||
consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[27]) % sp->name % shere->getName()); //Cast %s on %s
|
||||
switch (sp->id)
|
||||
@ -2365,53 +2358,53 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
||||
}
|
||||
isCastingPossible = true;
|
||||
break;
|
||||
case ANY_LOCATION:
|
||||
case PossiblePlayerBattleAction::ANY_LOCATION:
|
||||
sp = CGI->spellh->objects[creatureCasting ? creatureSpellToCast : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
|
||||
consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s
|
||||
isCastingPossible = true;
|
||||
break;
|
||||
case RANDOM_GENIE_SPELL: //we assume that teleport / sacrifice will never be available as random spell
|
||||
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: //we assume that teleport / sacrifice will never be available as random spell
|
||||
sp = nullptr;
|
||||
consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[301]) % shere->getName()); //Cast a spell on %
|
||||
creatureCasting = true;
|
||||
isCastingPossible = true;
|
||||
break;
|
||||
case TELEPORT:
|
||||
case PossiblePlayerBattleAction::TELEPORT:
|
||||
consoleMsg = CGI->generaltexth->allTexts[25]; //Teleport Here
|
||||
cursorFrame = ECursor::COMBAT_TELEPORT;
|
||||
isCastingPossible = true;
|
||||
break;
|
||||
case OBSTACLE:
|
||||
case PossiblePlayerBattleAction::OBSTACLE:
|
||||
consoleMsg = CGI->generaltexth->allTexts[550];
|
||||
//TODO: remove obstacle cursor
|
||||
isCastingPossible = true;
|
||||
break;
|
||||
case SACRIFICE:
|
||||
case PossiblePlayerBattleAction::SACRIFICE:
|
||||
consoleMsg = (boost::format(CGI->generaltexth->allTexts[549]) % shere->getName()).str(); //sacrifice the %s
|
||||
cursorFrame = ECursor::COMBAT_SACRIFICE;
|
||||
isCastingPossible = true;
|
||||
break;
|
||||
case FREE_LOCATION:
|
||||
case PossiblePlayerBattleAction::FREE_LOCATION:
|
||||
consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s
|
||||
isCastingPossible = true;
|
||||
break;
|
||||
case HEAL:
|
||||
case PossiblePlayerBattleAction::HEAL:
|
||||
cursorFrame = ECursor::COMBAT_HEAL;
|
||||
consoleMsg = (boost::format(CGI->generaltexth->allTexts[419]) % shere->getName()).str(); //Apply first aid to the %s
|
||||
realizeAction = [=](){ giveCommand(EActionType::STACK_HEAL, myNumber); }; //command healing
|
||||
break;
|
||||
case RISE_DEMONS:
|
||||
case PossiblePlayerBattleAction::RISE_DEMONS:
|
||||
cursorType = ECursor::SPELLBOOK;
|
||||
realizeAction = [=]()
|
||||
{
|
||||
giveCommand(EActionType::DAEMON_SUMMONING, myNumber);
|
||||
};
|
||||
break;
|
||||
case CATAPULT:
|
||||
case PossiblePlayerBattleAction::CATAPULT:
|
||||
cursorFrame = ECursor::COMBAT_SHOOT_CATAPULT;
|
||||
realizeAction = [=](){ giveCommand(EActionType::CATAPULT, myNumber); };
|
||||
break;
|
||||
case CREATURE_INFO:
|
||||
case PossiblePlayerBattleAction::CREATURE_INFO:
|
||||
{
|
||||
cursorFrame = ECursor::COMBAT_QUERY;
|
||||
consoleMsg = (boost::format(CGI->generaltexth->allTexts[297]) % shere->getName()).str();
|
||||
@ -2424,19 +2417,19 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
||||
{
|
||||
switch (illegalAction)
|
||||
{
|
||||
case AIMED_SPELL_CREATURE:
|
||||
case RANDOM_GENIE_SPELL:
|
||||
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
|
||||
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
|
||||
cursorFrame = ECursor::COMBAT_BLOCKED;
|
||||
consoleMsg = CGI->generaltexth->allTexts[23];
|
||||
break;
|
||||
case TELEPORT:
|
||||
case PossiblePlayerBattleAction::TELEPORT:
|
||||
cursorFrame = ECursor::COMBAT_BLOCKED;
|
||||
consoleMsg = CGI->generaltexth->allTexts[24]; //Invalid Teleport Destination
|
||||
break;
|
||||
case SACRIFICE:
|
||||
case PossiblePlayerBattleAction::SACRIFICE:
|
||||
consoleMsg = CGI->generaltexth->allTexts[543]; //choose army to sacrifice
|
||||
break;
|
||||
case FREE_LOCATION:
|
||||
case PossiblePlayerBattleAction::FREE_LOCATION:
|
||||
cursorFrame = ECursor::COMBAT_BLOCKED;
|
||||
consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[181]) % sp->name); //No room to place %s here
|
||||
break;
|
||||
@ -2453,8 +2446,8 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
||||
{
|
||||
switch (currentAction) //don't use that with teleport / sacrifice
|
||||
{
|
||||
case TELEPORT: //FIXME: more generic solution?
|
||||
case SACRIFICE:
|
||||
case PossiblePlayerBattleAction::TELEPORT: //FIXME: more generic solution?
|
||||
case PossiblePlayerBattleAction::SACRIFICE:
|
||||
break;
|
||||
default:
|
||||
cursorType = ECursor::SPELLBOOK;
|
||||
@ -2474,11 +2467,11 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
||||
{
|
||||
case SpellID::TELEPORT: //don't cast spell yet, only select target
|
||||
spellToCast->aimToUnit(shere);
|
||||
possibleActions.push_back(TELEPORT);
|
||||
possibleActions.push_back(PossiblePlayerBattleAction::TELEPORT);
|
||||
break;
|
||||
case SpellID::SACRIFICE:
|
||||
spellToCast->aimToHex(myNumber);
|
||||
possibleActions.push_back(SACRIFICE);
|
||||
possibleActions.push_back(PossiblePlayerBattleAction::SACRIFICE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -2526,7 +2519,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
||||
if (eventType == LCLICK && realizeAction)
|
||||
{
|
||||
//opening creature window shouldn't affect myTurn...
|
||||
if ((currentAction != CREATURE_INFO) && !secondaryTarget)
|
||||
if ((currentAction != PossiblePlayerBattleAction::CREATURE_INFO) && !secondaryTarget)
|
||||
{
|
||||
myTurn = false; //tends to crash with empty calls
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "CBattleAnimations.h"
|
||||
|
||||
#include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation
|
||||
#include "../../lib/battle/CBattleInfoCallback.h"
|
||||
|
||||
class CLabel;
|
||||
class CCreatureSet;
|
||||
@ -104,20 +105,16 @@ struct CatapultProjectileInfo
|
||||
double calculateY(double x);
|
||||
};
|
||||
|
||||
enum class MouseHoveredHexContext
|
||||
{
|
||||
UNOCCUPIED_HEX,
|
||||
OCCUPIED_HEX
|
||||
};
|
||||
|
||||
/// Big class which handles the overall battle interface actions and it is also responsible for
|
||||
/// drawing everything correctly.
|
||||
class CBattleInterface : public WindowBase
|
||||
{
|
||||
enum PossibleActions // actions performed at l-click
|
||||
{
|
||||
INVALID = -1, CREATURE_INFO,
|
||||
MOVE_TACTICS, CHOOSE_TACTICS_STACK,
|
||||
MOVE_STACK, ATTACK, WALK_AND_ATTACK, ATTACK_AND_RETURN, SHOOT, //OPEN_GATE, //we can open castle gate during siege
|
||||
NO_LOCATION, ANY_LOCATION, OBSTACLE, TELEPORT, SACRIFICE, RANDOM_GENIE_SPELL,
|
||||
FREE_LOCATION, //used with Force Field and Fire Wall - all tiles affected by spell must be free
|
||||
CATAPULT, HEAL, RISE_DEMONS,
|
||||
AIMED_SPELL_CREATURE
|
||||
};
|
||||
private:
|
||||
SDL_Surface *background, *menu, *amountNormal, *amountNegative, *amountPositive, *amountEffNeutral, *cellBorders, *backgroundWithHexes;
|
||||
|
||||
@ -169,12 +166,12 @@ private:
|
||||
std::shared_ptr<BattleAction> spellToCast; //spell for which player is choosing destination
|
||||
const CSpell *sp; //spell pointer for convenience
|
||||
si32 creatureSpellToCast;
|
||||
std::vector<PossibleActions> possibleActions; //all actions possible to call at the moment by player
|
||||
std::vector<PossibleActions> localActions; //actions possible to take on hovered hex
|
||||
std::vector<PossibleActions> illegalActions; //these actions display message in case of illegal target
|
||||
PossibleActions currentAction; //action that will be performed on l-click
|
||||
PossibleActions selectedAction; //last action chosen (and saved) by player
|
||||
PossibleActions illegalAction; //most likely action that can't be performed here
|
||||
std::vector<PossiblePlayerBattleAction> possibleActions; //all actions possible to call at the moment by player
|
||||
std::vector<PossiblePlayerBattleAction> localActions; //actions possible to take on hovered hex
|
||||
std::vector<PossiblePlayerBattleAction> illegalActions; //these actions display message in case of illegal target
|
||||
PossiblePlayerBattleAction currentAction; //action that will be performed on l-click
|
||||
PossiblePlayerBattleAction selectedAction; //last action chosen (and saved) by player
|
||||
PossiblePlayerBattleAction illegalAction; //most likely action that can't be performed here
|
||||
bool battleActionsStarted; //used for delaying battle actions until intro sound stops
|
||||
int battleIntroSoundChannel; //required as variable for disabling it via ESC key
|
||||
|
||||
@ -183,8 +180,9 @@ private:
|
||||
|
||||
void requestAutofightingAIToTakeAction();
|
||||
|
||||
void getPossibleActionsForStack (const CStack *stack, const bool forceCast); //called when stack gets its turn
|
||||
std::vector<PossiblePlayerBattleAction> getPossibleActionsForStack (const CStack *stack); //called when stack gets its turn
|
||||
void endCastingSpell(); //ends casting spell (eg. when spell has been cast or canceled)
|
||||
void reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context);
|
||||
|
||||
//force active stack to cast a spell if possible
|
||||
void enterCreatureCastingMode();
|
||||
@ -275,8 +273,6 @@ private:
|
||||
void redrawBackgroundWithHexes(const CStack *activeStack);
|
||||
/** End of battle screen blitting methods */
|
||||
|
||||
PossibleActions getCasterAction(const CSpell *spell, const spells::Caster *caster, spells::Mode mode) const;
|
||||
|
||||
void setHeroAnimation(ui8 side, int phase);
|
||||
public:
|
||||
static CondSh<bool> animsAreDisplayed; //for waiting with the end of battle for end of anims
|
||||
|
@ -270,6 +270,7 @@ public:
|
||||
BONUS_NAME(BLOCK_MAGIC_BELOW) /*blocks casting spells of the level < value */ \
|
||||
BONUS_NAME(DESTRUCTION) /*kills extra units after hit, subtype = 0 - kill percentage of units, 1 - kill amount, val = chance in percent to trigger, additional info - amount/percentage to kill*/ \
|
||||
BONUS_NAME(SPECIAL_CRYSTAL_GENERATION) /*crystal dragon crystal generation*/ \
|
||||
BONUS_NAME(NO_SPELLCAST_BY_DEFAULT) /*spellcast will not be default attack option for this creature*/ \
|
||||
|
||||
/* end of list */
|
||||
|
||||
|
@ -74,6 +74,17 @@ BattleAction BattleAction::makeShotAttack(const battle::Unit * shooter, const ba
|
||||
return ba;
|
||||
}
|
||||
|
||||
BattleAction BattleAction::makeCreatureSpellcast(const battle::Unit * stack, const battle::Target & target, SpellID spellID)
|
||||
{
|
||||
BattleAction ba;
|
||||
ba.actionType = EActionType::MONSTER_SPELL;
|
||||
ba.actionSubtype = spellID;
|
||||
ba.setTarget(target);
|
||||
ba.side = stack->unitSide();
|
||||
ba.stackNumber = stack->unitId();
|
||||
return ba;
|
||||
}
|
||||
|
||||
BattleAction BattleAction::makeMove(const battle::Unit * stack, BattleHex dest)
|
||||
{
|
||||
BattleAction ba;
|
||||
|
@ -35,6 +35,7 @@ public:
|
||||
static BattleAction makeWait(const battle::Unit * stack);
|
||||
static BattleAction makeMeleeAttack(const battle::Unit * stack, BattleHex destination, BattleHex attackFrom, bool returnAfterAttack = true);
|
||||
static BattleAction makeShotAttack(const battle::Unit * shooter, const battle::Unit * target);
|
||||
static BattleAction makeCreatureSpellcast(const battle::Unit * stack, const battle::Target & target, SpellID spellID);
|
||||
static BattleAction makeMove(const battle::Unit * stack, BattleHex dest);
|
||||
static BattleAction makeEndOFTacticPhase(ui8 side);
|
||||
|
||||
|
@ -203,6 +203,70 @@ si8 CBattleInfoCallback::battleCanTeleportTo(const battle::Unit * stack, BattleH
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<PossiblePlayerBattleAction> CBattleInfoCallback::getClientActionsForStack(const CStack * stack, const BattleClientInterfaceData & data)
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(std::vector<PossiblePlayerBattleAction>());
|
||||
std::vector<PossiblePlayerBattleAction> allowedActionList;
|
||||
if(data.tacticsMode) //would "if(battleGetTacticDist() > 0)" work?
|
||||
{
|
||||
allowedActionList.push_back(PossiblePlayerBattleAction::MOVE_TACTICS);
|
||||
allowedActionList.push_back(PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(stack->canCast()) //TODO: check for battlefield effects that prevent casting?
|
||||
{
|
||||
if(stack->hasBonusOfType(Bonus::SPELLCASTER) && data.creatureSpellToCast != -1)
|
||||
{
|
||||
const CSpell *spell = SpellID(data.creatureSpellToCast).toSpell();
|
||||
PossiblePlayerBattleAction act = getCasterAction(spell, stack, spells::Mode::CREATURE_ACTIVE);
|
||||
allowedActionList.push_back(act);
|
||||
}
|
||||
if(stack->hasBonusOfType(Bonus::RANDOM_SPELLCASTER))
|
||||
allowedActionList.push_back(PossiblePlayerBattleAction::RANDOM_GENIE_SPELL);
|
||||
if(stack->hasBonusOfType(Bonus::DAEMON_SUMMONING))
|
||||
allowedActionList.push_back(PossiblePlayerBattleAction::RISE_DEMONS);
|
||||
}
|
||||
if(stack->canShoot())
|
||||
allowedActionList.push_back(PossiblePlayerBattleAction::SHOOT);
|
||||
if(stack->hasBonusOfType(Bonus::RETURN_AFTER_STRIKE))
|
||||
allowedActionList.push_back(PossiblePlayerBattleAction::ATTACK_AND_RETURN);
|
||||
|
||||
allowedActionList.push_back(PossiblePlayerBattleAction::ATTACK); //all active stacks can attack
|
||||
allowedActionList.push_back(PossiblePlayerBattleAction::WALK_AND_ATTACK); //not all stacks can always walk, but we will check this elsewhere
|
||||
|
||||
if(stack->canMove() && stack->Speed(0, true)) //probably no reason to try move war machines or bound stacks
|
||||
allowedActionList.push_back(PossiblePlayerBattleAction::MOVE_STACK);
|
||||
|
||||
auto siegedTown = battleGetDefendedTown();
|
||||
if(siegedTown && siegedTown->hasFort() && stack->hasBonusOfType(Bonus::CATAPULT)) //TODO: check shots
|
||||
allowedActionList.push_back(PossiblePlayerBattleAction::CATAPULT);
|
||||
if(stack->hasBonusOfType(Bonus::HEALER))
|
||||
allowedActionList.push_back(PossiblePlayerBattleAction::HEAL);
|
||||
}
|
||||
|
||||
return allowedActionList;
|
||||
}
|
||||
|
||||
PossiblePlayerBattleAction CBattleInfoCallback::getCasterAction(const CSpell * spell, const spells::Caster * caster, spells::Mode mode) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(PossiblePlayerBattleAction::INVALID);
|
||||
PossiblePlayerBattleAction spellSelMode = PossiblePlayerBattleAction::ANY_LOCATION;
|
||||
|
||||
const CSpell::TargetInfo ti(spell, caster->getSpellSchoolLevel(spell), mode);
|
||||
|
||||
if(ti.massive || ti.type == spells::AimType::NO_TARGET)
|
||||
spellSelMode = PossiblePlayerBattleAction::NO_LOCATION;
|
||||
else if(ti.type == spells::AimType::LOCATION && ti.clearAffected)
|
||||
spellSelMode = PossiblePlayerBattleAction::FREE_LOCATION;
|
||||
else if(ti.type == spells::AimType::CREATURE)
|
||||
spellSelMode = PossiblePlayerBattleAction::AIMED_SPELL_CREATURE;
|
||||
else if(ti.type == spells::AimType::OBSTACLE)
|
||||
spellSelMode = PossiblePlayerBattleAction::OBSTACLE;
|
||||
|
||||
return spellSelMode;
|
||||
}
|
||||
|
||||
std::set<BattleHex> CBattleInfoCallback::battleGetAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos) const
|
||||
{
|
||||
std::set<BattleHex> attackedHexes;
|
||||
|
@ -32,6 +32,23 @@ struct DLL_LINKAGE AttackableTiles
|
||||
}
|
||||
};
|
||||
|
||||
enum class PossiblePlayerBattleAction // actions performed at l-click
|
||||
{
|
||||
INVALID = -1, CREATURE_INFO,
|
||||
MOVE_TACTICS, CHOOSE_TACTICS_STACK,
|
||||
MOVE_STACK, ATTACK, WALK_AND_ATTACK, ATTACK_AND_RETURN, SHOOT, //OPEN_GATE, //we can open castle gate during siege
|
||||
NO_LOCATION, ANY_LOCATION, OBSTACLE, TELEPORT, SACRIFICE, RANDOM_GENIE_SPELL,
|
||||
FREE_LOCATION, //used with Force Field and Fire Wall - all tiles affected by spell must be free
|
||||
CATAPULT, HEAL, RISE_DEMONS,
|
||||
AIMED_SPELL_CREATURE
|
||||
};
|
||||
|
||||
struct DLL_LINKAGE BattleClientInterfaceData
|
||||
{
|
||||
si32 creatureSpellToCast;
|
||||
ui8 tacticsMode;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE CBattleInfoCallback : public virtual CBattleInfoEssentials
|
||||
{
|
||||
public:
|
||||
@ -99,6 +116,8 @@ public:
|
||||
SpellID getRandomCastedSpell(CRandomGenerator & rand, const CStack * caster) const; //called at the beginning of turn for Faerie Dragon
|
||||
|
||||
si8 battleCanTeleportTo(const battle::Unit * stack, BattleHex destHex, int telportLevel) const; //checks if teleportation of given stack to given position can take place
|
||||
std::vector<PossiblePlayerBattleAction> getClientActionsForStack(const CStack * stack, const BattleClientInterfaceData & data);
|
||||
PossiblePlayerBattleAction getCasterAction(const CSpell * spell, const spells::Caster * caster, spells::Mode mode) const;
|
||||
|
||||
//convenience methods using the ones above
|
||||
bool isInTacticRange(BattleHex dest) const;
|
||||
|
Loading…
Reference in New Issue
Block a user