mirror of
https://github.com/vcmi/vcmi.git
synced 2025-03-21 21:17:49 +02:00
Moved battle actions handling into a separate class
This commit is contained in:
parent
b01737daf2
commit
02fe0425c7
@ -6,6 +6,7 @@ set(client_SRCS
|
|||||||
battle/CBattleControlPanel.cpp
|
battle/CBattleControlPanel.cpp
|
||||||
battle/CBattleInterfaceClasses.cpp
|
battle/CBattleInterfaceClasses.cpp
|
||||||
battle/CBattleInterface.cpp
|
battle/CBattleInterface.cpp
|
||||||
|
battle/CBattleActionsController.cpp
|
||||||
battle/CBattleFieldController.cpp
|
battle/CBattleFieldController.cpp
|
||||||
battle/CBattleObstacleController.cpp
|
battle/CBattleObstacleController.cpp
|
||||||
battle/CBattleProjectileController.cpp
|
battle/CBattleProjectileController.cpp
|
||||||
@ -85,6 +86,7 @@ set(client_HEADERS
|
|||||||
battle/CBattleControlPanel.h
|
battle/CBattleControlPanel.h
|
||||||
battle/CBattleInterfaceClasses.h
|
battle/CBattleInterfaceClasses.h
|
||||||
battle/CBattleInterface.h
|
battle/CBattleInterface.h
|
||||||
|
battle/CBattleActionsController.h
|
||||||
battle/CBattleFieldController.h
|
battle/CBattleFieldController.h
|
||||||
battle/CBattleObstacleController.h
|
battle/CBattleObstacleController.h
|
||||||
battle/CBattleProjectileController.h
|
battle/CBattleProjectileController.h
|
||||||
|
768
client/battle/CBattleActionsController.cpp
Normal file
768
client/battle/CBattleActionsController.cpp
Normal file
@ -0,0 +1,768 @@
|
|||||||
|
/*
|
||||||
|
* CBattleActionsController.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
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include "StdInc.h"
|
||||||
|
#include "CBattleActionsController.h"
|
||||||
|
|
||||||
|
#include "CBattleControlPanel.h"
|
||||||
|
#include "CBattleStacksController.h"
|
||||||
|
#include "CBattleInterface.h"
|
||||||
|
#include "CBattleFieldController.h"
|
||||||
|
#include "CBattleSiegeController.h"
|
||||||
|
#include "CBattleInterfaceClasses.h"
|
||||||
|
|
||||||
|
#include "../gui/CCursorHandler.h"
|
||||||
|
#include "../gui/CGuiHandler.h"
|
||||||
|
#include "../gui/CIntObject.h"
|
||||||
|
#include "../windows/CCreatureWindow.h"
|
||||||
|
#include "../CGameInfo.h"
|
||||||
|
#include "../CPlayerInterface.h"
|
||||||
|
#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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CBattleActionsController::CBattleActionsController(CBattleInterface * owner):
|
||||||
|
owner(owner),
|
||||||
|
creatureCasting(false),
|
||||||
|
spellDestSelectMode(false),
|
||||||
|
spellToCast(nullptr),
|
||||||
|
sp(nullptr)
|
||||||
|
{
|
||||||
|
currentAction = PossiblePlayerBattleAction::INVALID;
|
||||||
|
selectedAction = PossiblePlayerBattleAction::INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBattleActionsController::endCastingSpell()
|
||||||
|
{
|
||||||
|
if(spellDestSelectMode)
|
||||||
|
{
|
||||||
|
spellToCast.reset();
|
||||||
|
|
||||||
|
sp = nullptr;
|
||||||
|
spellDestSelectMode = false;
|
||||||
|
CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
|
||||||
|
|
||||||
|
if(owner->stacksController->getActiveStack())
|
||||||
|
{
|
||||||
|
possibleActions = getPossibleActionsForStack(owner->stacksController->getActiveStack()); //restore actions after they were cleared
|
||||||
|
owner->myTurn = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(owner->stacksController->getActiveStack())
|
||||||
|
{
|
||||||
|
possibleActions = getPossibleActionsForStack(owner->stacksController->getActiveStack());
|
||||||
|
GH.fakeMouseMove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBattleActionsController::enterCreatureCastingMode()
|
||||||
|
{
|
||||||
|
//silently check for possible errors
|
||||||
|
if (!owner->myTurn)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (owner->tacticsMode)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//hero is casting a spell
|
||||||
|
if (spellDestSelectMode)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!owner->stacksController->getActiveStack())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!owner->stacksController->activeStackSpellcaster())
|
||||||
|
return;
|
||||||
|
|
||||||
|
//random spellcaster
|
||||||
|
if (owner->stacksController->activeStackSpellToCast() == SpellID::NONE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (vstd::contains(possibleActions, PossiblePlayerBattleAction::NO_LOCATION))
|
||||||
|
{
|
||||||
|
const spells::Caster * caster = owner->stacksController->getActiveStack();
|
||||||
|
const CSpell * spell = owner->stacksController->activeStackSpellToCast().toSpell();
|
||||||
|
|
||||||
|
spells::Target target;
|
||||||
|
target.emplace_back();
|
||||||
|
|
||||||
|
spells::BattleCast cast(owner->curInt->cb.get(), caster, spells::Mode::CREATURE_ACTIVE, spell);
|
||||||
|
|
||||||
|
auto m = spell->battleMechanics(&cast);
|
||||||
|
spells::detail::ProblemImpl ignored;
|
||||||
|
|
||||||
|
const bool isCastingPossible = m->canBeCastAt(target, ignored);
|
||||||
|
|
||||||
|
if (isCastingPossible)
|
||||||
|
{
|
||||||
|
owner->myTurn = false;
|
||||||
|
owner->giveCommand(EActionType::MONSTER_SPELL, BattleHex::INVALID, owner->stacksController->activeStackSpellToCast());
|
||||||
|
owner->stacksController->setSelectedStack(nullptr);
|
||||||
|
|
||||||
|
CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
possibleActions = getPossibleActionsForStack(owner->stacksController->getActiveStack());
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<PossiblePlayerBattleAction> CBattleActionsController::getPossibleActionsForStack(const CStack *stack)
|
||||||
|
{
|
||||||
|
BattleClientInterfaceData data; //hard to get rid of these things so for now they're required data to pass
|
||||||
|
data.creatureSpellToCast = owner->stacksController->activeStackSpellToCast();
|
||||||
|
data.tacticsMode = owner->tacticsMode;
|
||||||
|
auto allActions = owner->curInt->cb->getClientActionsForStack(stack, data);
|
||||||
|
|
||||||
|
return std::vector<PossiblePlayerBattleAction>(allActions);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBattleActionsController::reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context)
|
||||||
|
{
|
||||||
|
if(owner->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
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBattleActionsController::castThisSpell(SpellID spellID)
|
||||||
|
{
|
||||||
|
spellToCast = std::make_shared<BattleAction>();
|
||||||
|
spellToCast->actionType = EActionType::HERO_SPELL;
|
||||||
|
spellToCast->actionSubtype = spellID; //spell number
|
||||||
|
spellToCast->stackNumber = (owner->attackingHeroInstance->tempOwner == owner->curInt->playerID) ? -1 : -2;
|
||||||
|
spellToCast->side = owner->defendingHeroInstance ? (owner->curInt->playerID == owner->defendingHeroInstance->tempOwner) : false;
|
||||||
|
spellDestSelectMode = true;
|
||||||
|
creatureCasting = false;
|
||||||
|
|
||||||
|
//choosing possible targets
|
||||||
|
const CGHeroInstance *castingHero = (owner->attackingHeroInstance->tempOwner == owner->curInt->playerID) ? owner->attackingHeroInstance : owner->defendingHeroInstance;
|
||||||
|
assert(castingHero); // code below assumes non-null hero
|
||||||
|
sp = spellID.toSpell();
|
||||||
|
PossiblePlayerBattleAction spellSelMode = owner->curInt->cb->getCasterAction(sp, castingHero, spells::Mode::HERO);
|
||||||
|
|
||||||
|
if (spellSelMode == PossiblePlayerBattleAction::NO_LOCATION) //user does not have to select location
|
||||||
|
{
|
||||||
|
spellToCast->aimToHex(BattleHex::INVALID);
|
||||||
|
owner->curInt->cb->battleMakeAction(spellToCast.get());
|
||||||
|
endCastingSpell();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
possibleActions.clear();
|
||||||
|
possibleActions.push_back (spellSelMode); //only this one action can be performed at the moment
|
||||||
|
GH.fakeMouseMove();//update cursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CBattleActionsController::handleHex(BattleHex myNumber, int eventType)
|
||||||
|
{
|
||||||
|
if (!owner->myTurn || !owner->battleActionsStarted) //we are not permit to do anything
|
||||||
|
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.
|
||||||
|
|
||||||
|
//used when hovering -> tooltip message and cursor to be set
|
||||||
|
std::string consoleMsg;
|
||||||
|
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.
|
||||||
|
const CStack * shere = owner->curInt->cb->battleGetStackByPos(myNumber, true);
|
||||||
|
if(!shere)
|
||||||
|
shere = owner->curInt->cb->battleGetStackByPos(myNumber, false);
|
||||||
|
|
||||||
|
if(!owner->stacksController->getActiveStack())
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool ourStack = false;
|
||||||
|
if (shere)
|
||||||
|
ourStack = shere->owner == owner->curInt->playerID;
|
||||||
|
|
||||||
|
//stack may have changed, update selection border
|
||||||
|
owner->stacksController->setHoveredStack(shere);
|
||||||
|
|
||||||
|
localActions.clear();
|
||||||
|
illegalActions.clear();
|
||||||
|
|
||||||
|
reorderPossibleActionsPriority(owner->stacksController->getActiveStack(), shere ? MouseHoveredHexContext::OCCUPIED_HEX : MouseHoveredHexContext::UNOCCUPIED_HEX);
|
||||||
|
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
|
||||||
|
{
|
||||||
|
if(canStackMoveHere(owner->stacksController->getActiveStack(), myNumber))
|
||||||
|
legalAction = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PossiblePlayerBattleAction::ATTACK:
|
||||||
|
case PossiblePlayerBattleAction::WALK_AND_ATTACK:
|
||||||
|
case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
|
||||||
|
{
|
||||||
|
if(owner->curInt->cb->battleCanAttack(owner->stacksController->getActiveStack(), shere, myNumber))
|
||||||
|
{
|
||||||
|
if (owner->fieldController->isTileAttackable(myNumber)) // move isTileAttackable to be part of battleCanAttack?
|
||||||
|
{
|
||||||
|
owner->fieldController->setBattleCursor(myNumber); // temporary - needed for following function :(
|
||||||
|
BattleHex attackFromHex = owner->fieldController->fromWhichHexAttack(myNumber);
|
||||||
|
|
||||||
|
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:
|
||||||
|
if(owner->curInt->cb->battleCanShoot(owner->stacksController->getActiveStack(), myNumber))
|
||||||
|
legalAction = true;
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::ANY_LOCATION:
|
||||||
|
if (myNumber > -1) //TODO: this should be checked for all actions
|
||||||
|
{
|
||||||
|
if(isCastingPossibleHere(owner->stacksController->getActiveStack(), shere, myNumber))
|
||||||
|
legalAction = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
|
||||||
|
if(shere && isCastingPossibleHere(owner->stacksController->getActiveStack(), shere, myNumber))
|
||||||
|
legalAction = true;
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
|
||||||
|
{
|
||||||
|
if(shere && ourStack && shere != owner->stacksController->getActiveStack() && shere->alive()) //only positive spells for other allied creatures
|
||||||
|
{
|
||||||
|
int spellID = owner->curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), shere, CBattleInfoCallback::RANDOM_GENIE);
|
||||||
|
if(spellID > -1)
|
||||||
|
{
|
||||||
|
legalAction = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::OBSTACLE:
|
||||||
|
if(isCastingPossibleHere(owner->stacksController->getActiveStack(), shere, myNumber))
|
||||||
|
legalAction = true;
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::TELEPORT:
|
||||||
|
{
|
||||||
|
//todo: move to mechanics
|
||||||
|
ui8 skill = 0;
|
||||||
|
if (creatureCasting)
|
||||||
|
skill = owner->stacksController->getActiveStack()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
|
||||||
|
else
|
||||||
|
skill = owner->getActiveHero()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
|
||||||
|
//TODO: explicitely save power, skill
|
||||||
|
if (owner->curInt->cb->battleCanTeleportTo(owner->stacksController->getSelectedStack(), myNumber, skill))
|
||||||
|
legalAction = true;
|
||||||
|
else
|
||||||
|
notLegal = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::SACRIFICE: //choose our living stack to sacrifice
|
||||||
|
if (shere && shere != owner->stacksController->getSelectedStack() && ourStack && shere->alive())
|
||||||
|
legalAction = true;
|
||||||
|
else
|
||||||
|
notLegal = true;
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::FREE_LOCATION:
|
||||||
|
legalAction = true;
|
||||||
|
if(!isCastingPossibleHere(owner->stacksController->getActiveStack(), shere, myNumber))
|
||||||
|
{
|
||||||
|
legalAction = false;
|
||||||
|
notLegal = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::CATAPULT:
|
||||||
|
if (owner->siegeController && owner->siegeController->isCatapultAttackable(myNumber))
|
||||||
|
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:
|
||||||
|
consoleMsg = (boost::format(CGI->generaltexth->allTexts[481]) % shere->getName()).str(); //Select %s
|
||||||
|
realizeAction = [=](){ owner->stackActivated(shere); };
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::MOVE_TACTICS:
|
||||||
|
case PossiblePlayerBattleAction::MOVE_STACK:
|
||||||
|
if (owner->stacksController->getActiveStack()->hasBonusOfType(Bonus::FLYING))
|
||||||
|
{
|
||||||
|
cursorFrame = ECursor::COMBAT_FLY;
|
||||||
|
consoleMsg = (boost::format(CGI->generaltexth->allTexts[295]) % owner->stacksController->getActiveStack()->getName()).str(); //Fly %s here
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cursorFrame = ECursor::COMBAT_MOVE;
|
||||||
|
consoleMsg = (boost::format(CGI->generaltexth->allTexts[294]) % owner->stacksController->getActiveStack()->getName()).str(); //Move %s here
|
||||||
|
}
|
||||||
|
|
||||||
|
realizeAction = [=]()
|
||||||
|
{
|
||||||
|
if(owner->stacksController->getActiveStack()->doubleWide())
|
||||||
|
{
|
||||||
|
std::vector<BattleHex> acc = owner->curInt->cb->battleGetAvailableHexes(owner->stacksController->getActiveStack());
|
||||||
|
BattleHex shiftedDest = myNumber.cloneInDirection(owner->stacksController->getActiveStack()->destShiftDir(), false);
|
||||||
|
if(vstd::contains(acc, myNumber))
|
||||||
|
owner->giveCommand(EActionType::WALK, myNumber);
|
||||||
|
else if(vstd::contains(acc, shiftedDest))
|
||||||
|
owner->giveCommand(EActionType::WALK, shiftedDest);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
owner->giveCommand(EActionType::WALK, myNumber);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::ATTACK:
|
||||||
|
case PossiblePlayerBattleAction::WALK_AND_ATTACK:
|
||||||
|
case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
|
||||||
|
{
|
||||||
|
owner->fieldController->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 == PossiblePlayerBattleAction::ATTACK_AND_RETURN;
|
||||||
|
|
||||||
|
realizeAction = [=]()
|
||||||
|
{
|
||||||
|
BattleHex attackFromHex = owner->fieldController->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(owner->stacksController->getActiveStack(), myNumber, attackFromHex, returnAfterAttack));
|
||||||
|
owner->sendCommand(command, owner->stacksController->getActiveStack());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TDmgRange damage = owner->curInt->cb->battleEstimateDamage(owner->stacksController->getActiveStack(), shere);
|
||||||
|
std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
|
||||||
|
consoleMsg = (boost::format(CGI->generaltexth->allTexts[36]) % shere->getName() % estDmgText).str(); //Attack %s (%s damage)
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::SHOOT:
|
||||||
|
{
|
||||||
|
if (owner->curInt->cb->battleHasShootingPenalty(owner->stacksController->getActiveStack(), myNumber))
|
||||||
|
cursorFrame = ECursor::COMBAT_SHOOT_PENALTY;
|
||||||
|
else
|
||||||
|
cursorFrame = ECursor::COMBAT_SHOOT;
|
||||||
|
|
||||||
|
realizeAction = [=](){owner->giveCommand(EActionType::SHOOT, myNumber);};
|
||||||
|
TDmgRange damage = owner->curInt->cb->battleEstimateDamage(owner->stacksController->getActiveStack(), shere);
|
||||||
|
std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
|
||||||
|
//printing - Shoot %s (%d shots left, %s damage)
|
||||||
|
consoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % owner->stacksController->getActiveStack()->shots.available() % estDmgText).str();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
|
||||||
|
sp = CGI->spellh->objects[creatureCasting ? owner->stacksController->activeStackSpellToCast() : 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)
|
||||||
|
{
|
||||||
|
case SpellID::SACRIFICE:
|
||||||
|
case SpellID::TELEPORT:
|
||||||
|
owner->stacksController->setSelectedStack(shere); //remember first target
|
||||||
|
secondaryTarget = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
isCastingPossible = true;
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::ANY_LOCATION:
|
||||||
|
sp = CGI->spellh->objects[creatureCasting ? owner->stacksController->activeStackSpellToCast() : 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 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 PossiblePlayerBattleAction::TELEPORT:
|
||||||
|
consoleMsg = CGI->generaltexth->allTexts[25]; //Teleport Here
|
||||||
|
cursorFrame = ECursor::COMBAT_TELEPORT;
|
||||||
|
isCastingPossible = true;
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::OBSTACLE:
|
||||||
|
consoleMsg = CGI->generaltexth->allTexts[550];
|
||||||
|
//TODO: remove obstacle cursor
|
||||||
|
isCastingPossible = true;
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::SACRIFICE:
|
||||||
|
consoleMsg = (boost::format(CGI->generaltexth->allTexts[549]) % shere->getName()).str(); //sacrifice the %s
|
||||||
|
cursorFrame = ECursor::COMBAT_SACRIFICE;
|
||||||
|
isCastingPossible = true;
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::FREE_LOCATION:
|
||||||
|
consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s
|
||||||
|
isCastingPossible = true;
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::HEAL:
|
||||||
|
cursorFrame = ECursor::COMBAT_HEAL;
|
||||||
|
consoleMsg = (boost::format(CGI->generaltexth->allTexts[419]) % shere->getName()).str(); //Apply first aid to the %s
|
||||||
|
realizeAction = [=](){ owner->giveCommand(EActionType::STACK_HEAL, myNumber); }; //command healing
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::RISE_DEMONS:
|
||||||
|
cursorType = ECursor::SPELLBOOK;
|
||||||
|
realizeAction = [=]()
|
||||||
|
{
|
||||||
|
owner->giveCommand(EActionType::DAEMON_SUMMONING, myNumber);
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::CATAPULT:
|
||||||
|
cursorFrame = ECursor::COMBAT_SHOOT_CATAPULT;
|
||||||
|
realizeAction = [=](){ owner->giveCommand(EActionType::CATAPULT, myNumber); };
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::CREATURE_INFO:
|
||||||
|
{
|
||||||
|
cursorFrame = ECursor::COMBAT_QUERY;
|
||||||
|
consoleMsg = (boost::format(CGI->generaltexth->allTexts[297]) % shere->getName()).str();
|
||||||
|
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;
|
||||||
|
consoleMsg = CGI->generaltexth->allTexts[23];
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::TELEPORT:
|
||||||
|
cursorFrame = ECursor::COMBAT_BLOCKED;
|
||||||
|
consoleMsg = CGI->generaltexth->allTexts[24]; //Invalid Teleport Destination
|
||||||
|
break;
|
||||||
|
case PossiblePlayerBattleAction::SACRIFICE:
|
||||||
|
consoleMsg = CGI->generaltexth->allTexts[543]; //choose army to sacrifice
|
||||||
|
break;
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
if (consoleMsg.empty() && sp)
|
||||||
|
consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
realizeAction = [=]()
|
||||||
|
{
|
||||||
|
if(secondaryTarget) //select that target now
|
||||||
|
{
|
||||||
|
|
||||||
|
possibleActions.clear();
|
||||||
|
switch (sp->id.toEnum())
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (sp)
|
||||||
|
{
|
||||||
|
owner->giveCommand(EActionType::MONSTER_SPELL, myNumber, owner->stacksController->activeStackSpellToCast());
|
||||||
|
}
|
||||||
|
else //unknown random spell
|
||||||
|
{
|
||||||
|
owner->giveCommand(EActionType::MONSTER_SPELL, myNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assert(sp);
|
||||||
|
switch (sp->id.toEnum())
|
||||||
|
{
|
||||||
|
case SpellID::SACRIFICE:
|
||||||
|
spellToCast->aimToUnit(shere);//victim
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
spellToCast->aimToHex(myNumber);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
owner->curInt->cb->battleMakeAction(spellToCast.get());
|
||||||
|
endCastingSpell();
|
||||||
|
}
|
||||||
|
owner->stacksController->setSelectedStack(nullptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
if (eventType == CIntObject::MOVE)
|
||||||
|
{
|
||||||
|
if (setCursor)
|
||||||
|
CCS->curh->changeGraphic(cursorType, cursorFrame);
|
||||||
|
owner->controlPanel->console->write(consoleMsg);
|
||||||
|
}
|
||||||
|
if (eventType == CIntObject::LCLICK && realizeAction)
|
||||||
|
{
|
||||||
|
//opening creature window shouldn't affect myTurn...
|
||||||
|
if ((currentAction != PossiblePlayerBattleAction::CREATURE_INFO) && !secondaryTarget)
|
||||||
|
{
|
||||||
|
owner->myTurn = false; //tends to crash with empty calls
|
||||||
|
}
|
||||||
|
realizeAction();
|
||||||
|
if (!secondaryTarget) //do not replace teleport or sacrifice cursor
|
||||||
|
CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
|
||||||
|
owner->controlPanel->console->clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool CBattleActionsController::isCastingPossibleHere(const CStack *sactive, const CStack *shere, BattleHex myNumber)
|
||||||
|
{
|
||||||
|
creatureCasting = owner->stacksController->activeStackSpellcaster() && !spellDestSelectMode; //TODO: allow creatures to cast aimed spells
|
||||||
|
|
||||||
|
bool isCastingPossible = true;
|
||||||
|
|
||||||
|
int spellID = -1;
|
||||||
|
if (creatureCasting)
|
||||||
|
{
|
||||||
|
if (owner->stacksController->activeStackSpellToCast() != SpellID::NONE && (shere != sactive)) //can't cast on itself
|
||||||
|
spellID = owner->stacksController->activeStackSpellToCast(); //TODO: merge with SpellTocast?
|
||||||
|
}
|
||||||
|
else //hero casting
|
||||||
|
{
|
||||||
|
spellID = spellToCast->actionSubtype;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sp = nullptr;
|
||||||
|
if (spellID >= 0)
|
||||||
|
sp = CGI->spellh->objects[spellID];
|
||||||
|
|
||||||
|
if (sp)
|
||||||
|
{
|
||||||
|
const spells::Caster *caster = creatureCasting ? static_cast<const spells::Caster *>(sactive) : static_cast<const spells::Caster *>(owner->curInt->cb->battleGetMyHero());
|
||||||
|
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);
|
||||||
|
|
||||||
|
spells::BattleCast cast(owner->curInt->cb.get(), caster, mode, sp);
|
||||||
|
|
||||||
|
auto m = sp->battleMechanics(&cast);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CBattleActionsController::canStackMoveHere(const CStack * stackToMove, BattleHex myNumber)
|
||||||
|
{
|
||||||
|
std::vector<BattleHex> acc = owner->curInt->cb->battleGetAvailableHexes(stackToMove);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBattleActionsController::activateStack()
|
||||||
|
{
|
||||||
|
const CStack * s = owner->stacksController->getActiveStack();
|
||||||
|
if(s)
|
||||||
|
possibleActions = getPossibleActionsForStack(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CBattleActionsController::spellcastingModeActive()
|
||||||
|
{
|
||||||
|
return spellDestSelectMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpellID CBattleActionsController::selectedSpell()
|
||||||
|
{
|
||||||
|
if (!spellToCast)
|
||||||
|
return SpellID::NONE;
|
||||||
|
return SpellID(spellToCast->actionSubtype);
|
||||||
|
}
|
57
client/battle/CBattleActionsController.h
Normal file
57
client/battle/CBattleActionsController.h
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* CBattleActionsController.h, 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
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../../lib/battle/CBattleInfoCallback.h"
|
||||||
|
|
||||||
|
class BattleAction;
|
||||||
|
class CBattleInterface;
|
||||||
|
|
||||||
|
enum class MouseHoveredHexContext
|
||||||
|
{
|
||||||
|
UNOCCUPIED_HEX,
|
||||||
|
OCCUPIED_HEX
|
||||||
|
};
|
||||||
|
|
||||||
|
class CBattleActionsController
|
||||||
|
{
|
||||||
|
CBattleInterface * owner;
|
||||||
|
|
||||||
|
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 creatureCasting; //if true, stack currently aims to cats a spell
|
||||||
|
bool spellDestSelectMode; //if true, player is choosing destination for his spell - only for GUI / console
|
||||||
|
std::shared_ptr<BattleAction> spellToCast; //spell for which player is choosing destination
|
||||||
|
const CSpell *sp; //spell pointer for convenience
|
||||||
|
|
||||||
|
bool isCastingPossibleHere (const CStack *sactive, const CStack *shere, BattleHex myNumber);
|
||||||
|
bool canStackMoveHere (const CStack *sactive, BattleHex MyNumber); //TODO: move to BattleState / callback
|
||||||
|
|
||||||
|
std::vector<PossiblePlayerBattleAction> getPossibleActionsForStack (const CStack *stack); //called when stack gets its turn
|
||||||
|
void reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context);
|
||||||
|
|
||||||
|
public:
|
||||||
|
CBattleActionsController(CBattleInterface * owner);
|
||||||
|
|
||||||
|
void activateStack();
|
||||||
|
void endCastingSpell(); //ends casting spell (eg. when spell has been cast or canceled)
|
||||||
|
void enterCreatureCastingMode();
|
||||||
|
|
||||||
|
SpellID selectedSpell();
|
||||||
|
bool spellcastingModeActive();
|
||||||
|
void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook
|
||||||
|
void handleHex(BattleHex myNumber, int eventType);
|
||||||
|
|
||||||
|
};
|
@ -12,6 +12,7 @@
|
|||||||
#include "CBattleInterface.h"
|
#include "CBattleInterface.h"
|
||||||
#include "CBattleInterfaceClasses.h"
|
#include "CBattleInterfaceClasses.h"
|
||||||
#include "CBattleStacksController.h"
|
#include "CBattleStacksController.h"
|
||||||
|
#include "CBattleActionsController.h"
|
||||||
#include "../widgets/Buttons.h"
|
#include "../widgets/Buttons.h"
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CBitmapHandler.h"
|
#include "../CBitmapHandler.h"
|
||||||
@ -67,13 +68,16 @@ void CBattleControlPanel::tacticPhaseEnded()
|
|||||||
{
|
{
|
||||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||||
|
|
||||||
|
btactNext.reset();
|
||||||
|
btactEnd.reset();
|
||||||
|
|
||||||
menu = std::make_shared<CPicture>("CBAR.BMP", 0, 0);
|
menu = std::make_shared<CPicture>("CBAR.BMP", 0, 0);
|
||||||
menu->colorize(owner->curInt->playerID);
|
menu->colorize(owner->curInt->playerID);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CBattleControlPanel::bOptionsf()
|
void CBattleControlPanel::bOptionsf()
|
||||||
{
|
{
|
||||||
if (owner->spellDestSelectMode) //we are casting a spell
|
if (owner->actionsController->spellcastingModeActive())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
|
CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
|
||||||
@ -85,7 +89,7 @@ void CBattleControlPanel::bOptionsf()
|
|||||||
|
|
||||||
void CBattleControlPanel::bSurrenderf()
|
void CBattleControlPanel::bSurrenderf()
|
||||||
{
|
{
|
||||||
if(owner->spellDestSelectMode) //we are casting a spell
|
if (owner->actionsController->spellcastingModeActive())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int cost = owner->curInt->cb->battleGetSurrenderCost();
|
int cost = owner->curInt->cb->battleGetSurrenderCost();
|
||||||
@ -105,7 +109,7 @@ void CBattleControlPanel::bSurrenderf()
|
|||||||
|
|
||||||
void CBattleControlPanel::bFleef()
|
void CBattleControlPanel::bFleef()
|
||||||
{
|
{
|
||||||
if (owner->spellDestSelectMode) //we are casting a spell
|
if (owner->actionsController->spellcastingModeActive())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if ( owner->curInt->cb->battleCanFlee() )
|
if ( owner->curInt->cb->battleCanFlee() )
|
||||||
@ -153,7 +157,7 @@ void CBattleControlPanel::reallySurrender()
|
|||||||
|
|
||||||
void CBattleControlPanel::bAutofightf()
|
void CBattleControlPanel::bAutofightf()
|
||||||
{
|
{
|
||||||
if(owner->spellDestSelectMode) //we are casting a spell
|
if (owner->actionsController->spellcastingModeActive())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
//Stop auto-fight mode
|
//Stop auto-fight mode
|
||||||
@ -180,7 +184,7 @@ void CBattleControlPanel::bAutofightf()
|
|||||||
|
|
||||||
void CBattleControlPanel::bSpellf()
|
void CBattleControlPanel::bSpellf()
|
||||||
{
|
{
|
||||||
if (owner->spellDestSelectMode) //we are casting a spell
|
if (owner->actionsController->spellcastingModeActive())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!owner->myTurn)
|
if (!owner->myTurn)
|
||||||
@ -222,7 +226,7 @@ void CBattleControlPanel::bSpellf()
|
|||||||
|
|
||||||
void CBattleControlPanel::bWaitf()
|
void CBattleControlPanel::bWaitf()
|
||||||
{
|
{
|
||||||
if (owner->spellDestSelectMode) //we are casting a spell
|
if (owner->actionsController->spellcastingModeActive())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (owner->stacksController->getActiveStack() != nullptr)
|
if (owner->stacksController->getActiveStack() != nullptr)
|
||||||
@ -231,7 +235,7 @@ void CBattleControlPanel::bWaitf()
|
|||||||
|
|
||||||
void CBattleControlPanel::bDefencef()
|
void CBattleControlPanel::bDefencef()
|
||||||
{
|
{
|
||||||
if (owner->spellDestSelectMode) //we are casting a spell
|
if (owner->actionsController->spellcastingModeActive())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (owner->stacksController->getActiveStack() != nullptr)
|
if (owner->stacksController->getActiveStack() != nullptr)
|
||||||
@ -240,7 +244,7 @@ void CBattleControlPanel::bDefencef()
|
|||||||
|
|
||||||
void CBattleControlPanel::bConsoleUpf()
|
void CBattleControlPanel::bConsoleUpf()
|
||||||
{
|
{
|
||||||
if (owner->spellDestSelectMode) //we are casting a spell
|
if (owner->actionsController->spellcastingModeActive())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
console->scrollUp();
|
console->scrollUp();
|
||||||
@ -248,7 +252,7 @@ void CBattleControlPanel::bConsoleUpf()
|
|||||||
|
|
||||||
void CBattleControlPanel::bConsoleDownf()
|
void CBattleControlPanel::bConsoleDownf()
|
||||||
{
|
{
|
||||||
if (owner->spellDestSelectMode) //we are casting a spell
|
if (owner->actionsController->spellcastingModeActive())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
console->scrollDown();
|
console->scrollDown();
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#include "StdInc.h"
|
#include "StdInc.h"
|
||||||
#include "CBattleFieldController.h"
|
#include "CBattleFieldController.h"
|
||||||
#include "CBattleInterface.h"
|
#include "CBattleInterface.h"
|
||||||
|
#include "CBattleActionsController.h"
|
||||||
#include "CBattleInterfaceClasses.h"
|
#include "CBattleInterfaceClasses.h"
|
||||||
#include "CBattleSiegeController.h"
|
#include "CBattleSiegeController.h"
|
||||||
#include "CBattleStacksController.h"
|
#include "CBattleStacksController.h"
|
||||||
@ -220,12 +221,12 @@ void CBattleFieldController::showHighlightedHexes(SDL_Surface *to)
|
|||||||
|
|
||||||
spells::Mode mode = spells::Mode::HERO;
|
spells::Mode mode = spells::Mode::HERO;
|
||||||
|
|
||||||
if(owner->spellToCast)//hero casts spell
|
if(owner->actionsController->spellcastingModeActive())//hero casts spell
|
||||||
{
|
{
|
||||||
spell = SpellID(owner->spellToCast->actionSubtype).toSpell();
|
spell = owner->actionsController->selectedSpell().toSpell();
|
||||||
caster = owner->getActiveHero();
|
caster = owner->getActiveHero();
|
||||||
}
|
}
|
||||||
else if(owner->stacksController->activeStackSpellToCast() != SpellID::NONE && owner->creatureCasting)//stack casts spell
|
else if(owner->stacksController->activeStackSpellToCast() != SpellID::NONE)//stack casts spell
|
||||||
{
|
{
|
||||||
spell = SpellID(owner->stacksController->activeStackSpellToCast()).toSpell();
|
spell = SpellID(owner->stacksController->activeStackSpellToCast()).toSpell();
|
||||||
caster = owner->stacksController->getActiveStack();
|
caster = owner->stacksController->getActiveStack();
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include "CBattleInterface.h"
|
#include "CBattleInterface.h"
|
||||||
|
|
||||||
#include "CBattleAnimations.h"
|
#include "CBattleAnimations.h"
|
||||||
|
#include "CBattleActionsController.h"
|
||||||
#include "CBattleInterfaceClasses.h"
|
#include "CBattleInterfaceClasses.h"
|
||||||
#include "CCreatureAnimation.h"
|
#include "CCreatureAnimation.h"
|
||||||
#include "CBattleProjectileController.h"
|
#include "CBattleProjectileController.h"
|
||||||
@ -62,7 +63,6 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
|
|||||||
const SDL_Rect & myRect,
|
const SDL_Rect & myRect,
|
||||||
std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt)
|
std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt)
|
||||||
: attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0),
|
: attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0),
|
||||||
creatureCasting(false), spellDestSelectMode(false), spellToCast(nullptr), sp(nullptr),
|
|
||||||
attackerInt(att), defenderInt(defen), curInt(att),
|
attackerInt(att), defenderInt(defen), curInt(att),
|
||||||
myTurn(false), moveStarted(false), moveSoundHander(-1), bresult(nullptr), battleActionsStarted(false)
|
myTurn(false), moveStarted(false), moveSoundHander(-1), bresult(nullptr), battleActionsStarted(false)
|
||||||
{
|
{
|
||||||
@ -129,6 +129,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
|
|||||||
//preparing menu background and terrain
|
//preparing menu background and terrain
|
||||||
fieldController.reset( new CBattleFieldController(this));
|
fieldController.reset( new CBattleFieldController(this));
|
||||||
stacksController.reset( new CBattleStacksController(this));
|
stacksController.reset( new CBattleStacksController(this));
|
||||||
|
actionsController.reset( new CBattleActionsController(this));
|
||||||
|
|
||||||
//loading hero animations
|
//loading hero animations
|
||||||
if(hero1) // attacking hero
|
if(hero1) // attacking hero
|
||||||
@ -197,8 +198,6 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
|
|||||||
|
|
||||||
CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed);
|
CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed);
|
||||||
|
|
||||||
currentAction = PossiblePlayerBattleAction::INVALID;
|
|
||||||
selectedAction = PossiblePlayerBattleAction::INVALID;
|
|
||||||
addUsedEvents(RCLICK | MOVE | KEYBOARD);
|
addUsedEvents(RCLICK | MOVE | KEYBOARD);
|
||||||
controlPanel->blockUI(true);
|
controlPanel->blockUI(true);
|
||||||
}
|
}
|
||||||
@ -300,28 +299,28 @@ void CBattleInterface::keyPressed(const SDL_KeyboardEvent & key)
|
|||||||
}
|
}
|
||||||
else if(key.keysym.sym == SDLK_f && key.state == SDL_PRESSED)
|
else if(key.keysym.sym == SDLK_f && key.state == SDL_PRESSED)
|
||||||
{
|
{
|
||||||
enterCreatureCastingMode();
|
actionsController->enterCreatureCastingMode();
|
||||||
}
|
}
|
||||||
else if(key.keysym.sym == SDLK_ESCAPE)
|
else if(key.keysym.sym == SDLK_ESCAPE)
|
||||||
{
|
{
|
||||||
if(!battleActionsStarted)
|
if(!battleActionsStarted)
|
||||||
CCS->soundh->stopSound(battleIntroSoundChannel);
|
CCS->soundh->stopSound(battleIntroSoundChannel);
|
||||||
else
|
else
|
||||||
endCastingSpell();
|
actionsController->endCastingSpell();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void CBattleInterface::mouseMoved(const SDL_MouseMotionEvent &sEvent)
|
void CBattleInterface::mouseMoved(const SDL_MouseMotionEvent &sEvent)
|
||||||
{
|
{
|
||||||
BattleHex selectedHex = fieldController->getHoveredHex();
|
BattleHex selectedHex = fieldController->getHoveredHex();
|
||||||
|
|
||||||
handleHex(selectedHex, MOVE);
|
actionsController->handleHex(selectedHex, MOVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CBattleInterface::clickRight(tribool down, bool previousState)
|
void CBattleInterface::clickRight(tribool down, bool previousState)
|
||||||
{
|
{
|
||||||
if (!down)
|
if (!down)
|
||||||
{
|
{
|
||||||
endCastingSpell();
|
actionsController->endCastingSpell();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -453,7 +452,7 @@ const CGHeroInstance * CBattleInterface::getActiveHero()
|
|||||||
|
|
||||||
void CBattleInterface::hexLclicked(int whichOne)
|
void CBattleInterface::hexLclicked(int whichOne)
|
||||||
{
|
{
|
||||||
handleHex(whichOne, LCLICK);
|
actionsController->handleHex(whichOne, LCLICK);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CBattleInterface::stackIsCatapulting(const CatapultAttack & ca)
|
void CBattleInterface::stackIsCatapulting(const CatapultAttack & ca)
|
||||||
@ -618,36 +617,6 @@ void CBattleInterface::setHeroAnimation(ui8 side, int phase)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CBattleInterface::castThisSpell(SpellID spellID)
|
|
||||||
{
|
|
||||||
spellToCast = std::make_shared<BattleAction>();
|
|
||||||
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;
|
|
||||||
spellDestSelectMode = true;
|
|
||||||
creatureCasting = false;
|
|
||||||
|
|
||||||
//choosing possible targets
|
|
||||||
const CGHeroInstance *castingHero = (attackingHeroInstance->tempOwner == curInt->playerID) ? attackingHeroInstance : defendingHeroInstance;
|
|
||||||
assert(castingHero); // code below assumes non-null hero
|
|
||||||
sp = spellID.toSpell();
|
|
||||||
PossiblePlayerBattleAction spellSelMode = curInt->cb->getCasterAction(sp, castingHero, spells::Mode::HERO);
|
|
||||||
|
|
||||||
if (spellSelMode == PossiblePlayerBattleAction::NO_LOCATION) //user does not have to select location
|
|
||||||
{
|
|
||||||
spellToCast->aimToHex(BattleHex::INVALID);
|
|
||||||
curInt->cb->battleMakeAction(spellToCast.get());
|
|
||||||
endCastingSpell();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
possibleActions.clear();
|
|
||||||
possibleActions.push_back (spellSelMode); //only this one action can be performed at the moment
|
|
||||||
GH.fakeMouseMove();//update cursor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleInterface::displayBattleLog(const std::vector<MetaString> & battleLog)
|
void CBattleInterface::displayBattleLog(const std::vector<MetaString> & battleLog)
|
||||||
{
|
{
|
||||||
for(const auto & line : battleLog)
|
for(const auto & line : battleLog)
|
||||||
@ -798,158 +767,10 @@ void CBattleInterface::activateStack()
|
|||||||
myTurn = true;
|
myTurn = true;
|
||||||
queue->update();
|
queue->update();
|
||||||
fieldController->redrawBackgroundWithHexes();
|
fieldController->redrawBackgroundWithHexes();
|
||||||
possibleActions = getPossibleActionsForStack(s);
|
actionsController->activateStack();
|
||||||
GH.fakeMouseMove();
|
GH.fakeMouseMove();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CBattleInterface::endCastingSpell()
|
|
||||||
{
|
|
||||||
if(spellDestSelectMode)
|
|
||||||
{
|
|
||||||
spellToCast.reset();
|
|
||||||
|
|
||||||
sp = nullptr;
|
|
||||||
spellDestSelectMode = false;
|
|
||||||
CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
|
|
||||||
|
|
||||||
if(stacksController->getActiveStack())
|
|
||||||
{
|
|
||||||
possibleActions = getPossibleActionsForStack(stacksController->getActiveStack()); //restore actions after they were cleared
|
|
||||||
myTurn = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(stacksController->getActiveStack())
|
|
||||||
{
|
|
||||||
possibleActions = getPossibleActionsForStack(stacksController->getActiveStack());
|
|
||||||
GH.fakeMouseMove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleInterface::enterCreatureCastingMode()
|
|
||||||
{
|
|
||||||
//silently check for possible errors
|
|
||||||
if (!myTurn)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (tacticsMode)
|
|
||||||
return;
|
|
||||||
|
|
||||||
//hero is casting a spell
|
|
||||||
if (spellDestSelectMode)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!stacksController->getActiveStack())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!stacksController->activeStackSpellcaster())
|
|
||||||
return;
|
|
||||||
|
|
||||||
//random spellcaster
|
|
||||||
if (stacksController->activeStackSpellToCast() == SpellID::NONE)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (vstd::contains(possibleActions, PossiblePlayerBattleAction::NO_LOCATION))
|
|
||||||
{
|
|
||||||
const spells::Caster * caster = stacksController->getActiveStack();
|
|
||||||
const CSpell * spell = stacksController->activeStackSpellToCast().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;
|
|
||||||
|
|
||||||
const bool isCastingPossible = m->canBeCastAt(target, ignored);
|
|
||||||
|
|
||||||
if (isCastingPossible)
|
|
||||||
{
|
|
||||||
myTurn = false;
|
|
||||||
giveCommand(EActionType::MONSTER_SPELL, BattleHex::INVALID, stacksController->activeStackSpellToCast());
|
|
||||||
stacksController->setSelectedStack(nullptr);
|
|
||||||
|
|
||||||
CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
possibleActions = getPossibleActionsForStack(stacksController->getActiveStack());
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<PossiblePlayerBattleAction> CBattleInterface::getPossibleActionsForStack(const CStack *stack)
|
|
||||||
{
|
|
||||||
BattleClientInterfaceData data; //hard to get rid of these things so for now they're required data to pass
|
|
||||||
data.creatureSpellToCast = stacksController->activeStackSpellToCast();
|
|
||||||
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
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleInterface::endAction(const BattleAction* action)
|
void CBattleInterface::endAction(const BattleAction* action)
|
||||||
{
|
{
|
||||||
const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
|
const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
|
||||||
@ -1114,531 +935,6 @@ void CBattleInterface::tacticNextStack(const CStack * current)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CBattleInterface::canStackMoveHere(const CStack * stackToMove, BattleHex myNumber)
|
|
||||||
{
|
|
||||||
std::vector<BattleHex> acc = curInt->cb->battleGetAvailableHexes(stackToMove);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
|
||||||
{
|
|
||||||
if (!myTurn || !battleActionsStarted) //we are not permit to do anything
|
|
||||||
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.
|
|
||||||
|
|
||||||
//used when hovering -> tooltip message and cursor to be set
|
|
||||||
std::string consoleMsg;
|
|
||||||
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.
|
|
||||||
const CStack * shere = curInt->cb->battleGetStackByPos(myNumber, true);
|
|
||||||
if(!shere)
|
|
||||||
shere = curInt->cb->battleGetStackByPos(myNumber, false);
|
|
||||||
|
|
||||||
if(!stacksController->getActiveStack())
|
|
||||||
return;
|
|
||||||
|
|
||||||
bool ourStack = false;
|
|
||||||
if (shere)
|
|
||||||
ourStack = shere->owner == curInt->playerID;
|
|
||||||
|
|
||||||
//stack may have changed, update selection border
|
|
||||||
stacksController->setHoveredStack(shere);
|
|
||||||
|
|
||||||
localActions.clear();
|
|
||||||
illegalActions.clear();
|
|
||||||
|
|
||||||
reorderPossibleActionsPriority(stacksController->getActiveStack(), shere ? MouseHoveredHexContext::OCCUPIED_HEX : MouseHoveredHexContext::UNOCCUPIED_HEX);
|
|
||||||
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
|
|
||||||
{
|
|
||||||
if(canStackMoveHere(stacksController->getActiveStack(), myNumber))
|
|
||||||
legalAction = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PossiblePlayerBattleAction::ATTACK:
|
|
||||||
case PossiblePlayerBattleAction::WALK_AND_ATTACK:
|
|
||||||
case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
|
|
||||||
{
|
|
||||||
if(curInt->cb->battleCanAttack(stacksController->getActiveStack(), shere, myNumber))
|
|
||||||
{
|
|
||||||
if (fieldController->isTileAttackable(myNumber)) // move isTileAttackable to be part of battleCanAttack?
|
|
||||||
{
|
|
||||||
fieldController->setBattleCursor(myNumber); // temporary - needed for following function :(
|
|
||||||
BattleHex attackFromHex = fieldController->fromWhichHexAttack(myNumber);
|
|
||||||
|
|
||||||
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:
|
|
||||||
if(curInt->cb->battleCanShoot(stacksController->getActiveStack(), myNumber))
|
|
||||||
legalAction = true;
|
|
||||||
break;
|
|
||||||
case PossiblePlayerBattleAction::ANY_LOCATION:
|
|
||||||
if (myNumber > -1) //TODO: this should be checked for all actions
|
|
||||||
{
|
|
||||||
if(isCastingPossibleHere(stacksController->getActiveStack(), shere, myNumber))
|
|
||||||
legalAction = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
|
|
||||||
if(shere && isCastingPossibleHere(stacksController->getActiveStack(), shere, myNumber))
|
|
||||||
legalAction = true;
|
|
||||||
break;
|
|
||||||
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
|
|
||||||
{
|
|
||||||
if(shere && ourStack && shere != stacksController->getActiveStack() && shere->alive()) //only positive spells for other allied creatures
|
|
||||||
{
|
|
||||||
int spellID = curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), shere, CBattleInfoCallback::RANDOM_GENIE);
|
|
||||||
if(spellID > -1)
|
|
||||||
{
|
|
||||||
legalAction = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PossiblePlayerBattleAction::OBSTACLE:
|
|
||||||
if(isCastingPossibleHere(stacksController->getActiveStack(), shere, myNumber))
|
|
||||||
legalAction = true;
|
|
||||||
break;
|
|
||||||
case PossiblePlayerBattleAction::TELEPORT:
|
|
||||||
{
|
|
||||||
//todo: move to mechanics
|
|
||||||
ui8 skill = 0;
|
|
||||||
if (creatureCasting)
|
|
||||||
skill = stacksController->getActiveStack()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
|
|
||||||
else
|
|
||||||
skill = getActiveHero()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
|
|
||||||
//TODO: explicitely save power, skill
|
|
||||||
if (curInt->cb->battleCanTeleportTo(stacksController->getSelectedStack(), myNumber, skill))
|
|
||||||
legalAction = true;
|
|
||||||
else
|
|
||||||
notLegal = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PossiblePlayerBattleAction::SACRIFICE: //choose our living stack to sacrifice
|
|
||||||
if (shere && shere != stacksController->getSelectedStack() && ourStack && shere->alive())
|
|
||||||
legalAction = true;
|
|
||||||
else
|
|
||||||
notLegal = true;
|
|
||||||
break;
|
|
||||||
case PossiblePlayerBattleAction::FREE_LOCATION:
|
|
||||||
legalAction = true;
|
|
||||||
if(!isCastingPossibleHere(stacksController->getActiveStack(), shere, myNumber))
|
|
||||||
{
|
|
||||||
legalAction = false;
|
|
||||||
notLegal = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PossiblePlayerBattleAction::CATAPULT:
|
|
||||||
if (siegeController && siegeController->isCatapultAttackable(myNumber))
|
|
||||||
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:
|
|
||||||
consoleMsg = (boost::format(CGI->generaltexth->allTexts[481]) % shere->getName()).str(); //Select %s
|
|
||||||
realizeAction = [=](){ stackActivated(shere); };
|
|
||||||
break;
|
|
||||||
case PossiblePlayerBattleAction::MOVE_TACTICS:
|
|
||||||
case PossiblePlayerBattleAction::MOVE_STACK:
|
|
||||||
if (stacksController->getActiveStack()->hasBonusOfType(Bonus::FLYING))
|
|
||||||
{
|
|
||||||
cursorFrame = ECursor::COMBAT_FLY;
|
|
||||||
consoleMsg = (boost::format(CGI->generaltexth->allTexts[295]) % stacksController->getActiveStack()->getName()).str(); //Fly %s here
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cursorFrame = ECursor::COMBAT_MOVE;
|
|
||||||
consoleMsg = (boost::format(CGI->generaltexth->allTexts[294]) % stacksController->getActiveStack()->getName()).str(); //Move %s here
|
|
||||||
}
|
|
||||||
|
|
||||||
realizeAction = [=]()
|
|
||||||
{
|
|
||||||
if(stacksController->getActiveStack()->doubleWide())
|
|
||||||
{
|
|
||||||
std::vector<BattleHex> acc = curInt->cb->battleGetAvailableHexes(stacksController->getActiveStack());
|
|
||||||
BattleHex shiftedDest = myNumber.cloneInDirection(stacksController->getActiveStack()->destShiftDir(), false);
|
|
||||||
if(vstd::contains(acc, myNumber))
|
|
||||||
giveCommand(EActionType::WALK, myNumber);
|
|
||||||
else if(vstd::contains(acc, shiftedDest))
|
|
||||||
giveCommand(EActionType::WALK, shiftedDest);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
giveCommand(EActionType::WALK, myNumber);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case PossiblePlayerBattleAction::ATTACK:
|
|
||||||
case PossiblePlayerBattleAction::WALK_AND_ATTACK:
|
|
||||||
case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
|
|
||||||
{
|
|
||||||
fieldController->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 == PossiblePlayerBattleAction::ATTACK_AND_RETURN;
|
|
||||||
|
|
||||||
realizeAction = [=]()
|
|
||||||
{
|
|
||||||
BattleHex attackFromHex = fieldController->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(stacksController->getActiveStack(), myNumber, attackFromHex, returnAfterAttack));
|
|
||||||
sendCommand(command, stacksController->getActiveStack());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
TDmgRange damage = curInt->cb->battleEstimateDamage(stacksController->getActiveStack(), shere);
|
|
||||||
std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
|
|
||||||
consoleMsg = (boost::format(CGI->generaltexth->allTexts[36]) % shere->getName() % estDmgText).str(); //Attack %s (%s damage)
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PossiblePlayerBattleAction::SHOOT:
|
|
||||||
{
|
|
||||||
if (curInt->cb->battleHasShootingPenalty(stacksController->getActiveStack(), myNumber))
|
|
||||||
cursorFrame = ECursor::COMBAT_SHOOT_PENALTY;
|
|
||||||
else
|
|
||||||
cursorFrame = ECursor::COMBAT_SHOOT;
|
|
||||||
|
|
||||||
realizeAction = [=](){giveCommand(EActionType::SHOOT, myNumber);};
|
|
||||||
TDmgRange damage = curInt->cb->battleEstimateDamage(stacksController->getActiveStack(), shere);
|
|
||||||
std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
|
|
||||||
//printing - Shoot %s (%d shots left, %s damage)
|
|
||||||
consoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % stacksController->getActiveStack()->shots.available() % estDmgText).str();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
|
|
||||||
sp = CGI->spellh->objects[creatureCasting ? stacksController->activeStackSpellToCast() : 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)
|
|
||||||
{
|
|
||||||
case SpellID::SACRIFICE:
|
|
||||||
case SpellID::TELEPORT:
|
|
||||||
stacksController->setSelectedStack(shere); //remember first target
|
|
||||||
secondaryTarget = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
isCastingPossible = true;
|
|
||||||
break;
|
|
||||||
case PossiblePlayerBattleAction::ANY_LOCATION:
|
|
||||||
sp = CGI->spellh->objects[creatureCasting ? stacksController->activeStackSpellToCast() : 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 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 PossiblePlayerBattleAction::TELEPORT:
|
|
||||||
consoleMsg = CGI->generaltexth->allTexts[25]; //Teleport Here
|
|
||||||
cursorFrame = ECursor::COMBAT_TELEPORT;
|
|
||||||
isCastingPossible = true;
|
|
||||||
break;
|
|
||||||
case PossiblePlayerBattleAction::OBSTACLE:
|
|
||||||
consoleMsg = CGI->generaltexth->allTexts[550];
|
|
||||||
//TODO: remove obstacle cursor
|
|
||||||
isCastingPossible = true;
|
|
||||||
break;
|
|
||||||
case PossiblePlayerBattleAction::SACRIFICE:
|
|
||||||
consoleMsg = (boost::format(CGI->generaltexth->allTexts[549]) % shere->getName()).str(); //sacrifice the %s
|
|
||||||
cursorFrame = ECursor::COMBAT_SACRIFICE;
|
|
||||||
isCastingPossible = true;
|
|
||||||
break;
|
|
||||||
case PossiblePlayerBattleAction::FREE_LOCATION:
|
|
||||||
consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s
|
|
||||||
isCastingPossible = true;
|
|
||||||
break;
|
|
||||||
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 PossiblePlayerBattleAction::RISE_DEMONS:
|
|
||||||
cursorType = ECursor::SPELLBOOK;
|
|
||||||
realizeAction = [=]()
|
|
||||||
{
|
|
||||||
giveCommand(EActionType::DAEMON_SUMMONING, myNumber);
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case PossiblePlayerBattleAction::CATAPULT:
|
|
||||||
cursorFrame = ECursor::COMBAT_SHOOT_CATAPULT;
|
|
||||||
realizeAction = [=](){ giveCommand(EActionType::CATAPULT, myNumber); };
|
|
||||||
break;
|
|
||||||
case PossiblePlayerBattleAction::CREATURE_INFO:
|
|
||||||
{
|
|
||||||
cursorFrame = ECursor::COMBAT_QUERY;
|
|
||||||
consoleMsg = (boost::format(CGI->generaltexth->allTexts[297]) % shere->getName()).str();
|
|
||||||
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;
|
|
||||||
consoleMsg = CGI->generaltexth->allTexts[23];
|
|
||||||
break;
|
|
||||||
case PossiblePlayerBattleAction::TELEPORT:
|
|
||||||
cursorFrame = ECursor::COMBAT_BLOCKED;
|
|
||||||
consoleMsg = CGI->generaltexth->allTexts[24]; //Invalid Teleport Destination
|
|
||||||
break;
|
|
||||||
case PossiblePlayerBattleAction::SACRIFICE:
|
|
||||||
consoleMsg = CGI->generaltexth->allTexts[543]; //choose army to sacrifice
|
|
||||||
break;
|
|
||||||
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;
|
|
||||||
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;
|
|
||||||
if (consoleMsg.empty() && sp)
|
|
||||||
consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
realizeAction = [=]()
|
|
||||||
{
|
|
||||||
if(secondaryTarget) //select that target now
|
|
||||||
{
|
|
||||||
|
|
||||||
possibleActions.clear();
|
|
||||||
switch (sp->id.toEnum())
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
if (sp)
|
|
||||||
{
|
|
||||||
giveCommand(EActionType::MONSTER_SPELL, myNumber, stacksController->activeStackSpellToCast());
|
|
||||||
}
|
|
||||||
else //unknown random spell
|
|
||||||
{
|
|
||||||
giveCommand(EActionType::MONSTER_SPELL, myNumber);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
assert(sp);
|
|
||||||
switch (sp->id.toEnum())
|
|
||||||
{
|
|
||||||
case SpellID::SACRIFICE:
|
|
||||||
spellToCast->aimToUnit(shere);//victim
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
spellToCast->aimToHex(myNumber);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
curInt->cb->battleMakeAction(spellToCast.get());
|
|
||||||
endCastingSpell();
|
|
||||||
}
|
|
||||||
stacksController->setSelectedStack(nullptr);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
if (eventType == MOVE)
|
|
||||||
{
|
|
||||||
if (setCursor)
|
|
||||||
CCS->curh->changeGraphic(cursorType, cursorFrame);
|
|
||||||
controlPanel->console->write(consoleMsg);
|
|
||||||
}
|
|
||||||
if (eventType == LCLICK && realizeAction)
|
|
||||||
{
|
|
||||||
//opening creature window shouldn't affect myTurn...
|
|
||||||
if ((currentAction != PossiblePlayerBattleAction::CREATURE_INFO) && !secondaryTarget)
|
|
||||||
{
|
|
||||||
myTurn = false; //tends to crash with empty calls
|
|
||||||
}
|
|
||||||
realizeAction();
|
|
||||||
if (!secondaryTarget) //do not replace teleport or sacrifice cursor
|
|
||||||
CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
|
|
||||||
controlPanel->console->clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CBattleInterface::isCastingPossibleHere(const CStack *sactive, const CStack *shere, BattleHex myNumber)
|
|
||||||
{
|
|
||||||
creatureCasting = stacksController->activeStackSpellcaster() && !spellDestSelectMode; //TODO: allow creatures to cast aimed spells
|
|
||||||
|
|
||||||
bool isCastingPossible = true;
|
|
||||||
|
|
||||||
int spellID = -1;
|
|
||||||
if (creatureCasting)
|
|
||||||
{
|
|
||||||
if (stacksController->activeStackSpellToCast() != SpellID::NONE && (shere != sactive)) //can't cast on itself
|
|
||||||
spellID = stacksController->activeStackSpellToCast(); //TODO: merge with SpellTocast?
|
|
||||||
}
|
|
||||||
else //hero casting
|
|
||||||
{
|
|
||||||
spellID = spellToCast->actionSubtype;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sp = nullptr;
|
|
||||||
if (spellID >= 0)
|
|
||||||
sp = CGI->spellh->objects[spellID];
|
|
||||||
|
|
||||||
if (sp)
|
|
||||||
{
|
|
||||||
const spells::Caster *caster = creatureCasting ? static_cast<const spells::Caster *>(sactive) : static_cast<const spells::Caster *>(curInt->cb->battleGetMyHero());
|
|
||||||
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);
|
|
||||||
|
|
||||||
spells::BattleCast cast(curInt->cb.get(), caster, mode, sp);
|
|
||||||
|
|
||||||
auto m = sp->battleMechanics(&cast);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleInterface::obstaclePlaced(const CObstacleInstance & oi)
|
void CBattleInterface::obstaclePlaced(const CObstacleInstance & oi)
|
||||||
{
|
{
|
||||||
obstacleController->obstaclePlaced(oi);
|
obstacleController->obstaclePlaced(oi);
|
||||||
@ -1784,8 +1080,6 @@ void CBattleInterface::showBattlefieldObjects(SDL_Surface *to)
|
|||||||
showHexEntry(objects.afterAll);
|
showHexEntry(objects.afterAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void CBattleInterface::showBattleEffects(SDL_Surface *to, const std::vector<const BattleEffect *> &battleEffects)
|
void CBattleInterface::showBattleEffects(SDL_Surface *to, const std::vector<const BattleEffect *> &battleEffects)
|
||||||
{
|
{
|
||||||
for (auto & elem : battleEffects)
|
for (auto & elem : battleEffects)
|
||||||
@ -1852,3 +1146,8 @@ BattleObjectsByHex CBattleInterface::sortObjectsByHex()
|
|||||||
|
|
||||||
return sorted;
|
return sorted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CBattleInterface::castThisSpell(SpellID spellID)
|
||||||
|
{
|
||||||
|
actionsController->castThisSpell(spellID);
|
||||||
|
}
|
||||||
|
@ -64,6 +64,7 @@ class CBattleObstacleController;
|
|||||||
class CBattleFieldController;
|
class CBattleFieldController;
|
||||||
class CBattleControlPanel;
|
class CBattleControlPanel;
|
||||||
class CBattleStacksController;
|
class CBattleStacksController;
|
||||||
|
class CBattleActionsController;
|
||||||
|
|
||||||
/// Small struct which contains information about the id of the attacked stack, the damage dealt,...
|
/// Small struct which contains information about the id of the attacked stack, the damage dealt,...
|
||||||
struct StackAttackedInfo
|
struct StackAttackedInfo
|
||||||
@ -109,12 +110,6 @@ struct BattleObjectsByHex
|
|||||||
std::array<HexData, GameConstants::BFIELD_SIZE> hex;
|
std::array<HexData, GameConstants::BFIELD_SIZE> hex;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class MouseHoveredHexContext
|
|
||||||
{
|
|
||||||
UNOCCUPIED_HEX,
|
|
||||||
OCCUPIED_HEX
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Big class which handles the overall battle interface actions and it is also responsible for
|
/// Big class which handles the overall battle interface actions and it is also responsible for
|
||||||
/// drawing everything correctly.
|
/// drawing everything correctly.
|
||||||
class CBattleInterface : public WindowBase
|
class CBattleInterface : public WindowBase
|
||||||
@ -135,16 +130,6 @@ private:
|
|||||||
ui8 animCount;
|
ui8 animCount;
|
||||||
|
|
||||||
bool tacticsMode;
|
bool tacticsMode;
|
||||||
bool creatureCasting; //if true, stack currently aims to cats a spell
|
|
||||||
bool spellDestSelectMode; //if true, player is choosing destination for his spell - only for GUI / console
|
|
||||||
std::shared_ptr<BattleAction> spellToCast; //spell for which player is choosing destination
|
|
||||||
const CSpell *sp; //spell pointer for convenience
|
|
||||||
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
|
bool battleActionsStarted; //used for delaying battle actions until intro sound stops
|
||||||
int battleIntroSoundChannel; //required as variable for disabling it via ESC key
|
int battleIntroSoundChannel; //required as variable for disabling it via ESC key
|
||||||
|
|
||||||
@ -154,12 +139,6 @@ private:
|
|||||||
void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
|
void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
|
||||||
void requestAutofightingAIToTakeAction();
|
void requestAutofightingAIToTakeAction();
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
void giveCommand(EActionType action, BattleHex tile = BattleHex(), si32 additional = -1);
|
void giveCommand(EActionType action, BattleHex tile = BattleHex(), si32 additional = -1);
|
||||||
void sendCommand(BattleAction *& command, const CStack * actor = nullptr);
|
void sendCommand(BattleAction *& command, const CStack * actor = nullptr);
|
||||||
@ -181,12 +160,12 @@ public:
|
|||||||
std::unique_ptr<CBattleObstacleController> obstacleController;
|
std::unique_ptr<CBattleObstacleController> obstacleController;
|
||||||
std::unique_ptr<CBattleFieldController> fieldController;
|
std::unique_ptr<CBattleFieldController> fieldController;
|
||||||
std::unique_ptr<CBattleStacksController> stacksController;
|
std::unique_ptr<CBattleStacksController> stacksController;
|
||||||
|
std::unique_ptr<CBattleActionsController> actionsController;
|
||||||
|
|
||||||
static CondSh<bool> animsAreDisplayed; //for waiting with the end of battle for end of anims
|
static CondSh<bool> animsAreDisplayed; //for waiting with the end of battle for end of anims
|
||||||
static CondSh<BattleAction *> givenCommand; //data != nullptr if we have i.e. moved current unit
|
static CondSh<BattleAction *> givenCommand; //data != nullptr if we have i.e. moved current unit
|
||||||
|
|
||||||
bool myTurn; //if true, interface is active (commands can be ordered)
|
bool myTurn; //if true, interface is active (commands can be ordered)
|
||||||
|
|
||||||
bool moveStarted; //if true, the creature that is already moving is going to make its first step
|
bool moveStarted; //if true, the creature that is already moving is going to make its first step
|
||||||
int moveSoundHander; // sound handler used when moving a unit
|
int moveSoundHander; // sound handler used when moving a unit
|
||||||
|
|
||||||
@ -251,10 +230,6 @@ public:
|
|||||||
void hideQueue();
|
void hideQueue();
|
||||||
void showQueue();
|
void showQueue();
|
||||||
|
|
||||||
void handleHex(BattleHex myNumber, int eventType);
|
|
||||||
bool isCastingPossibleHere (const CStack *sactive, const CStack *shere, BattleHex myNumber);
|
|
||||||
bool canStackMoveHere (const CStack *sactive, BattleHex MyNumber); //TODO: move to BattleState / callback
|
|
||||||
|
|
||||||
void obstaclePlaced(const CObstacleInstance & oi);
|
void obstaclePlaced(const CObstacleInstance & oi);
|
||||||
|
|
||||||
void gateStateChanged(const EGateState state);
|
void gateStateChanged(const EGateState state);
|
||||||
@ -286,4 +261,5 @@ public:
|
|||||||
friend class CBattleFieldController;
|
friend class CBattleFieldController;
|
||||||
friend class CBattleControlPanel;
|
friend class CBattleControlPanel;
|
||||||
friend class CBattleStacksController;
|
friend class CBattleStacksController;
|
||||||
|
friend class CBattleActionsController;
|
||||||
};
|
};
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include "CBattleInterfaceClasses.h"
|
#include "CBattleInterfaceClasses.h"
|
||||||
|
|
||||||
#include "CBattleInterface.h"
|
#include "CBattleInterface.h"
|
||||||
|
#include "CBattleActionsController.h"
|
||||||
#include "CBattleSiegeController.h"
|
#include "CBattleSiegeController.h"
|
||||||
#include "CBattleFieldController.h"
|
#include "CBattleFieldController.h"
|
||||||
#include "CBattleStacksController.h"
|
#include "CBattleStacksController.h"
|
||||||
@ -191,7 +192,7 @@ void CBattleHero::hover(bool on)
|
|||||||
|
|
||||||
void CBattleHero::clickLeft(tribool down, bool previousState)
|
void CBattleHero::clickLeft(tribool down, bool previousState)
|
||||||
{
|
{
|
||||||
if(myOwner->spellDestSelectMode) //we are casting a spell
|
if(myOwner->actionsController->spellcastingModeActive()) //we are casting a spell
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(boost::logic::indeterminate(down))
|
if(boost::logic::indeterminate(down))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user