CBattleActionsController.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768
  1. /*
  2. * CBattleActionsController.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 "CBattleActionsController.h"
  12. #include "CBattleControlPanel.h"
  13. #include "CBattleStacksController.h"
  14. #include "CBattleInterface.h"
  15. #include "CBattleFieldController.h"
  16. #include "CBattleSiegeController.h"
  17. #include "CBattleInterfaceClasses.h"
  18. #include "../gui/CCursorHandler.h"
  19. #include "../gui/CGuiHandler.h"
  20. #include "../gui/CIntObject.h"
  21. #include "../windows/CCreatureWindow.h"
  22. #include "../CGameInfo.h"
  23. #include "../CPlayerInterface.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. CBattleActionsController::CBattleActionsController(CBattleInterface * owner):
  39. owner(owner),
  40. creatureCasting(false),
  41. spellDestSelectMode(false),
  42. spellToCast(nullptr),
  43. sp(nullptr)
  44. {
  45. currentAction = PossiblePlayerBattleAction::INVALID;
  46. selectedAction = PossiblePlayerBattleAction::INVALID;
  47. }
  48. void CBattleActionsController::endCastingSpell()
  49. {
  50. if(spellDestSelectMode)
  51. {
  52. spellToCast.reset();
  53. sp = nullptr;
  54. spellDestSelectMode = false;
  55. CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::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 CBattleActionsController::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->changeGraphic(ECursor::COMBAT, ECursor::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> CBattleActionsController::getPossibleActionsForStack(const CStack *stack)
  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 CBattleActionsController::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::RISE_DEMONS:
  147. return 3; break;
  148. case PossiblePlayerBattleAction::SHOOT:
  149. return 4; break;
  150. case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
  151. return 5; break;
  152. case PossiblePlayerBattleAction::ATTACK:
  153. return 6; break;
  154. case PossiblePlayerBattleAction::WALK_AND_ATTACK:
  155. return 7; break;
  156. case PossiblePlayerBattleAction::MOVE_STACK:
  157. return 8; break;
  158. case PossiblePlayerBattleAction::CATAPULT:
  159. return 9; break;
  160. case PossiblePlayerBattleAction::HEAL:
  161. return 10; break;
  162. default:
  163. return 200; break;
  164. }
  165. };
  166. auto comparer = [&](PossiblePlayerBattleAction const & lhs, PossiblePlayerBattleAction const & rhs)
  167. {
  168. return assignPriority(lhs) > assignPriority(rhs);
  169. };
  170. std::make_heap(possibleActions.begin(), possibleActions.end(), comparer);
  171. }
  172. void CBattleActionsController::castThisSpell(SpellID spellID)
  173. {
  174. spellToCast = std::make_shared<BattleAction>();
  175. spellToCast->actionType = EActionType::HERO_SPELL;
  176. spellToCast->actionSubtype = spellID; //spell number
  177. spellToCast->stackNumber = (owner->attackingHeroInstance->tempOwner == owner->curInt->playerID) ? -1 : -2;
  178. spellToCast->side = owner->defendingHeroInstance ? (owner->curInt->playerID == owner->defendingHeroInstance->tempOwner) : false;
  179. spellDestSelectMode = true;
  180. creatureCasting = false;
  181. //choosing possible targets
  182. const CGHeroInstance *castingHero = (owner->attackingHeroInstance->tempOwner == owner->curInt->playerID) ? owner->attackingHeroInstance : owner->defendingHeroInstance;
  183. assert(castingHero); // code below assumes non-null hero
  184. sp = spellID.toSpell();
  185. PossiblePlayerBattleAction spellSelMode = owner->curInt->cb->getCasterAction(sp, castingHero, spells::Mode::HERO);
  186. if (spellSelMode == PossiblePlayerBattleAction::NO_LOCATION) //user does not have to select location
  187. {
  188. spellToCast->aimToHex(BattleHex::INVALID);
  189. owner->curInt->cb->battleMakeAction(spellToCast.get());
  190. endCastingSpell();
  191. }
  192. else
  193. {
  194. possibleActions.clear();
  195. possibleActions.push_back (spellSelMode); //only this one action can be performed at the moment
  196. GH.fakeMouseMove();//update cursor
  197. }
  198. }
  199. void CBattleActionsController::handleHex(BattleHex myNumber, int eventType)
  200. {
  201. if (!owner->myTurn || !owner->battleActionsStarted) //we are not permit to do anything
  202. return;
  203. // This function handles mouse move over hexes and l-clicking on them.
  204. // First we decide what happens if player clicks on this hex and set appropriately
  205. // consoleMsg, cursorFrame/Type and prepare lambda realizeAction.
  206. //
  207. // Then, depending whether it was hover/click we either call the action or set tooltip/cursor.
  208. //used when hovering -> tooltip message and cursor to be set
  209. std::string consoleMsg;
  210. bool setCursor = true; //if we want to suppress setting cursor
  211. ECursor::ECursorTypes cursorType = ECursor::COMBAT;
  212. int cursorFrame = ECursor::COMBAT_POINTER; //TODO: is this line used?
  213. //used when l-clicking -> action to be called upon the click
  214. std::function<void()> realizeAction;
  215. //Get stack on the hex - first try to grab the alive one, if not found -> allow dead stacks.
  216. const CStack * shere = owner->curInt->cb->battleGetStackByPos(myNumber, true);
  217. if(!shere)
  218. shere = owner->curInt->cb->battleGetStackByPos(myNumber, false);
  219. if(!owner->stacksController->getActiveStack())
  220. return;
  221. bool ourStack = false;
  222. if (shere)
  223. ourStack = shere->owner == owner->curInt->playerID;
  224. //stack may have changed, update selection border
  225. owner->stacksController->setHoveredStack(shere);
  226. localActions.clear();
  227. illegalActions.clear();
  228. reorderPossibleActionsPriority(owner->stacksController->getActiveStack(), shere ? MouseHoveredHexContext::OCCUPIED_HEX : MouseHoveredHexContext::UNOCCUPIED_HEX);
  229. const bool forcedAction = possibleActions.size() == 1;
  230. for (PossiblePlayerBattleAction action : possibleActions)
  231. {
  232. bool legalAction = false; //this action is legal and can be performed
  233. bool notLegal = false; //this action is not legal and should display message
  234. switch (action)
  235. {
  236. case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
  237. if (shere && ourStack)
  238. legalAction = true;
  239. break;
  240. case PossiblePlayerBattleAction::MOVE_TACTICS:
  241. case PossiblePlayerBattleAction::MOVE_STACK:
  242. {
  243. if (!(shere && shere->alive())) //we can walk on dead stacks
  244. {
  245. if(canStackMoveHere(owner->stacksController->getActiveStack(), myNumber))
  246. legalAction = true;
  247. }
  248. break;
  249. }
  250. case PossiblePlayerBattleAction::ATTACK:
  251. case PossiblePlayerBattleAction::WALK_AND_ATTACK:
  252. case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
  253. {
  254. if(owner->curInt->cb->battleCanAttack(owner->stacksController->getActiveStack(), shere, myNumber))
  255. {
  256. if (owner->fieldController->isTileAttackable(myNumber)) // move isTileAttackable to be part of battleCanAttack?
  257. {
  258. owner->fieldController->setBattleCursor(myNumber); // temporary - needed for following function :(
  259. BattleHex attackFromHex = owner->fieldController->fromWhichHexAttack(myNumber);
  260. if (attackFromHex >= 0) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
  261. legalAction = true;
  262. }
  263. }
  264. }
  265. break;
  266. case PossiblePlayerBattleAction::SHOOT:
  267. if(owner->curInt->cb->battleCanShoot(owner->stacksController->getActiveStack(), myNumber))
  268. legalAction = true;
  269. break;
  270. case PossiblePlayerBattleAction::ANY_LOCATION:
  271. if (myNumber > -1) //TODO: this should be checked for all actions
  272. {
  273. if(isCastingPossibleHere(owner->stacksController->getActiveStack(), shere, myNumber))
  274. legalAction = true;
  275. }
  276. break;
  277. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  278. if(shere && isCastingPossibleHere(owner->stacksController->getActiveStack(), shere, myNumber))
  279. legalAction = true;
  280. break;
  281. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
  282. {
  283. if(shere && ourStack && shere != owner->stacksController->getActiveStack() && shere->alive()) //only positive spells for other allied creatures
  284. {
  285. int spellID = owner->curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), shere, CBattleInfoCallback::RANDOM_GENIE);
  286. if(spellID > -1)
  287. {
  288. legalAction = true;
  289. }
  290. }
  291. }
  292. break;
  293. case PossiblePlayerBattleAction::OBSTACLE:
  294. if(isCastingPossibleHere(owner->stacksController->getActiveStack(), shere, myNumber))
  295. legalAction = true;
  296. break;
  297. case PossiblePlayerBattleAction::TELEPORT:
  298. {
  299. //todo: move to mechanics
  300. ui8 skill = 0;
  301. if (creatureCasting)
  302. skill = owner->stacksController->getActiveStack()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
  303. else
  304. skill = owner->getActiveHero()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
  305. //TODO: explicitely save power, skill
  306. if (owner->curInt->cb->battleCanTeleportTo(owner->stacksController->getSelectedStack(), myNumber, skill))
  307. legalAction = true;
  308. else
  309. notLegal = true;
  310. }
  311. break;
  312. case PossiblePlayerBattleAction::SACRIFICE: //choose our living stack to sacrifice
  313. if (shere && shere != owner->stacksController->getSelectedStack() && ourStack && shere->alive())
  314. legalAction = true;
  315. else
  316. notLegal = true;
  317. break;
  318. case PossiblePlayerBattleAction::FREE_LOCATION:
  319. legalAction = true;
  320. if(!isCastingPossibleHere(owner->stacksController->getActiveStack(), shere, myNumber))
  321. {
  322. legalAction = false;
  323. notLegal = true;
  324. }
  325. break;
  326. case PossiblePlayerBattleAction::CATAPULT:
  327. if (owner->siegeController && owner->siegeController->isAttackableByCatapult(myNumber))
  328. legalAction = true;
  329. break;
  330. case PossiblePlayerBattleAction::HEAL:
  331. if (shere && ourStack && shere->canBeHealed())
  332. legalAction = true;
  333. break;
  334. case PossiblePlayerBattleAction::RISE_DEMONS:
  335. if (shere && ourStack && !shere->alive())
  336. {
  337. if (!(shere->hasBonusOfType(Bonus::UNDEAD)
  338. || shere->hasBonusOfType(Bonus::NON_LIVING)
  339. || shere->hasBonusOfType(Bonus::GARGOYLE)
  340. || shere->summoned
  341. || shere->isClone()
  342. || shere->hasBonusOfType(Bonus::SIEGE_WEAPON)
  343. ))
  344. legalAction = true;
  345. }
  346. break;
  347. }
  348. if (legalAction)
  349. localActions.push_back (action);
  350. else if (notLegal || forcedAction)
  351. illegalActions.push_back (action);
  352. }
  353. illegalAction = PossiblePlayerBattleAction::INVALID; //clear it in first place
  354. if (vstd::contains(localActions, selectedAction)) //try to use last selected action by default
  355. currentAction = selectedAction;
  356. else if (localActions.size()) //if not possible, select first available action (they are sorted by suggested priority)
  357. currentAction = localActions.front();
  358. else //no legal action possible
  359. {
  360. currentAction = PossiblePlayerBattleAction::INVALID; //don't allow to do anything
  361. if (vstd::contains(illegalActions, selectedAction))
  362. illegalAction = selectedAction;
  363. else if (illegalActions.size())
  364. illegalAction = illegalActions.front();
  365. else if (shere && ourStack && shere->alive()) //last possibility - display info about our creature
  366. {
  367. currentAction = PossiblePlayerBattleAction::CREATURE_INFO;
  368. }
  369. else
  370. illegalAction = PossiblePlayerBattleAction::INVALID; //we should never be here
  371. }
  372. bool isCastingPossible = false;
  373. bool secondaryTarget = false;
  374. if (currentAction > PossiblePlayerBattleAction::INVALID)
  375. {
  376. switch (currentAction) //display console message, realize selected action
  377. {
  378. case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
  379. consoleMsg = (boost::format(CGI->generaltexth->allTexts[481]) % shere->getName()).str(); //Select %s
  380. realizeAction = [=](){ owner->stackActivated(shere); };
  381. break;
  382. case PossiblePlayerBattleAction::MOVE_TACTICS:
  383. case PossiblePlayerBattleAction::MOVE_STACK:
  384. if (owner->stacksController->getActiveStack()->hasBonusOfType(Bonus::FLYING))
  385. {
  386. cursorFrame = ECursor::COMBAT_FLY;
  387. consoleMsg = (boost::format(CGI->generaltexth->allTexts[295]) % owner->stacksController->getActiveStack()->getName()).str(); //Fly %s here
  388. }
  389. else
  390. {
  391. cursorFrame = ECursor::COMBAT_MOVE;
  392. consoleMsg = (boost::format(CGI->generaltexth->allTexts[294]) % owner->stacksController->getActiveStack()->getName()).str(); //Move %s here
  393. }
  394. realizeAction = [=]()
  395. {
  396. if(owner->stacksController->getActiveStack()->doubleWide())
  397. {
  398. std::vector<BattleHex> acc = owner->curInt->cb->battleGetAvailableHexes(owner->stacksController->getActiveStack());
  399. BattleHex shiftedDest = myNumber.cloneInDirection(owner->stacksController->getActiveStack()->destShiftDir(), false);
  400. if(vstd::contains(acc, myNumber))
  401. owner->giveCommand(EActionType::WALK, myNumber);
  402. else if(vstd::contains(acc, shiftedDest))
  403. owner->giveCommand(EActionType::WALK, shiftedDest);
  404. }
  405. else
  406. {
  407. owner->giveCommand(EActionType::WALK, myNumber);
  408. }
  409. };
  410. break;
  411. case PossiblePlayerBattleAction::ATTACK:
  412. case PossiblePlayerBattleAction::WALK_AND_ATTACK:
  413. case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
  414. {
  415. owner->fieldController->setBattleCursor(myNumber); //handle direction of cursor and attackable tile
  416. setCursor = false; //don't overwrite settings from the call above //TODO: what does it mean?
  417. bool returnAfterAttack = currentAction == PossiblePlayerBattleAction::ATTACK_AND_RETURN;
  418. realizeAction = [=]()
  419. {
  420. BattleHex attackFromHex = owner->fieldController->fromWhichHexAttack(myNumber);
  421. if(attackFromHex.isValid()) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
  422. {
  423. auto command = new BattleAction(BattleAction::makeMeleeAttack(owner->stacksController->getActiveStack(), myNumber, attackFromHex, returnAfterAttack));
  424. owner->sendCommand(command, owner->stacksController->getActiveStack());
  425. }
  426. };
  427. TDmgRange damage = owner->curInt->cb->battleEstimateDamage(owner->stacksController->getActiveStack(), shere);
  428. std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
  429. consoleMsg = (boost::format(CGI->generaltexth->allTexts[36]) % shere->getName() % estDmgText).str(); //Attack %s (%s damage)
  430. }
  431. break;
  432. case PossiblePlayerBattleAction::SHOOT:
  433. {
  434. if (owner->curInt->cb->battleHasShootingPenalty(owner->stacksController->getActiveStack(), myNumber))
  435. cursorFrame = ECursor::COMBAT_SHOOT_PENALTY;
  436. else
  437. cursorFrame = ECursor::COMBAT_SHOOT;
  438. realizeAction = [=](){owner->giveCommand(EActionType::SHOOT, myNumber);};
  439. TDmgRange damage = owner->curInt->cb->battleEstimateDamage(owner->stacksController->getActiveStack(), shere);
  440. std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
  441. //printing - Shoot %s (%d shots left, %s damage)
  442. consoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % owner->stacksController->getActiveStack()->shots.available() % estDmgText).str();
  443. }
  444. break;
  445. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  446. sp = CGI->spellh->objects[creatureCasting ? owner->stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
  447. consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[27]) % sp->name % shere->getName()); //Cast %s on %s
  448. switch (sp->id)
  449. {
  450. case SpellID::SACRIFICE:
  451. case SpellID::TELEPORT:
  452. owner->stacksController->setSelectedStack(shere); //remember first target
  453. secondaryTarget = true;
  454. break;
  455. }
  456. isCastingPossible = true;
  457. break;
  458. case PossiblePlayerBattleAction::ANY_LOCATION:
  459. sp = CGI->spellh->objects[creatureCasting ? owner->stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
  460. consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s
  461. isCastingPossible = true;
  462. break;
  463. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: //we assume that teleport / sacrifice will never be available as random spell
  464. sp = nullptr;
  465. consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[301]) % shere->getName()); //Cast a spell on %
  466. creatureCasting = true;
  467. isCastingPossible = true;
  468. break;
  469. case PossiblePlayerBattleAction::TELEPORT:
  470. consoleMsg = CGI->generaltexth->allTexts[25]; //Teleport Here
  471. cursorFrame = ECursor::COMBAT_TELEPORT;
  472. isCastingPossible = true;
  473. break;
  474. case PossiblePlayerBattleAction::OBSTACLE:
  475. consoleMsg = CGI->generaltexth->allTexts[550];
  476. //TODO: remove obstacle cursor
  477. isCastingPossible = true;
  478. break;
  479. case PossiblePlayerBattleAction::SACRIFICE:
  480. consoleMsg = (boost::format(CGI->generaltexth->allTexts[549]) % shere->getName()).str(); //sacrifice the %s
  481. cursorFrame = ECursor::COMBAT_SACRIFICE;
  482. isCastingPossible = true;
  483. break;
  484. case PossiblePlayerBattleAction::FREE_LOCATION:
  485. consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s
  486. isCastingPossible = true;
  487. break;
  488. case PossiblePlayerBattleAction::HEAL:
  489. cursorFrame = ECursor::COMBAT_HEAL;
  490. consoleMsg = (boost::format(CGI->generaltexth->allTexts[419]) % shere->getName()).str(); //Apply first aid to the %s
  491. realizeAction = [=](){ owner->giveCommand(EActionType::STACK_HEAL, myNumber); }; //command healing
  492. break;
  493. case PossiblePlayerBattleAction::RISE_DEMONS:
  494. cursorType = ECursor::SPELLBOOK;
  495. realizeAction = [=]()
  496. {
  497. owner->giveCommand(EActionType::DAEMON_SUMMONING, myNumber);
  498. };
  499. break;
  500. case PossiblePlayerBattleAction::CATAPULT:
  501. cursorFrame = ECursor::COMBAT_SHOOT_CATAPULT;
  502. realizeAction = [=](){ owner->giveCommand(EActionType::CATAPULT, myNumber); };
  503. break;
  504. case PossiblePlayerBattleAction::CREATURE_INFO:
  505. {
  506. cursorFrame = ECursor::COMBAT_QUERY;
  507. consoleMsg = (boost::format(CGI->generaltexth->allTexts[297]) % shere->getName()).str();
  508. realizeAction = [=](){ GH.pushIntT<CStackWindow>(shere, false); };
  509. break;
  510. }
  511. }
  512. }
  513. else //no possible valid action, display message
  514. {
  515. switch (illegalAction)
  516. {
  517. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  518. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
  519. cursorFrame = ECursor::COMBAT_BLOCKED;
  520. consoleMsg = CGI->generaltexth->allTexts[23];
  521. break;
  522. case PossiblePlayerBattleAction::TELEPORT:
  523. cursorFrame = ECursor::COMBAT_BLOCKED;
  524. consoleMsg = CGI->generaltexth->allTexts[24]; //Invalid Teleport Destination
  525. break;
  526. case PossiblePlayerBattleAction::SACRIFICE:
  527. consoleMsg = CGI->generaltexth->allTexts[543]; //choose army to sacrifice
  528. break;
  529. case PossiblePlayerBattleAction::FREE_LOCATION:
  530. cursorFrame = ECursor::COMBAT_BLOCKED;
  531. consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[181]) % sp->name); //No room to place %s here
  532. break;
  533. default:
  534. if (myNumber == -1)
  535. CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER); //set neutral cursor over menu etc.
  536. else
  537. cursorFrame = ECursor::COMBAT_BLOCKED;
  538. break;
  539. }
  540. }
  541. if (isCastingPossible) //common part
  542. {
  543. switch (currentAction) //don't use that with teleport / sacrifice
  544. {
  545. case PossiblePlayerBattleAction::TELEPORT: //FIXME: more generic solution?
  546. case PossiblePlayerBattleAction::SACRIFICE:
  547. break;
  548. default:
  549. cursorType = ECursor::SPELLBOOK;
  550. cursorFrame = 0;
  551. if (consoleMsg.empty() && sp)
  552. consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s
  553. break;
  554. }
  555. realizeAction = [=]()
  556. {
  557. if(secondaryTarget) //select that target now
  558. {
  559. possibleActions.clear();
  560. switch (sp->id.toEnum())
  561. {
  562. case SpellID::TELEPORT: //don't cast spell yet, only select target
  563. spellToCast->aimToUnit(shere);
  564. possibleActions.push_back(PossiblePlayerBattleAction::TELEPORT);
  565. break;
  566. case SpellID::SACRIFICE:
  567. spellToCast->aimToHex(myNumber);
  568. possibleActions.push_back(PossiblePlayerBattleAction::SACRIFICE);
  569. break;
  570. }
  571. }
  572. else
  573. {
  574. if (creatureCasting)
  575. {
  576. if (sp)
  577. {
  578. owner->giveCommand(EActionType::MONSTER_SPELL, myNumber, owner->stacksController->activeStackSpellToCast());
  579. }
  580. else //unknown random spell
  581. {
  582. owner->giveCommand(EActionType::MONSTER_SPELL, myNumber);
  583. }
  584. }
  585. else
  586. {
  587. assert(sp);
  588. switch (sp->id.toEnum())
  589. {
  590. case SpellID::SACRIFICE:
  591. spellToCast->aimToUnit(shere);//victim
  592. break;
  593. default:
  594. spellToCast->aimToHex(myNumber);
  595. break;
  596. }
  597. owner->curInt->cb->battleMakeAction(spellToCast.get());
  598. endCastingSpell();
  599. }
  600. owner->stacksController->setSelectedStack(nullptr);
  601. }
  602. };
  603. }
  604. {
  605. if (eventType == CIntObject::MOVE)
  606. {
  607. if (setCursor)
  608. CCS->curh->changeGraphic(cursorType, cursorFrame);
  609. owner->controlPanel->console->write(consoleMsg);
  610. }
  611. if (eventType == CIntObject::LCLICK && realizeAction)
  612. {
  613. //opening creature window shouldn't affect myTurn...
  614. if ((currentAction != PossiblePlayerBattleAction::CREATURE_INFO) && !secondaryTarget)
  615. {
  616. owner->myTurn = false; //tends to crash with empty calls
  617. }
  618. realizeAction();
  619. if (!secondaryTarget) //do not replace teleport or sacrifice cursor
  620. CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
  621. owner->controlPanel->console->clear();
  622. }
  623. }
  624. }
  625. bool CBattleActionsController::isCastingPossibleHere(const CStack *sactive, const CStack *shere, BattleHex myNumber)
  626. {
  627. creatureCasting = owner->stacksController->activeStackSpellcaster() && !spellDestSelectMode; //TODO: allow creatures to cast aimed spells
  628. bool isCastingPossible = true;
  629. int spellID = -1;
  630. if (creatureCasting)
  631. {
  632. if (owner->stacksController->activeStackSpellToCast() != SpellID::NONE && (shere != sactive)) //can't cast on itself
  633. spellID = owner->stacksController->activeStackSpellToCast(); //TODO: merge with SpellTocast?
  634. }
  635. else //hero casting
  636. {
  637. spellID = spellToCast->actionSubtype;
  638. }
  639. sp = nullptr;
  640. if (spellID >= 0)
  641. sp = CGI->spellh->objects[spellID];
  642. if (sp)
  643. {
  644. const spells::Caster *caster = creatureCasting ? static_cast<const spells::Caster *>(sactive) : static_cast<const spells::Caster *>(owner->curInt->cb->battleGetMyHero());
  645. if (caster == nullptr)
  646. {
  647. isCastingPossible = false;//just in case
  648. }
  649. else
  650. {
  651. const spells::Mode mode = creatureCasting ? spells::Mode::CREATURE_ACTIVE : spells::Mode::HERO;
  652. spells::Target target;
  653. target.emplace_back(myNumber);
  654. spells::BattleCast cast(owner->curInt->cb.get(), caster, mode, sp);
  655. auto m = sp->battleMechanics(&cast);
  656. spells::detail::ProblemImpl problem; //todo: display problem in status bar
  657. isCastingPossible = m->canBeCastAt(target, problem);
  658. }
  659. }
  660. else
  661. isCastingPossible = false;
  662. if (!myNumber.isAvailable() && !shere) //empty tile outside battlefield (or in the unavailable border column)
  663. isCastingPossible = false;
  664. return isCastingPossible;
  665. }
  666. bool CBattleActionsController::canStackMoveHere(const CStack * stackToMove, BattleHex myNumber)
  667. {
  668. std::vector<BattleHex> acc = owner->curInt->cb->battleGetAvailableHexes(stackToMove);
  669. BattleHex shiftedDest = myNumber.cloneInDirection(stackToMove->destShiftDir(), false);
  670. if (vstd::contains(acc, myNumber))
  671. return true;
  672. else if (stackToMove->doubleWide() && vstd::contains(acc, shiftedDest))
  673. return true;
  674. else
  675. return false;
  676. }
  677. void CBattleActionsController::activateStack()
  678. {
  679. const CStack * s = owner->stacksController->getActiveStack();
  680. if(s)
  681. possibleActions = getPossibleActionsForStack(s);
  682. }
  683. bool CBattleActionsController::spellcastingModeActive()
  684. {
  685. return spellDestSelectMode;
  686. }
  687. SpellID CBattleActionsController::selectedSpell()
  688. {
  689. if (!spellToCast)
  690. return SpellID::NONE;
  691. return SpellID(spellToCast->actionSubtype);
  692. }