BattleActionsController.cpp 30 KB

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