CBattleFieldController.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. /*
  2. * CBattleFieldController.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 "CBattleFieldController.h"
  12. #include "CBattleInterface.h"
  13. #include "CBattleActionsController.h"
  14. #include "CBattleInterfaceClasses.h"
  15. #include "CBattleSiegeController.h"
  16. #include "CBattleStacksController.h"
  17. #include "CBattleObstacleController.h"
  18. #include "../CGameInfo.h"
  19. #include "../CPlayerInterface.h"
  20. #include "../gui/CAnimation.h"
  21. #include "../gui/CCanvas.h"
  22. #include "../gui/CGuiHandler.h"
  23. #include "../gui/CCursorHandler.h"
  24. #include "../../CCallback.h"
  25. #include "../../lib/BattleFieldHandler.h"
  26. #include "../../lib/CConfigHandler.h"
  27. #include "../../lib/CStack.h"
  28. #include "../../lib/spells/ISpellMechanics.h"
  29. CBattleFieldController::CBattleFieldController(CBattleInterface * owner):
  30. owner(owner),
  31. attackingHex(BattleHex::INVALID)
  32. {
  33. OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
  34. pos.w = owner->pos.w;
  35. pos.h = owner->pos.h;
  36. //preparing cells and hexes
  37. cellBorder = IImage::createFromFile("CCELLGRD.BMP");
  38. cellShade = IImage::createFromFile("CCELLSHD.BMP");
  39. if(!owner->siegeController)
  40. {
  41. auto bfieldType = owner->curInt->cb->battleGetBattlefieldType();
  42. if(bfieldType == BattleField::NONE)
  43. logGlobal->error("Invalid battlefield returned for current battle");
  44. else
  45. background = IImage::createFromFile(bfieldType.getInfo()->graphics);
  46. }
  47. else
  48. {
  49. std::string backgroundName = owner->siegeController->getBattleBackgroundName();
  50. background = IImage::createFromFile(backgroundName);
  51. }
  52. //preparing graphic with cell borders
  53. cellBorders = std::make_shared<CCanvas>(Point(background->width(), background->height()));
  54. for (int i=0; i<GameConstants::BFIELD_SIZE; ++i)
  55. {
  56. if ( i % GameConstants::BFIELD_WIDTH == 0)
  57. continue;
  58. if ( i % GameConstants::BFIELD_WIDTH == GameConstants::BFIELD_WIDTH - 1)
  59. continue;
  60. cellBorders->draw(cellBorder, hexPositionLocal(i).topLeft());
  61. }
  62. backgroundWithHexes = std::make_shared<CCanvas>(Point(background->width(), background->height()));
  63. for (int h = 0; h < GameConstants::BFIELD_SIZE; ++h)
  64. {
  65. auto hex = std::make_shared<CClickableHex>();
  66. hex->myNumber = h;
  67. hex->pos = hexPosition(h);
  68. hex->myInterface = owner;
  69. bfield.push_back(hex);
  70. }
  71. auto accessibility = owner->curInt->cb->getAccesibility();
  72. for(int i = 0; i < accessibility.size(); i++)
  73. stackCountOutsideHexes[i] = (accessibility[i] == EAccessibility::ACCESSIBLE);
  74. }
  75. void CBattleFieldController::showBackground(std::shared_ptr<CCanvas> canvas)
  76. {
  77. if (owner->stacksController->getActiveStack() != nullptr ) //&& creAnims[stacksController->getActiveStack()->ID]->isIdle() //show everything with range
  78. showBackgroundImageWithHexes(canvas);
  79. else
  80. showBackgroundImage(canvas);
  81. showHighlightedHexes(canvas);
  82. }
  83. void CBattleFieldController::showBackgroundImage(std::shared_ptr<CCanvas> canvas)
  84. {
  85. canvas->draw(background, owner->pos.topLeft());
  86. owner->obstacleController->showAbsoluteObstacles(canvas, pos.topLeft());
  87. if ( owner->siegeController )
  88. owner->siegeController->showAbsoluteObstacles(canvas, pos.topLeft());
  89. if (settings["battle"]["cellBorders"].Bool())
  90. canvas->draw(cellBorders, owner->pos.topLeft());
  91. }
  92. void CBattleFieldController::showBackgroundImageWithHexes(std::shared_ptr<CCanvas> canvas)
  93. {
  94. canvas->draw(backgroundWithHexes, owner->pos.topLeft());
  95. }
  96. void CBattleFieldController::redrawBackgroundWithHexes()
  97. {
  98. const CStack *activeStack = owner->stacksController->getActiveStack();
  99. std::vector<BattleHex> attackableHexes;
  100. if (activeStack)
  101. occupyableHexes = owner->curInt->cb->battleGetAvailableHexes(activeStack, true, &attackableHexes);
  102. auto accessibility = owner->curInt->cb->getAccesibility();
  103. for(int i = 0; i < accessibility.size(); i++)
  104. stackCountOutsideHexes[i] = (accessibility[i] == EAccessibility::ACCESSIBLE);
  105. //prepare background graphic with hexes and shaded hexes
  106. backgroundWithHexes->draw(background, Point(0,0));
  107. owner->obstacleController->showAbsoluteObstacles(backgroundWithHexes, Point(0,0));
  108. if ( owner->siegeController )
  109. owner->siegeController->showAbsoluteObstacles(backgroundWithHexes, Point(0,0));
  110. if (settings["battle"]["stackRange"].Bool())
  111. {
  112. std::vector<BattleHex> hexesToShade = occupyableHexes;
  113. hexesToShade.insert(hexesToShade.end(), attackableHexes.begin(), attackableHexes.end());
  114. for (BattleHex hex : hexesToShade)
  115. {
  116. backgroundWithHexes->draw(cellShade, hexPositionLocal(hex).topLeft());
  117. }
  118. }
  119. if(settings["battle"]["cellBorders"].Bool())
  120. backgroundWithHexes->draw(cellBorders, Point(0, 0));
  121. }
  122. void CBattleFieldController::showHighlightedHex(std::shared_ptr<CCanvas> to, BattleHex hex, bool darkBorder)
  123. {
  124. Point hexPos = hexPosition(hex).topLeft();
  125. to->draw(cellShade, hexPos);
  126. if(!darkBorder && settings["battle"]["cellBorders"].Bool())
  127. to->draw(cellBorder, hexPos);
  128. }
  129. std::set<BattleHex> CBattleFieldController::getHighlightedHexesStackRange()
  130. {
  131. std::set<BattleHex> result;
  132. if ( !owner->stacksController->getActiveStack())
  133. return result;
  134. if ( !settings["battle"]["stackRange"].Bool())
  135. return result;
  136. auto hoveredHex = getHoveredHex();
  137. std::set<BattleHex> set = owner->curInt->cb->battleGetAttackedHexes(owner->stacksController->getActiveStack(), hoveredHex, attackingHex);
  138. for(BattleHex hex : set)
  139. result.insert(hex);
  140. // display the movement shadow of the stack at b (i.e. stack under mouse)
  141. const CStack * const shere = owner->curInt->cb->battleGetStackByPos(hoveredHex, false);
  142. if(shere && shere != owner->stacksController->getActiveStack() && shere->alive())
  143. {
  144. std::vector<BattleHex> v = owner->curInt->cb->battleGetAvailableHexes(shere, true, nullptr);
  145. for(BattleHex hex : v)
  146. result.insert(hex);
  147. }
  148. return result;
  149. }
  150. std::set<BattleHex> CBattleFieldController::getHighlightedHexesSpellRange()
  151. {
  152. std::set<BattleHex> result;
  153. auto hoveredHex = getHoveredHex();
  154. if(!settings["battle"]["mouseShadow"].Bool())
  155. return result;
  156. const spells::Caster *caster = nullptr;
  157. const CSpell *spell = nullptr;
  158. spells::Mode mode = spells::Mode::HERO;
  159. if(owner->actionsController->spellcastingModeActive())//hero casts spell
  160. {
  161. spell = owner->actionsController->selectedSpell().toSpell();
  162. caster = owner->getActiveHero();
  163. }
  164. else if(owner->stacksController->activeStackSpellToCast() != SpellID::NONE)//stack casts spell
  165. {
  166. spell = SpellID(owner->stacksController->activeStackSpellToCast()).toSpell();
  167. caster = owner->stacksController->getActiveStack();
  168. mode = spells::Mode::CREATURE_ACTIVE;
  169. }
  170. if(caster && spell) //when casting spell
  171. {
  172. // printing shaded hex(es)
  173. spells::BattleCast event(owner->curInt->cb.get(), caster, mode, spell);
  174. auto shaded = spell->battleMechanics(&event)->rangeInHexes(hoveredHex);
  175. for(BattleHex shadedHex : shaded)
  176. {
  177. if((shadedHex.getX() != 0) && (shadedHex.getX() != GameConstants::BFIELD_WIDTH - 1))
  178. result.insert(shadedHex);
  179. }
  180. }
  181. else if(owner->active) //always highlight pointed hex
  182. {
  183. if(hoveredHex.getX() != 0 && hoveredHex.getX() != GameConstants::BFIELD_WIDTH - 1)
  184. result.insert(hoveredHex);
  185. }
  186. return result;
  187. }
  188. void CBattleFieldController::showHighlightedHexes(std::shared_ptr<CCanvas> canvas)
  189. {
  190. std::set<BattleHex> hoveredStack = getHighlightedHexesStackRange();
  191. std::set<BattleHex> hoveredMouse = getHighlightedHexesSpellRange();
  192. for(int b=0; b<GameConstants::BFIELD_SIZE; ++b)
  193. {
  194. bool stack = hoveredStack.count(b);
  195. bool mouse = hoveredMouse.count(b);
  196. if ( stack && mouse )
  197. {
  198. // area where enemy stack can move AND affected by mouse cursor - create darker highlight by blitting twice
  199. showHighlightedHex(canvas, b, true);
  200. showHighlightedHex(canvas, b, true);
  201. }
  202. if ( !stack && mouse )
  203. {
  204. showHighlightedHex(canvas, b, true);
  205. }
  206. if ( stack && !mouse )
  207. {
  208. showHighlightedHex(canvas, b, false);
  209. }
  210. }
  211. }
  212. Rect CBattleFieldController::hexPositionLocal(BattleHex hex) const
  213. {
  214. int x = 14 + ((hex.getY())%2==0 ? 22 : 0) + 44*hex.getX();
  215. int y = 86 + 42 *hex.getY();
  216. int w = cellShade->width();
  217. int h = cellShade->height();
  218. return Rect(x, y, w, h);
  219. }
  220. Rect CBattleFieldController::hexPosition(BattleHex hex) const
  221. {
  222. return hexPositionLocal(hex) + owner->pos.topLeft();
  223. }
  224. bool CBattleFieldController::isPixelInHex(Point const & position)
  225. {
  226. return !cellShade->isTransparent(position);
  227. }
  228. BattleHex CBattleFieldController::getHoveredHex()
  229. {
  230. for ( auto const & hex : bfield)
  231. if (hex->hovered && hex->strictHovered)
  232. return hex->myNumber;
  233. return BattleHex::INVALID;
  234. }
  235. void CBattleFieldController::setBattleCursor(BattleHex myNumber)
  236. {
  237. Rect hoveredHexPos = hexPosition(myNumber);
  238. CCursorHandler *cursor = CCS->curh;
  239. const double subdividingAngle = 2.0*M_PI/6.0; // Divide a hex into six sectors.
  240. const double hexMidX = hoveredHexPos.x + hoveredHexPos.w/2.0;
  241. const double hexMidY = hoveredHexPos.y + hoveredHexPos.h/2.0;
  242. const double cursorHexAngle = M_PI - atan2(hexMidY - cursor->ypos, cursor->xpos - hexMidX) + subdividingAngle/2; //TODO: refactor this nightmare
  243. const double sector = fmod(cursorHexAngle/subdividingAngle, 6.0);
  244. const int zigzagCorrection = !((myNumber/GameConstants::BFIELD_WIDTH)%2); // Off-by-one correction needed to deal with the odd battlefield rows.
  245. std::vector<int> sectorCursor; // From left to bottom left.
  246. sectorCursor.push_back(8);
  247. sectorCursor.push_back(9);
  248. sectorCursor.push_back(10);
  249. sectorCursor.push_back(11);
  250. sectorCursor.push_back(12);
  251. sectorCursor.push_back(7);
  252. const bool doubleWide = owner->stacksController->getActiveStack()->doubleWide();
  253. bool aboveAttackable = true, belowAttackable = true;
  254. // Exclude directions which cannot be attacked from.
  255. // Check to the left.
  256. if (myNumber%GameConstants::BFIELD_WIDTH <= 1 || !vstd::contains(occupyableHexes, myNumber - 1))
  257. {
  258. sectorCursor[0] = -1;
  259. }
  260. // Check top left, top right as well as above for 2-hex creatures.
  261. if (myNumber/GameConstants::BFIELD_WIDTH == 0)
  262. {
  263. sectorCursor[1] = -1;
  264. sectorCursor[2] = -1;
  265. aboveAttackable = false;
  266. }
  267. else
  268. {
  269. if (doubleWide)
  270. {
  271. bool attackRow[4] = {true, true, true, true};
  272. if (myNumber%GameConstants::BFIELD_WIDTH <= 1 || !vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH - 2 + zigzagCorrection))
  273. attackRow[0] = false;
  274. if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
  275. attackRow[1] = false;
  276. if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH + zigzagCorrection))
  277. attackRow[2] = false;
  278. if (myNumber%GameConstants::BFIELD_WIDTH >= GameConstants::BFIELD_WIDTH - 2 || !vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH + 1 + zigzagCorrection))
  279. attackRow[3] = false;
  280. if (!(attackRow[0] && attackRow[1]))
  281. sectorCursor[1] = -1;
  282. if (!(attackRow[1] && attackRow[2]))
  283. aboveAttackable = false;
  284. if (!(attackRow[2] && attackRow[3]))
  285. sectorCursor[2] = -1;
  286. }
  287. else
  288. {
  289. if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
  290. sectorCursor[1] = -1;
  291. if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH + zigzagCorrection))
  292. sectorCursor[2] = -1;
  293. }
  294. }
  295. // Check to the right.
  296. if (myNumber%GameConstants::BFIELD_WIDTH >= GameConstants::BFIELD_WIDTH - 2 || !vstd::contains(occupyableHexes, myNumber + 1))
  297. {
  298. sectorCursor[3] = -1;
  299. }
  300. // Check bottom right, bottom left as well as below for 2-hex creatures.
  301. if (myNumber/GameConstants::BFIELD_WIDTH == GameConstants::BFIELD_HEIGHT - 1)
  302. {
  303. sectorCursor[4] = -1;
  304. sectorCursor[5] = -1;
  305. belowAttackable = false;
  306. }
  307. else
  308. {
  309. if (doubleWide)
  310. {
  311. bool attackRow[4] = {true, true, true, true};
  312. if (myNumber%GameConstants::BFIELD_WIDTH <= 1 || !vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH - 2 + zigzagCorrection))
  313. attackRow[0] = false;
  314. if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
  315. attackRow[1] = false;
  316. if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH + zigzagCorrection))
  317. attackRow[2] = false;
  318. if (myNumber%GameConstants::BFIELD_WIDTH >= GameConstants::BFIELD_WIDTH - 2 || !vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH + 1 + zigzagCorrection))
  319. attackRow[3] = false;
  320. if (!(attackRow[0] && attackRow[1]))
  321. sectorCursor[5] = -1;
  322. if (!(attackRow[1] && attackRow[2]))
  323. belowAttackable = false;
  324. if (!(attackRow[2] && attackRow[3]))
  325. sectorCursor[4] = -1;
  326. }
  327. else
  328. {
  329. if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH + zigzagCorrection))
  330. sectorCursor[4] = -1;
  331. if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
  332. sectorCursor[5] = -1;
  333. }
  334. }
  335. // Determine index from sector.
  336. int cursorIndex;
  337. if (doubleWide)
  338. {
  339. sectorCursor.insert(sectorCursor.begin() + 5, belowAttackable ? 13 : -1);
  340. sectorCursor.insert(sectorCursor.begin() + 2, aboveAttackable ? 14 : -1);
  341. if (sector < 1.5)
  342. cursorIndex = static_cast<int>(sector);
  343. else if (sector >= 1.5 && sector < 2.5)
  344. cursorIndex = 2;
  345. else if (sector >= 2.5 && sector < 4.5)
  346. cursorIndex = (int) sector + 1;
  347. else if (sector >= 4.5 && sector < 5.5)
  348. cursorIndex = 6;
  349. else
  350. cursorIndex = (int) sector + 2;
  351. }
  352. else
  353. {
  354. cursorIndex = static_cast<int>(sector);
  355. }
  356. // Generally should NEVER happen, but to avoid the possibility of having endless loop below... [#1016]
  357. if (!vstd::contains_if (sectorCursor, [](int sc) { return sc != -1; }))
  358. {
  359. logGlobal->error("Error: for hex %d cannot find a hex to attack from!", myNumber);
  360. attackingHex = -1;
  361. return;
  362. }
  363. // Find the closest direction attackable, starting with the right one.
  364. // FIXME: Is this really how the original H3 client does it?
  365. int i = 0;
  366. while (sectorCursor[(cursorIndex + i)%sectorCursor.size()] == -1) //Why hast thou forsaken me?
  367. i = i <= 0 ? 1 - i : -i; // 0, 1, -1, 2, -2, 3, -3 etc..
  368. int index = (cursorIndex + i)%sectorCursor.size(); //hopefully we get elements from sectorCursor
  369. cursor->changeGraphic(ECursor::COMBAT, sectorCursor[index]);
  370. switch (index)
  371. {
  372. case 0:
  373. attackingHex = myNumber - 1; //left
  374. break;
  375. case 1:
  376. attackingHex = myNumber - GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection; //top left
  377. break;
  378. case 2:
  379. attackingHex = myNumber - GameConstants::BFIELD_WIDTH + zigzagCorrection; //top right
  380. break;
  381. case 3:
  382. attackingHex = myNumber + 1; //right
  383. break;
  384. case 4:
  385. attackingHex = myNumber + GameConstants::BFIELD_WIDTH + zigzagCorrection; //bottom right
  386. break;
  387. case 5:
  388. attackingHex = myNumber + GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection; //bottom left
  389. break;
  390. }
  391. BattleHex hex(attackingHex);
  392. if (!hex.isValid())
  393. attackingHex = -1;
  394. }
  395. BattleHex CBattleFieldController::fromWhichHexAttack(BattleHex myNumber)
  396. {
  397. //TODO far too much repeating code
  398. BattleHex destHex;
  399. switch(CCS->curh->frame)
  400. {
  401. case 12: //from bottom right
  402. {
  403. bool doubleWide = owner->stacksController->getActiveStack()->doubleWide();
  404. destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 ) +
  405. (owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER && doubleWide ? 1 : 0);
  406. if(vstd::contains(occupyableHexes, destHex))
  407. return destHex;
  408. else if(owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER)
  409. {
  410. if (vstd::contains(occupyableHexes, destHex+1))
  411. return destHex+1;
  412. }
  413. else //if we are defender
  414. {
  415. if(vstd::contains(occupyableHexes, destHex-1))
  416. return destHex-1;
  417. }
  418. break;
  419. }
  420. case 7: //from bottom left
  421. {
  422. destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH-1 : GameConstants::BFIELD_WIDTH );
  423. if (vstd::contains(occupyableHexes, destHex))
  424. return destHex;
  425. else if(owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER)
  426. {
  427. if(vstd::contains(occupyableHexes, destHex+1))
  428. return destHex+1;
  429. }
  430. else //we are defender
  431. {
  432. if(vstd::contains(occupyableHexes, destHex-1))
  433. return destHex-1;
  434. }
  435. break;
  436. }
  437. case 8: //from left
  438. {
  439. if(owner->stacksController->getActiveStack()->doubleWide() && owner->stacksController->getActiveStack()->side == BattleSide::DEFENDER)
  440. {
  441. std::vector<BattleHex> acc = owner->curInt->cb->battleGetAvailableHexes(owner->stacksController->getActiveStack());
  442. if (vstd::contains(acc, myNumber))
  443. return myNumber - 1;
  444. else
  445. return myNumber - 2;
  446. }
  447. else
  448. {
  449. return myNumber - 1;
  450. }
  451. break;
  452. }
  453. case 9: //from top left
  454. {
  455. destHex = myNumber - ((myNumber/GameConstants::BFIELD_WIDTH) % 2 ? GameConstants::BFIELD_WIDTH + 1 : GameConstants::BFIELD_WIDTH);
  456. if(vstd::contains(occupyableHexes, destHex))
  457. return destHex;
  458. else if(owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER)
  459. {
  460. if(vstd::contains(occupyableHexes, destHex+1))
  461. return destHex+1;
  462. }
  463. else //if we are defender
  464. {
  465. if(vstd::contains(occupyableHexes, destHex-1))
  466. return destHex-1;
  467. }
  468. break;
  469. }
  470. case 10: //from top right
  471. {
  472. bool doubleWide = owner->stacksController->getActiveStack()->doubleWide();
  473. destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 ) +
  474. (owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER && doubleWide ? 1 : 0);
  475. if(vstd::contains(occupyableHexes, destHex))
  476. return destHex;
  477. else if(owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER)
  478. {
  479. if(vstd::contains(occupyableHexes, destHex+1))
  480. return destHex+1;
  481. }
  482. else //if we are defender
  483. {
  484. if(vstd::contains(occupyableHexes, destHex-1))
  485. return destHex-1;
  486. }
  487. break;
  488. }
  489. case 11: //from right
  490. {
  491. if(owner->stacksController->getActiveStack()->doubleWide() && owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER)
  492. {
  493. std::vector<BattleHex> acc = owner->curInt->cb->battleGetAvailableHexes(owner->stacksController->getActiveStack());
  494. if(vstd::contains(acc, myNumber))
  495. return myNumber + 1;
  496. else
  497. return myNumber + 2;
  498. }
  499. else
  500. {
  501. return myNumber + 1;
  502. }
  503. break;
  504. }
  505. case 13: //from bottom
  506. {
  507. destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 );
  508. if(vstd::contains(occupyableHexes, destHex))
  509. return destHex;
  510. else if(owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER)
  511. {
  512. if(vstd::contains(occupyableHexes, destHex+1))
  513. return destHex+1;
  514. }
  515. else //if we are defender
  516. {
  517. if(vstd::contains(occupyableHexes, destHex-1))
  518. return destHex-1;
  519. }
  520. break;
  521. }
  522. case 14: //from top
  523. {
  524. destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 );
  525. if (vstd::contains(occupyableHexes, destHex))
  526. return destHex;
  527. else if(owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER)
  528. {
  529. if(vstd::contains(occupyableHexes, destHex+1))
  530. return destHex+1;
  531. }
  532. else //if we are defender
  533. {
  534. if(vstd::contains(occupyableHexes, destHex-1))
  535. return destHex-1;
  536. }
  537. break;
  538. }
  539. }
  540. return -1;
  541. }
  542. bool CBattleFieldController::isTileAttackable(const BattleHex & number) const
  543. {
  544. for (auto & elem : occupyableHexes)
  545. {
  546. if (BattleHex::mutualPosition(elem, number) != -1 || elem == number)
  547. return true;
  548. }
  549. return false;
  550. }
  551. bool CBattleFieldController::stackCountOutsideHex(const BattleHex & number) const
  552. {
  553. return stackCountOutsideHexes[number];
  554. }