BattleActionsController.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890
  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. heroSpellToCast(nullptr),
  41. creatureSpellToCast(nullptr)
  42. {}
  43. void BattleActionsController::endCastingSpell()
  44. {
  45. if(heroSpellToCast)
  46. heroSpellToCast.reset();
  47. if(owner.stacksController->getActiveStack())
  48. possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack()); //restore actions after they were cleared
  49. GH.fakeMouseMove();
  50. }
  51. bool BattleActionsController::isActiveStackSpellcaster() const
  52. {
  53. const CStack * casterStack = owner.stacksController->getActiveStack();
  54. if (!casterStack)
  55. return false;
  56. const auto randomSpellcaster = casterStack->getBonusLocalFirst(Selector::type()(Bonus::SPELLCASTER));
  57. return (randomSpellcaster && casterStack->canCast());
  58. }
  59. void BattleActionsController::enterCreatureCastingMode()
  60. {
  61. //silently check for possible errors
  62. if (owner.tacticsMode)
  63. return;
  64. //hero is casting a spell
  65. if (heroSpellToCast)
  66. return;
  67. if (!owner.stacksController->getActiveStack())
  68. return;
  69. if (!isActiveStackSpellcaster())
  70. return;
  71. if (vstd::contains(possibleActions, PossiblePlayerBattleAction::NO_LOCATION))
  72. {
  73. const spells::Caster * caster = owner.stacksController->getActiveStack();
  74. const CSpell * spell = getStackSpellToCast();
  75. spells::Target target;
  76. target.emplace_back();
  77. spells::BattleCast cast(owner.curInt->cb.get(), caster, spells::Mode::CREATURE_ACTIVE, spell);
  78. auto m = spell->battleMechanics(&cast);
  79. spells::detail::ProblemImpl ignored;
  80. const bool isCastingPossible = m->canBeCastAt(target, ignored);
  81. if (isCastingPossible)
  82. {
  83. owner.giveCommand(EActionType::MONSTER_SPELL, BattleHex::INVALID, spell->getId());
  84. owner.stacksController->setSelectedStack(nullptr);
  85. CCS->curh->set(Cursor::Combat::POINTER);
  86. }
  87. }
  88. else
  89. {
  90. possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack());
  91. auto actionFilterPredicate = [](const PossiblePlayerBattleAction x)
  92. {
  93. return (x != PossiblePlayerBattleAction::ANY_LOCATION) && (x != PossiblePlayerBattleAction::NO_LOCATION) &&
  94. (x != PossiblePlayerBattleAction::FREE_LOCATION) && (x != PossiblePlayerBattleAction::AIMED_SPELL_CREATURE) &&
  95. (x != PossiblePlayerBattleAction::OBSTACLE);
  96. };
  97. vstd::erase_if(possibleActions, actionFilterPredicate);
  98. GH.fakeMouseMove();
  99. }
  100. }
  101. std::vector<PossiblePlayerBattleAction> BattleActionsController::getPossibleActionsForStack(const CStack *stack) const
  102. {
  103. BattleClientInterfaceData data; //hard to get rid of these things so for now they're required data to pass
  104. if (getStackSpellToCast())
  105. data.creatureSpellToCast = getStackSpellToCast()->getId();
  106. else
  107. data.creatureSpellToCast = SpellID::NONE;
  108. data.tacticsMode = owner.tacticsMode;
  109. auto allActions = owner.curInt->cb->getClientActionsForStack(stack, data);
  110. allActions.push_back(PossiblePlayerBattleAction::HERO_INFO);
  111. allActions.push_back(PossiblePlayerBattleAction::CREATURE_INFO);
  112. return std::vector<PossiblePlayerBattleAction>(allActions);
  113. }
  114. void BattleActionsController::reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context)
  115. {
  116. if(owner.tacticsMode || possibleActions.empty()) return; //this function is not supposed to be called in tactics mode or before getPossibleActionsForStack
  117. auto assignPriority = [&](PossiblePlayerBattleAction const & item) -> uint8_t //large lambda assigning priority which would have to be part of possibleActions without it
  118. {
  119. switch(item)
  120. {
  121. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  122. case PossiblePlayerBattleAction::ANY_LOCATION:
  123. case PossiblePlayerBattleAction::NO_LOCATION:
  124. case PossiblePlayerBattleAction::FREE_LOCATION:
  125. case PossiblePlayerBattleAction::OBSTACLE:
  126. if(!stack->hasBonusOfType(Bonus::NO_SPELLCAST_BY_DEFAULT) && context == MouseHoveredHexContext::OCCUPIED_HEX)
  127. return 1;
  128. else
  129. return 100;//bottom priority
  130. break;
  131. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
  132. return 2; break;
  133. case PossiblePlayerBattleAction::SHOOT:
  134. return 4; break;
  135. case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
  136. return 5; break;
  137. case PossiblePlayerBattleAction::ATTACK:
  138. return 6; break;
  139. case PossiblePlayerBattleAction::WALK_AND_ATTACK:
  140. return 7; break;
  141. case PossiblePlayerBattleAction::MOVE_STACK:
  142. return 8; break;
  143. case PossiblePlayerBattleAction::CATAPULT:
  144. return 9; break;
  145. case PossiblePlayerBattleAction::HEAL:
  146. return 10; break;
  147. case PossiblePlayerBattleAction::CREATURE_INFO:
  148. return 11; break;
  149. case PossiblePlayerBattleAction::HERO_INFO:
  150. return 12; break;
  151. case PossiblePlayerBattleAction::TELEPORT:
  152. return 13; break;
  153. default:
  154. assert(0);
  155. return 200; break;
  156. }
  157. };
  158. auto comparer = [&](PossiblePlayerBattleAction const & lhs, PossiblePlayerBattleAction const & rhs)
  159. {
  160. return assignPriority(lhs) < assignPriority(rhs);
  161. };
  162. std::sort(possibleActions.begin(), possibleActions.end(), comparer);
  163. }
  164. void BattleActionsController::castThisSpell(SpellID spellID)
  165. {
  166. heroSpellToCast = std::make_shared<BattleAction>();
  167. heroSpellToCast->actionType = EActionType::HERO_SPELL;
  168. heroSpellToCast->actionSubtype = spellID; //spell number
  169. heroSpellToCast->stackNumber = (owner.attackingHeroInstance->tempOwner == owner.curInt->playerID) ? -1 : -2;
  170. heroSpellToCast->side = owner.defendingHeroInstance ? (owner.curInt->playerID == owner.defendingHeroInstance->tempOwner) : false;
  171. //choosing possible targets
  172. const CGHeroInstance *castingHero = (owner.attackingHeroInstance->tempOwner == owner.curInt->playerID) ? owner.attackingHeroInstance : owner.defendingHeroInstance;
  173. assert(castingHero); // code below assumes non-null hero
  174. PossiblePlayerBattleAction spellSelMode = owner.curInt->cb->getCasterAction(spellID.toSpell(), castingHero, spells::Mode::HERO);
  175. if (spellSelMode == PossiblePlayerBattleAction::NO_LOCATION) //user does not have to select location
  176. {
  177. heroSpellToCast->aimToHex(BattleHex::INVALID);
  178. owner.curInt->cb->battleMakeAction(heroSpellToCast.get());
  179. endCastingSpell();
  180. }
  181. else
  182. {
  183. possibleActions.clear();
  184. possibleActions.push_back (spellSelMode); //only this one action can be performed at the moment
  185. GH.fakeMouseMove();//update cursor
  186. }
  187. }
  188. const CSpell * BattleActionsController::getHeroSpellToCast( ) const
  189. {
  190. if (heroSpellToCast)
  191. return SpellID(heroSpellToCast->actionSubtype).toSpell();
  192. return nullptr;
  193. }
  194. const CSpell * BattleActionsController::getStackSpellToCast( ) const
  195. {
  196. if (isActiveStackSpellcaster())
  197. return creatureSpellToCast;
  198. return nullptr;
  199. }
  200. const CSpell * BattleActionsController::getCurrentSpell( ) const
  201. {
  202. if (getHeroSpellToCast())
  203. return getHeroSpellToCast();
  204. return getStackSpellToCast();
  205. }
  206. const CStack * BattleActionsController::getStackForHex(BattleHex hoveredHex)
  207. {
  208. const CStack * shere = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
  209. if(shere)
  210. return shere;
  211. return owner.curInt->cb->battleGetStackByPos(hoveredHex, false);
  212. }
  213. void BattleActionsController::actionSetCursor(PossiblePlayerBattleAction action, BattleHex targetHex)
  214. {
  215. switch (action)
  216. {
  217. case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
  218. CCS->curh->set(Cursor::Combat::POINTER);
  219. return;
  220. case PossiblePlayerBattleAction::MOVE_TACTICS:
  221. case PossiblePlayerBattleAction::MOVE_STACK:
  222. if (owner.stacksController->getActiveStack()->hasBonusOfType(Bonus::FLYING))
  223. CCS->curh->set(Cursor::Combat::FLY);
  224. else
  225. CCS->curh->set(Cursor::Combat::MOVE);
  226. return;
  227. case PossiblePlayerBattleAction::ATTACK:
  228. case PossiblePlayerBattleAction::WALK_AND_ATTACK:
  229. case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
  230. owner.fieldController->setBattleCursor(targetHex);
  231. return;
  232. case PossiblePlayerBattleAction::SHOOT:
  233. if (owner.curInt->cb->battleHasShootingPenalty(owner.stacksController->getActiveStack(), targetHex))
  234. CCS->curh->set(Cursor::Combat::SHOOT_PENALTY);
  235. else
  236. CCS->curh->set(Cursor::Combat::SHOOT);
  237. return;
  238. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  239. case PossiblePlayerBattleAction::ANY_LOCATION:
  240. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
  241. case PossiblePlayerBattleAction::FREE_LOCATION:
  242. case PossiblePlayerBattleAction::OBSTACLE:
  243. CCS->curh->set(Cursor::Spellcast::SPELL);
  244. return;
  245. case PossiblePlayerBattleAction::TELEPORT:
  246. CCS->curh->set(Cursor::Combat::TELEPORT);
  247. return;
  248. case PossiblePlayerBattleAction::SACRIFICE:
  249. CCS->curh->set(Cursor::Combat::SACRIFICE);
  250. return;
  251. case PossiblePlayerBattleAction::HEAL:
  252. CCS->curh->set(Cursor::Combat::HEAL);
  253. return;
  254. case PossiblePlayerBattleAction::CATAPULT:
  255. CCS->curh->set(Cursor::Combat::SHOOT_CATAPULT);
  256. return;
  257. case PossiblePlayerBattleAction::CREATURE_INFO:
  258. CCS->curh->set(Cursor::Combat::QUERY);
  259. return;
  260. case PossiblePlayerBattleAction::HERO_INFO:
  261. CCS->curh->set(Cursor::Combat::HERO);
  262. return;
  263. }
  264. assert(0);
  265. }
  266. void BattleActionsController::actionSetCursorBlocked(PossiblePlayerBattleAction action, BattleHex targetHex)
  267. {
  268. switch (action)
  269. {
  270. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  271. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
  272. case PossiblePlayerBattleAction::TELEPORT:
  273. case PossiblePlayerBattleAction::SACRIFICE:
  274. case PossiblePlayerBattleAction::FREE_LOCATION:
  275. CCS->curh->set(Cursor::Combat::BLOCKED);
  276. return;
  277. default:
  278. if (targetHex == -1)
  279. CCS->curh->set(Cursor::Combat::POINTER);
  280. else
  281. CCS->curh->set(Cursor::Combat::BLOCKED);
  282. return;
  283. }
  284. assert(0);
  285. }
  286. std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattleAction action, BattleHex targetHex)
  287. {
  288. const CStack * targetStack = getStackForHex(targetHex);
  289. switch (action) //display console message, realize selected action
  290. {
  291. case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
  292. return (boost::format(CGI->generaltexth->allTexts[481]) % targetStack->getName()).str(); //Select %s
  293. case PossiblePlayerBattleAction::MOVE_TACTICS:
  294. case PossiblePlayerBattleAction::MOVE_STACK:
  295. if (owner.stacksController->getActiveStack()->hasBonusOfType(Bonus::FLYING))
  296. return (boost::format(CGI->generaltexth->allTexts[295]) % owner.stacksController->getActiveStack()->getName()).str(); //Fly %s here
  297. else
  298. return (boost::format(CGI->generaltexth->allTexts[294]) % owner.stacksController->getActiveStack()->getName()).str(); //Move %s here
  299. case PossiblePlayerBattleAction::ATTACK:
  300. case PossiblePlayerBattleAction::WALK_AND_ATTACK:
  301. case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
  302. {
  303. BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(targetHex);
  304. TDmgRange damage = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), targetStack, attackFromHex);
  305. std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
  306. return (boost::format(CGI->generaltexth->allTexts[36]) % targetStack->getName() % estDmgText).str(); //Attack %s (%s damage)
  307. }
  308. case PossiblePlayerBattleAction::SHOOT:
  309. {
  310. auto const * shooter = owner.stacksController->getActiveStack();
  311. TDmgRange damage = owner.curInt->cb->battleEstimateDamage(shooter, targetStack, shooter->getPosition());
  312. std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
  313. //printing - Shoot %s (%d shots left, %s damage)
  314. return (boost::format(CGI->generaltexth->allTexts[296]) % targetStack->getName() % shooter->shots.available() % estDmgText).str();
  315. }
  316. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  317. return boost::str(boost::format(CGI->generaltexth->allTexts[27]) % getCurrentSpell()->getNameTranslated() % targetStack->getName()); //Cast %s on %s
  318. case PossiblePlayerBattleAction::ANY_LOCATION:
  319. return boost::str(boost::format(CGI->generaltexth->allTexts[26]) % getCurrentSpell()->getNameTranslated()); //Cast %s
  320. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: //we assume that teleport / sacrifice will never be available as random spell
  321. return boost::str(boost::format(CGI->generaltexth->allTexts[301]) % targetStack->getName()); //Cast a spell on %
  322. case PossiblePlayerBattleAction::TELEPORT:
  323. return CGI->generaltexth->allTexts[25]; //Teleport Here
  324. case PossiblePlayerBattleAction::OBSTACLE:
  325. return CGI->generaltexth->allTexts[550];
  326. case PossiblePlayerBattleAction::SACRIFICE:
  327. return (boost::format(CGI->generaltexth->allTexts[549]) % targetStack->getName()).str(); //sacrifice the %s
  328. case PossiblePlayerBattleAction::FREE_LOCATION:
  329. return boost::str(boost::format(CGI->generaltexth->allTexts[26]) % getCurrentSpell()->getNameTranslated()); //Cast %s
  330. case PossiblePlayerBattleAction::HEAL:
  331. return (boost::format(CGI->generaltexth->allTexts[419]) % targetStack->getName()).str(); //Apply first aid to the %s
  332. case PossiblePlayerBattleAction::CATAPULT:
  333. return ""; // TODO
  334. case PossiblePlayerBattleAction::CREATURE_INFO:
  335. return (boost::format(CGI->generaltexth->allTexts[297]) % targetStack->getName()).str();
  336. case PossiblePlayerBattleAction::HERO_INFO:
  337. return CGI->generaltexth->translate("core.genrltxt.417"); // "View Hero Stats"
  338. }
  339. assert(0);
  340. return "";
  341. }
  342. std::string BattleActionsController::actionGetStatusMessageBlocked(PossiblePlayerBattleAction action, BattleHex targetHex)
  343. {
  344. switch (action)
  345. {
  346. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  347. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
  348. return CGI->generaltexth->allTexts[23];
  349. break;
  350. case PossiblePlayerBattleAction::TELEPORT:
  351. return CGI->generaltexth->allTexts[24]; //Invalid Teleport Destination
  352. break;
  353. case PossiblePlayerBattleAction::SACRIFICE:
  354. return CGI->generaltexth->allTexts[543]; //choose army to sacrifice
  355. break;
  356. case PossiblePlayerBattleAction::FREE_LOCATION:
  357. return boost::str(boost::format(CGI->generaltexth->allTexts[181]) % getCurrentSpell()->getNameTranslated()); //No room to place %s here
  358. break;
  359. default:
  360. return "";
  361. }
  362. }
  363. bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, BattleHex targetHex)
  364. {
  365. const CStack * targetStack = getStackForHex(targetHex);
  366. bool targetStackOwned = targetStack && targetStack->owner == owner.curInt->playerID;
  367. switch (action)
  368. {
  369. case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
  370. case PossiblePlayerBattleAction::CREATURE_INFO:
  371. return (targetStack && targetStackOwned);
  372. case PossiblePlayerBattleAction::HERO_INFO:
  373. if (targetHex == BattleHex::HERO_ATTACKER)
  374. return owner.attackingHero != nullptr;
  375. if (targetHex == BattleHex::HERO_DEFENDER)
  376. return owner.defendingHero != nullptr;
  377. return false;
  378. case PossiblePlayerBattleAction::MOVE_TACTICS:
  379. case PossiblePlayerBattleAction::MOVE_STACK:
  380. if (!(targetStack && targetStack->alive())) //we can walk on dead stacks
  381. {
  382. if(canStackMoveHere(owner.stacksController->getActiveStack(), targetHex))
  383. return true;
  384. }
  385. return false;
  386. case PossiblePlayerBattleAction::ATTACK:
  387. case PossiblePlayerBattleAction::WALK_AND_ATTACK:
  388. case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
  389. if(owner.curInt->cb->battleCanAttack(owner.stacksController->getActiveStack(), targetStack, targetHex))
  390. {
  391. if (owner.fieldController->isTileAttackable(targetHex)) // move isTileAttackable to be part of battleCanAttack?
  392. return true;
  393. }
  394. return false;
  395. case PossiblePlayerBattleAction::SHOOT:
  396. return owner.curInt->cb->battleCanShoot(owner.stacksController->getActiveStack(), targetHex);
  397. case PossiblePlayerBattleAction::ANY_LOCATION:
  398. return isCastingPossibleHere(owner.stacksController->getActiveStack(), targetStack, targetHex);
  399. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  400. return targetStack && isCastingPossibleHere(owner.stacksController->getActiveStack(), targetStack, targetHex);
  401. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
  402. if(targetStack && targetStackOwned && targetStack != owner.stacksController->getActiveStack() && targetStack->alive()) //only positive spells for other allied creatures
  403. {
  404. int spellID = owner.curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), targetStack, CBattleInfoCallback::RANDOM_GENIE);
  405. return spellID > -1;
  406. }
  407. return false;
  408. case PossiblePlayerBattleAction::TELEPORT:
  409. {
  410. ui8 skill = getCurrentSpellcaster()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
  411. return owner.curInt->cb->battleCanTeleportTo(owner.stacksController->getSelectedStack(), targetHex, skill);
  412. }
  413. case PossiblePlayerBattleAction::SACRIFICE: //choose our living stack to sacrifice
  414. return targetStack && targetStack != owner.stacksController->getSelectedStack() && targetStackOwned && targetStack->alive();
  415. case PossiblePlayerBattleAction::OBSTACLE:
  416. case PossiblePlayerBattleAction::FREE_LOCATION:
  417. return isCastingPossibleHere(owner.stacksController->getActiveStack(), targetStack, targetHex);
  418. return isCastingPossibleHere(owner.stacksController->getActiveStack(), targetStack, targetHex);
  419. case PossiblePlayerBattleAction::CATAPULT:
  420. return owner.siegeController && owner.siegeController->isAttackableByCatapult(targetHex);
  421. case PossiblePlayerBattleAction::HEAL:
  422. return targetStack && targetStackOwned && targetStack->canBeHealed();
  423. }
  424. assert(0);
  425. return false;
  426. }
  427. void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, BattleHex targetHex)
  428. {
  429. const CStack * targetStack = getStackForHex(targetHex);
  430. switch (action) //display console message, realize selected action
  431. {
  432. case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
  433. {
  434. owner.stackActivated(targetStack);
  435. return;
  436. }
  437. case PossiblePlayerBattleAction::MOVE_TACTICS:
  438. case PossiblePlayerBattleAction::MOVE_STACK:
  439. {
  440. if(owner.stacksController->getActiveStack()->doubleWide())
  441. {
  442. std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(owner.stacksController->getActiveStack());
  443. BattleHex shiftedDest = targetHex.cloneInDirection(owner.stacksController->getActiveStack()->destShiftDir(), false);
  444. if(vstd::contains(acc, targetHex))
  445. owner.giveCommand(EActionType::WALK, targetHex);
  446. else if(vstd::contains(acc, shiftedDest))
  447. owner.giveCommand(EActionType::WALK, shiftedDest);
  448. }
  449. else
  450. {
  451. owner.giveCommand(EActionType::WALK, targetHex);
  452. }
  453. return;
  454. }
  455. case PossiblePlayerBattleAction::ATTACK:
  456. case PossiblePlayerBattleAction::WALK_AND_ATTACK:
  457. case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
  458. {
  459. bool returnAfterAttack = action == PossiblePlayerBattleAction::ATTACK_AND_RETURN;
  460. BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(targetHex);
  461. if(attackFromHex.isValid()) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
  462. {
  463. auto command = new BattleAction(BattleAction::makeMeleeAttack(owner.stacksController->getActiveStack(), targetHex, attackFromHex, returnAfterAttack));
  464. owner.sendCommand(command, owner.stacksController->getActiveStack());
  465. }
  466. return;
  467. }
  468. case PossiblePlayerBattleAction::SHOOT:
  469. {
  470. owner.giveCommand(EActionType::SHOOT, targetHex);
  471. return;
  472. }
  473. case PossiblePlayerBattleAction::HEAL:
  474. {
  475. owner.giveCommand(EActionType::STACK_HEAL, targetHex);
  476. return;
  477. };
  478. case PossiblePlayerBattleAction::CATAPULT:
  479. {
  480. owner.giveCommand(EActionType::CATAPULT, targetHex);
  481. return;
  482. }
  483. case PossiblePlayerBattleAction::CREATURE_INFO:
  484. {
  485. GH.pushIntT<CStackWindow>(targetStack, false);
  486. return;
  487. }
  488. case PossiblePlayerBattleAction::HERO_INFO:
  489. {
  490. if (targetHex == BattleHex::HERO_ATTACKER)
  491. owner.attackingHero->heroLeftClicked();
  492. if (targetHex == BattleHex::HERO_DEFENDER)
  493. owner.defendingHero->heroLeftClicked();
  494. return;
  495. }
  496. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  497. case PossiblePlayerBattleAction::ANY_LOCATION:
  498. case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: //we assume that teleport / sacrifice will never be available as random spell
  499. case PossiblePlayerBattleAction::TELEPORT:
  500. case PossiblePlayerBattleAction::OBSTACLE:
  501. case PossiblePlayerBattleAction::SACRIFICE:
  502. case PossiblePlayerBattleAction::FREE_LOCATION:
  503. {
  504. if (action == PossiblePlayerBattleAction::AIMED_SPELL_CREATURE )
  505. {
  506. if (getCurrentSpell()->id == SpellID::SACRIFICE)
  507. {
  508. heroSpellToCast->aimToHex(targetHex);
  509. possibleActions.push_back(PossiblePlayerBattleAction::SACRIFICE);
  510. owner.stacksController->setSelectedStack(targetStack);
  511. return;
  512. }
  513. if (getCurrentSpell()->id == SpellID::TELEPORT)
  514. {
  515. heroSpellToCast->aimToUnit(targetStack);
  516. possibleActions.push_back(PossiblePlayerBattleAction::TELEPORT);
  517. owner.stacksController->setSelectedStack(targetStack);
  518. return;
  519. }
  520. }
  521. if (!spellcastingModeActive())
  522. {
  523. if (getStackSpellToCast())
  524. {
  525. owner.giveCommand(EActionType::MONSTER_SPELL, targetHex, getStackSpellToCast()->getId());
  526. }
  527. else //unknown random spell
  528. {
  529. owner.giveCommand(EActionType::MONSTER_SPELL, targetHex);
  530. }
  531. }
  532. else
  533. {
  534. assert(getHeroSpellToCast());
  535. switch (getHeroSpellToCast()->id.toEnum())
  536. {
  537. case SpellID::SACRIFICE:
  538. heroSpellToCast->aimToUnit(targetStack);//victim
  539. break;
  540. default:
  541. heroSpellToCast->aimToHex(targetHex);
  542. break;
  543. }
  544. owner.curInt->cb->battleMakeAction(heroSpellToCast.get());
  545. endCastingSpell();
  546. }
  547. owner.stacksController->setSelectedStack(nullptr);
  548. return;
  549. }
  550. }
  551. assert(0);
  552. return;
  553. }
  554. PossiblePlayerBattleAction BattleActionsController::selectAction(BattleHex targetHex)
  555. {
  556. assert(owner.stacksController->getActiveStack() != nullptr);
  557. assert(!possibleActions.empty());
  558. assert(targetHex.isValid());
  559. if (owner.stacksController->getActiveStack() == nullptr)
  560. return PossiblePlayerBattleAction::INVALID;
  561. if (possibleActions.empty())
  562. return PossiblePlayerBattleAction::INVALID;
  563. const CStack * targetStack = getStackForHex(targetHex);
  564. reorderPossibleActionsPriority(owner.stacksController->getActiveStack(), targetStack ? MouseHoveredHexContext::OCCUPIED_HEX : MouseHoveredHexContext::UNOCCUPIED_HEX);
  565. for (PossiblePlayerBattleAction action : possibleActions)
  566. {
  567. if (actionIsLegal(action, targetHex))
  568. return action;
  569. }
  570. return possibleActions.front();
  571. }
  572. void BattleActionsController::onHexHovered(BattleHex hoveredHex)
  573. {
  574. if (owner.stacksController->getActiveStack() == nullptr)
  575. return;
  576. if (hoveredHex == BattleHex::INVALID)
  577. {
  578. if (!currentConsoleMsg.empty())
  579. GH.statusbar->clearIfMatching(currentConsoleMsg);
  580. currentConsoleMsg.clear();
  581. CCS->curh->set(Cursor::Combat::BLOCKED);
  582. return;
  583. }
  584. auto action = selectAction(hoveredHex);
  585. std::string newConsoleMsg;
  586. if (actionIsLegal(action, hoveredHex))
  587. {
  588. actionSetCursor(action, hoveredHex);
  589. newConsoleMsg = actionGetStatusMessage(action, hoveredHex);
  590. }
  591. else
  592. {
  593. actionSetCursorBlocked(action, hoveredHex);
  594. newConsoleMsg = actionGetStatusMessageBlocked(action, hoveredHex);
  595. }
  596. if (!currentConsoleMsg.empty())
  597. GH.statusbar->clearIfMatching(currentConsoleMsg);
  598. if (!newConsoleMsg.empty())
  599. GH.statusbar->write(newConsoleMsg);
  600. currentConsoleMsg = newConsoleMsg;
  601. }
  602. void BattleActionsController::onHoverEnded()
  603. {
  604. CCS->curh->set(Cursor::Combat::POINTER);
  605. if (!currentConsoleMsg.empty())
  606. GH.statusbar->clearIfMatching(currentConsoleMsg);
  607. currentConsoleMsg.clear();
  608. }
  609. void BattleActionsController::onHexLeftClicked(BattleHex clickedHex)
  610. {
  611. if (owner.stacksController->getActiveStack() == nullptr)
  612. return;
  613. auto action = selectAction(clickedHex);
  614. std::string newConsoleMsg;
  615. if (!actionIsLegal(action, clickedHex))
  616. return;
  617. actionRealize(action, clickedHex);
  618. GH.statusbar->clear();
  619. }
  620. void BattleActionsController::tryActivateStackSpellcasting(const CStack *casterStack)
  621. {
  622. const auto spellcaster = casterStack->getBonusLocalFirst(Selector::type()(Bonus::SPELLCASTER));
  623. if(casterStack->canCast() && spellcaster)
  624. {
  625. // faerie dragon can cast only one, randomly selected spell until their next move
  626. //TODO: faerie dragon type spell should be selected by server
  627. creatureSpellToCast = owner.curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), casterStack, CBattleInfoCallback::RANDOM_AIMED).toSpell();
  628. }
  629. }
  630. const spells::Caster * BattleActionsController::getCurrentSpellcaster() const
  631. {
  632. if (heroSpellToCast)
  633. return owner.getActiveHero();
  634. else
  635. return owner.stacksController->getActiveStack();
  636. }
  637. spells::Mode BattleActionsController::getCurrentCastMode() const
  638. {
  639. if (heroSpellToCast)
  640. return spells::Mode::HERO;
  641. else
  642. return spells::Mode::CREATURE_ACTIVE;
  643. }
  644. bool BattleActionsController::isCastingPossibleHere(const CStack *casterStack, const CStack *targetStack, BattleHex targetHex)
  645. {
  646. auto currentSpell = getCurrentSpell();
  647. assert(currentSpell);
  648. if (!currentSpell)
  649. return false;
  650. auto caster = getCurrentSpellcaster();
  651. const spells::Mode mode = heroSpellToCast ? spells::Mode::HERO : spells::Mode::CREATURE_ACTIVE;
  652. spells::Target target;
  653. target.emplace_back(targetHex);
  654. spells::BattleCast cast(owner.curInt->cb.get(), caster, mode, currentSpell);
  655. auto m = currentSpell->battleMechanics(&cast);
  656. spells::detail::ProblemImpl problem; //todo: display problem in status bar
  657. return m->canBeCastAt(target, problem);
  658. }
  659. bool BattleActionsController::canStackMoveHere(const CStack * stackToMove, BattleHex myNumber) const
  660. {
  661. std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(stackToMove);
  662. BattleHex shiftedDest = myNumber.cloneInDirection(stackToMove->destShiftDir(), false);
  663. if (vstd::contains(acc, myNumber))
  664. return true;
  665. else if (stackToMove->doubleWide() && vstd::contains(acc, shiftedDest))
  666. return true;
  667. else
  668. return false;
  669. }
  670. void BattleActionsController::activateStack()
  671. {
  672. const CStack * s = owner.stacksController->getActiveStack();
  673. if(s)
  674. {
  675. tryActivateStackSpellcasting(s);
  676. possibleActions = getPossibleActionsForStack(s);
  677. std::list<PossiblePlayerBattleAction> actionsToSelect;
  678. if(!possibleActions.empty())
  679. {
  680. switch(possibleActions.front())
  681. {
  682. case PossiblePlayerBattleAction::SHOOT:
  683. actionsToSelect.push_back(possibleActions.front());
  684. actionsToSelect.push_back(PossiblePlayerBattleAction::ATTACK);
  685. break;
  686. case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
  687. actionsToSelect.push_back(possibleActions.front());
  688. actionsToSelect.push_back(PossiblePlayerBattleAction::WALK_AND_ATTACK);
  689. break;
  690. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  691. actionsToSelect.push_back(possibleActions.front());
  692. break;
  693. }
  694. }
  695. owner.windowObject->setAlternativeActions(actionsToSelect);
  696. }
  697. }
  698. void BattleActionsController::onHexRightClicked(BattleHex clickedHex)
  699. {
  700. auto selectedStack = owner.curInt->cb->battleGetStackByPos(clickedHex, true);
  701. if (selectedStack != nullptr)
  702. GH.pushIntT<CStackWindow>(selectedStack, true);
  703. if (clickedHex == BattleHex::HERO_ATTACKER && owner.attackingHero)
  704. owner.attackingHero->heroRightClicked();
  705. if (clickedHex == BattleHex::HERO_DEFENDER && owner.defendingHero)
  706. owner.defendingHero->heroRightClicked();
  707. }
  708. bool BattleActionsController::spellcastingModeActive() const
  709. {
  710. return heroSpellToCast != nullptr;;
  711. }
  712. bool BattleActionsController::currentActionSpellcasting(BattleHex hoveredHex)
  713. {
  714. if (heroSpellToCast)
  715. return true;
  716. if (!owner.stacksController->getActiveStack())
  717. return false;
  718. auto action = selectAction(hoveredHex);
  719. return
  720. action == PossiblePlayerBattleAction::ANY_LOCATION ||
  721. action == PossiblePlayerBattleAction::NO_LOCATION ||
  722. action == PossiblePlayerBattleAction::FREE_LOCATION ||
  723. action == PossiblePlayerBattleAction::AIMED_SPELL_CREATURE ||
  724. action == PossiblePlayerBattleAction::OBSTACLE;
  725. }
  726. const std::vector<PossiblePlayerBattleAction> & BattleActionsController::getPossibleActions() const
  727. {
  728. return possibleActions;
  729. }
  730. void BattleActionsController::removePossibleAction(PossiblePlayerBattleAction action)
  731. {
  732. vstd::erase(possibleActions, action);
  733. }
  734. void BattleActionsController::pushFrontPossibleAction(PossiblePlayerBattleAction action)
  735. {
  736. possibleActions.insert(possibleActions.begin(), action);
  737. }