CBattleInfoCallback.cpp 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607
  1. /*
  2. * CBattleCallback.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 "CBattleInfoCallback.h"
  12. #include "../CStack.h"
  13. #include "BattleInfo.h"
  14. #include "../NetPacks.h"
  15. #include "../spells/CSpellHandler.h"
  16. #include "../mapObjects/CGTownInstance.h"
  17. namespace SiegeStuffThatShouldBeMovedToHandlers // <=== TODO
  18. {
  19. static void retreiveTurretDamageRange(const CGTownInstance * town, const CStack * turret, double & outMinDmg, double & outMaxDmg)
  20. {
  21. assert(turret->getCreature()->idNumber == CreatureID::ARROW_TOWERS);
  22. assert(town);
  23. assert(turret->position >= -4 && turret->position <= -2);
  24. float multiplier = (turret->position == -2) ? 1 : 0.5;
  25. int baseMin = 6;
  26. int baseMax = 10;
  27. outMinDmg = multiplier * (baseMin + town->getTownLevel() * 2);
  28. outMaxDmg = multiplier * (baseMax + town->getTownLevel() * 3);
  29. }
  30. static BattleHex lineToWallHex(int line) //returns hex with wall in given line (y coordinate)
  31. {
  32. static const BattleHex lineToHex[] = {12, 29, 45, 62, 78, 95, 112, 130, 147, 165, 182};
  33. return lineToHex[line];
  34. }
  35. static bool sameSideOfWall(BattleHex pos1, BattleHex pos2)
  36. {
  37. const int wallInStackLine = lineToWallHex(pos1.getY());
  38. const int wallInDestLine = lineToWallHex(pos2.getY());
  39. const bool stackLeft = pos1 < wallInStackLine;
  40. const bool destLeft = pos2 < wallInDestLine;
  41. return stackLeft == destLeft;
  42. }
  43. // parts of wall
  44. static const std::pair<int, EWallPart::EWallPart> wallParts[] =
  45. {
  46. std::make_pair(50, EWallPart::KEEP),
  47. std::make_pair(183, EWallPart::BOTTOM_TOWER),
  48. std::make_pair(182, EWallPart::BOTTOM_WALL),
  49. std::make_pair(130, EWallPart::BELOW_GATE),
  50. std::make_pair(78, EWallPart::OVER_GATE),
  51. std::make_pair(29, EWallPart::UPPER_WALL),
  52. std::make_pair(12, EWallPart::UPPER_TOWER),
  53. std::make_pair(95, EWallPart::INDESTRUCTIBLE_PART_OF_GATE),
  54. std::make_pair(96, EWallPart::GATE),
  55. std::make_pair(45, EWallPart::INDESTRUCTIBLE_PART),
  56. std::make_pair(62, EWallPart::INDESTRUCTIBLE_PART),
  57. std::make_pair(112, EWallPart::INDESTRUCTIBLE_PART),
  58. std::make_pair(147, EWallPart::INDESTRUCTIBLE_PART),
  59. std::make_pair(165, EWallPart::INDESTRUCTIBLE_PART)
  60. };
  61. static EWallPart::EWallPart hexToWallPart(BattleHex hex)
  62. {
  63. for(auto & elem : wallParts)
  64. {
  65. if(elem.first == hex)
  66. return elem.second;
  67. }
  68. return EWallPart::INVALID; //not found!
  69. }
  70. static BattleHex WallPartToHex(EWallPart::EWallPart part)
  71. {
  72. for(auto & elem : wallParts)
  73. {
  74. if(elem.second == part)
  75. return elem.first;
  76. }
  77. return BattleHex::INVALID; //not found!
  78. }
  79. }
  80. using namespace SiegeStuffThatShouldBeMovedToHandlers;
  81. ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(const ISpellCaster * caster, ECastingMode::ECastingMode mode) const
  82. {
  83. RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
  84. if(caster == nullptr)
  85. {
  86. logGlobal->error("CBattleInfoCallback::battleCanCastSpell: no spellcaster.");
  87. return ESpellCastProblem::INVALID;
  88. }
  89. const PlayerColor player = caster->getOwner();
  90. const auto side = playerToSide(player);
  91. if(!side)
  92. return ESpellCastProblem::INVALID;
  93. if(!battleDoWeKnowAbout(side.get()))
  94. {
  95. logGlobal->warnStream() << "You can't check if enemy can cast given spell!";
  96. return ESpellCastProblem::INVALID;
  97. }
  98. if(battleTacticDist())
  99. return ESpellCastProblem::ONGOING_TACTIC_PHASE;
  100. switch (mode)
  101. {
  102. case ECastingMode::HERO_CASTING:
  103. {
  104. if(battleCastSpells(side.get()) > 0)
  105. return ESpellCastProblem::ALREADY_CASTED_THIS_TURN;
  106. auto hero = dynamic_cast<const CGHeroInstance *>(caster);
  107. if(!hero)
  108. return ESpellCastProblem::NO_HERO_TO_CAST_SPELL;
  109. if(hero->hasBonusOfType(Bonus::BLOCK_ALL_MAGIC))
  110. return ESpellCastProblem::MAGIC_IS_BLOCKED;
  111. }
  112. break;
  113. default:
  114. break;
  115. }
  116. return ESpellCastProblem::OK;
  117. }
  118. si8 CBattleInfoCallback::battleHasWallPenalty(const CStack * stack, BattleHex destHex) const
  119. {
  120. return battleHasWallPenalty(stack, stack->position, destHex);
  121. }
  122. si8 CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * bonusBearer, BattleHex shooterPosition, BattleHex destHex) const
  123. {
  124. RETURN_IF_NOT_BATTLE(false);
  125. if (!battleGetSiegeLevel() || bonusBearer->hasBonusOfType(Bonus::NO_WALL_PENALTY))
  126. return false;
  127. const int wallInStackLine = lineToWallHex(shooterPosition.getY());
  128. const int wallInDestLine = lineToWallHex(destHex.getY());
  129. const bool stackLeft = shooterPosition < wallInStackLine;
  130. const bool destRight = destHex > wallInDestLine;
  131. if (stackLeft && destRight) //shooting from outside to inside
  132. {
  133. int row = (shooterPosition + destHex) / (2 * GameConstants::BFIELD_WIDTH);
  134. if (shooterPosition > destHex && ((destHex % GameConstants::BFIELD_WIDTH - shooterPosition % GameConstants::BFIELD_WIDTH) < 2)) //shooting up high
  135. row -= 2;
  136. const int wallPos = lineToWallHex(row);
  137. if (!isWallPartPotentiallyAttackable(battleHexToWallPart(wallPos))) return true;
  138. }
  139. return false;
  140. }
  141. si8 CBattleInfoCallback::battleCanTeleportTo(const CStack * stack, BattleHex destHex, int telportLevel) const
  142. {
  143. RETURN_IF_NOT_BATTLE(false);
  144. if (!getAccesibility(stack).accessible(destHex, stack))
  145. return false;
  146. const ui8 siegeLevel = battleGetSiegeLevel();
  147. //check for wall
  148. //advanced teleport can pass wall of fort|citadel, expert - of castle
  149. if ((siegeLevel > CGTownInstance::NONE && telportLevel < 2) || (siegeLevel >= CGTownInstance::CASTLE && telportLevel < 3))
  150. return sameSideOfWall(stack->position, destHex);
  151. return true;
  152. }
  153. std::set<BattleHex> CBattleInfoCallback::battleGetAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos /*= BattleHex::INVALID*/) const
  154. {
  155. std::set<BattleHex> attackedHexes;
  156. RETURN_IF_NOT_BATTLE(attackedHexes);
  157. AttackableTiles at = getPotentiallyAttackableHexes(attacker, destinationTile, attackerPos);
  158. for (BattleHex tile : at.hostileCreaturePositions)
  159. {
  160. const CStack * st = battleGetStackByPos(tile, true);
  161. if(st && st->owner != attacker->owner) //only hostile stacks - does it work well with Berserk?
  162. {
  163. attackedHexes.insert(tile);
  164. }
  165. }
  166. for (BattleHex tile : at.friendlyCreaturePositions)
  167. {
  168. if(battleGetStackByPos(tile, true)) //friendly stacks can also be damaged by Dragon Breath
  169. {
  170. attackedHexes.insert(tile);
  171. }
  172. }
  173. return attackedHexes;
  174. }
  175. SpellID CBattleInfoCallback::battleGetRandomStackSpell(CRandomGenerator & rand, const CStack * stack, ERandomSpell mode) const
  176. {
  177. switch (mode)
  178. {
  179. case RANDOM_GENIE:
  180. return getRandomBeneficialSpell(rand, stack); //target
  181. break;
  182. case RANDOM_AIMED:
  183. return getRandomCastedSpell(rand, stack); //caster
  184. break;
  185. default:
  186. logGlobal->errorStream() << "Incorrect mode of battleGetRandomSpell (" << mode <<")";
  187. return SpellID::NONE;
  188. }
  189. }
  190. const CStack* CBattleInfoCallback::battleGetStackByPos(BattleHex pos, bool onlyAlive) const
  191. {
  192. RETURN_IF_NOT_BATTLE(nullptr);
  193. for(auto s : battleGetAllStacks(true))
  194. if(vstd::contains(s->getHexes(), pos) && (!onlyAlive || s->alive()))
  195. return s;
  196. return nullptr;
  197. }
  198. void CBattleInfoCallback::battleGetStackQueue(std::vector<const CStack *> &out, const int howMany, const int turn /*= 0*/, int lastMoved /*= -1*/) const
  199. {
  200. RETURN_IF_NOT_BATTLE();
  201. //let's define a huge lambda
  202. auto takeStack = [&](std::vector<const CStack *> &st) -> const CStack*
  203. {
  204. const CStack * ret = nullptr;
  205. unsigned i, //fastest stack
  206. j=0; //fastest stack of the other side
  207. for(i = 0; i < st.size(); i++)
  208. if(st[i])
  209. break;
  210. //no stacks left
  211. if(i == st.size())
  212. return nullptr;
  213. const CStack * fastest = st[i], *other = nullptr;
  214. int bestSpeed = fastest->Speed(turn);
  215. //FIXME: comparison between bool and integer. Logic does not makes sense either
  216. if(fastest->side != lastMoved)
  217. {
  218. ret = fastest;
  219. }
  220. else
  221. {
  222. for(j = i + 1; j < st.size(); j++)
  223. {
  224. if(!st[j]) continue;
  225. if(st[j]->side != lastMoved || st[j]->Speed(turn) != bestSpeed)
  226. break;
  227. }
  228. if(j >= st.size())
  229. {
  230. ret = fastest;
  231. }
  232. else
  233. {
  234. other = st[j];
  235. if(other->Speed(turn) != bestSpeed)
  236. ret = fastest;
  237. else
  238. ret = other;
  239. }
  240. }
  241. assert(ret);
  242. if(ret == fastest)
  243. st[i] = nullptr;
  244. else
  245. st[j] = nullptr;
  246. lastMoved = ret->side;
  247. return ret;
  248. };
  249. //We'll split creatures with remaining movement to 4 buckets
  250. // [0] - turrets/catapult,
  251. // [1] - normal (unmoved) creatures, other war machines,
  252. // [2] - waited cres that had morale,
  253. // [3] - rest of waited cres
  254. std::vector<const CStack *> phase[4];
  255. int toMove = 0; //how many stacks still has move
  256. const CStack * active = battleActiveStack();
  257. //active stack hasn't taken any action yet - must be placed at the beginning of queue, no matter what
  258. if(!turn && active && active->willMove() && !active->waited())
  259. {
  260. out.push_back(active);
  261. if(out.size() == howMany)
  262. return;
  263. }
  264. auto allStacks = battleGetAllStacks(true);
  265. if(!vstd::contains_if(allStacks, [](const CStack *stack) { return stack->willMove(100000); })) //little evil, but 100000 should be enough for all effects to disappear
  266. {
  267. //No stack will be able to move, battle is over.
  268. out.clear();
  269. return;
  270. }
  271. for(auto s : battleGetAllStacks(true))
  272. {
  273. if((turn <= 0 && !s->willMove()) //we are considering current round and stack won't move
  274. || (turn > 0 && !s->canMove(turn)) //stack won't be able to move in later rounds
  275. || (turn <= 0 && s == active && out.size() && s == out.front())) //it's active stack already added at the beginning of queue
  276. {
  277. continue;
  278. }
  279. int p = -1; //in which phase this tack will move?
  280. if(turn <= 0 && s->waited()) //consider waiting state only for ongoing round
  281. {
  282. if(vstd::contains(s->state, EBattleStackState::HAD_MORALE))
  283. p = 2;
  284. else
  285. p = 3;
  286. }
  287. else if(s->getCreature()->idNumber == CreatureID::CATAPULT || s->getCreature()->idNumber == CreatureID::ARROW_TOWERS) //catapult and turrets are first
  288. {
  289. p = 0;
  290. }
  291. else
  292. {
  293. p = 1;
  294. }
  295. phase[p].push_back(s);
  296. toMove++;
  297. }
  298. for(int i = 0; i < 4; i++)
  299. boost::sort(phase[i], CMP_stack(i, turn > 0 ? turn : 0));
  300. for(size_t i = 0; i < phase[0].size() && i < howMany; i++)
  301. out.push_back(phase[0][i]);
  302. if(out.size() == howMany)
  303. return;
  304. if(lastMoved == -1)
  305. {
  306. if(active)
  307. {
  308. //FIXME: both branches contain same code!!!
  309. if(out.size() && out.front() == active)
  310. lastMoved = active->side;
  311. else
  312. lastMoved = active->side;
  313. }
  314. else
  315. {
  316. lastMoved = 0;
  317. }
  318. }
  319. int pi = 1;
  320. while(out.size() < howMany)
  321. {
  322. const CStack * hlp = takeStack(phase[pi]);
  323. if(!hlp)
  324. {
  325. pi++;
  326. if(pi > 3)
  327. {
  328. //if(turn != 2)
  329. battleGetStackQueue(out, howMany, turn + 1, lastMoved);
  330. return;
  331. }
  332. }
  333. else
  334. {
  335. out.push_back(hlp);
  336. }
  337. }
  338. }
  339. void CBattleInfoCallback::battleGetStackCountOutsideHexes(bool *ac) const
  340. {
  341. RETURN_IF_NOT_BATTLE();
  342. auto accessibility = getAccesibility();
  343. for(int i = 0; i < accessibility.size(); i++)
  344. ac[i] = (accessibility[i] == EAccessibility::ACCESSIBLE);
  345. }
  346. std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const CStack * stack, bool addOccupiable, std::vector<BattleHex> * attackable) const
  347. {
  348. std::vector<BattleHex> ret;
  349. RETURN_IF_NOT_BATTLE(ret);
  350. if(!stack->position.isValid()) //turrets
  351. return ret;
  352. auto reachability = getReachability(stack);
  353. for (int i = 0; i < GameConstants::BFIELD_SIZE; ++i)
  354. {
  355. // If obstacles or other stacks makes movement impossible, it can't be helped.
  356. if(!reachability.isReachable(i))
  357. continue;
  358. if(battleTacticDist() && battleGetTacticsSide() == stack->side)
  359. {
  360. //Stack has to perform tactic-phase movement -> can enter any reachable tile within given range
  361. if(!isInTacticRange(i))
  362. continue;
  363. }
  364. else
  365. {
  366. //Not tactics phase -> destination must be reachable and within stack range.
  367. if(reachability.distances[i] > stack->Speed(0, true))
  368. continue;
  369. }
  370. ret.push_back(i);
  371. if(addOccupiable && stack->doubleWide())
  372. {
  373. //If two-hex stack can stand on hex i then obviously it can occupy its second hex from that position
  374. ret.push_back(stack->occupiedHex(i));
  375. }
  376. }
  377. if(attackable)
  378. {
  379. auto meleeAttackable = [&](BattleHex hex) -> bool
  380. {
  381. // Return true if given hex has at least one available neighbour.
  382. // Available hexes are already present in ret vector.
  383. auto availableNeighbor = boost::find_if(ret, [=] (BattleHex availableHex)
  384. {
  385. return BattleHex::mutualPosition(hex, availableHex) >= 0;
  386. });
  387. return availableNeighbor != ret.end();
  388. };
  389. for(const CStack * otherSt : battleAliveStacks(1-stack->side))
  390. {
  391. if(!otherSt->isValidTarget(false))
  392. continue;
  393. std::vector<BattleHex> occupied = otherSt->getHexes();
  394. if(battleCanShoot(stack, otherSt->position))
  395. {
  396. attackable->insert(attackable->end(), occupied.begin(), occupied.end());
  397. continue;
  398. }
  399. for(BattleHex he : occupied)
  400. {
  401. if(meleeAttackable(he))
  402. attackable->push_back(he);
  403. }
  404. }
  405. }
  406. //adding occupiable likely adds duplicates to ret -> clean it up
  407. boost::sort(ret);
  408. ret.erase(boost::unique(ret).end(), ret.end());
  409. return ret;
  410. }
  411. bool CBattleInfoCallback::battleCanAttack(const CStack * stack, const CStack * target, BattleHex dest) const
  412. {
  413. RETURN_IF_NOT_BATTLE(false);
  414. if(battleTacticDist())
  415. return false;
  416. if (!stack || !target)
  417. return false;
  418. if(!battleMatchOwner(stack, target))
  419. return false;
  420. auto &id = stack->getCreature()->idNumber;
  421. if (id == CreatureID::FIRST_AID_TENT || id == CreatureID::CATAPULT)
  422. return false;
  423. if (!target->alive())
  424. return false;
  425. return true;
  426. }
  427. bool CBattleInfoCallback::battleCanShoot(const CStack * stack, BattleHex dest) const
  428. {
  429. RETURN_IF_NOT_BATTLE(false);
  430. if(battleTacticDist()) //no shooting during tactics
  431. return false;
  432. const CStack * dst = battleGetStackByPos(dest);
  433. if(!stack || !dst)
  434. return false;
  435. //forgetfulness
  436. TBonusListPtr forgetfulList = stack->getBonuses(Selector::type(Bonus::FORGETFULL),"");
  437. if(!forgetfulList->empty())
  438. {
  439. int forgetful = forgetfulList->valOfBonuses(Selector::type(Bonus::FORGETFULL));
  440. //advanced+ level
  441. if(forgetful > 1)
  442. return false;
  443. }
  444. if(stack->getCreature()->idNumber == CreatureID::CATAPULT && dst) //catapult cannot attack creatures
  445. return false;
  446. if(stack->canShoot()
  447. && battleMatchOwner(stack, dst)
  448. && dst->alive()
  449. && (!battleIsStackBlocked(stack) || stack->hasBonusOfType(Bonus::FREE_SHOOTING)))
  450. return true;
  451. return false;
  452. }
  453. TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo & info) const
  454. {
  455. auto battleBonusValue = [&](const IBonusBearer * bearer, CSelector selector) -> int
  456. {
  457. auto noLimit = Selector::effectRange(Bonus::NO_LIMIT);
  458. auto limitMatches = info.shooting
  459. ? Selector::effectRange(Bonus::ONLY_DISTANCE_FIGHT)
  460. : Selector::effectRange(Bonus::ONLY_MELEE_FIGHT);
  461. //any regular bonuses or just ones for melee/ranged
  462. return bearer->getBonuses(selector, noLimit.Or(limitMatches))->totalValue();
  463. };
  464. double additiveBonus = 1.0, multBonus = 1.0,
  465. minDmg = info.attackerBonuses->getMinDamage() * info.attackerHealth.getCount(),//TODO: ONLY_MELEE_FIGHT / ONLY_DISTANCE_FIGHT
  466. maxDmg = info.attackerBonuses->getMaxDamage() * info.attackerHealth.getCount();
  467. const CCreature *attackerType = info.attacker->getCreature(),
  468. *defenderType = info.defender->getCreature();
  469. if(attackerType->idNumber == CreatureID::ARROW_TOWERS)
  470. {
  471. SiegeStuffThatShouldBeMovedToHandlers::retreiveTurretDamageRange(battleGetDefendedTown(), info.attacker, minDmg, maxDmg);
  472. }
  473. if(info.attackerBonuses->hasBonusOfType(Bonus::SIEGE_WEAPON) && attackerType->idNumber != CreatureID::ARROW_TOWERS) //any siege weapon, but only ballista can attack (second condition - not arrow turret)
  474. { //minDmg and maxDmg are multiplied by hero attack + 1
  475. auto retreiveHeroPrimSkill = [&](int skill) -> int
  476. {
  477. const std::shared_ptr<Bonus> b = info.attackerBonuses->getBonus(Selector::sourceTypeSel(Bonus::HERO_BASE_SKILL).And(Selector::typeSubtype(Bonus::PRIMARY_SKILL, skill)));
  478. return b ? b->val : 0; //if there is no hero or no info on his primary skill, return 0
  479. };
  480. minDmg *= retreiveHeroPrimSkill(PrimarySkill::ATTACK) + 1;
  481. maxDmg *= retreiveHeroPrimSkill(PrimarySkill::ATTACK) + 1;
  482. }
  483. int attackDefenceDifference = 0;
  484. double multAttackReduction = (100 - battleBonusValue (info.attackerBonuses, Selector::type(Bonus::GENERAL_ATTACK_REDUCTION))) / 100.0;
  485. attackDefenceDifference += battleBonusValue (info.attackerBonuses, Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK)) * multAttackReduction;
  486. double multDefenceReduction = (100 - battleBonusValue (info.attackerBonuses, Selector::type(Bonus::ENEMY_DEFENCE_REDUCTION))) / 100.0;
  487. attackDefenceDifference -= info.defenderBonuses->Defense() * multDefenceReduction;
  488. if(const std::shared_ptr<Bonus> slayerEffect = info.attackerBonuses->getBonus(Selector::type(Bonus::SLAYER))) //slayer handling //TODO: apply only ONLY_MELEE_FIGHT / DISTANCE_FIGHT?
  489. {
  490. std::vector<int> affectedIds;
  491. int spLevel = slayerEffect->val;
  492. //FIXME: do not check all creatures
  493. for(int g = 0; g < VLC->creh->creatures.size(); ++g)
  494. {
  495. for(const std::shared_ptr<Bonus> b : VLC->creh->creatures[g]->getBonusList())
  496. {
  497. if ((b->type == Bonus::KING3 && spLevel >= 3) || //expert
  498. (b->type == Bonus::KING2 && spLevel >= 2) || //adv +
  499. (b->type == Bonus::KING1 && spLevel >= 0)) //none or basic +
  500. {
  501. affectedIds.push_back(g);
  502. break;
  503. }
  504. }
  505. }
  506. for(auto & affectedId : affectedIds)
  507. {
  508. if(defenderType->idNumber == affectedId)
  509. {
  510. attackDefenceDifference += SpellID(SpellID::SLAYER).toSpell()->getPower(spLevel);
  511. break;
  512. }
  513. }
  514. }
  515. //bonus from attack/defense skills
  516. if(attackDefenceDifference < 0) //decreasing dmg
  517. {
  518. const double dec = std::min(0.025 * (-attackDefenceDifference), 0.7);
  519. multBonus *= 1.0 - dec;
  520. }
  521. else //increasing dmg
  522. {
  523. const double inc = std::min(0.05 * attackDefenceDifference, 4.0);
  524. additiveBonus += inc;
  525. }
  526. //applying jousting bonus
  527. if(info.attackerBonuses->hasBonusOfType(Bonus::JOUSTING) && !info.defenderBonuses->hasBonusOfType(Bonus::CHARGE_IMMUNITY))
  528. additiveBonus += info.chargedFields * 0.05;
  529. //handling secondary abilities and artifacts giving premies to them
  530. if(info.shooting)
  531. additiveBonus += info.attackerBonuses->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::ARCHERY) / 100.0;
  532. else
  533. additiveBonus += info.attackerBonuses->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::OFFENCE) / 100.0;
  534. if(info.defenderBonuses)
  535. multBonus *= (std::max(0, 100 - info.defenderBonuses->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::ARMORER))) / 100.0;
  536. //handling hate effect
  537. additiveBonus += info.attackerBonuses->valOfBonuses(Bonus::HATE, defenderType->idNumber.toEnum()) / 100.;
  538. //luck bonus
  539. if (info.luckyHit)
  540. {
  541. additiveBonus += 1.0;
  542. }
  543. //unlucky hit, used only if negative luck is enabled
  544. if (info.unluckyHit)
  545. {
  546. additiveBonus -= 0.5; // FIXME: how bad (and luck in general) should work with following bonuses?
  547. }
  548. //ballista double dmg
  549. if(info.ballistaDoubleDamage)
  550. {
  551. additiveBonus += 1.0;
  552. }
  553. if (info.deathBlow) //Dread Knight and many WoGified creatures
  554. {
  555. additiveBonus += 1.0;
  556. }
  557. //handling spell effects
  558. if(!info.shooting) //eg. shield
  559. {
  560. multBonus *= (100 - info.defenderBonuses->valOfBonuses(Bonus::GENERAL_DAMAGE_REDUCTION, 0)) / 100.0;
  561. }
  562. else if(info.shooting) //eg. air shield
  563. {
  564. multBonus *= (100 - info.defenderBonuses->valOfBonuses(Bonus::GENERAL_DAMAGE_REDUCTION, 1)) / 100.0;
  565. }
  566. if(info.shooting)
  567. {
  568. //todo: set actual percentage in spell bonus configuration instead of just level; requires non trivial backward compatibility handling
  569. //get list first, total value of 0 also counts
  570. TBonusListPtr forgetfulList = info.attackerBonuses->getBonuses(Selector::type(Bonus::FORGETFULL),"");
  571. if(!forgetfulList->empty())
  572. {
  573. int forgetful = forgetfulList->valOfBonuses(Selector::type(Bonus::FORGETFULL));
  574. //none of basic level
  575. if(forgetful == 0 || forgetful == 1)
  576. multBonus *= 0.5;
  577. else
  578. logGlobal->warn("Attempt to calculate shooting damage with adv+ FORGETFULL effect");
  579. }
  580. }
  581. TBonusListPtr curseEffects = info.attackerBonuses->getBonuses(Selector::type(Bonus::ALWAYS_MINIMUM_DAMAGE));
  582. TBonusListPtr blessEffects = info.attackerBonuses->getBonuses(Selector::type(Bonus::ALWAYS_MAXIMUM_DAMAGE));
  583. int curseBlessAdditiveModifier = blessEffects->totalValue() - curseEffects->totalValue();
  584. double curseMultiplicativePenalty = curseEffects->size() ? (*std::max_element(curseEffects->begin(), curseEffects->end(), &Bonus::compareByAdditionalInfo<std::shared_ptr<Bonus>>))->additionalInfo : 0;
  585. if(curseMultiplicativePenalty) //curse handling (partial, the rest is below)
  586. {
  587. multBonus *= 1.0 - curseMultiplicativePenalty/100;
  588. }
  589. auto isAdvancedAirShield = [](const Bonus* bonus)
  590. {
  591. return bonus->source == Bonus::SPELL_EFFECT
  592. && bonus->sid == SpellID::AIR_SHIELD
  593. && bonus->val >= SecSkillLevel::ADVANCED;
  594. };
  595. //wall / distance penalty + advanced air shield
  596. const bool distPenalty = !info.attackerBonuses->hasBonusOfType(Bonus::NO_DISTANCE_PENALTY) && battleHasDistancePenalty(info.attackerBonuses, info.attackerPosition, info.defenderPosition);
  597. const bool obstaclePenalty = battleHasWallPenalty(info.attackerBonuses, info.attackerPosition, info.defenderPosition);
  598. if(info.shooting)
  599. {
  600. if (distPenalty || info.defenderBonuses->hasBonus(isAdvancedAirShield))
  601. {
  602. multBonus *= 0.5;
  603. }
  604. if (obstaclePenalty)
  605. {
  606. multBonus *= 0.5; //cumulative
  607. }
  608. }
  609. if(!info.shooting && info.attackerBonuses->hasBonusOfType(Bonus::SHOOTER) && !info.attackerBonuses->hasBonusOfType(Bonus::NO_MELEE_PENALTY))
  610. {
  611. multBonus *= 0.5;
  612. }
  613. // psychic elementals versus mind immune units 50%
  614. if(attackerType->idNumber == CreatureID::PSYCHIC_ELEMENTAL
  615. && info.defenderBonuses->hasBonusOfType(Bonus::MIND_IMMUNITY))
  616. {
  617. multBonus *= 0.5;
  618. }
  619. // TODO attack on petrified unit 50%
  620. // blinded unit retaliates
  621. minDmg *= additiveBonus * multBonus;
  622. maxDmg *= additiveBonus * multBonus;
  623. TDmgRange returnedVal;
  624. if(curseEffects->size()) //curse handling (rest)
  625. {
  626. minDmg += curseBlessAdditiveModifier;
  627. returnedVal = std::make_pair(int(minDmg), int(minDmg));
  628. }
  629. else if(blessEffects->size()) //bless handling
  630. {
  631. maxDmg += curseBlessAdditiveModifier;
  632. returnedVal = std::make_pair(int(maxDmg), int(maxDmg));
  633. }
  634. else
  635. {
  636. returnedVal = std::make_pair(int(minDmg), int(maxDmg));
  637. }
  638. //damage cannot be less than 1
  639. vstd::amax(returnedVal.first, 1);
  640. vstd::amax(returnedVal.second, 1);
  641. return returnedVal;
  642. }
  643. TDmgRange CBattleInfoCallback::battleEstimateDamage(CRandomGenerator & rand, const CStack * attacker, const CStack * defender, TDmgRange * retaliationDmg) const
  644. {
  645. RETURN_IF_NOT_BATTLE(std::make_pair(0, 0));
  646. const bool shooting = battleCanShoot(attacker, defender->position);
  647. const BattleAttackInfo bai(attacker, defender, shooting);
  648. return battleEstimateDamage(rand, bai, retaliationDmg);
  649. }
  650. std::pair<ui32, ui32> CBattleInfoCallback::battleEstimateDamage(CRandomGenerator & rand, const BattleAttackInfo & bai, std::pair<ui32, ui32> * retaliationDmg /*= nullptr*/) const
  651. {
  652. RETURN_IF_NOT_BATTLE(std::make_pair(0, 0));
  653. //const bool shooting = battleCanShoot(bai.attacker, bai.defenderPosition); //TODO handle bonus bearer
  654. TDmgRange ret = calculateDmgRange(bai);
  655. if(retaliationDmg)
  656. {
  657. if(bai.shooting)
  658. {
  659. retaliationDmg->first = retaliationDmg->second = 0;
  660. }
  661. else
  662. {
  663. ui32 TDmgRange::* pairElems[] = {&TDmgRange::first, &TDmgRange::second};
  664. for (int i=0; i<2; ++i)
  665. {
  666. BattleStackAttacked bsa;
  667. bsa.damageAmount = ret.*pairElems[i];
  668. bai.defender->prepareAttacked(bsa, rand, bai.defenderHealth);
  669. auto retaliationAttack = bai.reverse();
  670. retaliationAttack.attackerHealth = retaliationAttack.attacker->healthAfterAttacked(bsa.damageAmount);
  671. retaliationDmg->*pairElems[!i] = calculateDmgRange(retaliationAttack).*pairElems[!i];
  672. }
  673. }
  674. }
  675. return ret;
  676. }
  677. std::vector<std::shared_ptr<const CObstacleInstance>> CBattleInfoCallback::battleGetAllObstaclesOnPos(BattleHex tile, bool onlyBlocking) const
  678. {
  679. std::vector<std::shared_ptr<const CObstacleInstance>> obstacles = std::vector<std::shared_ptr<const CObstacleInstance>>();
  680. RETURN_IF_NOT_BATTLE(obstacles);
  681. for(auto & obs : battleGetAllObstacles())
  682. {
  683. if(vstd::contains(obs->getBlockedTiles(), tile)
  684. || (!onlyBlocking && vstd::contains(obs->getAffectedTiles(), tile)))
  685. {
  686. obstacles.push_back(obs);
  687. }
  688. }
  689. return obstacles;
  690. }
  691. AccessibilityInfo CBattleInfoCallback::getAccesibility() const
  692. {
  693. AccessibilityInfo ret;
  694. ret.fill(EAccessibility::ACCESSIBLE);
  695. //removing accessibility for side columns of hexes
  696. for(int y = 0; y < GameConstants::BFIELD_HEIGHT; y++)
  697. {
  698. ret[BattleHex(GameConstants::BFIELD_WIDTH - 1, y)] = EAccessibility::SIDE_COLUMN;
  699. ret[BattleHex(0, y)] = EAccessibility::SIDE_COLUMN;
  700. }
  701. //gate -> should be before stacks
  702. if(battleGetSiegeLevel() > 0)
  703. {
  704. EAccessibility accessability = EAccessibility::ACCESSIBLE;
  705. switch(battleGetGateState())
  706. {
  707. case EGateState::CLOSED:
  708. accessability = EAccessibility::GATE;
  709. break;
  710. case EGateState::BLOCKED:
  711. accessability = EAccessibility::UNAVAILABLE;
  712. break;
  713. }
  714. ret[ESiegeHex::GATE_OUTER] = ret[ESiegeHex::GATE_INNER] = accessability;
  715. }
  716. //tiles occupied by standing stacks
  717. for(auto stack : battleAliveStacks())
  718. {
  719. for(auto hex : stack->getHexes())
  720. if(hex.isAvailable()) //towers can have <0 pos; we don't also want to overwrite side columns
  721. ret[hex] = EAccessibility::ALIVE_STACK;
  722. }
  723. //obstacles
  724. for(const auto &obst : battleGetAllObstacles())
  725. {
  726. for(auto hex : obst->getBlockedTiles())
  727. ret[hex] = EAccessibility::OBSTACLE;
  728. }
  729. //walls
  730. if(battleGetSiegeLevel() > 0)
  731. {
  732. static const int permanentlyLocked[] = {12, 45, 62, 112, 147, 165};
  733. for(auto hex : permanentlyLocked)
  734. ret[hex] = EAccessibility::UNAVAILABLE;
  735. //TODO likely duplicated logic
  736. static const std::pair<int, BattleHex> lockedIfNotDestroyed[] =
  737. {
  738. //which part of wall, which hex is blocked if this part of wall is not destroyed
  739. std::make_pair(2, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_4)),
  740. std::make_pair(3, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_3)),
  741. std::make_pair(4, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_2)),
  742. std::make_pair(5, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_1))
  743. };
  744. for(auto & elem : lockedIfNotDestroyed)
  745. {
  746. if(battleGetWallState(elem.first) != EWallState::DESTROYED)
  747. ret[elem.second] = EAccessibility::DESTRUCTIBLE_WALL;
  748. }
  749. }
  750. return ret;
  751. }
  752. AccessibilityInfo CBattleInfoCallback::getAccesibility(const CStack * stack) const
  753. {
  754. return getAccesibility(stack->getHexes());
  755. }
  756. AccessibilityInfo CBattleInfoCallback::getAccesibility(const std::vector<BattleHex> & accessibleHexes) const
  757. {
  758. auto ret = getAccesibility();
  759. for(auto hex : accessibleHexes)
  760. if(hex.isValid())
  761. ret[hex] = EAccessibility::ACCESSIBLE;
  762. return ret;
  763. }
  764. ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibility, const ReachabilityInfo::Parameters & params) const
  765. {
  766. ReachabilityInfo ret;
  767. ret.accessibility = accessibility;
  768. ret.params = params;
  769. ret.predecessors.fill(BattleHex::INVALID);
  770. ret.distances.fill(ReachabilityInfo::INFINITE_DIST);
  771. if(!params.startPosition.isValid()) //if got call for arrow turrets
  772. return ret;
  773. const std::set<BattleHex> quicksands = getStoppers(params.perspective);
  774. //const bool twoHexCreature = params.doubleWide;
  775. std::queue<BattleHex> hexq; //bfs queue
  776. //first element
  777. hexq.push(params.startPosition);
  778. ret.distances[params.startPosition] = 0;
  779. while(!hexq.empty()) //bfs loop
  780. {
  781. const BattleHex curHex = hexq.front();
  782. hexq.pop();
  783. //walking stack can't step past the quicksands
  784. //TODO what if second hex of two-hex creature enters quicksand
  785. if(curHex != params.startPosition && vstd::contains(quicksands, curHex))
  786. continue;
  787. const int costToNeighbour = ret.distances[curHex] + 1;
  788. for(BattleHex neighbour : curHex.neighbouringTiles())
  789. {
  790. const bool accessible = accessibility.accessible(neighbour, params.doubleWide, params.side);
  791. const int costFoundSoFar = ret.distances[neighbour];
  792. if(accessible && costToNeighbour < costFoundSoFar)
  793. {
  794. hexq.push(neighbour);
  795. ret.distances[neighbour] = costToNeighbour;
  796. ret.predecessors[neighbour] = curHex;
  797. }
  798. }
  799. }
  800. return ret;
  801. }
  802. ReachabilityInfo CBattleInfoCallback::makeBFS(const CStack * stack) const
  803. {
  804. return makeBFS(getAccesibility(stack), ReachabilityInfo::Parameters(stack));
  805. }
  806. std::set<BattleHex> CBattleInfoCallback::getStoppers(BattlePerspective::BattlePerspective whichSidePerspective) const
  807. {
  808. std::set<BattleHex> ret;
  809. RETURN_IF_NOT_BATTLE(ret);
  810. for(auto &oi : battleGetAllObstacles(whichSidePerspective))
  811. {
  812. if(battleIsObstacleVisibleForSide(*oi, whichSidePerspective))
  813. {
  814. range::copy(oi->getStoppingTile(), vstd::set_inserter(ret));
  815. }
  816. }
  817. return ret;
  818. }
  819. std::pair<const CStack *, BattleHex> CBattleInfoCallback::getNearestStack(const CStack * closest, BattleSideOpt side) const
  820. {
  821. auto reachability = getReachability(closest);
  822. auto avHexes = battleGetAvailableHexes(closest, false);
  823. // I hate std::pairs with their undescriptive member names first / second
  824. struct DistStack
  825. {
  826. int distanceToPred;
  827. BattleHex destination;
  828. const CStack * stack;
  829. };
  830. std::vector<DistStack> stackPairs;
  831. std::vector<const CStack *> possibleStacks = battleGetStacksIf([=](const CStack * s)
  832. {
  833. return s->isValidTarget(false) && s != closest && (!side || side.get() == s->side);
  834. });
  835. for(const CStack * st : possibleStacks)
  836. for(BattleHex hex : avHexes)
  837. if(CStack::isMeleeAttackPossible(closest, st, hex))
  838. {
  839. DistStack hlp = {reachability.distances[hex], hex, st};
  840. stackPairs.push_back(hlp);
  841. }
  842. if (stackPairs.size())
  843. {
  844. auto comparator = [](DistStack lhs, DistStack rhs) { return lhs.distanceToPred < rhs.distanceToPred; };
  845. auto minimal = boost::min_element(stackPairs, comparator);
  846. return std::make_pair(minimal->stack, minimal->destination);
  847. }
  848. else
  849. return std::make_pair<const CStack * , BattleHex>(nullptr, BattleHex::INVALID);
  850. }
  851. si8 CBattleInfoCallback::battleGetTacticDist() const
  852. {
  853. RETURN_IF_NOT_BATTLE(0);
  854. //TODO get rid of this method
  855. if(battleDoWeKnowAbout(battleGetTacticsSide()))
  856. return battleTacticDist();
  857. return 0;
  858. }
  859. bool CBattleInfoCallback::isInTacticRange(BattleHex dest) const
  860. {
  861. RETURN_IF_NOT_BATTLE(false);
  862. auto side = battleGetTacticsSide();
  863. auto dist = battleGetTacticDist();
  864. return ((!side && dest.getX() > 0 && dest.getX() <= dist)
  865. || (side && dest.getX() < GameConstants::BFIELD_WIDTH - 1 && dest.getX() >= GameConstants::BFIELD_WIDTH - dist - 1));
  866. }
  867. ReachabilityInfo CBattleInfoCallback::getReachability(const CStack *stack) const
  868. {
  869. ReachabilityInfo::Parameters params(stack);
  870. if(!battleDoWeKnowAbout(stack->side))
  871. {
  872. //Stack is held by enemy, we can't use his perspective to check for reachability.
  873. // Happens ie. when hovering enemy stack for its range. The arg could be set properly, but it's easier to fix it here.
  874. params.perspective = battleGetMySide();
  875. }
  876. return getReachability(params);
  877. }
  878. ReachabilityInfo CBattleInfoCallback::getReachability(const ReachabilityInfo::Parameters &params) const
  879. {
  880. if(params.flying)
  881. return getFlyingReachability(params);
  882. else
  883. return makeBFS(getAccesibility(params.knownAccessible), params);
  884. }
  885. ReachabilityInfo CBattleInfoCallback::getFlyingReachability(const ReachabilityInfo::Parameters &params) const
  886. {
  887. ReachabilityInfo ret;
  888. ret.accessibility = getAccesibility(params.knownAccessible);
  889. for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
  890. {
  891. if(ret.accessibility.accessible(i, params.doubleWide, params.side))
  892. {
  893. ret.predecessors[i] = params.startPosition;
  894. ret.distances[i] = BattleHex::getDistance(params.startPosition, i);
  895. }
  896. }
  897. return ret;
  898. }
  899. AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes (const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos) const
  900. {
  901. //does not return hex attacked directly
  902. //TODO: apply rotation to two-hex attackers
  903. bool isAttacker = attacker->side == BattleSide::ATTACKER;
  904. AttackableTiles at;
  905. RETURN_IF_NOT_BATTLE(at);
  906. const int WN = GameConstants::BFIELD_WIDTH;
  907. BattleHex hex = (attackerPos != BattleHex::INVALID) ? attackerPos.hex : attacker->position.hex; //real or hypothetical (cursor) position
  908. //FIXME: dragons or cerbers can rotate before attack, making their base hex different (#1124)
  909. bool reverse = isToReverse (hex, destinationTile, isAttacker, attacker->doubleWide(), isAttacker);
  910. if (reverse && attacker->doubleWide())
  911. {
  912. hex = attacker->occupiedHex(hex); //the other hex stack stands on
  913. }
  914. if (attacker->hasBonusOfType(Bonus::ATTACKS_ALL_ADJACENT))
  915. {
  916. boost::copy (attacker->getSurroundingHexes (attackerPos), vstd::set_inserter (at.hostileCreaturePositions));
  917. }
  918. if (attacker->hasBonusOfType(Bonus::THREE_HEADED_ATTACK))
  919. {
  920. std::vector<BattleHex> hexes = attacker->getSurroundingHexes(attackerPos);
  921. for (BattleHex tile : hexes)
  922. {
  923. if ((BattleHex::mutualPosition(tile, destinationTile) > -1 && BattleHex::mutualPosition (tile, hex) > -1)) //adjacent both to attacker's head and attacked tile
  924. {
  925. const CStack * st = battleGetStackByPos(tile, true);
  926. if(st && st->owner != attacker->owner) //only hostile stacks - does it work well with Berserk?
  927. {
  928. at.hostileCreaturePositions.insert(tile);
  929. }
  930. }
  931. }
  932. }
  933. if (attacker->hasBonusOfType(Bonus::TWO_HEX_ATTACK_BREATH) && BattleHex::mutualPosition (destinationTile.hex, hex) > -1) //only adjacent hexes are subject of dragon breath calculation
  934. {
  935. std::vector<BattleHex> hexes; //only one, in fact
  936. int pseudoVector = destinationTile.hex - hex;
  937. switch (pseudoVector)
  938. {
  939. case 1:
  940. case -1:
  941. BattleHex::checkAndPush (destinationTile.hex + pseudoVector, hexes);
  942. break;
  943. case WN: //17 //left-down or right-down
  944. case -WN: //-17 //left-up or right-up
  945. case WN + 1: //18 //right-down
  946. case -WN + 1: //-16 //right-up
  947. BattleHex::checkAndPush (destinationTile.hex + pseudoVector + (((hex/WN)%2) ? 1 : -1), hexes);
  948. break;
  949. case WN-1: //16 //left-down
  950. case -WN-1: //-18 //left-up
  951. BattleHex::checkAndPush (destinationTile.hex + pseudoVector + (((hex/WN)%2) ? 1 : 0), hexes);
  952. break;
  953. }
  954. for (BattleHex tile : hexes)
  955. {
  956. //friendly stacks can also be damaged by Dragon Breath
  957. if (battleGetStackByPos (tile, true))
  958. at.friendlyCreaturePositions.insert (tile);
  959. }
  960. }
  961. return at;
  962. }
  963. std::set<const CStack*> CBattleInfoCallback::getAttackedCreatures(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos /*= BattleHex::INVALID*/) const
  964. {
  965. std::set<const CStack*> attackedCres;
  966. RETURN_IF_NOT_BATTLE(attackedCres);
  967. AttackableTiles at = getPotentiallyAttackableHexes(attacker, destinationTile, attackerPos);
  968. for (BattleHex tile : at.hostileCreaturePositions) //all around & three-headed attack
  969. {
  970. const CStack * st = battleGetStackByPos(tile, true);
  971. if(st && st->owner != attacker->owner) //only hostile stacks - does it work well with Berserk?
  972. {
  973. attackedCres.insert(st);
  974. }
  975. }
  976. for (BattleHex tile : at.friendlyCreaturePositions)
  977. {
  978. const CStack * st = battleGetStackByPos(tile, true);
  979. if(st) //friendly stacks can also be damaged by Dragon Breath
  980. {
  981. attackedCres.insert(st);
  982. }
  983. }
  984. return attackedCres;
  985. }
  986. //TODO: this should apply also to mechanics and cursor interface
  987. bool CBattleInfoCallback::isToReverseHlp (BattleHex hexFrom, BattleHex hexTo, bool curDir) const
  988. {
  989. int fromX = hexFrom.getX();
  990. int fromY = hexFrom.getY();
  991. int toX = hexTo.getX();
  992. int toY = hexTo.getY();
  993. if (curDir) // attacker, facing right
  994. {
  995. if (fromX < toX)
  996. return false;
  997. if (fromX > toX)
  998. return true;
  999. if (fromY % 2 == 0 && toY % 2 == 1)
  1000. return true;
  1001. return false;
  1002. }
  1003. else // defender, facing left
  1004. {
  1005. if(fromX < toX)
  1006. return true;
  1007. if(fromX > toX)
  1008. return false;
  1009. if (fromY % 2 == 1 && toY % 2 == 0)
  1010. return true;
  1011. return false;
  1012. }
  1013. }
  1014. //TODO: this should apply also to mechanics and cursor interface
  1015. bool CBattleInfoCallback::isToReverse (BattleHex hexFrom, BattleHex hexTo, bool curDir, bool toDoubleWide, bool toDir) const
  1016. {
  1017. if (hexTo < 0 || hexFrom < 0) //turret
  1018. return false;
  1019. if (toDoubleWide)
  1020. {
  1021. if (isToReverseHlp (hexFrom, hexTo, curDir))
  1022. {
  1023. if (toDir)
  1024. return isToReverseHlp (hexFrom, hexTo-1, curDir);
  1025. else
  1026. return isToReverseHlp (hexFrom, hexTo+1, curDir);
  1027. }
  1028. return false;
  1029. }
  1030. else
  1031. {
  1032. return isToReverseHlp(hexFrom, hexTo, curDir);
  1033. }
  1034. }
  1035. ReachabilityInfo::TDistances CBattleInfoCallback::battleGetDistances(const CStack * stack, BattleHex hex /*= BattleHex::INVALID*/, BattleHex * predecessors /*= nullptr*/) const
  1036. {
  1037. ReachabilityInfo::TDistances ret;
  1038. ret.fill(-1);
  1039. RETURN_IF_NOT_BATTLE(ret);
  1040. ReachabilityInfo::Parameters params(stack);
  1041. params.perspective = battleGetMySide();
  1042. params.startPosition = hex.isValid() ? hex : stack->position;
  1043. auto reachability = getReachability(params);
  1044. boost::copy(reachability.distances, ret.begin());
  1045. if(predecessors)
  1046. for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
  1047. predecessors[i] = reachability.predecessors[i];
  1048. return ret;
  1049. }
  1050. si8 CBattleInfoCallback::battleHasDistancePenalty(const CStack * stack, BattleHex destHex) const
  1051. {
  1052. return battleHasDistancePenalty(stack, stack->position, destHex);
  1053. }
  1054. si8 CBattleInfoCallback::battleHasDistancePenalty(const IBonusBearer *bonusBearer, BattleHex shooterPosition, BattleHex destHex) const
  1055. {
  1056. RETURN_IF_NOT_BATTLE(false);
  1057. if(bonusBearer->hasBonusOfType(Bonus::NO_DISTANCE_PENALTY))
  1058. return false;
  1059. if(const CStack * dstStack = battleGetStackByPos(destHex, false))
  1060. {
  1061. //If any hex of target creature is within range, there is no penalty
  1062. for(auto hex : dstStack->getHexes())
  1063. if(BattleHex::getDistance(shooterPosition, hex) <= GameConstants::BATTLE_PENALTY_DISTANCE)
  1064. return false;
  1065. //TODO what about two-hex shooters?
  1066. }
  1067. else
  1068. {
  1069. if (BattleHex::getDistance(shooterPosition, destHex) <= GameConstants::BATTLE_PENALTY_DISTANCE)
  1070. return false;
  1071. }
  1072. return true;
  1073. }
  1074. BattleHex CBattleInfoCallback::wallPartToBattleHex(EWallPart::EWallPart part) const
  1075. {
  1076. RETURN_IF_NOT_BATTLE(BattleHex::INVALID);
  1077. return WallPartToHex(part);
  1078. }
  1079. EWallPart::EWallPart CBattleInfoCallback::battleHexToWallPart(BattleHex hex) const
  1080. {
  1081. RETURN_IF_NOT_BATTLE(EWallPart::INVALID);
  1082. return hexToWallPart(hex);
  1083. }
  1084. bool CBattleInfoCallback::isWallPartPotentiallyAttackable(EWallPart::EWallPart wallPart) const
  1085. {
  1086. RETURN_IF_NOT_BATTLE(false);
  1087. return wallPart != EWallPart::INDESTRUCTIBLE_PART && wallPart != EWallPart::INDESTRUCTIBLE_PART_OF_GATE &&
  1088. wallPart != EWallPart::INVALID;
  1089. }
  1090. std::vector<BattleHex> CBattleInfoCallback::getAttackableBattleHexes() const
  1091. {
  1092. std::vector<BattleHex> attackableBattleHexes;
  1093. RETURN_IF_NOT_BATTLE(attackableBattleHexes);
  1094. for(auto & wallPartPair : wallParts)
  1095. {
  1096. if(isWallPartPotentiallyAttackable(wallPartPair.second))
  1097. {
  1098. auto wallState = static_cast<EWallState::EWallState>(battleGetWallState(static_cast<int>(wallPartPair.second)));
  1099. if(wallState == EWallState::INTACT || wallState == EWallState::DAMAGED)
  1100. {
  1101. attackableBattleHexes.push_back(BattleHex(wallPartPair.first));
  1102. }
  1103. }
  1104. }
  1105. return attackableBattleHexes;
  1106. }
  1107. ui32 CBattleInfoCallback::battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const
  1108. {
  1109. RETURN_IF_NOT_BATTLE(-1);
  1110. //TODO should be replaced using bonus system facilities (propagation onto battle node)
  1111. ui32 ret = caster->getSpellCost(sp);
  1112. //checking for friendly stacks reducing cost of the spell and
  1113. //enemy stacks increasing it
  1114. si32 manaReduction = 0;
  1115. si32 manaIncrease = 0;
  1116. for(auto stack : battleAliveStacks())
  1117. {
  1118. if(stack->owner == caster->tempOwner && stack->hasBonusOfType(Bonus::CHANGES_SPELL_COST_FOR_ALLY))
  1119. {
  1120. vstd::amax(manaReduction, stack->valOfBonuses(Bonus::CHANGES_SPELL_COST_FOR_ALLY));
  1121. }
  1122. if(stack->owner != caster->tempOwner && stack->hasBonusOfType(Bonus::CHANGES_SPELL_COST_FOR_ENEMY))
  1123. {
  1124. vstd::amax(manaIncrease, stack->valOfBonuses(Bonus::CHANGES_SPELL_COST_FOR_ENEMY));
  1125. }
  1126. }
  1127. return ret - manaReduction + manaIncrease;
  1128. }
  1129. const CStack * CBattleInfoCallback::getStackIf(std::function<bool(const CStack*)> pred) const
  1130. {
  1131. RETURN_IF_NOT_BATTLE(nullptr);
  1132. auto stacks = battleGetAllStacks();
  1133. auto stackItr = range::find_if(stacks, pred);
  1134. return stackItr == stacks.end() ? nullptr : *stackItr;
  1135. }
  1136. si8 CBattleInfoCallback::battleHasShootingPenalty(const CStack * stack, BattleHex destHex)
  1137. {
  1138. return battleHasDistancePenalty(stack, destHex) || battleHasWallPenalty(stack, destHex);
  1139. }
  1140. bool CBattleInfoCallback::battleIsStackBlocked(const CStack * stack) const
  1141. {
  1142. RETURN_IF_NOT_BATTLE(false);
  1143. if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON)) //siege weapons cannot be blocked
  1144. return false;
  1145. for(const CStack * s : batteAdjacentCreatures(stack))
  1146. {
  1147. if (s->owner != stack->owner) //blocked by enemy stack
  1148. return true;
  1149. }
  1150. return false;
  1151. }
  1152. std::set<const CStack*> CBattleInfoCallback:: batteAdjacentCreatures(const CStack * stack) const
  1153. {
  1154. std::set<const CStack*> stacks;
  1155. RETURN_IF_NOT_BATTLE(stacks);
  1156. for (BattleHex hex : stack->getSurroundingHexes())
  1157. if(const CStack * neighbour = battleGetStackByPos(hex, true))
  1158. stacks.insert(neighbour);
  1159. return stacks;
  1160. }
  1161. SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, const CStack * subject) const
  1162. {
  1163. RETURN_IF_NOT_BATTLE(SpellID::NONE);
  1164. //This is complete list. No spells from mods.
  1165. //todo: this should be Spellbook of caster Stack
  1166. static const std::set<SpellID> allPossibleSpells =
  1167. {
  1168. SpellID::AIR_SHIELD,
  1169. SpellID::ANTI_MAGIC,
  1170. SpellID::BLESS,
  1171. SpellID::BLOODLUST,
  1172. SpellID::COUNTERSTRIKE,
  1173. SpellID::CURE,
  1174. SpellID::FIRE_SHIELD,
  1175. SpellID::FORTUNE,
  1176. SpellID::HASTE,
  1177. SpellID::MAGIC_MIRROR,
  1178. SpellID::MIRTH,
  1179. SpellID::PRAYER,
  1180. SpellID::PRECISION,
  1181. SpellID::PROTECTION_FROM_AIR,
  1182. SpellID::PROTECTION_FROM_EARTH,
  1183. SpellID::PROTECTION_FROM_FIRE,
  1184. SpellID::PROTECTION_FROM_WATER,
  1185. SpellID::SHIELD,
  1186. SpellID::SLAYER,
  1187. SpellID::STONE_SKIN
  1188. };
  1189. std::vector<SpellID> beneficialSpells;
  1190. auto getAliveEnemy = [=](const std::function<bool(const CStack *)> & pred)
  1191. {
  1192. return getStackIf([=](const CStack * stack)
  1193. {
  1194. return pred(stack) && stack->owner != subject->owner && stack->alive();
  1195. });
  1196. };
  1197. for(const SpellID spellID : allPossibleSpells)
  1198. {
  1199. std::stringstream cachingStr;
  1200. cachingStr << "source_" << Bonus::SPELL_EFFECT << "id_" << spellID.num;
  1201. if(subject->hasBonus(Selector::source(Bonus::SPELL_EFFECT, spellID), Selector::all, cachingStr.str())
  1202. //TODO: this ability has special limitations
  1203. || spellID.toSpell()->canBeCastAt(this, subject, ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK)
  1204. continue;
  1205. switch (spellID)
  1206. {
  1207. case SpellID::SHIELD:
  1208. case SpellID::FIRE_SHIELD: // not if all enemy units are shooters
  1209. {
  1210. auto walker = getAliveEnemy([&](const CStack * stack) //look for enemy, non-shooting stack
  1211. {
  1212. return !stack->canShoot();
  1213. });
  1214. if (!walker)
  1215. continue;
  1216. }
  1217. break;
  1218. case SpellID::AIR_SHIELD: //only against active shooters
  1219. {
  1220. auto shooter = getAliveEnemy([&](const CStack * stack) //look for enemy, non-shooting stack
  1221. {
  1222. return stack->canShoot();
  1223. });
  1224. if (!shooter)
  1225. continue;
  1226. }
  1227. break;
  1228. case SpellID::ANTI_MAGIC:
  1229. case SpellID::MAGIC_MIRROR:
  1230. case SpellID::PROTECTION_FROM_AIR:
  1231. case SpellID::PROTECTION_FROM_EARTH:
  1232. case SpellID::PROTECTION_FROM_FIRE:
  1233. case SpellID::PROTECTION_FROM_WATER:
  1234. {
  1235. const ui8 enemySide = 1 - subject->side;
  1236. //todo: only if enemy has spellbook
  1237. if (!battleHasHero(enemySide)) //only if there is enemy hero
  1238. continue;
  1239. }
  1240. break;
  1241. case SpellID::CURE: //only damaged units
  1242. {
  1243. //do not cast on affected by debuffs
  1244. if(!subject->canBeHealed())
  1245. continue;
  1246. }
  1247. break;
  1248. case SpellID::BLOODLUST:
  1249. {
  1250. if(subject->canShoot()) //TODO: if can shoot - only if enemy units are adjacent
  1251. continue;
  1252. }
  1253. break;
  1254. case SpellID::PRECISION:
  1255. {
  1256. if(!subject->canShoot())
  1257. continue;
  1258. }
  1259. break;
  1260. case SpellID::SLAYER://only if monsters are present
  1261. {
  1262. auto kingMonster = getAliveEnemy([&](const CStack * stack) -> bool //look for enemy, non-shooting stack
  1263. {
  1264. const auto isKing = Selector::type(Bonus::KING1)
  1265. .Or(Selector::type(Bonus::KING2))
  1266. .Or(Selector::type(Bonus::KING3));
  1267. return stack->hasBonus(isKing);
  1268. });
  1269. if (!kingMonster)
  1270. continue;
  1271. }
  1272. break;
  1273. }
  1274. beneficialSpells.push_back(spellID);
  1275. }
  1276. if(!beneficialSpells.empty())
  1277. {
  1278. return *RandomGeneratorUtil::nextItem(beneficialSpells, rand);
  1279. }
  1280. else
  1281. {
  1282. return SpellID::NONE;
  1283. }
  1284. }
  1285. SpellID CBattleInfoCallback::getRandomCastedSpell(CRandomGenerator & rand,const CStack * caster) const
  1286. {
  1287. RETURN_IF_NOT_BATTLE(SpellID::NONE);
  1288. TBonusListPtr bl = caster->getBonuses(Selector::type(Bonus::SPELLCASTER));
  1289. if (!bl->size())
  1290. return SpellID::NONE;
  1291. int totalWeight = 0;
  1292. for(auto b : *bl)
  1293. {
  1294. totalWeight += std::max(b->additionalInfo, 1); //minimal chance to cast is 1
  1295. }
  1296. int randomPos = rand.nextInt(totalWeight - 1);
  1297. for(auto b : *bl)
  1298. {
  1299. randomPos -= std::max(b->additionalInfo, 1);
  1300. if(randomPos < 0)
  1301. {
  1302. return SpellID(b->subtype);
  1303. }
  1304. }
  1305. return SpellID::NONE;
  1306. }
  1307. int CBattleInfoCallback::battleGetSurrenderCost(PlayerColor Player) const
  1308. {
  1309. RETURN_IF_NOT_BATTLE(-3);
  1310. if(!battleCanSurrender(Player))
  1311. return -1;
  1312. const auto side = playerToSide(Player);
  1313. if(!side)
  1314. return -1;
  1315. int ret = 0;
  1316. double discount = 0;
  1317. for(const CStack * s : battleAliveStacks(side.get()))
  1318. if(s->base) //we pay for our stack that comes from our army slots - condition eliminates summoned cres and war machines
  1319. ret += s->getCreature()->cost[Res::GOLD] * s->getCount(); //todo: extract CStack method
  1320. if(const CGHeroInstance * h = battleGetFightingHero(side.get()))
  1321. discount += h->valOfBonuses(Bonus::SURRENDER_DISCOUNT);
  1322. ret *= (100.0 - discount) / 100.0;
  1323. vstd::amax(ret, 0); //no negative costs for >100% discounts (impossible in original H3 mechanics, but some day...)
  1324. return ret;
  1325. }
  1326. si8 CBattleInfoCallback::battleMaxSpellLevel(ui8 side) const
  1327. {
  1328. const IBonusBearer *node = nullptr;
  1329. if(const CGHeroInstance * h = battleGetFightingHero(side))
  1330. node = h;
  1331. else
  1332. node = getBattleNode();
  1333. if(!node)
  1334. return GameConstants::SPELL_LEVELS;
  1335. //We can't "just get value" - it'd be 0 if there are bonuses (and all would be blocked)
  1336. auto b = node->getBonuses(Selector::type(Bonus::BLOCK_MAGIC_ABOVE));
  1337. if(b->size())
  1338. return b->totalValue();
  1339. return GameConstants::SPELL_LEVELS;
  1340. }
  1341. boost::optional<int> CBattleInfoCallback::battleIsFinished() const
  1342. {
  1343. auto stacks = battleGetAllStacks();
  1344. //checking winning condition
  1345. bool hasStack[2]; //hasStack[0] - true if attacker has a living stack; defender similarly
  1346. hasStack[0] = hasStack[1] = false;
  1347. for(auto & stack : stacks)
  1348. {
  1349. if(stack->alive() && !stack->hasBonusOfType(Bonus::SIEGE_WEAPON))
  1350. {
  1351. hasStack[stack->side] = true;
  1352. }
  1353. }
  1354. if(!hasStack[0] && !hasStack[1])
  1355. return 2;
  1356. if(!hasStack[1])
  1357. return 0;
  1358. if(!hasStack[0])
  1359. return 1;
  1360. return boost::none;
  1361. }