CBattleInfoCallback.cpp 62 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006
  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 retrieveTurretDamageRange(const CGTownInstance * town, const battle::Unit * turret, double & outMinDmg, double & outMaxDmg)//does not match OH3 yet, but damage is somewhat close
  37. {
  38. assert(turret->creatureIndex() == CreatureID::ARROW_TOWERS);
  39. assert(town);
  40. assert(turret->getPosition() >= -4 && turret->getPosition() <= -2);
  41. const float multiplier = (turret->getPosition() == -2) ? 1.0f : 0.5f;
  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 spells::Caster * caster, spells::Mode 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 spells::Mode::HERO:
  122. {
  123. if(battleCastSpells(side.get()) > 0)
  124. return ESpellCastProblem::CASTS_PER_TURN_LIMIT;
  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. bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const
  138. {
  139. RETURN_IF_NOT_BATTLE(false);
  140. if(!battleGetSiegeLevel())
  141. return false;
  142. const std::string cachingStrNoWallPenalty = "type_NO_WALL_PENALTY";
  143. static const auto selectorNoWallPenalty = Selector::type()(Bonus::NO_WALL_PENALTY);
  144. if(shooter->hasBonus(selectorNoWallPenalty, cachingStrNoWallPenalty))
  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 battle::Unit * 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->getPosition(), destHex);
  170. return true;
  171. }
  172. std::vector<PossiblePlayerBattleAction> CBattleInfoCallback::getClientActionsForStack(const CStack * stack, const BattleClientInterfaceData & data)
  173. {
  174. RETURN_IF_NOT_BATTLE(std::vector<PossiblePlayerBattleAction>());
  175. std::vector<PossiblePlayerBattleAction> allowedActionList;
  176. if(data.tacticsMode) //would "if(battleGetTacticDist() > 0)" work?
  177. {
  178. allowedActionList.push_back(PossiblePlayerBattleAction::MOVE_TACTICS);
  179. allowedActionList.push_back(PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK);
  180. }
  181. else
  182. {
  183. if(stack->canCast()) //TODO: check for battlefield effects that prevent casting?
  184. {
  185. if(stack->hasBonusOfType(Bonus::SPELLCASTER) && data.creatureSpellToCast != -1)
  186. {
  187. const CSpell *spell = SpellID(data.creatureSpellToCast).toSpell();
  188. PossiblePlayerBattleAction act = getCasterAction(spell, stack, spells::Mode::CREATURE_ACTIVE);
  189. allowedActionList.push_back(act);
  190. }
  191. if(stack->hasBonusOfType(Bonus::RANDOM_SPELLCASTER))
  192. allowedActionList.push_back(PossiblePlayerBattleAction::RANDOM_GENIE_SPELL);
  193. if(stack->hasBonusOfType(Bonus::DAEMON_SUMMONING))
  194. allowedActionList.push_back(PossiblePlayerBattleAction::RISE_DEMONS);
  195. }
  196. if(stack->canShoot())
  197. allowedActionList.push_back(PossiblePlayerBattleAction::SHOOT);
  198. if(stack->hasBonusOfType(Bonus::RETURN_AFTER_STRIKE))
  199. allowedActionList.push_back(PossiblePlayerBattleAction::ATTACK_AND_RETURN);
  200. allowedActionList.push_back(PossiblePlayerBattleAction::ATTACK); //all active stacks can attack
  201. allowedActionList.push_back(PossiblePlayerBattleAction::WALK_AND_ATTACK); //not all stacks can always walk, but we will check this elsewhere
  202. if(stack->canMove() && stack->Speed(0, true)) //probably no reason to try move war machines or bound stacks
  203. allowedActionList.push_back(PossiblePlayerBattleAction::MOVE_STACK);
  204. auto siegedTown = battleGetDefendedTown();
  205. if(siegedTown && siegedTown->hasFort() && stack->hasBonusOfType(Bonus::CATAPULT)) //TODO: check shots
  206. allowedActionList.push_back(PossiblePlayerBattleAction::CATAPULT);
  207. if(stack->hasBonusOfType(Bonus::HEALER))
  208. allowedActionList.push_back(PossiblePlayerBattleAction::HEAL);
  209. }
  210. return allowedActionList;
  211. }
  212. PossiblePlayerBattleAction CBattleInfoCallback::getCasterAction(const CSpell * spell, const spells::Caster * caster, spells::Mode mode) const
  213. {
  214. RETURN_IF_NOT_BATTLE(PossiblePlayerBattleAction::INVALID);
  215. PossiblePlayerBattleAction spellSelMode = PossiblePlayerBattleAction::ANY_LOCATION;
  216. const CSpell::TargetInfo ti(spell, caster->getSpellSchoolLevel(spell), mode);
  217. if(ti.massive || ti.type == spells::AimType::NO_TARGET)
  218. spellSelMode = PossiblePlayerBattleAction::NO_LOCATION;
  219. else if(ti.type == spells::AimType::LOCATION && ti.clearAffected)
  220. spellSelMode = PossiblePlayerBattleAction::FREE_LOCATION;
  221. else if(ti.type == spells::AimType::CREATURE)
  222. spellSelMode = PossiblePlayerBattleAction::AIMED_SPELL_CREATURE;
  223. else if(ti.type == spells::AimType::OBSTACLE)
  224. spellSelMode = PossiblePlayerBattleAction::OBSTACLE;
  225. return spellSelMode;
  226. }
  227. std::set<BattleHex> CBattleInfoCallback::battleGetAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos) const
  228. {
  229. std::set<BattleHex> attackedHexes;
  230. RETURN_IF_NOT_BATTLE(attackedHexes);
  231. AttackableTiles at = getPotentiallyAttackableHexes(attacker, destinationTile, attackerPos);
  232. for (BattleHex tile : at.hostileCreaturePositions)
  233. {
  234. const CStack * st = battleGetStackByPos(tile, true);
  235. if(st && st->owner != attacker->owner) //only hostile stacks - does it work well with Berserk?
  236. {
  237. attackedHexes.insert(tile);
  238. }
  239. }
  240. for (BattleHex tile : at.friendlyCreaturePositions)
  241. {
  242. if(battleGetStackByPos(tile, true)) //friendly stacks can also be damaged by Dragon Breath
  243. {
  244. attackedHexes.insert(tile);
  245. }
  246. }
  247. return attackedHexes;
  248. }
  249. SpellID CBattleInfoCallback::battleGetRandomStackSpell(CRandomGenerator & rand, const CStack * stack, ERandomSpell mode) const
  250. {
  251. switch (mode)
  252. {
  253. case RANDOM_GENIE:
  254. return getRandomBeneficialSpell(rand, stack); //target
  255. break;
  256. case RANDOM_AIMED:
  257. return getRandomCastedSpell(rand, stack); //caster
  258. break;
  259. default:
  260. logGlobal->error("Incorrect mode of battleGetRandomSpell (%d)", static_cast<int>(mode));
  261. return SpellID::NONE;
  262. }
  263. }
  264. const CStack* CBattleInfoCallback::battleGetStackByPos(BattleHex pos, bool onlyAlive) const
  265. {
  266. RETURN_IF_NOT_BATTLE(nullptr);
  267. for(auto s : battleGetAllStacks(true))
  268. if(vstd::contains(s->getHexes(), pos) && (!onlyAlive || s->alive()))
  269. return s;
  270. return nullptr;
  271. }
  272. const battle::Unit * CBattleInfoCallback::battleGetUnitByPos(BattleHex pos, bool onlyAlive) const
  273. {
  274. RETURN_IF_NOT_BATTLE(nullptr);
  275. auto ret = battleGetUnitsIf([=](const battle::Unit * unit)
  276. {
  277. return !unit->isGhost()
  278. && vstd::contains(battle::Unit::getHexes(unit->getPosition(), unit->doubleWide(), unit->unitSide()), pos)
  279. && (!onlyAlive || unit->alive());
  280. });
  281. if(!ret.empty())
  282. return ret.front();
  283. else
  284. return nullptr;
  285. }
  286. battle::Units CBattleInfoCallback::battleAliveUnits() const
  287. {
  288. return battleGetUnitsIf([](const battle::Unit * unit)
  289. {
  290. return unit->isValidTarget(false);
  291. });
  292. }
  293. battle::Units CBattleInfoCallback::battleAliveUnits(ui8 side) const
  294. {
  295. return battleGetUnitsIf([=](const battle::Unit * unit)
  296. {
  297. return unit->isValidTarget(false) && unit->unitSide() == side;
  298. });
  299. }
  300. //T is battle::Unit descendant
  301. template <typename T>
  302. const T * takeOneUnit(std::vector<const T*> & all, const int turn, int8_t & lastMoved, int phase)
  303. {
  304. const T * returnedUnit = nullptr;
  305. size_t currentUnitIndex = 0;
  306. for(size_t i = 0; i < all.size(); i++)
  307. {
  308. int32_t currentUnitSpeed = -1;
  309. int32_t returnedUnitSpeed = -1;
  310. if(returnedUnit)
  311. returnedUnitSpeed = returnedUnit->getInitiative(turn);
  312. if(all[i])
  313. {
  314. currentUnitSpeed = all[i]->getInitiative(turn);
  315. switch(phase)
  316. {
  317. case 1: // Faster first, attacker priority, higher slot first
  318. if(returnedUnit == nullptr || currentUnitSpeed > returnedUnitSpeed)
  319. {
  320. returnedUnit = all[i];
  321. currentUnitIndex = i;
  322. }
  323. else if(currentUnitSpeed == returnedUnitSpeed)
  324. {
  325. if(lastMoved == -1 && turn <= 0 && all[i]->unitSide() == BattleSide::ATTACKER
  326. && !(returnedUnit->unitSide() == all[i]->unitSide() && returnedUnit->unitSlot() < all[i]->unitSlot())) // Turn 0 attacker priority
  327. {
  328. returnedUnit = all[i];
  329. currentUnitIndex = i;
  330. }
  331. else if(lastMoved != -1 && all[i]->unitSide() != lastMoved
  332. && !(returnedUnit->unitSide() == all[i]->unitSide() && returnedUnit->unitSlot() < all[i]->unitSlot())) // Alternate equal speeds units
  333. {
  334. returnedUnit = all[i];
  335. currentUnitIndex = i;
  336. }
  337. }
  338. break;
  339. case 2: // Slower first, higher slot first
  340. case 3:
  341. if(returnedUnit == nullptr || currentUnitSpeed < returnedUnitSpeed)
  342. {
  343. returnedUnit = all[i];
  344. currentUnitIndex = i;
  345. }
  346. else if(currentUnitSpeed == returnedUnitSpeed && lastMoved != -1 && all[i]->unitSide() != lastMoved
  347. && !(returnedUnit->unitSide() == all[i]->unitSide() && returnedUnit->unitSlot() < all[i]->unitSlot())) // Alternate equal speeds units
  348. {
  349. returnedUnit = all[i];
  350. currentUnitIndex = i;
  351. }
  352. break;
  353. default:
  354. break;
  355. }
  356. }
  357. }
  358. if(!returnedUnit)
  359. return nullptr;
  360. all[currentUnitIndex] = nullptr;
  361. return returnedUnit;
  362. }
  363. void CBattleInfoCallback::battleGetTurnOrder(std::vector<battle::Units> & out, const size_t maxUnits, const int maxTurns, const int turn, int8_t lastMoved) const
  364. {
  365. RETURN_IF_NOT_BATTLE();
  366. if(maxUnits == 0 && maxTurns == 0)
  367. {
  368. logGlobal->error("Attempt to get infinite battle queue");
  369. return;
  370. }
  371. auto actualTurn = turn > 0 ? turn : 0;
  372. auto outputFull = [&]() -> bool
  373. {
  374. if(maxUnits == 0)
  375. return false;//no limit
  376. size_t outSize = 0;
  377. for(const auto & oneTurn : out)
  378. outSize += oneTurn.size();
  379. return outSize >= maxUnits;
  380. };
  381. out.emplace_back();
  382. //We'll split creatures with remaining movement to 4 buckets
  383. // [0] - turrets/catapult,
  384. // [1] - normal (unmoved) creatures, other war machines,
  385. // [2] - waited cres that had morale,
  386. // [3] - rest of waited cres
  387. std::array<battle::Units, 4> phase;
  388. const battle::Unit * active = battleActiveUnit();
  389. if(active)
  390. {
  391. //its first turn and active unit hasn't taken any action yet - must be placed at the beginning of queue, no matter what
  392. if(turn == 0 && active->willMove() && !active->waited())
  393. {
  394. out.back().push_back(active);
  395. if(outputFull())
  396. return;
  397. }
  398. //its first or current turn, turn priority for active stack side
  399. //TODO: what if active stack mind-controlled?
  400. if(turn <= 0 && lastMoved < 0)
  401. lastMoved = active->unitSide();
  402. }
  403. auto all = battleGetUnitsIf([](const battle::Unit * unit)
  404. {
  405. return !unit->isGhost();
  406. });
  407. if(!vstd::contains_if(all, [](const battle::Unit * unit) { return unit->willMove(100000); })) //little evil, but 100000 should be enough for all effects to disappear
  408. {
  409. //No unit will be able to move, battle is over.
  410. out.clear();
  411. return;
  412. }
  413. for(auto one : all)
  414. {
  415. if((actualTurn == 0 && !one->willMove()) //we are considering current round and unit won't move
  416. || (actualTurn > 0 && !one->canMove(turn)) //unit won't be able to move in later rounds
  417. || (actualTurn == 0 && one == active && !out.at(0).empty() && one == out.front().front())) //it's active unit already added at the beginning of queue
  418. {
  419. continue;
  420. }
  421. int p = one->battleQueuePhase(turn);
  422. phase[p].push_back(one);
  423. }
  424. boost::sort(phase[0], CMP_stack(0, actualTurn, lastMoved));
  425. std::copy(phase[0].begin(), phase[0].end(), std::back_inserter(out.back()));
  426. if(outputFull())
  427. return;
  428. for(int i = 1; i < 4; i++)
  429. boost::sort(phase[i], CMP_stack(i, actualTurn, lastMoved));
  430. int pi = 1;
  431. while(!outputFull() && pi < 4)
  432. {
  433. const battle::Unit * current = nullptr;
  434. if(phase[pi].empty())
  435. pi++;
  436. else
  437. {
  438. current = takeOneUnit(phase[pi], actualTurn, lastMoved, pi);
  439. if(!current)
  440. {
  441. pi++;
  442. }
  443. else
  444. {
  445. out.back().push_back(current);
  446. lastMoved = current->unitSide();
  447. }
  448. }
  449. }
  450. if(lastMoved < 0)
  451. lastMoved = BattleSide::ATTACKER;
  452. if(!outputFull() && (maxTurns == 0 || out.size() < maxTurns))
  453. battleGetTurnOrder(out, maxUnits, maxTurns, actualTurn + 1, lastMoved);
  454. }
  455. void CBattleInfoCallback::battleGetStackCountOutsideHexes(bool *ac) const
  456. {
  457. RETURN_IF_NOT_BATTLE();
  458. auto accessibility = getAccesibility();
  459. for(int i = 0; i < accessibility.size(); i++)
  460. ac[i] = (accessibility[i] == EAccessibility::ACCESSIBLE);
  461. }
  462. std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const battle::Unit * unit) const
  463. {
  464. RETURN_IF_NOT_BATTLE(std::vector<BattleHex>());
  465. if(!unit->getPosition().isValid()) //turrets
  466. return std::vector<BattleHex>();
  467. auto reachability = getReachability(unit);
  468. return battleGetAvailableHexes(reachability, unit);
  469. }
  470. std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const ReachabilityInfo & cache, const battle::Unit * unit) const
  471. {
  472. std::vector<BattleHex> ret;
  473. RETURN_IF_NOT_BATTLE(ret);
  474. if(!unit->getPosition().isValid()) //turrets
  475. return ret;
  476. auto unitSpeed = unit->Speed(0, true);
  477. const bool tacticPhase = battleTacticDist() && battleGetTacticsSide() == unit->unitSide();
  478. for(int i = 0; i < GameConstants::BFIELD_SIZE; ++i)
  479. {
  480. // If obstacles or other stacks makes movement impossible, it can't be helped.
  481. if(!cache.isReachable(i))
  482. continue;
  483. if(tacticPhase)
  484. {
  485. //Stack has to perform tactic-phase movement -> can enter any reachable tile within given range
  486. if(!isInTacticRange(i))
  487. continue;
  488. }
  489. else
  490. {
  491. //Not tactics phase -> destination must be reachable and within unit range.
  492. if(cache.distances[i] > (int)unitSpeed)
  493. continue;
  494. }
  495. ret.push_back(i);
  496. }
  497. return ret;
  498. }
  499. std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const battle::Unit * unit, bool addOccupiable, std::vector<BattleHex> * attackable) const
  500. {
  501. std::vector<BattleHex> ret = battleGetAvailableHexes(unit);
  502. if(ret.empty())
  503. return ret;
  504. if(addOccupiable && unit->doubleWide())
  505. {
  506. std::vector<BattleHex> occupiable;
  507. for(auto hex : ret)
  508. occupiable.push_back(unit->occupiedHex(hex));
  509. vstd::concatenate(ret, occupiable);
  510. }
  511. if(attackable)
  512. {
  513. auto meleeAttackable = [&](BattleHex hex) -> bool
  514. {
  515. // Return true if given hex has at least one available neighbour.
  516. // Available hexes are already present in ret vector.
  517. auto availableNeighbor = boost::find_if(ret, [=] (BattleHex availableHex)
  518. {
  519. return BattleHex::mutualPosition(hex, availableHex) >= 0;
  520. });
  521. return availableNeighbor != ret.end();
  522. };
  523. for(auto otherSt : battleAliveUnits(otherSide(unit->unitSide())))
  524. {
  525. if(!otherSt->isValidTarget(false))
  526. continue;
  527. std::vector<BattleHex> occupied = otherSt->getHexes();
  528. if(battleCanShoot(unit, otherSt->getPosition()))
  529. {
  530. attackable->insert(attackable->end(), occupied.begin(), occupied.end());
  531. continue;
  532. }
  533. for(BattleHex he : occupied)
  534. {
  535. if(meleeAttackable(he))
  536. attackable->push_back(he);
  537. }
  538. }
  539. }
  540. //adding occupiable likely adds duplicates to ret -> clean it up
  541. boost::sort(ret);
  542. ret.erase(boost::unique(ret).end(), ret.end());
  543. return ret;
  544. }
  545. bool CBattleInfoCallback::battleCanAttack(const CStack * stack, const CStack * target, BattleHex dest) const
  546. {
  547. RETURN_IF_NOT_BATTLE(false);
  548. if(battleTacticDist())
  549. return false;
  550. if (!stack || !target)
  551. return false;
  552. if(!battleMatchOwner(stack, target))
  553. return false;
  554. auto &id = stack->getCreature()->idNumber;
  555. if (id == CreatureID::FIRST_AID_TENT || id == CreatureID::CATAPULT)
  556. return false;
  557. return target->alive();
  558. }
  559. bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker) const
  560. {
  561. RETURN_IF_NOT_BATTLE(false);
  562. if (battleTacticDist()) //no shooting during tactics
  563. return false;
  564. if (!attacker)
  565. return false;
  566. if (attacker->creatureIndex() == CreatureID::CATAPULT) //catapult cannot attack creatures
  567. return false;
  568. //forgetfulness
  569. TConstBonusListPtr forgetfulList = attacker->getBonuses(Selector::type()(Bonus::FORGETFULL));
  570. if (!forgetfulList->empty())
  571. {
  572. int forgetful = forgetfulList->valOfBonuses(Selector::type()(Bonus::FORGETFULL));
  573. //advanced+ level
  574. if (forgetful > 1)
  575. return false;
  576. }
  577. return attacker->canShoot() && (!battleIsUnitBlocked(attacker)
  578. || attacker->hasBonusOfType(Bonus::FREE_SHOOTING));
  579. }
  580. bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker, BattleHex dest) const
  581. {
  582. RETURN_IF_NOT_BATTLE(false);
  583. const battle::Unit * defender = battleGetUnitByPos(dest);
  584. if(!attacker || !defender)
  585. return false;
  586. if(battleMatchOwner(attacker, defender) && defender->alive())
  587. return battleCanShoot(attacker);
  588. return false;
  589. }
  590. TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo & info) const
  591. {
  592. auto battleBonusValue = [&](const IBonusBearer * bearer, CSelector selector) -> int
  593. {
  594. auto noLimit = Selector::effectRange()(Bonus::NO_LIMIT);
  595. auto limitMatches = info.shooting
  596. ? Selector::effectRange()(Bonus::ONLY_DISTANCE_FIGHT)
  597. : Selector::effectRange()(Bonus::ONLY_MELEE_FIGHT);
  598. //any regular bonuses or just ones for melee/ranged
  599. return bearer->getBonuses(selector, noLimit.Or(limitMatches))->totalValue();
  600. };
  601. const IBonusBearer * attackerBonuses = info.attacker;
  602. const IBonusBearer * defenderBonuses = info.defender;
  603. double additiveBonus = 1.0 + info.additiveBonus;
  604. double multBonus = 1.0 * info.multBonus;
  605. double minDmg = 0.0;
  606. double maxDmg = 0.0;
  607. minDmg = info.attacker->getMinDamage(info.shooting);
  608. maxDmg = info.attacker->getMaxDamage(info.shooting);
  609. minDmg *= info.attacker->getCount(),
  610. maxDmg *= info.attacker->getCount();
  611. if(info.attacker->creatureIndex() == CreatureID::ARROW_TOWERS)
  612. {
  613. SiegeStuffThatShouldBeMovedToHandlers::retrieveTurretDamageRange(battleGetDefendedTown(), info.attacker, minDmg, maxDmg);
  614. TDmgRange unmodifiableTowerDamage = std::make_pair(int64_t(minDmg), int64_t(maxDmg));
  615. return unmodifiableTowerDamage;
  616. }
  617. const std::string cachingStrSiedgeWeapon = "type_SIEGE_WEAPON";
  618. static const auto selectorSiedgeWeapon = Selector::type()(Bonus::SIEGE_WEAPON);
  619. if(attackerBonuses->hasBonus(selectorSiedgeWeapon, cachingStrSiedgeWeapon) && info.attacker->creatureIndex() != CreatureID::ARROW_TOWERS) //any siege weapon, but only ballista can attack (second condition - not arrow turret)
  620. { //minDmg and maxDmg are multiplied by hero attack + 1
  621. auto retrieveHeroPrimSkill = [&](int skill) -> int
  622. {
  623. std::shared_ptr<const Bonus> b = attackerBonuses->getBonus(Selector::sourceTypeSel(Bonus::HERO_BASE_SKILL).And(Selector::typeSubtype(Bonus::PRIMARY_SKILL, skill)));
  624. return b ? b->val : 0; //if there is no hero or no info on his primary skill, return 0
  625. };
  626. minDmg *= retrieveHeroPrimSkill(PrimarySkill::ATTACK) + 1;
  627. maxDmg *= retrieveHeroPrimSkill(PrimarySkill::ATTACK) + 1;
  628. }
  629. double attackDefenceDifference = 0.0;
  630. double multAttackReduction = 1.0 - battleBonusValue(attackerBonuses, Selector::type()(Bonus::GENERAL_ATTACK_REDUCTION)) / 100.0;
  631. attackDefenceDifference += info.attacker->getAttack(info.shooting) * multAttackReduction;
  632. double multDefenceReduction = 1.0 - battleBonusValue(attackerBonuses, Selector::type()(Bonus::ENEMY_DEFENCE_REDUCTION)) / 100.0;
  633. attackDefenceDifference -= info.defender->getDefence(info.shooting) * multDefenceReduction;
  634. const std::string cachingStrSlayer = "type_SLAYER";
  635. static const auto selectorSlayer = Selector::type()(Bonus::SLAYER);
  636. //slayer handling //TODO: apply only ONLY_MELEE_FIGHT / DISTANCE_FIGHT?
  637. auto slayerEffects = attackerBonuses->getBonuses(selectorSlayer, cachingStrSlayer);
  638. if(std::shared_ptr<const Bonus> slayerEffect = slayerEffects->getFirst(Selector::all))
  639. {
  640. std::vector<int32_t> affectedIds;
  641. const auto spLevel = slayerEffect->val;
  642. const CCreature * defenderType = info.defender->unitType();
  643. bool isAffected = false;
  644. for(const auto & b : defenderType->getBonusList())
  645. {
  646. if((b->type == Bonus::KING3 && spLevel >= 3) || //expert
  647. (b->type == Bonus::KING2 && spLevel >= 2) || //adv +
  648. (b->type == Bonus::KING1 && spLevel >= 0)) //none or basic +
  649. {
  650. isAffected = true;
  651. break;
  652. }
  653. }
  654. if(isAffected)
  655. {
  656. attackDefenceDifference += SpellID(SpellID::SLAYER).toSpell()->getPower(spLevel);
  657. if(info.attacker->hasBonusOfType(Bonus::SPECIAL_PECULIAR_ENCHANT, SpellID::SLAYER))
  658. {
  659. ui8 attackerTier = info.attacker->unitType()->level;
  660. ui8 specialtyBonus = std::max(5 - attackerTier, 0);
  661. attackDefenceDifference += specialtyBonus;
  662. }
  663. }
  664. }
  665. //bonus from attack/defense skills
  666. if(attackDefenceDifference < 0) //decreasing dmg
  667. {
  668. const double dec = std::min(0.025 * (-attackDefenceDifference), 0.7);
  669. multBonus *= 1.0 - dec;
  670. }
  671. else //increasing dmg
  672. {
  673. const double inc = std::min(0.05 * attackDefenceDifference, 4.0);
  674. additiveBonus += inc;
  675. }
  676. const std::string cachingStrJousting = "type_JOUSTING";
  677. static const auto selectorJousting = Selector::type()(Bonus::JOUSTING);
  678. const std::string cachingStrChargeImmunity = "type_CHARGE_IMMUNITY";
  679. static const auto selectorChargeImmunity = Selector::type()(Bonus::CHARGE_IMMUNITY);
  680. //applying jousting bonus
  681. if(info.chargedFields > 0 && attackerBonuses->hasBonus(selectorJousting, cachingStrJousting) && !defenderBonuses->hasBonus(selectorChargeImmunity, cachingStrChargeImmunity))
  682. additiveBonus += info.chargedFields * 0.05;
  683. //handling secondary abilities and artifacts giving premies to them
  684. const std::string cachingStrArchery = "type_SECONDARY_SKILL_PREMYs_ARCHERY";
  685. static const auto selectorArchery = Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::ARCHERY);
  686. const std::string cachingStrOffence = "type_SECONDARY_SKILL_PREMYs_OFFENCE";
  687. static const auto selectorOffence = Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::OFFENCE);
  688. const std::string cachingStrArmorer = "type_SECONDARY_SKILL_PREMYs_ARMORER";
  689. static const auto selectorArmorer = Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::ARMORER);
  690. if(info.shooting)
  691. additiveBonus += attackerBonuses->valOfBonuses(selectorArchery, cachingStrArchery) / 100.0;
  692. else
  693. additiveBonus += attackerBonuses->valOfBonuses(selectorOffence, cachingStrOffence) / 100.0;
  694. multBonus *= (std::max(0, 100 - defenderBonuses->valOfBonuses(selectorArmorer, cachingStrArmorer))) / 100.0;
  695. //handling hate effect
  696. //assume that unit have only few HATE features and cache them all
  697. const std::string cachingStrHate = "type_HATE";
  698. static const auto selectorHate = Selector::type()(Bonus::HATE);
  699. auto allHateEffects = attackerBonuses->getBonuses(selectorHate, cachingStrHate);
  700. additiveBonus += allHateEffects->valOfBonuses(Selector::subtype()(info.defender->creatureIndex())) / 100.0;
  701. const std::string cachingStrMeleeReduction = "type_GENERAL_DAMAGE_REDUCTIONs_0";
  702. static const auto selectorMeleeReduction = Selector::typeSubtype(Bonus::GENERAL_DAMAGE_REDUCTION, 0);
  703. const std::string cachingStrRangedReduction = "type_GENERAL_DAMAGE_REDUCTIONs_1";
  704. static const auto selectorRangedReduction = Selector::typeSubtype(Bonus::GENERAL_DAMAGE_REDUCTION, 1);
  705. //handling spell effects
  706. if(!info.shooting) //eg. shield
  707. {
  708. multBonus *= (100 - defenderBonuses->valOfBonuses(selectorMeleeReduction, cachingStrMeleeReduction)) / 100.0;
  709. }
  710. else //eg. air shield
  711. {
  712. multBonus *= (100 - defenderBonuses->valOfBonuses(selectorRangedReduction, cachingStrRangedReduction)) / 100.0;
  713. }
  714. if(info.shooting)
  715. {
  716. //todo: set actual percentage in spell bonus configuration instead of just level; requires non trivial backward compatibility handling
  717. //get list first, total value of 0 also counts
  718. TConstBonusListPtr forgetfulList = attackerBonuses->getBonuses(Selector::type()(Bonus::FORGETFULL),"type_FORGETFULL");
  719. if(!forgetfulList->empty())
  720. {
  721. int forgetful = forgetfulList->valOfBonuses(Selector::all);
  722. //none of basic level
  723. if(forgetful == 0 || forgetful == 1)
  724. multBonus *= 0.5;
  725. else
  726. logGlobal->warn("Attempt to calculate shooting damage with adv+ FORGETFULL effect");
  727. }
  728. }
  729. const std::string cachingStrForcedMinDamage = "type_ALWAYS_MINIMUM_DAMAGE";
  730. static const auto selectorForcedMinDamage = Selector::type()(Bonus::ALWAYS_MINIMUM_DAMAGE);
  731. const std::string cachingStrForcedMaxDamage = "type_ALWAYS_MAXIMUM_DAMAGE";
  732. static const auto selectorForcedMaxDamage = Selector::type()(Bonus::ALWAYS_MAXIMUM_DAMAGE);
  733. TConstBonusListPtr curseEffects = attackerBonuses->getBonuses(selectorForcedMinDamage, cachingStrForcedMinDamage);
  734. TConstBonusListPtr blessEffects = attackerBonuses->getBonuses(selectorForcedMaxDamage, cachingStrForcedMaxDamage);
  735. int curseBlessAdditiveModifier = blessEffects->totalValue() - curseEffects->totalValue();
  736. double curseMultiplicativePenalty = curseEffects->size() ? (*std::max_element(curseEffects->begin(), curseEffects->end(), &Bonus::compareByAdditionalInfo<std::shared_ptr<Bonus>>))->additionalInfo[0] : 0;
  737. if(curseMultiplicativePenalty) //curse handling (partial, the rest is below)
  738. {
  739. multBonus *= 1.0 - curseMultiplicativePenalty/100;
  740. }
  741. const std::string cachingStrAdvAirShield = "isAdvancedAirShield";
  742. auto isAdvancedAirShield = [](const Bonus* bonus)
  743. {
  744. return bonus->source == Bonus::SPELL_EFFECT
  745. && bonus->sid == SpellID::AIR_SHIELD
  746. && bonus->val >= SecSkillLevel::ADVANCED;
  747. };
  748. if(info.shooting)
  749. {
  750. //wall / distance penalty + advanced air shield
  751. BattleHex attackerPos = info.attackerPos.isValid() ? info.attackerPos : info.attacker->getPosition();
  752. BattleHex defenderPos = info.defenderPos.isValid() ? info.defenderPos : info.defender->getPosition();
  753. const bool distPenalty = battleHasDistancePenalty(attackerBonuses, attackerPos, defenderPos);
  754. const bool obstaclePenalty = battleHasWallPenalty(attackerBonuses, attackerPos, defenderPos);
  755. if(distPenalty || defenderBonuses->hasBonus(isAdvancedAirShield, cachingStrAdvAirShield))
  756. multBonus *= 0.5;
  757. if(obstaclePenalty)
  758. multBonus *= 0.5; //cumulative
  759. }
  760. else
  761. {
  762. const std::string cachingStrNoMeleePenalty = "type_NO_MELEE_PENALTY";
  763. static const auto selectorNoMeleePenalty = Selector::type()(Bonus::NO_MELEE_PENALTY);
  764. if(info.attacker->isShooter() && !attackerBonuses->hasBonus(selectorNoMeleePenalty, cachingStrNoMeleePenalty))
  765. multBonus *= 0.5;
  766. }
  767. // psychic elementals versus mind immune units 50%
  768. if(info.attacker->creatureIndex() == CreatureID::PSYCHIC_ELEMENTAL)
  769. {
  770. const std::string cachingStrMindImmunity = "type_MIND_IMMUNITY";
  771. static const auto selectorMindImmunity = Selector::type()(Bonus::MIND_IMMUNITY);
  772. if(defenderBonuses->hasBonus(selectorMindImmunity, cachingStrMindImmunity))
  773. multBonus *= 0.5;
  774. }
  775. // TODO attack on petrified unit 50%
  776. // blinded unit retaliates
  777. minDmg *= additiveBonus * multBonus;
  778. maxDmg *= additiveBonus * multBonus;
  779. if(curseEffects->size()) //curse handling (rest)
  780. {
  781. minDmg += curseBlessAdditiveModifier;
  782. maxDmg = minDmg;
  783. }
  784. else if(blessEffects->size()) //bless handling
  785. {
  786. maxDmg += curseBlessAdditiveModifier;
  787. minDmg = maxDmg;
  788. }
  789. TDmgRange returnedVal = std::make_pair(int64_t(minDmg), int64_t(maxDmg));
  790. //damage cannot be less than 1
  791. vstd::amax(returnedVal.first, 1);
  792. vstd::amax(returnedVal.second, 1);
  793. return returnedVal;
  794. }
  795. TDmgRange CBattleInfoCallback::battleEstimateDamage(const CStack * attacker, const CStack * defender, TDmgRange * retaliationDmg) const
  796. {
  797. RETURN_IF_NOT_BATTLE(std::make_pair(0, 0));
  798. const bool shooting = battleCanShoot(attacker, defender->getPosition());
  799. const BattleAttackInfo bai(attacker, defender, shooting);
  800. return battleEstimateDamage(bai, retaliationDmg);
  801. }
  802. TDmgRange CBattleInfoCallback::battleEstimateDamage(const BattleAttackInfo & bai, TDmgRange * retaliationDmg) const
  803. {
  804. RETURN_IF_NOT_BATTLE(std::make_pair(0, 0));
  805. TDmgRange ret = calculateDmgRange(bai);
  806. if(retaliationDmg)
  807. {
  808. if(bai.shooting)
  809. {
  810. //FIXME: handle RANGED_RETALIATION
  811. retaliationDmg->first = retaliationDmg->second = 0;
  812. }
  813. else
  814. {
  815. //TODO: rewrite using boost::numeric::interval
  816. //TODO: rewire once more using interval-based fuzzy arithmetic
  817. int64_t TDmgRange::* pairElems[] = {&TDmgRange::first, &TDmgRange::second};
  818. for (int i=0; i<2; ++i)
  819. {
  820. auto retaliationAttack = bai.reverse();
  821. int64_t dmg = ret.*pairElems[i];
  822. auto state = retaliationAttack.attacker->acquireState();
  823. state->damage(dmg);
  824. retaliationAttack.attacker = state.get();
  825. retaliationDmg->*pairElems[!i] = calculateDmgRange(retaliationAttack).*pairElems[!i];
  826. }
  827. }
  828. }
  829. return ret;
  830. }
  831. std::vector<std::shared_ptr<const CObstacleInstance>> CBattleInfoCallback::battleGetAllObstaclesOnPos(BattleHex tile, bool onlyBlocking) const
  832. {
  833. std::vector<std::shared_ptr<const CObstacleInstance>> obstacles = std::vector<std::shared_ptr<const CObstacleInstance>>();
  834. RETURN_IF_NOT_BATTLE(obstacles);
  835. for(auto & obs : battleGetAllObstacles())
  836. {
  837. if(vstd::contains(obs->getBlockedTiles(), tile)
  838. || (!onlyBlocking && vstd::contains(obs->getAffectedTiles(), tile)))
  839. {
  840. obstacles.push_back(obs);
  841. }
  842. }
  843. return obstacles;
  844. }
  845. std::vector<std::shared_ptr<const CObstacleInstance>> CBattleInfoCallback::getAllAffectedObstaclesByStack(const CStack * stack) const
  846. {
  847. std::vector<std::shared_ptr<const CObstacleInstance>> affectedObstacles = std::vector<std::shared_ptr<const CObstacleInstance>>();
  848. RETURN_IF_NOT_BATTLE(affectedObstacles);
  849. if(stack->alive())
  850. {
  851. affectedObstacles = battleGetAllObstaclesOnPos(stack->getPosition(), false);
  852. if(stack->doubleWide())
  853. {
  854. BattleHex otherHex = stack->occupiedHex(stack->getPosition());
  855. if(otherHex.isValid())
  856. for(auto & i : battleGetAllObstaclesOnPos(otherHex, false))
  857. affectedObstacles.push_back(i);
  858. }
  859. for(auto hex : stack->getHexes())
  860. if(hex == ESiegeHex::GATE_BRIDGE)
  861. if(battleGetGateState() == EGateState::OPENED || battleGetGateState() == EGateState::DESTROYED)
  862. for(int i=0; i<affectedObstacles.size(); i++)
  863. if(affectedObstacles.at(i)->obstacleType == CObstacleInstance::MOAT)
  864. affectedObstacles.erase(affectedObstacles.begin()+i);
  865. }
  866. return affectedObstacles;
  867. }
  868. AccessibilityInfo CBattleInfoCallback::getAccesibility() const
  869. {
  870. AccessibilityInfo ret;
  871. ret.fill(EAccessibility::ACCESSIBLE);
  872. //removing accessibility for side columns of hexes
  873. for(int y = 0; y < GameConstants::BFIELD_HEIGHT; y++)
  874. {
  875. ret[BattleHex(GameConstants::BFIELD_WIDTH - 1, y)] = EAccessibility::SIDE_COLUMN;
  876. ret[BattleHex(0, y)] = EAccessibility::SIDE_COLUMN;
  877. }
  878. //special battlefields with logically unavailable tiles
  879. std::vector<BattleHex> impassableHexes;
  880. if(battleGetBattlefieldType().num == BFieldType::SHIP_TO_SHIP)
  881. {
  882. impassableHexes =
  883. {
  884. 6, 7, 8, 9,
  885. 24, 25, 26,
  886. 58, 59, 60,
  887. 75, 76, 77,
  888. 92, 93, 94,
  889. 109, 110, 111,
  890. 126, 127, 128,
  891. 159, 160, 161, 162, 163,
  892. 176, 177, 178, 179, 180
  893. };
  894. }
  895. for(auto hex : impassableHexes)
  896. ret[hex] = EAccessibility::UNAVAILABLE;
  897. //gate -> should be before stacks
  898. if(battleGetSiegeLevel() > 0)
  899. {
  900. EAccessibility accessability = EAccessibility::ACCESSIBLE;
  901. switch(battleGetGateState())
  902. {
  903. case EGateState::CLOSED:
  904. accessability = EAccessibility::GATE;
  905. break;
  906. case EGateState::BLOCKED:
  907. accessability = EAccessibility::UNAVAILABLE;
  908. break;
  909. }
  910. ret[ESiegeHex::GATE_OUTER] = ret[ESiegeHex::GATE_INNER] = accessability;
  911. }
  912. //tiles occupied by standing stacks
  913. for(auto unit : battleAliveUnits())
  914. {
  915. for(auto hex : unit->getHexes())
  916. if(hex.isAvailable()) //towers can have <0 pos; we don't also want to overwrite side columns
  917. ret[hex] = EAccessibility::ALIVE_STACK;
  918. }
  919. //obstacles
  920. for(const auto &obst : battleGetAllObstacles())
  921. {
  922. for(auto hex : obst->getBlockedTiles())
  923. ret[hex] = EAccessibility::OBSTACLE;
  924. }
  925. //walls
  926. if(battleGetSiegeLevel() > 0)
  927. {
  928. static const int permanentlyLocked[] = {12, 45, 62, 112, 147, 165};
  929. for(auto hex : permanentlyLocked)
  930. ret[hex] = EAccessibility::UNAVAILABLE;
  931. //TODO likely duplicated logic
  932. static const std::pair<int, BattleHex> lockedIfNotDestroyed[] =
  933. {
  934. //which part of wall, which hex is blocked if this part of wall is not destroyed
  935. std::make_pair(2, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_4)),
  936. std::make_pair(3, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_3)),
  937. std::make_pair(4, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_2)),
  938. std::make_pair(5, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_1))
  939. };
  940. for(auto & elem : lockedIfNotDestroyed)
  941. {
  942. if(battleGetWallState(elem.first) != EWallState::DESTROYED)
  943. ret[elem.second] = EAccessibility::DESTRUCTIBLE_WALL;
  944. }
  945. }
  946. return ret;
  947. }
  948. AccessibilityInfo CBattleInfoCallback::getAccesibility(const battle::Unit * stack) const
  949. {
  950. return getAccesibility(battle::Unit::getHexes(stack->getPosition(), stack->doubleWide(), stack->unitSide()));
  951. }
  952. AccessibilityInfo CBattleInfoCallback::getAccesibility(const std::vector<BattleHex> & accessibleHexes) const
  953. {
  954. auto ret = getAccesibility();
  955. for(auto hex : accessibleHexes)
  956. if(hex.isValid())
  957. ret[hex] = EAccessibility::ACCESSIBLE;
  958. return ret;
  959. }
  960. ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibility, const ReachabilityInfo::Parameters & params) const
  961. {
  962. ReachabilityInfo ret;
  963. ret.accessibility = accessibility;
  964. ret.params = params;
  965. ret.predecessors.fill(BattleHex::INVALID);
  966. ret.distances.fill(ReachabilityInfo::INFINITE_DIST);
  967. if(!params.startPosition.isValid()) //if got call for arrow turrets
  968. return ret;
  969. const std::set<BattleHex> obstacles = getStoppers(params.perspective);
  970. std::queue<BattleHex> hexq; //bfs queue
  971. //first element
  972. hexq.push(params.startPosition);
  973. ret.distances[params.startPosition] = 0;
  974. std::array<bool, GameConstants::BFIELD_SIZE> accessibleCache;
  975. for(int hex = 0; hex < GameConstants::BFIELD_SIZE; hex++)
  976. accessibleCache[hex] = accessibility.accessible(hex, params.doubleWide, params.side);
  977. while(!hexq.empty()) //bfs loop
  978. {
  979. const BattleHex curHex = hexq.front();
  980. hexq.pop();
  981. //walking stack can't step past the obstacles
  982. if(curHex != params.startPosition && isInObstacle(curHex, obstacles, params))
  983. continue;
  984. const int costToNeighbour = ret.distances[curHex.hex] + 1;
  985. for(BattleHex neighbour : BattleHex::neighbouringTilesCache[curHex.hex])
  986. {
  987. if(neighbour.isValid())
  988. {
  989. const int costFoundSoFar = ret.distances[neighbour.hex];
  990. if(accessibleCache[neighbour.hex] && costToNeighbour < costFoundSoFar)
  991. {
  992. hexq.push(neighbour);
  993. ret.distances[neighbour.hex] = costToNeighbour;
  994. ret.predecessors[neighbour.hex] = curHex;
  995. }
  996. }
  997. }
  998. }
  999. return ret;
  1000. }
  1001. bool CBattleInfoCallback::isInObstacle(
  1002. BattleHex hex,
  1003. const std::set<BattleHex> & obstacles,
  1004. const ReachabilityInfo::Parameters & params) const
  1005. {
  1006. auto occupiedHexes = battle::Unit::getHexes(hex, params.doubleWide, params.side);
  1007. for(auto occupiedHex : occupiedHexes)
  1008. {
  1009. if(vstd::contains(obstacles, occupiedHex))
  1010. {
  1011. if(occupiedHex == ESiegeHex::GATE_BRIDGE)
  1012. {
  1013. if(battleGetGateState() != EGateState::DESTROYED && params.side == BattleSide::ATTACKER)
  1014. return true;
  1015. }
  1016. else
  1017. return true;
  1018. }
  1019. }
  1020. return false;
  1021. }
  1022. std::set<BattleHex> CBattleInfoCallback::getStoppers(BattlePerspective::BattlePerspective whichSidePerspective) const
  1023. {
  1024. std::set<BattleHex> ret;
  1025. RETURN_IF_NOT_BATTLE(ret);
  1026. for(auto &oi : battleGetAllObstacles(whichSidePerspective))
  1027. {
  1028. if(battleIsObstacleVisibleForSide(*oi, whichSidePerspective))
  1029. {
  1030. range::copy(oi->getStoppingTile(), vstd::set_inserter(ret));
  1031. }
  1032. }
  1033. return ret;
  1034. }
  1035. std::pair<const battle::Unit *, BattleHex> CBattleInfoCallback::getNearestStack(const battle::Unit * closest) const
  1036. {
  1037. auto reachability = getReachability(closest);
  1038. auto avHexes = battleGetAvailableHexes(reachability, closest);
  1039. // I hate std::pairs with their undescriptive member names first / second
  1040. struct DistStack
  1041. {
  1042. int distanceToPred;
  1043. BattleHex destination;
  1044. const battle::Unit * stack;
  1045. };
  1046. std::vector<DistStack> stackPairs;
  1047. std::vector<const battle::Unit *> possible = battleGetUnitsIf([=](const battle::Unit * unit)
  1048. {
  1049. return unit->isValidTarget(false) && unit != closest;
  1050. });
  1051. for(const battle::Unit * st : possible)
  1052. {
  1053. for(BattleHex hex : avHexes)
  1054. if(CStack::isMeleeAttackPossible(closest, st, hex))
  1055. {
  1056. DistStack hlp = {reachability.distances[hex], hex, st};
  1057. stackPairs.push_back(hlp);
  1058. }
  1059. }
  1060. if (stackPairs.size())
  1061. {
  1062. auto comparator = [](DistStack lhs, DistStack rhs) { return lhs.distanceToPred < rhs.distanceToPred; };
  1063. auto minimal = boost::min_element(stackPairs, comparator);
  1064. return std::make_pair(minimal->stack, minimal->destination);
  1065. }
  1066. else
  1067. return std::make_pair<const battle::Unit * , BattleHex>(nullptr, BattleHex::INVALID);
  1068. }
  1069. BattleHex CBattleInfoCallback::getAvaliableHex(CreatureID creID, ui8 side, int initialPos) const
  1070. {
  1071. bool twoHex = VLC->creh->creatures[creID]->isDoubleWide();
  1072. //bool flying = VLC->creh->creatures[creID]->isFlying();
  1073. int pos;
  1074. if (initialPos > -1)
  1075. pos = initialPos;
  1076. else //summon elementals depending on player side
  1077. {
  1078. if(side == BattleSide::ATTACKER)
  1079. pos = 0; //top left
  1080. else
  1081. pos = GameConstants::BFIELD_WIDTH - 1; //top right
  1082. }
  1083. auto accessibility = getAccesibility();
  1084. std::set<BattleHex> occupyable;
  1085. for(int i = 0; i < accessibility.size(); i++)
  1086. if(accessibility.accessible(i, twoHex, side))
  1087. occupyable.insert(i);
  1088. if(occupyable.empty())
  1089. {
  1090. return BattleHex::INVALID; //all tiles are covered
  1091. }
  1092. return BattleHex::getClosestTile(side, pos, occupyable);
  1093. }
  1094. si8 CBattleInfoCallback::battleGetTacticDist() const
  1095. {
  1096. RETURN_IF_NOT_BATTLE(0);
  1097. //TODO get rid of this method
  1098. if(battleDoWeKnowAbout(battleGetTacticsSide()))
  1099. return battleTacticDist();
  1100. return 0;
  1101. }
  1102. bool CBattleInfoCallback::isInTacticRange(BattleHex dest) const
  1103. {
  1104. RETURN_IF_NOT_BATTLE(false);
  1105. auto side = battleGetTacticsSide();
  1106. auto dist = battleGetTacticDist();
  1107. return ((!side && dest.getX() > 0 && dest.getX() <= dist)
  1108. || (side && dest.getX() < GameConstants::BFIELD_WIDTH - 1 && dest.getX() >= GameConstants::BFIELD_WIDTH - dist - 1));
  1109. }
  1110. ReachabilityInfo CBattleInfoCallback::getReachability(const battle::Unit * unit) const
  1111. {
  1112. ReachabilityInfo::Parameters params(unit, unit->getPosition());
  1113. if(!battleDoWeKnowAbout(unit->unitSide()))
  1114. {
  1115. //Stack is held by enemy, we can't use his perspective to check for reachability.
  1116. // Happens ie. when hovering enemy stack for its range. The arg could be set properly, but it's easier to fix it here.
  1117. params.perspective = battleGetMySide();
  1118. }
  1119. return getReachability(params);
  1120. }
  1121. ReachabilityInfo CBattleInfoCallback::getReachability(const ReachabilityInfo::Parameters &params) const
  1122. {
  1123. if(params.flying)
  1124. return getFlyingReachability(params);
  1125. else
  1126. return makeBFS(getAccesibility(params.knownAccessible), params);
  1127. }
  1128. ReachabilityInfo CBattleInfoCallback::getFlyingReachability(const ReachabilityInfo::Parameters &params) const
  1129. {
  1130. ReachabilityInfo ret;
  1131. ret.accessibility = getAccesibility(params.knownAccessible);
  1132. for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
  1133. {
  1134. if(ret.accessibility.accessible(i, params.doubleWide, params.side))
  1135. {
  1136. ret.predecessors[i] = params.startPosition;
  1137. ret.distances[i] = BattleHex::getDistance(params.startPosition, i);
  1138. }
  1139. }
  1140. return ret;
  1141. }
  1142. AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes (const battle::Unit* attacker, BattleHex destinationTile, BattleHex attackerPos) const
  1143. {
  1144. //does not return hex attacked directly
  1145. //TODO: apply rotation to two-hex attackers
  1146. bool isAttacker = attacker->unitSide() == BattleSide::ATTACKER;
  1147. AttackableTiles at;
  1148. RETURN_IF_NOT_BATTLE(at);
  1149. const int WN = GameConstants::BFIELD_WIDTH;
  1150. BattleHex hex = (attackerPos != BattleHex::INVALID) ? attackerPos : attacker->getPosition(); //real or hypothetical (cursor) position
  1151. //FIXME: dragons or cerbers can rotate before attack, making their base hex different (#1124)
  1152. bool reverse = isToReverse(hex, destinationTile, isAttacker, attacker->doubleWide(), isAttacker);
  1153. if(reverse && attacker->doubleWide())
  1154. {
  1155. hex = attacker->occupiedHex(hex); //the other hex stack stands on
  1156. }
  1157. if(attacker->hasBonusOfType(Bonus::ATTACKS_ALL_ADJACENT))
  1158. {
  1159. boost::copy(attacker->getSurroundingHexes(attackerPos), vstd::set_inserter(at.hostileCreaturePositions));
  1160. }
  1161. if(attacker->hasBonusOfType(Bonus::THREE_HEADED_ATTACK))
  1162. {
  1163. std::vector<BattleHex> hexes = attacker->getSurroundingHexes(attackerPos);
  1164. for(BattleHex tile : hexes)
  1165. {
  1166. if((BattleHex::mutualPosition(tile, destinationTile) > -1 && BattleHex::mutualPosition(tile, hex) > -1)) //adjacent both to attacker's head and attacked tile
  1167. {
  1168. auto st = battleGetUnitByPos(tile, true);
  1169. if(st && battleMatchOwner(st, attacker)) //only hostile stacks - does it work well with Berserk?
  1170. {
  1171. at.hostileCreaturePositions.insert(tile);
  1172. }
  1173. }
  1174. }
  1175. }
  1176. if(attacker->hasBonusOfType(Bonus::WIDE_BREATH))
  1177. {
  1178. std::vector<BattleHex> hexes = destinationTile.neighbouringTiles();
  1179. for(int i = 0; i<hexes.size(); i++)
  1180. {
  1181. if(hexes.at(i) == hex)
  1182. {
  1183. hexes.erase(hexes.begin() + i);
  1184. i = 0;
  1185. }
  1186. }
  1187. for(BattleHex tile : hexes)
  1188. {
  1189. //friendly stacks can also be damaged by Dragon Breath
  1190. auto st = battleGetUnitByPos(tile, true);
  1191. if(st && st != attacker)
  1192. {
  1193. at.friendlyCreaturePositions.insert(tile);
  1194. }
  1195. }
  1196. }
  1197. else if(attacker->hasBonusOfType(Bonus::TWO_HEX_ATTACK_BREATH))
  1198. {
  1199. int pos = BattleHex::mutualPosition(destinationTile, hex);
  1200. if (pos > -1) //only adjacent hexes are subject of dragon breath calculation
  1201. {
  1202. std::vector<BattleHex> hexes; //only one, in fact
  1203. int pseudoVector = destinationTile.hex - hex;
  1204. switch (pseudoVector)
  1205. {
  1206. case 1:
  1207. case -1:
  1208. BattleHex::checkAndPush(destinationTile.hex + pseudoVector, hexes);
  1209. break;
  1210. case WN: //17 //left-down or right-down
  1211. case -WN: //-17 //left-up or right-up
  1212. case WN + 1: //18 //right-down
  1213. case -WN + 1: //-16 //right-up
  1214. BattleHex::checkAndPush(destinationTile.hex + pseudoVector + (((hex / WN) % 2) ? 1 : -1), hexes);
  1215. break;
  1216. case WN - 1: //16 //left-down
  1217. case -WN - 1: //-18 //left-up
  1218. BattleHex::checkAndPush(destinationTile.hex + pseudoVector + (((hex / WN) % 2) ? 1 : 0), hexes);
  1219. break;
  1220. }
  1221. for (BattleHex tile : hexes)
  1222. {
  1223. //friendly stacks can also be damaged by Dragon Breath
  1224. auto st = battleGetUnitByPos(tile, true);
  1225. if (st != nullptr)
  1226. at.friendlyCreaturePositions.insert(tile);
  1227. }
  1228. }
  1229. }
  1230. return at;
  1231. }
  1232. AttackableTiles CBattleInfoCallback::getPotentiallyShootableHexes(const battle::Unit * attacker, BattleHex destinationTile, BattleHex attackerPos) const
  1233. {
  1234. //does not return hex attacked directly
  1235. AttackableTiles at;
  1236. RETURN_IF_NOT_BATTLE(at);
  1237. if(attacker->hasBonusOfType(Bonus::SHOOTS_ALL_ADJACENT) && !vstd::contains(attackerPos.neighbouringTiles(), destinationTile))
  1238. {
  1239. std::vector<BattleHex> targetHexes = destinationTile.neighbouringTiles();
  1240. targetHexes.push_back(destinationTile);
  1241. boost::copy(targetHexes, vstd::set_inserter(at.hostileCreaturePositions));
  1242. }
  1243. return at;
  1244. }
  1245. std::vector<const battle::Unit*> CBattleInfoCallback::getAttackedBattleUnits(const battle::Unit* attacker, BattleHex destinationTile, bool rangedAttack, BattleHex attackerPos) const
  1246. {
  1247. std::vector<const battle::Unit*> units;
  1248. RETURN_IF_NOT_BATTLE(units);
  1249. AttackableTiles at;
  1250. if (rangedAttack)
  1251. at = getPotentiallyShootableHexes(attacker, destinationTile, attackerPos);
  1252. else
  1253. at = getPotentiallyAttackableHexes(attacker, destinationTile, attackerPos);
  1254. units = battleGetUnitsIf([=](const battle::Unit * unit)
  1255. {
  1256. if (unit->isGhost() || !unit->alive())
  1257. return false;
  1258. for (BattleHex hex : battle::Unit::getHexes(unit->getPosition(), unit->doubleWide(), unit->unitSide()))
  1259. {
  1260. if (vstd::contains(at.hostileCreaturePositions, hex))
  1261. return true;
  1262. if (vstd::contains(at.friendlyCreaturePositions, hex))
  1263. return true;
  1264. }
  1265. return false;
  1266. });
  1267. return units;
  1268. }
  1269. std::set<const CStack*> CBattleInfoCallback::getAttackedCreatures(const CStack* attacker, BattleHex destinationTile, bool rangedAttack, BattleHex attackerPos) const
  1270. {
  1271. std::set<const CStack*> attackedCres;
  1272. RETURN_IF_NOT_BATTLE(attackedCres);
  1273. AttackableTiles at;
  1274. if(rangedAttack)
  1275. at = getPotentiallyShootableHexes(attacker, destinationTile, attackerPos);
  1276. else
  1277. at = getPotentiallyAttackableHexes(attacker, destinationTile, attackerPos);
  1278. for (BattleHex tile : at.hostileCreaturePositions) //all around & three-headed attack
  1279. {
  1280. const CStack * st = battleGetStackByPos(tile, true);
  1281. if(st && st->owner != attacker->owner) //only hostile stacks - does it work well with Berserk?
  1282. {
  1283. attackedCres.insert(st);
  1284. }
  1285. }
  1286. for (BattleHex tile : at.friendlyCreaturePositions)
  1287. {
  1288. const CStack * st = battleGetStackByPos(tile, true);
  1289. if(st) //friendly stacks can also be damaged by Dragon Breath
  1290. {
  1291. attackedCres.insert(st);
  1292. }
  1293. }
  1294. return attackedCres;
  1295. }
  1296. //TODO: this should apply also to mechanics and cursor interface
  1297. bool CBattleInfoCallback::isToReverseHlp (BattleHex hexFrom, BattleHex hexTo, bool curDir) const
  1298. {
  1299. int fromX = hexFrom.getX();
  1300. int fromY = hexFrom.getY();
  1301. int toX = hexTo.getX();
  1302. int toY = hexTo.getY();
  1303. if (curDir) // attacker, facing right
  1304. {
  1305. if (fromX < toX)
  1306. return false;
  1307. if (fromX > toX)
  1308. return true;
  1309. if (fromY % 2 == 0 && toY % 2 == 1)
  1310. return true;
  1311. return false;
  1312. }
  1313. else // defender, facing left
  1314. {
  1315. if(fromX < toX)
  1316. return true;
  1317. if(fromX > toX)
  1318. return false;
  1319. if (fromY % 2 == 1 && toY % 2 == 0)
  1320. return true;
  1321. return false;
  1322. }
  1323. }
  1324. //TODO: this should apply also to mechanics and cursor interface
  1325. bool CBattleInfoCallback::isToReverse (BattleHex hexFrom, BattleHex hexTo, bool curDir, bool toDoubleWide, bool toDir) const
  1326. {
  1327. if (hexTo < 0 || hexFrom < 0) //turret
  1328. return false;
  1329. if (toDoubleWide)
  1330. {
  1331. if (isToReverseHlp (hexFrom, hexTo, curDir))
  1332. {
  1333. if (toDir)
  1334. return isToReverseHlp (hexFrom, hexTo-1, curDir);
  1335. else
  1336. return isToReverseHlp (hexFrom, hexTo+1, curDir);
  1337. }
  1338. return false;
  1339. }
  1340. else
  1341. {
  1342. return isToReverseHlp(hexFrom, hexTo, curDir);
  1343. }
  1344. }
  1345. ReachabilityInfo::TDistances CBattleInfoCallback::battleGetDistances(const battle::Unit * unit, BattleHex assumedPosition) const
  1346. {
  1347. ReachabilityInfo::TDistances ret;
  1348. ret.fill(-1);
  1349. RETURN_IF_NOT_BATTLE(ret);
  1350. auto reachability = getReachability(unit);
  1351. boost::copy(reachability.distances, ret.begin());
  1352. return ret;
  1353. }
  1354. bool CBattleInfoCallback::battleHasDistancePenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const
  1355. {
  1356. RETURN_IF_NOT_BATTLE(false);
  1357. const std::string cachingStrNoDistancePenalty = "type_NO_DISTANCE_PENALTY";
  1358. static const auto selectorNoDistancePenalty = Selector::type()(Bonus::NO_DISTANCE_PENALTY);
  1359. if(shooter->hasBonus(selectorNoDistancePenalty, cachingStrNoDistancePenalty))
  1360. return false;
  1361. if(auto target = battleGetUnitByPos(destHex, true))
  1362. {
  1363. //If any hex of target creature is within range, there is no penalty
  1364. for(auto hex : target->getHexes())
  1365. if(BattleHex::getDistance(shooterPosition, hex) <= GameConstants::BATTLE_PENALTY_DISTANCE)
  1366. return false;
  1367. //TODO what about two-hex shooters?
  1368. }
  1369. else
  1370. {
  1371. if(BattleHex::getDistance(shooterPosition, destHex) <= GameConstants::BATTLE_PENALTY_DISTANCE)
  1372. return false;
  1373. }
  1374. return true;
  1375. }
  1376. BattleHex CBattleInfoCallback::wallPartToBattleHex(EWallPart::EWallPart part) const
  1377. {
  1378. RETURN_IF_NOT_BATTLE(BattleHex::INVALID);
  1379. return WallPartToHex(part);
  1380. }
  1381. EWallPart::EWallPart CBattleInfoCallback::battleHexToWallPart(BattleHex hex) const
  1382. {
  1383. RETURN_IF_NOT_BATTLE(EWallPart::INVALID);
  1384. return hexToWallPart(hex);
  1385. }
  1386. bool CBattleInfoCallback::isWallPartPotentiallyAttackable(EWallPart::EWallPart wallPart) const
  1387. {
  1388. RETURN_IF_NOT_BATTLE(false);
  1389. return wallPart != EWallPart::INDESTRUCTIBLE_PART && wallPart != EWallPart::INDESTRUCTIBLE_PART_OF_GATE &&
  1390. wallPart != EWallPart::INVALID;
  1391. }
  1392. std::vector<BattleHex> CBattleInfoCallback::getAttackableBattleHexes() const
  1393. {
  1394. std::vector<BattleHex> attackableBattleHexes;
  1395. RETURN_IF_NOT_BATTLE(attackableBattleHexes);
  1396. for(auto & wallPartPair : wallParts)
  1397. {
  1398. if(isWallPartPotentiallyAttackable(wallPartPair.second))
  1399. {
  1400. auto wallState = static_cast<EWallState::EWallState>(battleGetWallState(static_cast<int>(wallPartPair.second)));
  1401. if(wallState == EWallState::INTACT || wallState == EWallState::DAMAGED)
  1402. {
  1403. attackableBattleHexes.push_back(BattleHex(wallPartPair.first));
  1404. }
  1405. }
  1406. }
  1407. return attackableBattleHexes;
  1408. }
  1409. ui32 CBattleInfoCallback::battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const
  1410. {
  1411. RETURN_IF_NOT_BATTLE(-1);
  1412. //TODO should be replaced using bonus system facilities (propagation onto battle node)
  1413. ui32 ret = caster->getSpellCost(sp);
  1414. //checking for friendly stacks reducing cost of the spell and
  1415. //enemy stacks increasing it
  1416. si32 manaReduction = 0;
  1417. si32 manaIncrease = 0;
  1418. for(auto unit : battleAliveUnits())
  1419. {
  1420. if(unit->unitOwner() == caster->tempOwner && unit->hasBonusOfType(Bonus::CHANGES_SPELL_COST_FOR_ALLY))
  1421. {
  1422. vstd::amax(manaReduction, unit->valOfBonuses(Bonus::CHANGES_SPELL_COST_FOR_ALLY));
  1423. }
  1424. if(unit->unitOwner() != caster->tempOwner && unit->hasBonusOfType(Bonus::CHANGES_SPELL_COST_FOR_ENEMY))
  1425. {
  1426. vstd::amax(manaIncrease, unit->valOfBonuses(Bonus::CHANGES_SPELL_COST_FOR_ENEMY));
  1427. }
  1428. }
  1429. return ret - manaReduction + manaIncrease;
  1430. }
  1431. bool CBattleInfoCallback::battleHasShootingPenalty(const battle::Unit * shooter, BattleHex destHex) const
  1432. {
  1433. return battleHasDistancePenalty(shooter, shooter->getPosition(), destHex) || battleHasWallPenalty(shooter, shooter->getPosition(), destHex);
  1434. }
  1435. bool CBattleInfoCallback::battleIsUnitBlocked(const battle::Unit * unit) const
  1436. {
  1437. RETURN_IF_NOT_BATTLE(false);
  1438. if(unit->hasBonusOfType(Bonus::SIEGE_WEAPON)) //siege weapons cannot be blocked
  1439. return false;
  1440. for(auto adjacent : battleAdjacentUnits(unit))
  1441. {
  1442. if(adjacent->unitOwner() != unit->unitOwner()) //blocked by enemy stack
  1443. return true;
  1444. }
  1445. return false;
  1446. }
  1447. std::set<const battle::Unit *> CBattleInfoCallback::battleAdjacentUnits(const battle::Unit * unit) const
  1448. {
  1449. std::set<const battle::Unit *> ret;
  1450. RETURN_IF_NOT_BATTLE(ret);
  1451. for(auto hex : unit->getSurroundingHexes())
  1452. {
  1453. if(auto neighbour = battleGetUnitByPos(hex, true))
  1454. ret.insert(neighbour);
  1455. }
  1456. return ret;
  1457. }
  1458. SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, const CStack * subject) const
  1459. {
  1460. RETURN_IF_NOT_BATTLE(SpellID::NONE);
  1461. //This is complete list. No spells from mods.
  1462. //todo: this should be Spellbook of caster Stack
  1463. static const std::set<SpellID> allPossibleSpells =
  1464. {
  1465. SpellID::AIR_SHIELD,
  1466. SpellID::ANTI_MAGIC,
  1467. SpellID::BLESS,
  1468. SpellID::BLOODLUST,
  1469. SpellID::COUNTERSTRIKE,
  1470. SpellID::CURE,
  1471. SpellID::FIRE_SHIELD,
  1472. SpellID::FORTUNE,
  1473. SpellID::HASTE,
  1474. SpellID::MAGIC_MIRROR,
  1475. SpellID::MIRTH,
  1476. SpellID::PRAYER,
  1477. SpellID::PRECISION,
  1478. SpellID::PROTECTION_FROM_AIR,
  1479. SpellID::PROTECTION_FROM_EARTH,
  1480. SpellID::PROTECTION_FROM_FIRE,
  1481. SpellID::PROTECTION_FROM_WATER,
  1482. SpellID::SHIELD,
  1483. SpellID::SLAYER,
  1484. SpellID::STONE_SKIN
  1485. };
  1486. std::vector<SpellID> beneficialSpells;
  1487. auto getAliveEnemy = [=](const std::function<bool(const CStack *)> & pred) -> const CStack *
  1488. {
  1489. auto stacks = battleGetStacksIf([=](const CStack * stack)
  1490. {
  1491. return pred(stack) && stack->owner != subject->owner && stack->isValidTarget(false);
  1492. });
  1493. if(stacks.empty())
  1494. return nullptr;
  1495. else
  1496. return stacks.front();
  1497. };
  1498. for(const SpellID spellID : allPossibleSpells)
  1499. {
  1500. std::stringstream cachingStr;
  1501. cachingStr << "source_" << Bonus::SPELL_EFFECT << "id_" << spellID.num;
  1502. if(subject->hasBonus(Selector::source(Bonus::SPELL_EFFECT, spellID), Selector::all, cachingStr.str())
  1503. //TODO: this ability has special limitations
  1504. || !(spellID.toSpell()->canBeCast(this, spells::Mode::CREATURE_ACTIVE, subject)))
  1505. continue;
  1506. switch (spellID)
  1507. {
  1508. case SpellID::SHIELD:
  1509. case SpellID::FIRE_SHIELD: // not if all enemy units are shooters
  1510. {
  1511. auto walker = getAliveEnemy([&](const CStack * stack) //look for enemy, non-shooting stack
  1512. {
  1513. return !stack->canShoot();
  1514. });
  1515. if (!walker)
  1516. continue;
  1517. }
  1518. break;
  1519. case SpellID::AIR_SHIELD: //only against active shooters
  1520. {
  1521. auto shooter = getAliveEnemy([&](const CStack * stack) //look for enemy, non-shooting stack
  1522. {
  1523. return stack->canShoot();
  1524. });
  1525. if (!shooter)
  1526. continue;
  1527. }
  1528. break;
  1529. case SpellID::ANTI_MAGIC:
  1530. case SpellID::MAGIC_MIRROR:
  1531. case SpellID::PROTECTION_FROM_AIR:
  1532. case SpellID::PROTECTION_FROM_EARTH:
  1533. case SpellID::PROTECTION_FROM_FIRE:
  1534. case SpellID::PROTECTION_FROM_WATER:
  1535. {
  1536. const ui8 enemySide = 1 - subject->side;
  1537. //todo: only if enemy has spellbook
  1538. if (!battleHasHero(enemySide)) //only if there is enemy hero
  1539. continue;
  1540. }
  1541. break;
  1542. case SpellID::CURE: //only damaged units
  1543. {
  1544. //do not cast on affected by debuffs
  1545. if(!subject->canBeHealed())
  1546. continue;
  1547. }
  1548. break;
  1549. case SpellID::BLOODLUST:
  1550. {
  1551. if(subject->canShoot()) //TODO: if can shoot - only if enemy units are adjacent
  1552. continue;
  1553. }
  1554. break;
  1555. case SpellID::PRECISION:
  1556. {
  1557. if(!subject->canShoot())
  1558. continue;
  1559. }
  1560. break;
  1561. case SpellID::SLAYER://only if monsters are present
  1562. {
  1563. auto kingMonster = getAliveEnemy([&](const CStack * stack) -> bool //look for enemy, non-shooting stack
  1564. {
  1565. const auto isKing = Selector::type()(Bonus::KING1)
  1566. .Or(Selector::type()(Bonus::KING2))
  1567. .Or(Selector::type()(Bonus::KING3));
  1568. return stack->hasBonus(isKing);
  1569. });
  1570. if (!kingMonster)
  1571. continue;
  1572. }
  1573. break;
  1574. }
  1575. beneficialSpells.push_back(spellID);
  1576. }
  1577. if(!beneficialSpells.empty())
  1578. {
  1579. return *RandomGeneratorUtil::nextItem(beneficialSpells, rand);
  1580. }
  1581. else
  1582. {
  1583. return SpellID::NONE;
  1584. }
  1585. }
  1586. SpellID CBattleInfoCallback::getRandomCastedSpell(CRandomGenerator & rand,const CStack * caster) const
  1587. {
  1588. RETURN_IF_NOT_BATTLE(SpellID::NONE);
  1589. TConstBonusListPtr bl = caster->getBonuses(Selector::type()(Bonus::SPELLCASTER));
  1590. if (!bl->size())
  1591. return SpellID::NONE;
  1592. int totalWeight = 0;
  1593. for(auto b : *bl)
  1594. {
  1595. totalWeight += std::max(b->additionalInfo[0], 1); //minimal chance to cast is 1
  1596. }
  1597. int randomPos = rand.nextInt(totalWeight - 1);
  1598. for(auto b : *bl)
  1599. {
  1600. randomPos -= std::max(b->additionalInfo[0], 1);
  1601. if(randomPos < 0)
  1602. {
  1603. return SpellID(b->subtype);
  1604. }
  1605. }
  1606. return SpellID::NONE;
  1607. }
  1608. int CBattleInfoCallback::battleGetSurrenderCost(PlayerColor Player) const
  1609. {
  1610. RETURN_IF_NOT_BATTLE(-3);
  1611. if(!battleCanSurrender(Player))
  1612. return -1;
  1613. const auto sideOpt = playerToSide(Player);
  1614. if(!sideOpt)
  1615. return -1;
  1616. const auto side = sideOpt.get();
  1617. int ret = 0;
  1618. double discount = 0;
  1619. for(auto unit : battleAliveUnits(side))
  1620. ret += unit->getRawSurrenderCost();
  1621. if(const CGHeroInstance * h = battleGetFightingHero(side))
  1622. discount += h->valOfBonuses(Bonus::SURRENDER_DISCOUNT);
  1623. ret = static_cast<int>(ret * (100.0 - discount) / 100.0);
  1624. vstd::amax(ret, 0); //no negative costs for >100% discounts (impossible in original H3 mechanics, but some day...)
  1625. return ret;
  1626. }
  1627. si8 CBattleInfoCallback::battleMinSpellLevel(ui8 side) const
  1628. {
  1629. const IBonusBearer * node = nullptr;
  1630. if(const CGHeroInstance * h = battleGetFightingHero(side))
  1631. node = h;
  1632. else
  1633. node = getBattleNode();
  1634. if(!node)
  1635. return 0;
  1636. auto b = node->getBonuses(Selector::type()(Bonus::BLOCK_MAGIC_BELOW));
  1637. if(b->size())
  1638. return b->totalValue();
  1639. return 0;
  1640. }
  1641. si8 CBattleInfoCallback::battleMaxSpellLevel(ui8 side) const
  1642. {
  1643. const IBonusBearer *node = nullptr;
  1644. if(const CGHeroInstance * h = battleGetFightingHero(side))
  1645. node = h;
  1646. else
  1647. node = getBattleNode();
  1648. if(!node)
  1649. return GameConstants::SPELL_LEVELS;
  1650. //We can't "just get value" - it'd be 0 if there are bonuses (and all would be blocked)
  1651. auto b = node->getBonuses(Selector::type()(Bonus::BLOCK_MAGIC_ABOVE));
  1652. if(b->size())
  1653. return b->totalValue();
  1654. return GameConstants::SPELL_LEVELS;
  1655. }
  1656. boost::optional<int> CBattleInfoCallback::battleIsFinished() const
  1657. {
  1658. auto units = battleGetUnitsIf([=](const battle::Unit * unit)
  1659. {
  1660. return unit->alive() && !unit->isTurret() && unit->alive();
  1661. });
  1662. std::array<bool, 2> hasUnit = {false, false}; //index is BattleSide
  1663. for(auto & unit : units)
  1664. {
  1665. //todo: move SIEGE_WEAPON check to Unit state
  1666. if(!unit->hasBonusOfType(Bonus::SIEGE_WEAPON))
  1667. {
  1668. hasUnit.at(unit->unitSide()) = true;
  1669. }
  1670. if(hasUnit[0] && hasUnit[1])
  1671. break;
  1672. }
  1673. if(!hasUnit[0] && !hasUnit[1])
  1674. return 2;
  1675. if(!hasUnit[1])
  1676. return 0;
  1677. if(!hasUnit[0])
  1678. return 1;
  1679. return boost::none;
  1680. }