123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791 |
- /*
- * BattleActionsController.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 "BattleActionsController.h"
- #include "BattleWindow.h"
- #include "BattleStacksController.h"
- #include "BattleInterface.h"
- #include "BattleFieldController.h"
- #include "BattleSiegeController.h"
- #include "BattleInterfaceClasses.h"
- #include "../CGameInfo.h"
- #include "../CPlayerInterface.h"
- #include "../gui/CursorHandler.h"
- #include "../gui/CGuiHandler.h"
- #include "../gui/CIntObject.h"
- #include "../windows/CCreatureWindow.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();
- }
- BattleActionsController::BattleActionsController(BattleInterface & owner):
- owner(owner),
- creatureCasting(false),
- spellDestSelectMode(false),
- spellToCast(nullptr),
- currentSpell(nullptr)
- {
- currentAction = PossiblePlayerBattleAction::INVALID;
- selectedAction = PossiblePlayerBattleAction::INVALID;
- }
- void BattleActionsController::endCastingSpell()
- {
- if(spellDestSelectMode)
- {
- spellToCast.reset();
- currentSpell = nullptr;
- spellDestSelectMode = false;
- CCS->curh->set(Cursor::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 BattleActionsController::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->set(Cursor::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> BattleActionsController::getPossibleActionsForStack(const CStack *stack) const
- {
- 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 BattleActionsController::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::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 BattleActionsController::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
- currentSpell = spellID.toSpell();
- PossiblePlayerBattleAction spellSelMode = owner.curInt->cb->getCasterAction(currentSpell, 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 BattleActionsController::handleHex(BattleHex myNumber, int eventType)
- {
- if (!owner.myTurn) //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.
- std::string newConsoleMsg;
- //used when hovering -> tooltip message and cursor to be set
- bool setCursor = true; //if we want to suppress setting cursor
- bool spellcastingCursor = false;
- auto cursorFrame = Cursor::Combat::POINTER;
- //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;
- 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?
- {
- 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->isAttackableByCatapult(myNumber))
- legalAction = true;
- break;
- case PossiblePlayerBattleAction::HEAL:
- if (shere && ourStack && shere->canBeHealed())
- 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:
- newConsoleMsg = (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 = Cursor::Combat::FLY;
- newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[295]) % owner.stacksController->getActiveStack()->getName()).str(); //Fly %s here
- }
- else
- {
- cursorFrame = Cursor::Combat::MOVE;
- newConsoleMsg = (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
- 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
- newConsoleMsg = (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 = Cursor::Combat::SHOOT_PENALTY;
- else
- cursorFrame = Cursor::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)
- newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % owner.stacksController->getActiveStack()->shots.available() % estDmgText).str();
- }
- break;
- case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
- currentSpell = CGI->spellh->objects[creatureCasting ? owner.stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
- newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[27]) % currentSpell->name % shere->getName()); //Cast %s on %s
- switch (currentSpell->id)
- {
- case SpellID::SACRIFICE:
- case SpellID::TELEPORT:
- owner.stacksController->setSelectedStack(shere); //remember first target
- secondaryTarget = true;
- break;
- }
- isCastingPossible = true;
- break;
- case PossiblePlayerBattleAction::ANY_LOCATION:
- currentSpell = CGI->spellh->objects[creatureCasting ? owner.stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
- newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->name); //Cast %s
- isCastingPossible = true;
- break;
- case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: //we assume that teleport / sacrifice will never be available as random spell
- currentSpell = nullptr;
- newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[301]) % shere->getName()); //Cast a spell on %
- creatureCasting = true;
- isCastingPossible = true;
- break;
- case PossiblePlayerBattleAction::TELEPORT:
- newConsoleMsg = CGI->generaltexth->allTexts[25]; //Teleport Here
- cursorFrame = Cursor::Combat::TELEPORT;
- isCastingPossible = true;
- break;
- case PossiblePlayerBattleAction::OBSTACLE:
- newConsoleMsg = CGI->generaltexth->allTexts[550];
- //TODO: remove obstacle cursor
- isCastingPossible = true;
- break;
- case PossiblePlayerBattleAction::SACRIFICE:
- newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[549]) % shere->getName()).str(); //sacrifice the %s
- cursorFrame = Cursor::Combat::SACRIFICE;
- isCastingPossible = true;
- break;
- case PossiblePlayerBattleAction::FREE_LOCATION:
- newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->name); //Cast %s
- isCastingPossible = true;
- break;
- case PossiblePlayerBattleAction::HEAL:
- cursorFrame = Cursor::Combat::HEAL;
- newConsoleMsg = (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::CATAPULT:
- cursorFrame = Cursor::Combat::SHOOT_CATAPULT;
- realizeAction = [=](){ owner.giveCommand(EActionType::CATAPULT, myNumber); };
- break;
- case PossiblePlayerBattleAction::CREATURE_INFO:
- {
- cursorFrame = Cursor::Combat::QUERY;
- newConsoleMsg = (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 = Cursor::Combat::BLOCKED;
- newConsoleMsg = CGI->generaltexth->allTexts[23];
- break;
- case PossiblePlayerBattleAction::TELEPORT:
- cursorFrame = Cursor::Combat::BLOCKED;
- newConsoleMsg = CGI->generaltexth->allTexts[24]; //Invalid Teleport Destination
- break;
- case PossiblePlayerBattleAction::SACRIFICE:
- newConsoleMsg = CGI->generaltexth->allTexts[543]; //choose army to sacrifice
- break;
- case PossiblePlayerBattleAction::FREE_LOCATION:
- cursorFrame = Cursor::Combat::BLOCKED;
- newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[181]) % currentSpell->name); //No room to place %s here
- break;
- default:
- if (myNumber == -1)
- CCS->curh->set(Cursor::Combat::POINTER);
- else
- cursorFrame = Cursor::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:
- spellcastingCursor = true;
- if (newConsoleMsg.empty() && currentSpell)
- newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->name); //Cast %s
- break;
- }
- realizeAction = [=]()
- {
- if(secondaryTarget) //select that target now
- {
- possibleActions.clear();
- switch (currentSpell->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 (currentSpell)
- {
- owner.giveCommand(EActionType::MONSTER_SPELL, myNumber, owner.stacksController->activeStackSpellToCast());
- }
- else //unknown random spell
- {
- owner.giveCommand(EActionType::MONSTER_SPELL, myNumber);
- }
- }
- else
- {
- assert(currentSpell);
- switch (currentSpell->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)
- {
- if (spellcastingCursor)
- CCS->curh->set(Cursor::Spellcast::SPELL);
- else
- CCS->curh->set(cursorFrame);
- }
- if (!currentConsoleMsg.empty())
- GH.statusbar->clearIfMatching(currentConsoleMsg);
- if (!newConsoleMsg.empty())
- GH.statusbar->write(newConsoleMsg);
- currentConsoleMsg = newConsoleMsg;
- }
- 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->set(Cursor::Combat::POINTER);
- GH.statusbar->clear();
- }
- }
- }
- bool BattleActionsController::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;
- }
- currentSpell = nullptr;
- if (spellID >= 0)
- currentSpell = CGI->spellh->objects[spellID];
- if (currentSpell)
- {
- 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, currentSpell);
- auto m = currentSpell->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 BattleActionsController::canStackMoveHere(const CStack * stackToMove, BattleHex myNumber) const
- {
- 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 BattleActionsController::activateStack()
- {
- const CStack * s = owner.stacksController->getActiveStack();
- if(s)
- {
- possibleActions = getPossibleActionsForStack(s);
- std::list<PossiblePlayerBattleAction> actionsToSelect;
- if(!possibleActions.empty())
- {
- switch(possibleActions.front())
- {
- case PossiblePlayerBattleAction::SHOOT:
- actionsToSelect.push_back(possibleActions.front());
- actionsToSelect.push_back(PossiblePlayerBattleAction::ATTACK);
- break;
-
- case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
- actionsToSelect.push_back(possibleActions.front());
- actionsToSelect.push_back(PossiblePlayerBattleAction::WALK_AND_ATTACK);
- break;
-
- case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
- actionsToSelect.push_back(possibleActions.front());
- break;
- }
- }
- owner.windowObject->setAlternativeActions(actionsToSelect);
- }
- }
- bool BattleActionsController::spellcastingModeActive() const
- {
- return spellDestSelectMode;
- }
- SpellID BattleActionsController::selectedSpell() const
- {
- if (!spellToCast)
- return SpellID::NONE;
- return SpellID(spellToCast->actionSubtype);
- }
- const std::vector<PossiblePlayerBattleAction> & BattleActionsController::getPossibleActions() const
- {
- return possibleActions;
- }
- void BattleActionsController::removePossibleAction(PossiblePlayerBattleAction action)
- {
- vstd::erase(possibleActions, action);
- }
- void BattleActionsController::pushFrontPossibleAction(PossiblePlayerBattleAction action)
- {
- possibleActions.insert(possibleActions.begin(), action);
- }
|