CBattleInfoCallback.cpp 50 KB

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