2
0

CDefaultSpellMechanics.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802
  1. /*
  2. * CDefaultSpellMechanics.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 "CDefaultSpellMechanics.h"
  12. #include "../BattleState.h"
  13. #include "../CGeneralTextHandler.h"
  14. namespace SRSLPraserHelpers
  15. {
  16. static int XYToHex(int x, int y)
  17. {
  18. return x + GameConstants::BFIELD_WIDTH * y;
  19. }
  20. static int XYToHex(std::pair<int, int> xy)
  21. {
  22. return XYToHex(xy.first, xy.second);
  23. }
  24. static int hexToY(int battleFieldPosition)
  25. {
  26. return battleFieldPosition/GameConstants::BFIELD_WIDTH;
  27. }
  28. static int hexToX(int battleFieldPosition)
  29. {
  30. int pos = battleFieldPosition - hexToY(battleFieldPosition) * GameConstants::BFIELD_WIDTH;
  31. return pos;
  32. }
  33. static std::pair<int, int> hexToPair(int battleFieldPosition)
  34. {
  35. return std::make_pair(hexToX(battleFieldPosition), hexToY(battleFieldPosition));
  36. }
  37. //moves hex by one hex in given direction
  38. //0 - left top, 1 - right top, 2 - right, 3 - right bottom, 4 - left bottom, 5 - left
  39. static std::pair<int, int> gotoDir(int x, int y, int direction)
  40. {
  41. switch(direction)
  42. {
  43. case 0: //top left
  44. return std::make_pair((y%2) ? x-1 : x, y-1);
  45. case 1: //top right
  46. return std::make_pair((y%2) ? x : x+1, y-1);
  47. case 2: //right
  48. return std::make_pair(x+1, y);
  49. case 3: //right bottom
  50. return std::make_pair((y%2) ? x : x+1, y+1);
  51. case 4: //left bottom
  52. return std::make_pair((y%2) ? x-1 : x, y+1);
  53. case 5: //left
  54. return std::make_pair(x-1, y);
  55. default:
  56. throw std::runtime_error("Disaster: wrong direction in SRSLPraserHelpers::gotoDir!\n");
  57. }
  58. }
  59. static std::pair<int, int> gotoDir(std::pair<int, int> xy, int direction)
  60. {
  61. return gotoDir(xy.first, xy.second, direction);
  62. }
  63. static bool isGoodHex(std::pair<int, int> xy)
  64. {
  65. return xy.first >=0 && xy.first < GameConstants::BFIELD_WIDTH && xy.second >= 0 && xy.second < GameConstants::BFIELD_HEIGHT;
  66. }
  67. //helper function for rangeInHexes
  68. static std::set<ui16> getInRange(unsigned int center, int low, int high)
  69. {
  70. std::set<ui16> ret;
  71. if(low == 0)
  72. {
  73. ret.insert(center);
  74. }
  75. std::pair<int, int> mainPointForLayer[6]; //A, B, C, D, E, F points
  76. for(auto & elem : mainPointForLayer)
  77. elem = hexToPair(center);
  78. for(int it=1; it<=high; ++it) //it - distance to the center
  79. {
  80. for(int b=0; b<6; ++b)
  81. mainPointForLayer[b] = gotoDir(mainPointForLayer[b], b);
  82. if(it>=low)
  83. {
  84. std::pair<int, int> curHex;
  85. //adding lines (A-b, B-c, C-d, etc)
  86. for(int v=0; v<6; ++v)
  87. {
  88. curHex = mainPointForLayer[v];
  89. for(int h=0; h<it; ++h)
  90. {
  91. if(isGoodHex(curHex))
  92. ret.insert(XYToHex(curHex));
  93. curHex = gotoDir(curHex, (v+2)%6);
  94. }
  95. }
  96. } //if(it>=low)
  97. }
  98. return ret;
  99. }
  100. }
  101. SpellCastContext::SpellCastContext(const DefaultSpellMechanics * mechanics_, const SpellCastEnvironment * env_, const BattleSpellCastParameters & parameters_):
  102. mechanics(mechanics_), env(env_), attackedCres(), sc(), si(), parameters(parameters_), otherHero(nullptr), spellCost(0)
  103. {
  104. sc.side = parameters.casterSide;
  105. sc.id = mechanics->owner->id;
  106. sc.skill = parameters.spellLvl;
  107. sc.tile = parameters.getFirstDestinationHex();
  108. sc.dmgToDisplay = 0;
  109. sc.castByHero = parameters.mode == ECastingMode::HERO_CASTING;
  110. sc.casterStack = (parameters.casterStack ? parameters.casterStack->ID : -1);
  111. sc.manaGained = 0;
  112. //check it there is opponent hero
  113. const ui8 otherSide = 1-parameters.casterSide;
  114. if(parameters.cb->battleHasHero(otherSide))
  115. otherHero = parameters.cb->battleGetFightingHero(otherSide);
  116. logGlobal->debugStream() << "Started spell cast. Spell: " << mechanics->owner->name << "; mode:" << parameters.mode;
  117. }
  118. SpellCastContext::~SpellCastContext()
  119. {
  120. logGlobal->debugStream() << "Finished spell cast. Spell: " << mechanics->owner->name << "; mode:" << parameters.mode;
  121. }
  122. void SpellCastContext::addDamageToDisplay(const si32 value)
  123. {
  124. sc.dmgToDisplay += value;
  125. }
  126. void SpellCastContext::setDamageToDisplay(const si32 value)
  127. {
  128. sc.dmgToDisplay = value;
  129. }
  130. void SpellCastContext::sendCastPacket()
  131. {
  132. for(auto sta : attackedCres)
  133. {
  134. sc.affectedCres.insert(sta->ID);
  135. }
  136. env->sendAndApply(&sc);
  137. }
  138. void SpellCastContext::beforeCast()
  139. {
  140. //calculate spell cost
  141. if(parameters.mode == ECastingMode::HERO_CASTING)
  142. {
  143. spellCost = parameters.cb->battleGetSpellCost(mechanics->owner, parameters.casterHero);
  144. if(nullptr != otherHero) //handle mana channel
  145. {
  146. int manaChannel = 0;
  147. for(const CStack * stack : parameters.cb->battleGetAllStacks(true)) //TODO: shouldn't bonus system handle it somehow?
  148. {
  149. if(stack->owner == otherHero->tempOwner)
  150. {
  151. vstd::amax(manaChannel, stack->valOfBonuses(Bonus::MANA_CHANNELING));
  152. }
  153. }
  154. sc.manaGained = (manaChannel * spellCost) / 100;
  155. }
  156. logGlobal->debugStream() << "spellCost: " << spellCost;
  157. }
  158. }
  159. void SpellCastContext::afterCast()
  160. {
  161. sendCastPacket();
  162. if(parameters.mode == ECastingMode::HERO_CASTING)
  163. {
  164. //spend mana
  165. SetMana sm;
  166. sm.absolute = false;
  167. sm.hid = parameters.casterHero->id;
  168. sm.val = -spellCost;
  169. env->sendAndApply(&sm);
  170. if(sc.manaGained > 0)
  171. {
  172. assert(otherHero);
  173. sm.hid = otherHero->id;
  174. sm.val = sc.manaGained;
  175. env->sendAndApply(&sm);
  176. }
  177. }
  178. else if (parameters.mode == ECastingMode::CREATURE_ACTIVE_CASTING || parameters.mode == ECastingMode::ENCHANTER_CASTING)
  179. {
  180. //reduce number of casts remaining
  181. assert(parameters.casterStack);
  182. BattleSetStackProperty ssp;
  183. ssp.stackID = parameters.casterStack->ID;
  184. ssp.which = BattleSetStackProperty::CASTS;
  185. ssp.val = -1;
  186. ssp.absolute = false;
  187. env->sendAndApply(&ssp);
  188. }
  189. if(!si.stacks.empty()) //after spellcast info shows
  190. env->sendAndApply(&si);
  191. }
  192. ///DefaultSpellMechanics
  193. void DefaultSpellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
  194. {
  195. if (packet->castByHero)
  196. {
  197. if (packet->side < 2)
  198. {
  199. battle->sides[packet->side].castSpellsCount++;
  200. }
  201. }
  202. //handle countering spells
  203. for(auto stackID : packet->affectedCres)
  204. {
  205. CStack * s = battle->getStack(stackID);
  206. s->popBonuses([&](const Bonus * b) -> bool
  207. {
  208. //check for each bonus if it should be removed
  209. const bool isSpellEffect = Selector::sourceType(Bonus::SPELL_EFFECT)(b);
  210. const int spellID = isSpellEffect ? b->sid : -1;
  211. //No exceptions, ANY spell can be countered, even if it can`t be dispelled.
  212. return isSpellEffect && vstd::contains(owner->counteredSpells, spellID);
  213. });
  214. }
  215. }
  216. void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters) const
  217. {
  218. if(nullptr == parameters.caster)
  219. {
  220. env->complain("No spell-caster provided.");
  221. return;
  222. }
  223. std::vector <const CStack*> reflected;//for magic mirror
  224. cast(env, parameters, reflected);
  225. //Magic Mirror effect
  226. for(auto & attackedCre : reflected)
  227. {
  228. if(parameters.mode == ECastingMode::MAGIC_MIRROR)
  229. {
  230. logGlobal->error("Magic mirror recurrence!");
  231. return;
  232. }
  233. TStacks mirrorTargets = parameters.cb->battleGetStacksIf([this, parameters](const CStack * battleStack)
  234. {
  235. //Get all enemy stacks. Magic mirror can reflect to immune creature (with no effect)
  236. return battleStack->owner == parameters.casterColor && battleStack->isValidTarget(false);
  237. });
  238. if(!mirrorTargets.empty())
  239. {
  240. int targetHex = (*RandomGeneratorUtil::nextItem(mirrorTargets, env->getRandomGenerator()))->position;
  241. BattleSpellCastParameters mirrorParameters(parameters.cb, attackedCre, owner);
  242. mirrorParameters.aimToHex(targetHex);
  243. mirrorParameters.mode = ECastingMode::MAGIC_MIRROR;
  244. mirrorParameters.spellLvl = parameters.spellLvl;
  245. mirrorParameters.effectLevel = parameters.effectLevel;
  246. mirrorParameters.effectPower = parameters.effectPower;
  247. mirrorParameters.effectValue = parameters.effectValue;
  248. mirrorParameters.enchantPower = parameters.enchantPower;
  249. mirrorParameters.cast(env);
  250. }
  251. }
  252. }
  253. void DefaultSpellMechanics::cast(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, std::vector <const CStack*> & reflected) const
  254. {
  255. SpellCastContext ctx(this, env, parameters);
  256. ctx.beforeCast();
  257. ctx.attackedCres = owner->getAffectedStacks(parameters.cb, parameters.mode, parameters.caster, parameters.spellLvl, parameters.getFirstDestinationHex());
  258. logGlobal->debugStream() << "will affect " << ctx.attackedCres.size() << " stacks";
  259. handleResistance(env, ctx);
  260. if(parameters.mode != ECastingMode::MAGIC_MIRROR)
  261. handleMagicMirror(env, ctx, reflected);
  262. applyBattleEffects(env, parameters, ctx);
  263. ctx.afterCast();
  264. }
  265. void DefaultSpellMechanics::battleLogSingleTarget(std::vector<std::string> & logLines, const BattleSpellCast * packet,
  266. const std::string & casterName, const CStack * attackedStack, bool & displayDamage) const
  267. {
  268. const std::string attackedName = attackedStack->getName();
  269. const std::string attackedNameSing = attackedStack->getCreature()->nameSing;
  270. const std::string attackedNamePl = attackedStack->getCreature()->namePl;
  271. auto getPluralFormat = [attackedStack](const int baseTextID) -> boost::format
  272. {
  273. return boost::format(VLC->generaltexth->allTexts[(attackedStack->count > 1 ? baseTextID + 1 : baseTextID)]);
  274. };
  275. auto logSimple = [&logLines, getPluralFormat, attackedName](const int baseTextID)
  276. {
  277. boost::format fmt = getPluralFormat(baseTextID);
  278. fmt % attackedName;
  279. logLines.push_back(fmt.str());
  280. };
  281. auto logPlural = [&logLines, attackedNamePl](const int baseTextID)
  282. {
  283. boost::format fmt(VLC->generaltexth->allTexts[baseTextID]);
  284. fmt % attackedNamePl;
  285. logLines.push_back(fmt.str());
  286. };
  287. displayDamage = false; //in most following cases damage info text is custom
  288. switch(owner->id)
  289. {
  290. case SpellID::STONE_GAZE:
  291. logSimple(558);
  292. break;
  293. case SpellID::POISON:
  294. logSimple(561);
  295. break;
  296. case SpellID::BIND:
  297. logPlural(560);//Roots and vines bind the %s to the ground!
  298. break;
  299. case SpellID::DISEASE:
  300. logSimple(553);
  301. break;
  302. case SpellID::PARALYZE:
  303. logSimple(563);
  304. break;
  305. case SpellID::AGE:
  306. {
  307. boost::format text = getPluralFormat(551);
  308. text % attackedName;
  309. //The %s shrivel with age, and lose %d hit points."
  310. TBonusListPtr bl = attackedStack->getBonuses(Selector::type(Bonus::STACK_HEALTH));
  311. const int fullHP = bl->totalValue();
  312. bl->remove_if(Selector::source(Bonus::SPELL_EFFECT, SpellID::AGE));
  313. text % (fullHP - bl->totalValue());
  314. logLines.push_back(text.str());
  315. }
  316. break;
  317. case SpellID::THUNDERBOLT:
  318. {
  319. logPlural(367);
  320. std::string text = VLC->generaltexth->allTexts[343].substr(1, VLC->generaltexth->allTexts[343].size() - 1); //Does %d points of damage.
  321. boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(packet->dmgToDisplay)); //no more text afterwards
  322. logLines.push_back(text);
  323. }
  324. break;
  325. case SpellID::DISPEL_HELPFUL_SPELLS:
  326. logPlural(555);
  327. break;
  328. case SpellID::DEATH_STARE:
  329. if (packet->dmgToDisplay > 0)
  330. {
  331. std::string text;
  332. if (packet->dmgToDisplay > 1)
  333. {
  334. text = VLC->generaltexth->allTexts[119]; //%d %s die under the terrible gaze of the %s.
  335. boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(packet->dmgToDisplay));
  336. boost::algorithm::replace_first(text, "%s", attackedNamePl);
  337. }
  338. else
  339. {
  340. text = VLC->generaltexth->allTexts[118]; //One %s dies under the terrible gaze of the %s.
  341. boost::algorithm::replace_first(text, "%s", attackedNameSing);
  342. }
  343. boost::algorithm::replace_first(text, "%s", casterName); //casting stack
  344. logLines.push_back(text);
  345. }
  346. break;
  347. default:
  348. {
  349. boost::format text(VLC->generaltexth->allTexts[565]); //The %s casts %s
  350. text % casterName % owner->name;
  351. displayDamage = true;
  352. logLines.push_back(text.str());
  353. }
  354. break;
  355. }
  356. }
  357. void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
  358. {
  359. //applying effects
  360. if(owner->isOffensiveSpell())
  361. {
  362. int spellDamage = parameters.effectValue;
  363. int chainLightningModifier = 0;
  364. for(auto & attackedCre : ctx.attackedCres)
  365. {
  366. BattleStackAttacked bsa;
  367. if(spellDamage != 0)
  368. bsa.damageAmount = owner->adjustRawDamage(parameters.caster, attackedCre, spellDamage) >> chainLightningModifier;
  369. else
  370. bsa.damageAmount = owner->calculateDamage(parameters.caster, attackedCre, parameters.effectLevel, parameters.effectPower) >> chainLightningModifier;
  371. ctx.addDamageToDisplay(bsa.damageAmount);
  372. bsa.stackAttacked = (attackedCre)->ID;
  373. if(parameters.mode == ECastingMode::ENCHANTER_CASTING) //multiple damage spells cast
  374. bsa.attackerID = parameters.casterStack->ID;
  375. else
  376. bsa.attackerID = -1;
  377. (attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
  378. ctx.si.stacks.push_back(bsa);
  379. if(owner->id == SpellID::CHAIN_LIGHTNING)
  380. ++chainLightningModifier;
  381. }
  382. }
  383. if(owner->hasEffects())
  384. {
  385. SetStackEffect sse;
  386. //get default spell duration (spell power with bonuses for heroes)
  387. int duration = parameters.enchantPower;
  388. //generate actual stack bonuses
  389. {
  390. int maxDuration = 0;
  391. std::vector<Bonus> tmp;
  392. owner->getEffects(tmp, parameters.effectLevel);
  393. for(Bonus& b : tmp)
  394. {
  395. //use configured duration if present
  396. if(b.turnsRemain == 0)
  397. b.turnsRemain = duration;
  398. vstd::amax(maxDuration, b.turnsRemain);
  399. sse.effect.push_back(b);
  400. }
  401. //if all spell effects have special duration, use it
  402. duration = maxDuration;
  403. }
  404. //fix to original config: shield should display damage reduction
  405. if(owner->id == SpellID::SHIELD || owner->id == SpellID::AIR_SHIELD)
  406. {
  407. sse.effect.back().val = (100 - sse.effect.back().val);
  408. }
  409. //we need to know who cast Bind
  410. if(owner->id == SpellID::BIND && parameters.casterStack)
  411. {
  412. sse.effect.back().additionalInfo = parameters.casterStack->ID;
  413. }
  414. const Bonus * bonus = nullptr;
  415. if(parameters.casterHero)
  416. bonus = parameters.casterHero->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, owner->id));
  417. //TODO does hero specialty should affects his stack casting spells?
  418. si32 power = 0;
  419. for(const CStack * affected : ctx.attackedCres)
  420. {
  421. sse.stacks.push_back(affected->ID);
  422. //Apply hero specials - peculiar enchants
  423. const ui8 tier = std::max((ui8)1, affected->getCreature()->level); //don't divide by 0 for certain creatures (commanders, war machines)
  424. if(bonus)
  425. {
  426. switch(bonus->additionalInfo)
  427. {
  428. case 0: //normal
  429. {
  430. switch(tier)
  431. {
  432. case 1: case 2:
  433. power = 3;
  434. break;
  435. case 3: case 4:
  436. power = 2;
  437. break;
  438. case 5: case 6:
  439. power = 1;
  440. break;
  441. }
  442. Bonus specialBonus(sse.effect.back());
  443. specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely
  444. sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional premy to given effect
  445. }
  446. break;
  447. case 1: //only Coronius as yet
  448. {
  449. power = std::max(5 - tier, 0);
  450. Bonus specialBonus = CStack::featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, duration);
  451. specialBonus.sid = owner->id;
  452. sse.uniqueBonuses.push_back(std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional attack to Slayer effect
  453. }
  454. break;
  455. }
  456. }
  457. if (parameters.casterHero && parameters.casterHero->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, owner->id)) //TODO: better handling of bonus percentages
  458. {
  459. int damagePercent = parameters.casterHero->level * parameters.casterHero->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, owner->id.toEnum()) / tier;
  460. Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, duration);
  461. specialBonus.valType = Bonus::PERCENT_TO_ALL;
  462. specialBonus.sid = owner->id;
  463. sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus));
  464. }
  465. }
  466. if(!sse.stacks.empty())
  467. env->sendAndApply(&sse);
  468. }
  469. }
  470. std::vector<BattleHex> DefaultSpellMechanics::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
  471. {
  472. using namespace SRSLPraserHelpers;
  473. std::vector<BattleHex> ret;
  474. std::string rng = owner->getLevelInfo(schoolLvl).range + ','; //copy + artificial comma for easier handling
  475. if(rng.size() >= 2 && rng[0] != 'X') //there is at least one hex in range (+artificial comma)
  476. {
  477. std::string number1, number2;
  478. int beg, end;
  479. bool readingFirst = true;
  480. for(auto & elem : rng)
  481. {
  482. if(std::isdigit(elem) ) //reading number
  483. {
  484. if(readingFirst)
  485. number1 += elem;
  486. else
  487. number2 += elem;
  488. }
  489. else if(elem == ',') //comma
  490. {
  491. //calculating variables
  492. if(readingFirst)
  493. {
  494. beg = atoi(number1.c_str());
  495. number1 = "";
  496. }
  497. else
  498. {
  499. end = atoi(number2.c_str());
  500. number2 = "";
  501. }
  502. //obtaining new hexes
  503. std::set<ui16> curLayer;
  504. if(readingFirst)
  505. {
  506. curLayer = getInRange(centralHex, beg, beg);
  507. }
  508. else
  509. {
  510. curLayer = getInRange(centralHex, beg, end);
  511. readingFirst = true;
  512. }
  513. //adding abtained hexes
  514. for(auto & curLayer_it : curLayer)
  515. {
  516. ret.push_back(curLayer_it);
  517. }
  518. }
  519. else if(elem == '-') //dash
  520. {
  521. beg = atoi(number1.c_str());
  522. number1 = "";
  523. readingFirst = false;
  524. }
  525. }
  526. }
  527. //remove duplicates (TODO check if actually needed)
  528. range::unique(ret);
  529. return ret;
  530. }
  531. std::vector<const CStack *> DefaultSpellMechanics::getAffectedStacks(const CBattleInfoCallback * cb, SpellTargetingContext & ctx) const
  532. {
  533. std::vector<const CStack *> attackedCres = calculateAffectedStacks(cb, ctx);
  534. handleImmunities(cb, ctx, attackedCres);
  535. return attackedCres;
  536. }
  537. std::vector<const CStack *> DefaultSpellMechanics::calculateAffectedStacks(const CBattleInfoCallback* cb, const SpellTargetingContext& ctx) const
  538. {
  539. std::set<const CStack* > attackedCres;//std::set to exclude multiple occurrences of two hex creatures
  540. const ui8 attackerSide = cb->playerToSide(ctx.caster->getOwner()) == 1;
  541. const auto attackedHexes = rangeInHexes(ctx.destination, ctx.schoolLvl, attackerSide);
  542. auto mainFilter = [=](const CStack * s)
  543. {
  544. const bool positiveToAlly = owner->isPositive() && s->owner == ctx.caster->getOwner();
  545. const bool negativeToEnemy = owner->isNegative() && s->owner != ctx.caster->getOwner();
  546. const bool validTarget = s->isValidTarget(!ctx.ti.onlyAlive); //todo: this should be handled by spell class
  547. const bool positivenessFlag = !ctx.ti.smart || owner->isNeutral() || positiveToAlly || negativeToEnemy;
  548. return positivenessFlag && validTarget;
  549. };
  550. if(ctx.ti.type == CSpell::CREATURE && attackedHexes.size() == 1)
  551. {
  552. //for single target spells we must select one target. Alive stack is preferred (issue #1763)
  553. auto predicate = [&](const CStack * s)
  554. {
  555. return s->coversPos(attackedHexes.at(0)) && mainFilter(s);
  556. };
  557. TStacks stacks = cb->battleGetStacksIf(predicate);
  558. for(auto stack : stacks)
  559. {
  560. if(stack->alive())
  561. {
  562. attackedCres.insert(stack);
  563. break;
  564. }
  565. }
  566. if(attackedCres.empty() && !stacks.empty())
  567. {
  568. attackedCres.insert(stacks.front());
  569. }
  570. }
  571. else if(ctx.ti.massive)
  572. {
  573. TStacks stacks = cb->battleGetStacksIf(mainFilter);
  574. for (auto stack : stacks)
  575. attackedCres.insert(stack);
  576. }
  577. else //custom range from attackedHexes
  578. {
  579. for(BattleHex hex : attackedHexes)
  580. {
  581. if(const CStack * st = cb->battleGetStackByPos(hex, ctx.ti.onlyAlive))
  582. if(mainFilter(st))
  583. attackedCres.insert(st);;
  584. }
  585. }
  586. std::vector<const CStack *> res;
  587. std::copy(attackedCres.begin(), attackedCres.end(), std::back_inserter(res));
  588. return res;
  589. }
  590. ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::canBeCast(const CBattleInfoCallback * cb, const ISpellCaster * caster) const
  591. {
  592. //no problems by default, this method is for spell-specific problems
  593. return ESpellCastProblem::OK;
  594. }
  595. ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const
  596. {
  597. //no problems by default, this method is for spell-specific problems
  598. //common problems handled by CSpell
  599. return ESpellCastProblem::OK;
  600. }
  601. ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
  602. {
  603. //by default use general algorithm
  604. return owner->internalIsImmune(caster, obj);
  605. }
  606. void DefaultSpellMechanics::doDispell(BattleInfo * battle, const BattleSpellCast * packet, const CSelector & selector) const
  607. {
  608. auto localSelector = [](const Bonus * bonus)
  609. {
  610. const CSpell * sourceSpell = bonus->sourceSpell();
  611. if(sourceSpell != nullptr)
  612. {
  613. //Special case: DISRUPTING_RAY is "immune" to dispell
  614. //Other even PERMANENT effects can be removed (f.e. BIND)
  615. if(sourceSpell->id == SpellID::DISRUPTING_RAY)
  616. return false;
  617. }
  618. return true;
  619. };
  620. for(auto stackID : packet->affectedCres)
  621. {
  622. CStack *s = battle->getStack(stackID);
  623. s->popBonuses(CSelector(localSelector).And(selector));
  624. }
  625. }
  626. void DefaultSpellMechanics::handleImmunities(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx, std::vector<const CStack*> & stacks) const
  627. {
  628. //now handle immunities
  629. auto predicate = [&, this](const CStack * s)->bool
  630. {
  631. bool hitDirectly = ctx.ti.alwaysHitDirectly && s->coversPos(ctx.destination);
  632. bool notImmune = (ESpellCastProblem::OK == owner->isImmuneByStack(ctx.caster, s));
  633. return !(hitDirectly || notImmune);
  634. };
  635. vstd::erase_if(stacks, predicate);
  636. }
  637. void DefaultSpellMechanics::handleMagicMirror(const SpellCastEnvironment * env, SpellCastContext & ctx, std::vector <const CStack*> & reflected) const
  638. {
  639. //reflection is applied only to negative spells
  640. //if it is actual spell and can be reflected to single target, no recurrence
  641. const bool tryMagicMirror = owner->isNegative() && owner->level && owner->getLevelInfo(0).range == "0";
  642. if(tryMagicMirror)
  643. {
  644. for(auto s : ctx.attackedCres)
  645. {
  646. const int mirrorChance = (s)->valOfBonuses(Bonus::MAGIC_MIRROR);
  647. if(env->getRandomGenerator().nextInt(99) < mirrorChance)
  648. reflected.push_back(s);
  649. }
  650. vstd::erase_if(ctx.attackedCres, [&reflected](const CStack * s)
  651. {
  652. return vstd::contains(reflected, s);
  653. });
  654. for(auto s : reflected)
  655. {
  656. BattleSpellCast::CustomEffect effect;
  657. effect.effect = 3;
  658. effect.stack = s->ID;
  659. ctx.sc.customEffects.push_back(effect);
  660. }
  661. }
  662. }
  663. void DefaultSpellMechanics::handleResistance(const SpellCastEnvironment * env, SpellCastContext & ctx) const
  664. {
  665. //checking if creatures resist
  666. //resistance is applied only to negative spells
  667. if(owner->isNegative())
  668. {
  669. std::vector <const CStack*> resisted;
  670. for(auto s : ctx.attackedCres)
  671. {
  672. //magic resistance
  673. const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in %
  674. if(env->getRandomGenerator().nextInt(99) < prob)
  675. {
  676. resisted.push_back(s);
  677. }
  678. }
  679. vstd::erase_if(ctx.attackedCres, [&resisted](const CStack * s)
  680. {
  681. return vstd::contains(resisted, s);
  682. });
  683. for(auto s : resisted)
  684. {
  685. BattleSpellCast::CustomEffect effect;
  686. effect.effect = 78;
  687. effect.stack = s->ID;
  688. ctx.sc.customEffects.push_back(effect);
  689. }
  690. }
  691. }
  692. bool DefaultSpellMechanics::requiresCreatureTarget() const
  693. {
  694. //most spells affects creatures somehow regardless of Target Type
  695. //for few exceptions see overrides
  696. return true;
  697. }
  698. std::vector<const CStack *> SpecialSpellMechanics::calculateAffectedStacks(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const
  699. {
  700. return std::vector<const CStack *>();
  701. }