BattleActionsController.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794
  1. /*
  2. * BattleActionsController.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "BattleActionsController.h"
  12. #include "BattleWindow.h"
  13. #include "BattleStacksController.h"
  14. #include "BattleInterface.h"
  15. #include "BattleFieldController.h"
  16. #include "BattleSiegeController.h"
  17. #include "BattleInterfaceClasses.h"
  18. #include "../CGameInfo.h"
  19. #include "../CPlayerInterface.h"
  20. #include "../gui/CursorHandler.h"
  21. #include "../gui/CGuiHandler.h"
  22. #include "../gui/CIntObject.h"
  23. #include "../windows/CCreatureWindow.h"
  24. #include "../../CCallback.h"
  25. #include "../../lib/CStack.h"
  26. #include "../../lib/battle/BattleAction.h"
  27. #include "../../lib/spells/CSpellHandler.h"
  28. #include "../../lib/spells/ISpellMechanics.h"
  29. #include "../../lib/spells/Problem.h"
  30. #include "../../lib/CGeneralTextHandler.h"
  31. static std::string formatDmgRange(std::pair<ui32, ui32> dmgRange)
  32. {
  33. if (dmgRange.first != dmgRange.second)
  34. return (boost::format("%d - %d") % dmgRange.first % dmgRange.second).str();
  35. else
  36. return (boost::format("%d") % dmgRange.first).str();
  37. }
  38. BattleActionsController::BattleActionsController(BattleInterface & owner):
  39. owner(owner),
  40. creatureCasting(false),
  41. spellDestSelectMode(false),
  42. spellToCast(nullptr),
  43. currentSpell(nullptr)
  44. {
  45. currentAction = PossiblePlayerBattleAction::INVALID;
  46. selectedAction = PossiblePlayerBattleAction::INVALID;
  47. }
  48. void BattleActionsController::endCastingSpell()
  49. {
  50. if(spellDestSelectMode)
  51. {
  52. spellToCast.reset();
  53. currentSpell = nullptr;
  54. spellDestSelectMode = false;
  55. CCS->curh->set(Cursor::Combat::POINTER);
  56. if(owner.stacksController->getActiveStack())
  57. {
  58. possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack()); //restore actions after they were cleared
  59. owner.myTurn = true;
  60. }
  61. }
  62. else
  63. {
  64. if(owner.stacksController->getActiveStack())
  65. {
  66. possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack());
  67. GH.fakeMouseMove();
  68. }
  69. }
  70. }
  71. void BattleActionsController::enterCreatureCastingMode()
  72. {
  73. //silently check for possible errors
  74. if (!owner.myTurn)
  75. return;
  76. if (owner.tacticsMode)
  77. return;
  78. //hero is casting a spell
  79. if (spellDestSelectMode)
  80. return;
  81. if (!owner.stacksController->getActiveStack())
  82. return;
  83. if (!owner.stacksController->activeStackSpellcaster())
  84. return;
  85. //random spellcaster
  86. if (owner.stacksController->activeStackSpellToCast() == SpellID::NONE)
  87. return;
  88. if (vstd::contains(possibleActions, PossiblePlayerBattleAction::NO_LOCATION))
  89. {
  90. const spells::Caster * caster = owner.stacksController->getActiveStack();
  91. const CSpell * spell = owner.stacksController->activeStackSpellToCast().toSpell();
  92. spells::Target target;
  93. target.emplace_back();
  94. spells::BattleCast cast(owner.curInt->cb.get(), caster, spells::Mode::CREATURE_ACTIVE, spell);
  95. auto m = spell->battleMechanics(&cast);
  96. spells::detail::ProblemImpl ignored;
  97. const bool isCastingPossible = m->canBeCastAt(target, ignored);
  98. if (isCastingPossible)
  99. {
  100. owner.myTurn = false;
  101. owner.giveCommand(EActionType::MONSTER_SPELL, BattleHex::INVALID, owner.stacksController->activeStackSpellToCast());
  102. owner.stacksController->setSelectedStack(nullptr);
  103. CCS->curh->set(Cursor::Combat::POINTER);
  104. }
  105. }
  106. else
  107. {
  108. possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack());
  109. auto actionFilterPredicate = [](const PossiblePlayerBattleAction x)
  110. {
  111. return (x != PossiblePlayerBattleAction::ANY_LOCATION) && (x != PossiblePlayerBattleAction::NO_LOCATION) &&
  112. (x != PossiblePlayerBattleAction::FREE_LOCATION) && (x != PossiblePlayerBattleAction::AIMED_SPELL_CREATURE) &&
  113. (x != PossiblePlayerBattleAction::OBSTACLE);
  114. };
  115. vstd::erase_if(possibleActions, actionFilterPredicate);
  116. GH.fakeMouseMove();
  117. }
  118. }
  119. std::vector<PossiblePlayerBattleAction> BattleActionsController::getPossibleActionsForStack(const CStack *stack) const
  120. {
  121. BattleClientInterfaceData data; //hard to get rid of these things so for now they're required data to pass
  122. data.creatureSpellToCast = owner.stacksController->activeStackSpellToCast();
  123. data.tacticsMode = owner.tacticsMode;
  124. auto allActions = owner.curInt->cb->getClientActionsForStack(stack, data);
  125. return std::vector<PossiblePlayerBattleAction>(allActions);
  126. }
  127. void BattleActionsController::reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context)
  128. {
  129. if(owner.tacticsMode || possibleActions.empty()) return; //this function is not supposed to be called in tactics mode or before getPossibleActionsForStack
  130. auto assignPriority = [&](PossiblePlayerBattleAction const & item) -> uint8_t //large lambda assigning priority which would have to be part of possibleActions without it
  131. {
  132. switch(item)
  133. {
  134. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  135. case PossiblePlayerBattleAction::ANY_LOCATION:
  136. case PossiblePlayerBattleAction::NO_LOCATION:
  137. case PossiblePlayerBattleAction::FREE_LOCATION:
  138. case PossiblePlayerBattleAction::OBSTACLE:
  139. if(!stack->hasBonusOfType(Bonus::NO_SPELLCAST_BY_DEFAULT) && context == MouseHoveredHexContext::OCCUPIED_HEX)
  140. return 1;
  141. else
  142. return 100;//bottom priority
  143. break;
  144. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
  145. return 2; break;
  146. case PossiblePlayerBattleAction::SHOOT:
  147. return 4; break;
  148. case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
  149. return 5; break;
  150. case PossiblePlayerBattleAction::ATTACK:
  151. return 6; break;
  152. case PossiblePlayerBattleAction::WALK_AND_ATTACK:
  153. return 7; break;
  154. case PossiblePlayerBattleAction::MOVE_STACK:
  155. return 8; break;
  156. case PossiblePlayerBattleAction::CATAPULT:
  157. return 9; break;
  158. case PossiblePlayerBattleAction::HEAL:
  159. return 10; break;
  160. default:
  161. return 200; break;
  162. }
  163. };
  164. auto comparer = [&](PossiblePlayerBattleAction const & lhs, PossiblePlayerBattleAction const & rhs)
  165. {
  166. return assignPriority(lhs) > assignPriority(rhs);
  167. };
  168. std::make_heap(possibleActions.begin(), possibleActions.end(), comparer);
  169. }
  170. void BattleActionsController::castThisSpell(SpellID spellID)
  171. {
  172. spellToCast = std::make_shared<BattleAction>();
  173. spellToCast->actionType = EActionType::HERO_SPELL;
  174. spellToCast->actionSubtype = spellID; //spell number
  175. spellToCast->stackNumber = (owner.attackingHeroInstance->tempOwner == owner.curInt->playerID) ? -1 : -2;
  176. spellToCast->side = owner.defendingHeroInstance ? (owner.curInt->playerID == owner.defendingHeroInstance->tempOwner) : false;
  177. spellDestSelectMode = true;
  178. creatureCasting = false;
  179. //choosing possible targets
  180. const CGHeroInstance *castingHero = (owner.attackingHeroInstance->tempOwner == owner.curInt->playerID) ? owner.attackingHeroInstance : owner.defendingHeroInstance;
  181. assert(castingHero); // code below assumes non-null hero
  182. currentSpell = spellID.toSpell();
  183. PossiblePlayerBattleAction spellSelMode = owner.curInt->cb->getCasterAction(currentSpell, castingHero, spells::Mode::HERO);
  184. if (spellSelMode == PossiblePlayerBattleAction::NO_LOCATION) //user does not have to select location
  185. {
  186. spellToCast->aimToHex(BattleHex::INVALID);
  187. owner.curInt->cb->battleMakeAction(spellToCast.get());
  188. endCastingSpell();
  189. }
  190. else
  191. {
  192. possibleActions.clear();
  193. possibleActions.push_back (spellSelMode); //only this one action can be performed at the moment
  194. GH.fakeMouseMove();//update cursor
  195. }
  196. }
  197. void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
  198. {
  199. if (!owner.myTurn) //we are not permit to do anything
  200. return;
  201. // This function handles mouse move over hexes and l-clicking on them.
  202. // First we decide what happens if player clicks on this hex and set appropriately
  203. // consoleMsg, cursorFrame/Type and prepare lambda realizeAction.
  204. //
  205. // Then, depending whether it was hover/click we either call the action or set tooltip/cursor.
  206. std::string newConsoleMsg;
  207. //used when hovering -> tooltip message and cursor to be set
  208. bool setCursor = true; //if we want to suppress setting cursor
  209. bool spellcastingCursor = false;
  210. auto cursorFrame = Cursor::Combat::POINTER;
  211. //used when l-clicking -> action to be called upon the click
  212. std::function<void()> realizeAction;
  213. //Get stack on the hex - first try to grab the alive one, if not found -> allow dead stacks.
  214. const CStack * shere = owner.curInt->cb->battleGetStackByPos(myNumber, true);
  215. if(!shere)
  216. shere = owner.curInt->cb->battleGetStackByPos(myNumber, false);
  217. if(!owner.stacksController->getActiveStack())
  218. return;
  219. bool ourStack = false;
  220. if (shere)
  221. ourStack = shere->owner == owner.curInt->playerID;
  222. localActions.clear();
  223. illegalActions.clear();
  224. reorderPossibleActionsPriority(owner.stacksController->getActiveStack(), shere ? MouseHoveredHexContext::OCCUPIED_HEX : MouseHoveredHexContext::UNOCCUPIED_HEX);
  225. const bool forcedAction = possibleActions.size() == 1;
  226. for (PossiblePlayerBattleAction action : possibleActions)
  227. {
  228. bool legalAction = false; //this action is legal and can be performed
  229. bool notLegal = false; //this action is not legal and should display message
  230. switch (action)
  231. {
  232. case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
  233. if (shere && ourStack)
  234. legalAction = true;
  235. break;
  236. case PossiblePlayerBattleAction::MOVE_TACTICS:
  237. case PossiblePlayerBattleAction::MOVE_STACK:
  238. {
  239. if (!(shere && shere->alive())) //we can walk on dead stacks
  240. {
  241. if(canStackMoveHere(owner.stacksController->getActiveStack(), myNumber))
  242. legalAction = true;
  243. }
  244. break;
  245. }
  246. case PossiblePlayerBattleAction::ATTACK:
  247. case PossiblePlayerBattleAction::WALK_AND_ATTACK:
  248. case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
  249. {
  250. if(owner.curInt->cb->battleCanAttack(owner.stacksController->getActiveStack(), shere, myNumber))
  251. {
  252. if (owner.fieldController->isTileAttackable(myNumber)) // move isTileAttackable to be part of battleCanAttack?
  253. {
  254. BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(myNumber);
  255. if (attackFromHex >= 0) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
  256. legalAction = true;
  257. }
  258. }
  259. }
  260. break;
  261. case PossiblePlayerBattleAction::SHOOT:
  262. if(owner.curInt->cb->battleCanShoot(owner.stacksController->getActiveStack(), myNumber))
  263. legalAction = true;
  264. break;
  265. case PossiblePlayerBattleAction::ANY_LOCATION:
  266. if (myNumber > -1) //TODO: this should be checked for all actions
  267. {
  268. if(isCastingPossibleHere(owner.stacksController->getActiveStack(), shere, myNumber))
  269. legalAction = true;
  270. }
  271. break;
  272. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  273. if(shere && isCastingPossibleHere(owner.stacksController->getActiveStack(), shere, myNumber))
  274. legalAction = true;
  275. break;
  276. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
  277. {
  278. if(shere && ourStack && shere != owner.stacksController->getActiveStack() && shere->alive()) //only positive spells for other allied creatures
  279. {
  280. int spellID = owner.curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), shere, CBattleInfoCallback::RANDOM_GENIE);
  281. if(spellID > -1)
  282. {
  283. legalAction = true;
  284. }
  285. }
  286. }
  287. break;
  288. case PossiblePlayerBattleAction::OBSTACLE:
  289. if(isCastingPossibleHere(owner.stacksController->getActiveStack(), shere, myNumber))
  290. legalAction = true;
  291. break;
  292. case PossiblePlayerBattleAction::TELEPORT:
  293. {
  294. //todo: move to mechanics
  295. ui8 skill = 0;
  296. if (creatureCasting)
  297. skill = owner.stacksController->getActiveStack()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
  298. else
  299. skill = owner.getActiveHero()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
  300. //TODO: explicitely save power, skill
  301. if (owner.curInt->cb->battleCanTeleportTo(owner.stacksController->getSelectedStack(), myNumber, skill))
  302. legalAction = true;
  303. else
  304. notLegal = true;
  305. }
  306. break;
  307. case PossiblePlayerBattleAction::SACRIFICE: //choose our living stack to sacrifice
  308. if (shere && shere != owner.stacksController->getSelectedStack() && ourStack && shere->alive())
  309. legalAction = true;
  310. else
  311. notLegal = true;
  312. break;
  313. case PossiblePlayerBattleAction::FREE_LOCATION:
  314. legalAction = true;
  315. if(!isCastingPossibleHere(owner.stacksController->getActiveStack(), shere, myNumber))
  316. {
  317. legalAction = false;
  318. notLegal = true;
  319. }
  320. break;
  321. case PossiblePlayerBattleAction::CATAPULT:
  322. if (owner.siegeController && owner.siegeController->isAttackableByCatapult(myNumber))
  323. legalAction = true;
  324. break;
  325. case PossiblePlayerBattleAction::HEAL:
  326. if (shere && ourStack && shere->canBeHealed())
  327. legalAction = true;
  328. break;
  329. }
  330. if (legalAction)
  331. localActions.push_back (action);
  332. else if (notLegal || forcedAction)
  333. illegalActions.push_back (action);
  334. }
  335. illegalAction = PossiblePlayerBattleAction::INVALID; //clear it in first place
  336. if (vstd::contains(localActions, selectedAction)) //try to use last selected action by default
  337. currentAction = selectedAction;
  338. else if (localActions.size()) //if not possible, select first available action (they are sorted by suggested priority)
  339. currentAction = localActions.front();
  340. else //no legal action possible
  341. {
  342. currentAction = PossiblePlayerBattleAction::INVALID; //don't allow to do anything
  343. if (vstd::contains(illegalActions, selectedAction))
  344. illegalAction = selectedAction;
  345. else if (illegalActions.size())
  346. illegalAction = illegalActions.front();
  347. else if (shere && ourStack && shere->alive()) //last possibility - display info about our creature
  348. {
  349. currentAction = PossiblePlayerBattleAction::CREATURE_INFO;
  350. }
  351. else
  352. illegalAction = PossiblePlayerBattleAction::INVALID; //we should never be here
  353. }
  354. bool isCastingPossible = false;
  355. bool secondaryTarget = false;
  356. if (currentAction > PossiblePlayerBattleAction::INVALID)
  357. {
  358. switch (currentAction) //display console message, realize selected action
  359. {
  360. case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
  361. newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[481]) % shere->getName()).str(); //Select %s
  362. realizeAction = [=](){ owner.stackActivated(shere); };
  363. break;
  364. case PossiblePlayerBattleAction::MOVE_TACTICS:
  365. case PossiblePlayerBattleAction::MOVE_STACK:
  366. if (owner.stacksController->getActiveStack()->hasBonusOfType(Bonus::FLYING))
  367. {
  368. cursorFrame = Cursor::Combat::FLY;
  369. newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[295]) % owner.stacksController->getActiveStack()->getName()).str(); //Fly %s here
  370. }
  371. else
  372. {
  373. cursorFrame = Cursor::Combat::MOVE;
  374. newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[294]) % owner.stacksController->getActiveStack()->getName()).str(); //Move %s here
  375. }
  376. realizeAction = [=]()
  377. {
  378. if(owner.stacksController->getActiveStack()->doubleWide())
  379. {
  380. std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(owner.stacksController->getActiveStack());
  381. BattleHex shiftedDest = myNumber.cloneInDirection(owner.stacksController->getActiveStack()->destShiftDir(), false);
  382. if(vstd::contains(acc, myNumber))
  383. owner.giveCommand(EActionType::WALK, myNumber);
  384. else if(vstd::contains(acc, shiftedDest))
  385. owner.giveCommand(EActionType::WALK, shiftedDest);
  386. }
  387. else
  388. {
  389. owner.giveCommand(EActionType::WALK, myNumber);
  390. }
  391. };
  392. break;
  393. case PossiblePlayerBattleAction::ATTACK:
  394. case PossiblePlayerBattleAction::WALK_AND_ATTACK:
  395. case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
  396. {
  397. owner.fieldController->setBattleCursor(myNumber); //handle direction of cursor
  398. setCursor = false; //don't overwrite settings from the call above //TODO: what does it mean?
  399. bool returnAfterAttack = currentAction == PossiblePlayerBattleAction::ATTACK_AND_RETURN;
  400. realizeAction = [=]()
  401. {
  402. BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(myNumber);
  403. if(attackFromHex.isValid()) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
  404. {
  405. auto command = new BattleAction(BattleAction::makeMeleeAttack(owner.stacksController->getActiveStack(), myNumber, attackFromHex, returnAfterAttack));
  406. owner.sendCommand(command, owner.stacksController->getActiveStack());
  407. }
  408. };
  409. BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(myNumber);
  410. TDmgRange damage = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), shere, attackFromHex);
  411. std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
  412. newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[36]) % shere->getName() % estDmgText).str(); //Attack %s (%s damage)
  413. }
  414. break;
  415. case PossiblePlayerBattleAction::SHOOT:
  416. {
  417. auto const * shooter = owner.stacksController->getActiveStack();
  418. if (owner.curInt->cb->battleHasShootingPenalty(shooter, myNumber))
  419. cursorFrame = Cursor::Combat::SHOOT_PENALTY;
  420. else
  421. cursorFrame = Cursor::Combat::SHOOT;
  422. realizeAction = [=](){owner.giveCommand(EActionType::SHOOT, myNumber);};
  423. TDmgRange damage = owner.curInt->cb->battleEstimateDamage(shooter, shere, shooter->getPosition());
  424. std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
  425. //printing - Shoot %s (%d shots left, %s damage)
  426. newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % shooter->shots.available() % estDmgText).str();
  427. }
  428. break;
  429. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  430. currentSpell = CGI->spellh->objects[creatureCasting ? owner.stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
  431. newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[27]) % currentSpell->getNameTranslated() % shere->getName()); //Cast %s on %s
  432. switch (currentSpell->id)
  433. {
  434. case SpellID::SACRIFICE:
  435. case SpellID::TELEPORT:
  436. owner.stacksController->setSelectedStack(shere); //remember first target
  437. secondaryTarget = true;
  438. break;
  439. }
  440. isCastingPossible = true;
  441. break;
  442. case PossiblePlayerBattleAction::ANY_LOCATION:
  443. currentSpell = CGI->spellh->objects[creatureCasting ? owner.stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
  444. newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->getNameTranslated()); //Cast %s
  445. isCastingPossible = true;
  446. break;
  447. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: //we assume that teleport / sacrifice will never be available as random spell
  448. currentSpell = nullptr;
  449. newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[301]) % shere->getName()); //Cast a spell on %
  450. creatureCasting = true;
  451. isCastingPossible = true;
  452. break;
  453. case PossiblePlayerBattleAction::TELEPORT:
  454. newConsoleMsg = CGI->generaltexth->allTexts[25]; //Teleport Here
  455. cursorFrame = Cursor::Combat::TELEPORT;
  456. isCastingPossible = true;
  457. break;
  458. case PossiblePlayerBattleAction::OBSTACLE:
  459. newConsoleMsg = CGI->generaltexth->allTexts[550];
  460. //TODO: remove obstacle cursor
  461. isCastingPossible = true;
  462. break;
  463. case PossiblePlayerBattleAction::SACRIFICE:
  464. newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[549]) % shere->getName()).str(); //sacrifice the %s
  465. cursorFrame = Cursor::Combat::SACRIFICE;
  466. isCastingPossible = true;
  467. break;
  468. case PossiblePlayerBattleAction::FREE_LOCATION:
  469. newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->getNameTranslated()); //Cast %s
  470. isCastingPossible = true;
  471. break;
  472. case PossiblePlayerBattleAction::HEAL:
  473. cursorFrame = Cursor::Combat::HEAL;
  474. newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[419]) % shere->getName()).str(); //Apply first aid to the %s
  475. realizeAction = [=](){ owner.giveCommand(EActionType::STACK_HEAL, myNumber); }; //command healing
  476. break;
  477. case PossiblePlayerBattleAction::CATAPULT:
  478. cursorFrame = Cursor::Combat::SHOOT_CATAPULT;
  479. realizeAction = [=](){ owner.giveCommand(EActionType::CATAPULT, myNumber); };
  480. break;
  481. case PossiblePlayerBattleAction::CREATURE_INFO:
  482. {
  483. cursorFrame = Cursor::Combat::QUERY;
  484. newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[297]) % shere->getName()).str();
  485. realizeAction = [=](){ GH.pushIntT<CStackWindow>(shere, false); };
  486. break;
  487. }
  488. }
  489. }
  490. else //no possible valid action, display message
  491. {
  492. switch (illegalAction)
  493. {
  494. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  495. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
  496. cursorFrame = Cursor::Combat::BLOCKED;
  497. newConsoleMsg = CGI->generaltexth->allTexts[23];
  498. break;
  499. case PossiblePlayerBattleAction::TELEPORT:
  500. cursorFrame = Cursor::Combat::BLOCKED;
  501. newConsoleMsg = CGI->generaltexth->allTexts[24]; //Invalid Teleport Destination
  502. break;
  503. case PossiblePlayerBattleAction::SACRIFICE:
  504. newConsoleMsg = CGI->generaltexth->allTexts[543]; //choose army to sacrifice
  505. break;
  506. case PossiblePlayerBattleAction::FREE_LOCATION:
  507. cursorFrame = Cursor::Combat::BLOCKED;
  508. newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[181]) % currentSpell->getNameTranslated()); //No room to place %s here
  509. break;
  510. default:
  511. if (myNumber == -1)
  512. CCS->curh->set(Cursor::Combat::POINTER);
  513. else
  514. cursorFrame = Cursor::Combat::BLOCKED;
  515. break;
  516. }
  517. }
  518. if (isCastingPossible) //common part
  519. {
  520. switch (currentAction) //don't use that with teleport / sacrifice
  521. {
  522. case PossiblePlayerBattleAction::TELEPORT: //FIXME: more generic solution?
  523. case PossiblePlayerBattleAction::SACRIFICE:
  524. break;
  525. default:
  526. spellcastingCursor = true;
  527. if (newConsoleMsg.empty() && currentSpell)
  528. newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->getNameTranslated()); //Cast %s
  529. break;
  530. }
  531. realizeAction = [=]()
  532. {
  533. if(secondaryTarget) //select that target now
  534. {
  535. possibleActions.clear();
  536. switch (currentSpell->id.toEnum())
  537. {
  538. case SpellID::TELEPORT: //don't cast spell yet, only select target
  539. spellToCast->aimToUnit(shere);
  540. possibleActions.push_back(PossiblePlayerBattleAction::TELEPORT);
  541. break;
  542. case SpellID::SACRIFICE:
  543. spellToCast->aimToHex(myNumber);
  544. possibleActions.push_back(PossiblePlayerBattleAction::SACRIFICE);
  545. break;
  546. }
  547. }
  548. else
  549. {
  550. if (creatureCasting)
  551. {
  552. if (currentSpell)
  553. {
  554. owner.giveCommand(EActionType::MONSTER_SPELL, myNumber, owner.stacksController->activeStackSpellToCast());
  555. }
  556. else //unknown random spell
  557. {
  558. owner.giveCommand(EActionType::MONSTER_SPELL, myNumber);
  559. }
  560. }
  561. else
  562. {
  563. assert(currentSpell);
  564. switch (currentSpell->id.toEnum())
  565. {
  566. case SpellID::SACRIFICE:
  567. spellToCast->aimToUnit(shere);//victim
  568. break;
  569. default:
  570. spellToCast->aimToHex(myNumber);
  571. break;
  572. }
  573. owner.curInt->cb->battleMakeAction(spellToCast.get());
  574. endCastingSpell();
  575. }
  576. owner.stacksController->setSelectedStack(nullptr);
  577. }
  578. };
  579. }
  580. {
  581. if (eventType == CIntObject::MOVE)
  582. {
  583. if (setCursor)
  584. {
  585. if (spellcastingCursor)
  586. CCS->curh->set(Cursor::Spellcast::SPELL);
  587. else
  588. CCS->curh->set(cursorFrame);
  589. }
  590. if (!currentConsoleMsg.empty())
  591. GH.statusbar->clearIfMatching(currentConsoleMsg);
  592. if (!newConsoleMsg.empty())
  593. GH.statusbar->write(newConsoleMsg);
  594. currentConsoleMsg = newConsoleMsg;
  595. }
  596. if (eventType == CIntObject::LCLICK && realizeAction)
  597. {
  598. //opening creature window shouldn't affect myTurn...
  599. if ((currentAction != PossiblePlayerBattleAction::CREATURE_INFO) && !secondaryTarget)
  600. {
  601. owner.myTurn = false; //tends to crash with empty calls
  602. }
  603. realizeAction();
  604. if (!secondaryTarget) //do not replace teleport or sacrifice cursor
  605. CCS->curh->set(Cursor::Combat::POINTER);
  606. GH.statusbar->clear();
  607. }
  608. }
  609. }
  610. bool BattleActionsController::isCastingPossibleHere(const CStack *sactive, const CStack *shere, BattleHex myNumber)
  611. {
  612. creatureCasting = owner.stacksController->activeStackSpellcaster() && !spellDestSelectMode; //TODO: allow creatures to cast aimed spells
  613. bool isCastingPossible = true;
  614. int spellID = -1;
  615. if (creatureCasting)
  616. {
  617. if (owner.stacksController->activeStackSpellToCast() != SpellID::NONE && (shere != sactive)) //can't cast on itself
  618. spellID = owner.stacksController->activeStackSpellToCast(); //TODO: merge with SpellTocast?
  619. }
  620. else //hero casting
  621. {
  622. spellID = spellToCast->actionSubtype;
  623. }
  624. currentSpell = nullptr;
  625. if (spellID >= 0)
  626. currentSpell = CGI->spellh->objects[spellID];
  627. if (currentSpell)
  628. {
  629. const spells::Caster *caster = creatureCasting ? static_cast<const spells::Caster *>(sactive) : static_cast<const spells::Caster *>(owner.curInt->cb->battleGetMyHero());
  630. if (caster == nullptr)
  631. {
  632. isCastingPossible = false;//just in case
  633. }
  634. else
  635. {
  636. const spells::Mode mode = creatureCasting ? spells::Mode::CREATURE_ACTIVE : spells::Mode::HERO;
  637. spells::Target target;
  638. target.emplace_back(myNumber);
  639. spells::BattleCast cast(owner.curInt->cb.get(), caster, mode, currentSpell);
  640. auto m = currentSpell->battleMechanics(&cast);
  641. spells::detail::ProblemImpl problem; //todo: display problem in status bar
  642. isCastingPossible = m->canBeCastAt(target, problem);
  643. }
  644. }
  645. else
  646. isCastingPossible = false;
  647. if (!myNumber.isAvailable() && !shere) //empty tile outside battlefield (or in the unavailable border column)
  648. isCastingPossible = false;
  649. return isCastingPossible;
  650. }
  651. bool BattleActionsController::canStackMoveHere(const CStack * stackToMove, BattleHex myNumber) const
  652. {
  653. std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(stackToMove);
  654. BattleHex shiftedDest = myNumber.cloneInDirection(stackToMove->destShiftDir(), false);
  655. if (vstd::contains(acc, myNumber))
  656. return true;
  657. else if (stackToMove->doubleWide() && vstd::contains(acc, shiftedDest))
  658. return true;
  659. else
  660. return false;
  661. }
  662. void BattleActionsController::activateStack()
  663. {
  664. const CStack * s = owner.stacksController->getActiveStack();
  665. if(s)
  666. {
  667. possibleActions = getPossibleActionsForStack(s);
  668. std::list<PossiblePlayerBattleAction> actionsToSelect;
  669. if(!possibleActions.empty())
  670. {
  671. switch(possibleActions.front())
  672. {
  673. case PossiblePlayerBattleAction::SHOOT:
  674. actionsToSelect.push_back(possibleActions.front());
  675. actionsToSelect.push_back(PossiblePlayerBattleAction::ATTACK);
  676. break;
  677. case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
  678. actionsToSelect.push_back(possibleActions.front());
  679. actionsToSelect.push_back(PossiblePlayerBattleAction::WALK_AND_ATTACK);
  680. break;
  681. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  682. actionsToSelect.push_back(possibleActions.front());
  683. break;
  684. }
  685. }
  686. owner.windowObject->setAlternativeActions(actionsToSelect);
  687. }
  688. }
  689. bool BattleActionsController::spellcastingModeActive() const
  690. {
  691. return spellDestSelectMode;
  692. }
  693. SpellID BattleActionsController::selectedSpell() const
  694. {
  695. if (!spellToCast)
  696. return SpellID::NONE;
  697. return SpellID(spellToCast->actionSubtype);
  698. }
  699. const std::vector<PossiblePlayerBattleAction> & BattleActionsController::getPossibleActions() const
  700. {
  701. return possibleActions;
  702. }
  703. void BattleActionsController::removePossibleAction(PossiblePlayerBattleAction action)
  704. {
  705. vstd::erase(possibleActions, action);
  706. }
  707. void BattleActionsController::pushFrontPossibleAction(PossiblePlayerBattleAction action)
  708. {
  709. possibleActions.insert(possibleActions.begin(), action);
  710. }