SpellMechanics.cpp 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720
  1. /*
  2. * SpellMechanics.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 "SpellMechanics.h"
  12. #include "CObstacleInstance.h"
  13. #include "mapObjects/CGHeroInstance.h"
  14. #include "BattleState.h"
  15. #include "CRandomGenerator.h"
  16. #include "NetPacks.h"
  17. #include "mapping/CMap.h"
  18. #include "CGameInfoCallback.h"
  19. #include "CGameState.h"
  20. namespace SRSLPraserHelpers
  21. {
  22. static int XYToHex(int x, int y)
  23. {
  24. return x + GameConstants::BFIELD_WIDTH * y;
  25. }
  26. static int XYToHex(std::pair<int, int> xy)
  27. {
  28. return XYToHex(xy.first, xy.second);
  29. }
  30. static int hexToY(int battleFieldPosition)
  31. {
  32. return battleFieldPosition/GameConstants::BFIELD_WIDTH;
  33. }
  34. static int hexToX(int battleFieldPosition)
  35. {
  36. int pos = battleFieldPosition - hexToY(battleFieldPosition) * GameConstants::BFIELD_WIDTH;
  37. return pos;
  38. }
  39. static std::pair<int, int> hexToPair(int battleFieldPosition)
  40. {
  41. return std::make_pair(hexToX(battleFieldPosition), hexToY(battleFieldPosition));
  42. }
  43. //moves hex by one hex in given direction
  44. //0 - left top, 1 - right top, 2 - right, 3 - right bottom, 4 - left bottom, 5 - left
  45. static std::pair<int, int> gotoDir(int x, int y, int direction)
  46. {
  47. switch(direction)
  48. {
  49. case 0: //top left
  50. return std::make_pair((y%2) ? x-1 : x, y-1);
  51. case 1: //top right
  52. return std::make_pair((y%2) ? x : x+1, y-1);
  53. case 2: //right
  54. return std::make_pair(x+1, y);
  55. case 3: //right bottom
  56. return std::make_pair((y%2) ? x : x+1, y+1);
  57. case 4: //left bottom
  58. return std::make_pair((y%2) ? x-1 : x, y+1);
  59. case 5: //left
  60. return std::make_pair(x-1, y);
  61. default:
  62. throw std::runtime_error("Disaster: wrong direction in SRSLPraserHelpers::gotoDir!\n");
  63. }
  64. }
  65. static std::pair<int, int> gotoDir(std::pair<int, int> xy, int direction)
  66. {
  67. return gotoDir(xy.first, xy.second, direction);
  68. }
  69. static bool isGoodHex(std::pair<int, int> xy)
  70. {
  71. return xy.first >=0 && xy.first < GameConstants::BFIELD_WIDTH && xy.second >= 0 && xy.second < GameConstants::BFIELD_HEIGHT;
  72. }
  73. //helper function for rangeInHexes
  74. static std::set<ui16> getInRange(unsigned int center, int low, int high)
  75. {
  76. std::set<ui16> ret;
  77. if(low == 0)
  78. {
  79. ret.insert(center);
  80. }
  81. std::pair<int, int> mainPointForLayer[6]; //A, B, C, D, E, F points
  82. for(auto & elem : mainPointForLayer)
  83. elem = hexToPair(center);
  84. for(int it=1; it<=high; ++it) //it - distance to the center
  85. {
  86. for(int b=0; b<6; ++b)
  87. mainPointForLayer[b] = gotoDir(mainPointForLayer[b], b);
  88. if(it>=low)
  89. {
  90. std::pair<int, int> curHex;
  91. //adding lines (A-b, B-c, C-d, etc)
  92. for(int v=0; v<6; ++v)
  93. {
  94. curHex = mainPointForLayer[v];
  95. for(int h=0; h<it; ++h)
  96. {
  97. if(isGoodHex(curHex))
  98. ret.insert(XYToHex(curHex));
  99. curHex = gotoDir(curHex, (v+2)%6);
  100. }
  101. }
  102. } //if(it>=low)
  103. }
  104. return ret;
  105. }
  106. }
  107. struct SpellCastContext
  108. {
  109. SpellCastContext(std::vector<const CStack*> & attackedCres, BattleSpellCast & sc, StacksInjured & si):
  110. attackedCres(attackedCres), sc(sc), si(si){};
  111. std::vector<const CStack*> & attackedCres;
  112. BattleSpellCast & sc;
  113. StacksInjured & si;
  114. };
  115. class DefaultSpellMechanics: public ISpellMechanics
  116. {
  117. public:
  118. DefaultSpellMechanics(CSpell * s): ISpellMechanics(s){};
  119. std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr) const override;
  120. std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const override;
  121. ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
  122. bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override final;
  123. void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const override;
  124. void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override;
  125. protected:
  126. virtual void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;
  127. virtual int calculateDuration(const CGHeroInstance * caster, int usedSpellPower) const;
  128. ///calculate healed HP for all spells casted by hero
  129. ui32 calculateHealedHP(const CGHeroInstance* caster, const CStack* stack, const CStack* sacrificedStack) const;
  130. ///actual adventure cast implementation
  131. virtual bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const;
  132. };
  133. ///ADVENTURE SPELLS
  134. //todo: make configurable
  135. class AdventureBonusingMechanics: public DefaultSpellMechanics
  136. {
  137. public:
  138. AdventureBonusingMechanics(CSpell * s, Bonus::BonusType _bonusTypeID): DefaultSpellMechanics(s), bonusTypeID(_bonusTypeID){};
  139. protected:
  140. bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
  141. private:
  142. Bonus::BonusType bonusTypeID;
  143. };
  144. class SummonBoatMechanics: public DefaultSpellMechanics
  145. {
  146. public:
  147. SummonBoatMechanics(CSpell * s): DefaultSpellMechanics(s){};
  148. protected:
  149. bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
  150. };
  151. class ScuttleBoatMechanics: public DefaultSpellMechanics
  152. {
  153. public:
  154. ScuttleBoatMechanics(CSpell * s): DefaultSpellMechanics(s){};
  155. protected:
  156. bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
  157. };
  158. class DimensionDoorMechanics: public DefaultSpellMechanics
  159. {
  160. public:
  161. DimensionDoorMechanics(CSpell * s): DefaultSpellMechanics(s){};
  162. protected:
  163. bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
  164. };
  165. class TownPortalMechanics: public DefaultSpellMechanics
  166. {
  167. public:
  168. TownPortalMechanics(CSpell * s): DefaultSpellMechanics(s){};
  169. protected:
  170. bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
  171. };
  172. ///BATTLE SPELLS
  173. class AcidBreathDamageMechnics: public DefaultSpellMechanics
  174. {
  175. public:
  176. AcidBreathDamageMechnics(CSpell * s): DefaultSpellMechanics(s){};
  177. protected:
  178. void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
  179. };
  180. class ChainLightningMechanics: public DefaultSpellMechanics
  181. {
  182. public:
  183. ChainLightningMechanics(CSpell * s): DefaultSpellMechanics(s){};
  184. std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const override;
  185. };
  186. class CloneMechanics: public DefaultSpellMechanics
  187. {
  188. public:
  189. CloneMechanics(CSpell * s): DefaultSpellMechanics(s){};
  190. ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
  191. protected:
  192. void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
  193. };
  194. class CureMechanics: public DefaultSpellMechanics
  195. {
  196. public:
  197. CureMechanics(CSpell * s): DefaultSpellMechanics(s){};
  198. void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override;
  199. };
  200. class DeathStareMechnics: public DefaultSpellMechanics
  201. {
  202. public:
  203. DeathStareMechnics(CSpell * s): DefaultSpellMechanics(s){};
  204. protected:
  205. void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
  206. };
  207. class DispellHelpfulMechanics: public DefaultSpellMechanics
  208. {
  209. public:
  210. DispellHelpfulMechanics(CSpell * s): DefaultSpellMechanics(s){};
  211. void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override;
  212. ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
  213. };
  214. class DispellMechanics: public DefaultSpellMechanics
  215. {
  216. public:
  217. DispellMechanics(CSpell * s): DefaultSpellMechanics(s){};
  218. void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override;
  219. };
  220. class HypnotizeMechanics: public DefaultSpellMechanics
  221. {
  222. public:
  223. HypnotizeMechanics(CSpell * s): DefaultSpellMechanics(s){};
  224. ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
  225. };
  226. class ObstacleMechanics: public DefaultSpellMechanics
  227. {
  228. public:
  229. ObstacleMechanics(CSpell * s): DefaultSpellMechanics(s){};
  230. protected:
  231. void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
  232. };
  233. class WallMechanics: public ObstacleMechanics
  234. {
  235. public:
  236. WallMechanics(CSpell * s): ObstacleMechanics(s){};
  237. std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes = nullptr) const override;
  238. };
  239. class RemoveObstacleMechanics: public DefaultSpellMechanics
  240. {
  241. public:
  242. RemoveObstacleMechanics(CSpell * s): DefaultSpellMechanics(s){};
  243. protected:
  244. void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
  245. };
  246. ///all rising spells
  247. class RisingSpellMechanics: public DefaultSpellMechanics
  248. {
  249. public:
  250. RisingSpellMechanics(CSpell * s): DefaultSpellMechanics(s){};
  251. };
  252. class SacrificeMechanics: public RisingSpellMechanics
  253. {
  254. public:
  255. SacrificeMechanics(CSpell * s): RisingSpellMechanics(s){};
  256. protected:
  257. void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
  258. };
  259. ///all rising spells but SACRIFICE
  260. class SpecialRisingSpellMechanics: public RisingSpellMechanics
  261. {
  262. public:
  263. SpecialRisingSpellMechanics(CSpell * s): RisingSpellMechanics(s){};
  264. ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
  265. };
  266. class SummonMechanics: public DefaultSpellMechanics
  267. {
  268. public:
  269. SummonMechanics(CSpell * s): DefaultSpellMechanics(s){};
  270. protected:
  271. void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
  272. };
  273. class TeleportMechanics: public DefaultSpellMechanics
  274. {
  275. public:
  276. TeleportMechanics(CSpell * s): DefaultSpellMechanics(s){};
  277. protected:
  278. void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
  279. };
  280. ///ISpellMechanics
  281. ISpellMechanics::ISpellMechanics(CSpell * s):
  282. owner(s)
  283. {
  284. }
  285. ISpellMechanics * ISpellMechanics::createMechanics(CSpell * s)
  286. {
  287. switch (s->id)
  288. {
  289. case SpellID::ACID_BREATH_DAMAGE:
  290. return new AcidBreathDamageMechnics(s);
  291. case SpellID::CHAIN_LIGHTNING:
  292. return new ChainLightningMechanics(s);
  293. case SpellID::CLONE:
  294. return new CloneMechanics(s);
  295. case SpellID::CURE:
  296. return new CureMechanics(s);
  297. case SpellID::DEATH_STARE:
  298. return new DeathStareMechnics(s);
  299. case SpellID::DISPEL:
  300. return new DispellMechanics(s);
  301. case SpellID::DISPEL_HELPFUL_SPELLS:
  302. return new DispellHelpfulMechanics(s);
  303. case SpellID::FIRE_WALL:
  304. case SpellID::FORCE_FIELD:
  305. return new WallMechanics(s);
  306. case SpellID::HYPNOTIZE:
  307. return new HypnotizeMechanics(s);
  308. case SpellID::LAND_MINE:
  309. case SpellID::QUICKSAND:
  310. return new ObstacleMechanics(s);
  311. case SpellID::REMOVE_OBSTACLE:
  312. return new RemoveObstacleMechanics(s);
  313. case SpellID::SACRIFICE:
  314. return new SacrificeMechanics(s);
  315. case SpellID::SUMMON_FIRE_ELEMENTAL:
  316. case SpellID::SUMMON_EARTH_ELEMENTAL:
  317. case SpellID::SUMMON_WATER_ELEMENTAL:
  318. case SpellID::SUMMON_AIR_ELEMENTAL:
  319. return new SummonMechanics(s);
  320. case SpellID::TELEPORT:
  321. return new TeleportMechanics(s);
  322. case SpellID::SUMMON_BOAT:
  323. return new SummonBoatMechanics(s);
  324. case SpellID::SCUTTLE_BOAT:
  325. return new ScuttleBoatMechanics(s);
  326. case SpellID::DIMENSION_DOOR:
  327. return new DimensionDoorMechanics(s);
  328. case SpellID::FLY:
  329. return new AdventureBonusingMechanics(s, Bonus::FLYING_MOVEMENT);
  330. case SpellID::WATER_WALK:
  331. return new AdventureBonusingMechanics(s, Bonus::WATER_WALKING);
  332. case SpellID::TOWN_PORTAL:
  333. return new TownPortalMechanics(s);
  334. case SpellID::VISIONS:
  335. case SpellID::VIEW_EARTH:
  336. case SpellID::DISGUISE:
  337. case SpellID::VIEW_AIR:
  338. default:
  339. if(s->isRisingSpell())
  340. return new SpecialRisingSpellMechanics(s);
  341. else
  342. return new DefaultSpellMechanics(s);
  343. }
  344. }
  345. ///DefaultSpellMechanics
  346. void DefaultSpellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
  347. {
  348. if (packet->castedByHero)
  349. {
  350. if (packet->side < 2)
  351. {
  352. battle->sides[packet->side].castSpellsCount++;
  353. }
  354. }
  355. //handle countering spells
  356. for(auto stackID : packet->affectedCres)
  357. {
  358. if(vstd::contains(packet->resisted, stackID))
  359. continue;
  360. CStack * s = battle->getStack(stackID);
  361. s->popBonuses([&](const Bonus * b) -> bool
  362. {
  363. //check for each bonus if it should be removed
  364. const bool isSpellEffect = Selector::sourceType(Bonus::SPELL_EFFECT)(b);
  365. const int spellID = isSpellEffect ? b->sid : -1;
  366. return isSpellEffect && vstd::contains(owner->counteredSpells, spellID);
  367. });
  368. }
  369. }
  370. bool DefaultSpellMechanics::adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
  371. {
  372. if(!owner->isAdventureSpell())
  373. {
  374. env->complain("Attempt to cast non adventure spell in adventure mode");
  375. return false;
  376. }
  377. const CGHeroInstance * caster = parameters.caster;
  378. const int cost = caster->getSpellCost(owner);
  379. if(!caster->canCastThisSpell(owner))
  380. {
  381. env->complain("Hero cannot cast this spell!");
  382. return false;
  383. }
  384. if(caster->mana < cost)
  385. {
  386. env->complain("Hero doesn't have enough spell points to cast this spell!");
  387. return false;
  388. }
  389. {
  390. AdvmapSpellCast asc;
  391. asc.caster = caster;
  392. asc.spellID = owner->id;
  393. env->sendAndApply(&asc);
  394. }
  395. if(applyAdventureEffects(env, parameters))
  396. {
  397. SetMana sm;
  398. sm.hid = caster->id;
  399. sm.absolute = false;
  400. sm.val = -cost;
  401. env->sendAndApply(&sm);
  402. return true;
  403. }
  404. return false;
  405. }
  406. bool DefaultSpellMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
  407. {
  408. //There is no generic algorithm of adventure cast
  409. env->complain("Unimplemented adventure spell");
  410. return false;
  411. }
  412. void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const
  413. {
  414. BattleSpellCast sc;
  415. sc.side = parameters.casterSide;
  416. sc.id = owner->id;
  417. sc.skill = parameters.spellLvl;
  418. sc.tile = parameters.destination;
  419. sc.dmgToDisplay = 0;
  420. sc.castedByHero = nullptr != parameters.caster;
  421. sc.casterStack = (parameters.casterStack ? parameters.casterStack->ID : -1);
  422. sc.manaGained = 0;
  423. int spellCost = 0;
  424. //calculate spell cost
  425. if(parameters.caster)
  426. {
  427. spellCost = parameters.cb->battleGetSpellCost(owner, parameters.caster);
  428. if(parameters.secHero && parameters.mode == ECastingMode::HERO_CASTING) //handle mana channel
  429. {
  430. int manaChannel = 0;
  431. for(const CStack * stack : parameters.cb->battleGetAllStacks(true)) //TODO: shouldn't bonus system handle it somehow?
  432. {
  433. if(stack->owner == parameters.secHero->tempOwner)
  434. {
  435. vstd::amax(manaChannel, stack->valOfBonuses(Bonus::MANA_CHANNELING));
  436. }
  437. }
  438. sc.manaGained = (manaChannel * spellCost) / 100;
  439. }
  440. }
  441. //calculating affected creatures for all spells
  442. //must be vector, as in Chain Lightning order matters
  443. std::vector<const CStack*> attackedCres; //CStack vector is somewhat more suitable than ID vector
  444. auto creatures = owner->getAffectedStacks(parameters.cb, parameters.mode, parameters.casterColor, parameters.spellLvl, parameters.destination, parameters.caster);
  445. std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres));
  446. for (auto cre : attackedCres)
  447. {
  448. sc.affectedCres.insert(cre->ID);
  449. }
  450. //checking if creatures resist
  451. //resistance is applied only to negative spells
  452. if(owner->isNegative())
  453. {
  454. for(auto s : attackedCres)
  455. {
  456. const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in %
  457. if(env->getRandomGenerator().nextInt(99) < prob)
  458. {
  459. sc.resisted.push_back(s->ID);
  460. }
  461. }
  462. }
  463. StacksInjured si;
  464. SpellCastContext ctx(attackedCres, sc, si);
  465. applyBattleEffects(env, parameters, ctx);
  466. env->sendAndApply(&sc);
  467. //spend mana
  468. if(parameters.caster)
  469. {
  470. SetMana sm;
  471. sm.absolute = false;
  472. sm.hid = parameters.caster->id;
  473. sm.val = -spellCost;
  474. env->sendAndApply(&sm);
  475. if(sc.manaGained > 0)
  476. {
  477. assert(parameters.secHero);
  478. sm.hid = parameters.secHero->id;
  479. sm.val = sc.manaGained;
  480. env->sendAndApply(&sm);
  481. }
  482. }
  483. if(!si.stacks.empty()) //after spellcast info shows
  484. env->sendAndApply(&si);
  485. //reduce number of casts remaining
  486. //TODO: this should be part of BattleSpellCast apply
  487. if (parameters.mode == ECastingMode::CREATURE_ACTIVE_CASTING || parameters.mode == ECastingMode::ENCHANTER_CASTING)
  488. {
  489. assert(parameters.casterStack);
  490. BattleSetStackProperty ssp;
  491. ssp.stackID = parameters.casterStack->ID;
  492. ssp.which = BattleSetStackProperty::CASTS;
  493. ssp.val = -1;
  494. ssp.absolute = false;
  495. env->sendAndApply(&ssp);
  496. }
  497. //Magic Mirror effect
  498. if(owner->isNegative() && parameters.mode != ECastingMode::MAGIC_MIRROR && owner->level && owner->getLevelInfo(0).range == "0") //it is actual spell and can be reflected to single target, no recurrence
  499. {
  500. for(auto & attackedCre : attackedCres)
  501. {
  502. int mirrorChance = (attackedCre)->valOfBonuses(Bonus::MAGIC_MIRROR);
  503. if(mirrorChance > env->getRandomGenerator().nextInt(99))
  504. {
  505. std::vector<const CStack *> mirrorTargets;
  506. auto battleStacks = parameters.cb->battleGetAllStacks(true);
  507. for(auto & battleStack : battleStacks)
  508. {
  509. if(battleStack->owner == parameters.casterColor) //get enemy stacks which can be affected by this spell
  510. {
  511. if (ESpellCastProblem::OK == owner->isImmuneByStack(nullptr, battleStack))
  512. mirrorTargets.push_back(battleStack);
  513. }
  514. }
  515. if(!mirrorTargets.empty())
  516. {
  517. int targetHex = (*RandomGeneratorUtil::nextItem(mirrorTargets, env->getRandomGenerator()))->position;
  518. BattleSpellCastParameters mirrorParameters = parameters;
  519. mirrorParameters.spellLvl = 0;
  520. mirrorParameters.casterSide = 1-parameters.casterSide;
  521. mirrorParameters.casterColor = (attackedCre)->owner;
  522. mirrorParameters.caster = nullptr;
  523. mirrorParameters.destination = targetHex;
  524. mirrorParameters.secHero = parameters.caster;
  525. mirrorParameters.mode = ECastingMode::MAGIC_MIRROR;
  526. mirrorParameters.casterStack = (attackedCre);
  527. mirrorParameters.selectedStack = nullptr;
  528. battleCast(env, mirrorParameters);
  529. }
  530. }
  531. }
  532. }
  533. }
  534. int DefaultSpellMechanics::calculateDuration(const CGHeroInstance * caster, int usedSpellPower) const
  535. {
  536. if(!caster)
  537. {
  538. if (!usedSpellPower)
  539. return 3; //default duration of all creature spells
  540. else
  541. return usedSpellPower; //use creature spell power
  542. }
  543. switch(owner->id)
  544. {
  545. case SpellID::FRENZY:
  546. return 1;
  547. default: //other spells
  548. return caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + caster->valOfBonuses(Bonus::SPELL_DURATION);
  549. }
  550. }
  551. ui32 DefaultSpellMechanics::calculateHealedHP(const CGHeroInstance* caster, const CStack* stack, const CStack* sacrificedStack) const
  552. {
  553. int healedHealth;
  554. if(!owner->isHealingSpell())
  555. {
  556. logGlobal->errorStream() << "calculateHealedHP called for nonhealing spell "<< owner->name;
  557. return 0;
  558. }
  559. const int spellPowerSkill = caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
  560. const int levelPower = owner->getPower(caster->getSpellSchoolLevel(owner));
  561. if (owner->id == SpellID::SACRIFICE && sacrificedStack)
  562. healedHealth = (spellPowerSkill + sacrificedStack->MaxHealth() + levelPower) * sacrificedStack->count;
  563. else
  564. healedHealth = spellPowerSkill * owner->power + levelPower; //???
  565. healedHealth = owner->calculateBonus(healedHealth, caster, stack);
  566. return std::min<ui32>(healedHealth, stack->MaxHealth() - stack->firstHPleft + (owner->isRisingSpell() ? stack->baseAmount * stack->MaxHealth() : 0));
  567. }
  568. void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
  569. {
  570. //applying effects
  571. if(owner->isOffensiveSpell())
  572. {
  573. int spellDamage = 0;
  574. if(parameters.casterStack && parameters.mode != ECastingMode::MAGIC_MIRROR)
  575. {
  576. int unitSpellPower = parameters.casterStack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, owner->id.toEnum());
  577. if(unitSpellPower)
  578. ctx.sc.dmgToDisplay = spellDamage = parameters.casterStack->count * unitSpellPower; //TODO: handle immunities
  579. else //Faerie Dragon
  580. {
  581. parameters.usedSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * parameters.casterStack->count / 100;
  582. ctx.sc.dmgToDisplay = 0;
  583. }
  584. }
  585. int chainLightningModifier = 0;
  586. for(auto & attackedCre : ctx.attackedCres)
  587. {
  588. if(vstd::contains(ctx.sc.resisted, (attackedCre)->ID)) //this creature resisted the spell
  589. continue;
  590. BattleStackAttacked bsa;
  591. if(spellDamage)
  592. bsa.damageAmount = spellDamage >> chainLightningModifier;
  593. else
  594. bsa.damageAmount = owner->calculateDamage(parameters.caster, attackedCre, parameters.spellLvl, parameters.usedSpellPower) >> chainLightningModifier;
  595. ctx.sc.dmgToDisplay += bsa.damageAmount;
  596. bsa.stackAttacked = (attackedCre)->ID;
  597. if(parameters.mode == ECastingMode::ENCHANTER_CASTING) //multiple damage spells cast
  598. bsa.attackerID = parameters.casterStack->ID;
  599. else
  600. bsa.attackerID = -1;
  601. (attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
  602. ctx.si.stacks.push_back(bsa);
  603. if(owner->id == SpellID::CHAIN_LIGHTNING)
  604. ++chainLightningModifier;
  605. }
  606. }
  607. if(owner->hasEffects())
  608. {
  609. int stackSpellPower = 0;
  610. if(parameters.casterStack && parameters.mode != ECastingMode::MAGIC_MIRROR)
  611. {
  612. stackSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
  613. }
  614. SetStackEffect sse;
  615. Bonus pseudoBonus;
  616. pseudoBonus.sid = owner->id;
  617. pseudoBonus.val = parameters.spellLvl;
  618. pseudoBonus.turnsRemain = calculateDuration(parameters.caster, stackSpellPower ? stackSpellPower : parameters.usedSpellPower);
  619. CStack::stackEffectToFeature(sse.effect, pseudoBonus);
  620. if(owner->id == SpellID::SHIELD || owner->id == SpellID::AIR_SHIELD)
  621. {
  622. sse.effect.back().val = (100 - sse.effect.back().val); //fix to original config: shield should display damage reduction
  623. }
  624. if(owner->id == SpellID::BIND && parameters.casterStack)//bind
  625. {
  626. sse.effect.back().additionalInfo = parameters.casterStack->ID; //we need to know who casted Bind
  627. }
  628. const Bonus * bonus = nullptr;
  629. if(parameters.caster)
  630. bonus = parameters.caster->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, owner->id));
  631. //TODO does hero specialty should affects his stack casting spells?
  632. si32 power = 0;
  633. for(const CStack * affected : ctx.attackedCres)
  634. {
  635. if(vstd::contains(ctx.sc.resisted, affected->ID)) //this creature resisted the spell
  636. continue;
  637. sse.stacks.push_back(affected->ID);
  638. //Apply hero specials - peculiar enchants
  639. const ui8 tier = std::max((ui8)1, affected->getCreature()->level); //don't divide by 0 for certain creatures (commanders, war machines)
  640. if(bonus)
  641. {
  642. switch(bonus->additionalInfo)
  643. {
  644. case 0: //normal
  645. {
  646. switch(tier)
  647. {
  648. case 1: case 2:
  649. power = 3;
  650. break;
  651. case 3: case 4:
  652. power = 2;
  653. break;
  654. case 5: case 6:
  655. power = 1;
  656. break;
  657. }
  658. Bonus specialBonus(sse.effect.back());
  659. specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely
  660. sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional premy to given effect
  661. }
  662. break;
  663. case 1: //only Coronius as yet
  664. {
  665. power = std::max(5 - tier, 0);
  666. Bonus specialBonus = CStack::featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, pseudoBonus.turnsRemain);
  667. specialBonus.sid = owner->id;
  668. sse.uniqueBonuses.push_back(std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional attack to Slayer effect
  669. }
  670. break;
  671. }
  672. }
  673. if (parameters.caster && parameters.caster->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, owner->id)) //TODO: better handling of bonus percentages
  674. {
  675. int damagePercent = parameters.caster->level * parameters.caster->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, owner->id.toEnum()) / tier;
  676. Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, pseudoBonus.turnsRemain);
  677. specialBonus.valType = Bonus::PERCENT_TO_ALL;
  678. specialBonus.sid = owner->id;
  679. sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus));
  680. }
  681. }
  682. if(!sse.stacks.empty())
  683. env->sendAndApply(&sse);
  684. }
  685. if(owner->isHealingSpell())
  686. {
  687. int hpGained = 0;
  688. if(parameters.casterStack)
  689. {
  690. int unitSpellPower = parameters.casterStack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, owner->id.toEnum());
  691. if(unitSpellPower)
  692. hpGained = parameters.casterStack->count * unitSpellPower; //Archangel
  693. else //Faerie Dragon-like effect - unused so far
  694. parameters.usedSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * parameters.casterStack->count / 100;
  695. }
  696. StacksHealedOrResurrected shr;
  697. shr.lifeDrain = false;
  698. shr.tentHealing = false;
  699. for(auto & attackedCre : ctx.attackedCres)
  700. {
  701. StacksHealedOrResurrected::HealInfo hi;
  702. hi.stackID = (attackedCre)->ID;
  703. if (parameters.casterStack) //casted by creature
  704. {
  705. const bool resurrect = owner->isRisingSpell();
  706. if (hpGained)
  707. {
  708. //archangel
  709. hi.healedHP = std::min<ui32>(hpGained, attackedCre->MaxHealth() - attackedCre->firstHPleft + (resurrect ? attackedCre->baseAmount * attackedCre->MaxHealth() : 0));
  710. }
  711. else
  712. {
  713. //any typical spell (commander's cure or animate dead)
  714. int healedHealth = parameters.usedSpellPower * owner->power + owner->getPower(parameters.spellLvl);
  715. hi.healedHP = std::min<ui32>(healedHealth, attackedCre->MaxHealth() - attackedCre->firstHPleft + (resurrect ? attackedCre->baseAmount * attackedCre->MaxHealth() : 0));
  716. }
  717. }
  718. else
  719. hi.healedHP = calculateHealedHP(parameters.caster, attackedCre, parameters.selectedStack); //Casted by hero
  720. hi.lowLevelResurrection = parameters.spellLvl <= 1;
  721. shr.healedStacks.push_back(hi);
  722. }
  723. if(!shr.healedStacks.empty())
  724. env->sendAndApply(&shr);
  725. }
  726. }
  727. std::vector<BattleHex> DefaultSpellMechanics::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
  728. {
  729. using namespace SRSLPraserHelpers;
  730. std::vector<BattleHex> ret;
  731. std::string rng = owner->getLevelInfo(schoolLvl).range + ','; //copy + artificial comma for easier handling
  732. if(rng.size() >= 2 && rng[0] != 'X') //there is at lest one hex in range (+artificial comma)
  733. {
  734. std::string number1, number2;
  735. int beg, end;
  736. bool readingFirst = true;
  737. for(auto & elem : rng)
  738. {
  739. if(std::isdigit(elem) ) //reading number
  740. {
  741. if(readingFirst)
  742. number1 += elem;
  743. else
  744. number2 += elem;
  745. }
  746. else if(elem == ',') //comma
  747. {
  748. //calculating variables
  749. if(readingFirst)
  750. {
  751. beg = atoi(number1.c_str());
  752. number1 = "";
  753. }
  754. else
  755. {
  756. end = atoi(number2.c_str());
  757. number2 = "";
  758. }
  759. //obtaining new hexes
  760. std::set<ui16> curLayer;
  761. if(readingFirst)
  762. {
  763. curLayer = getInRange(centralHex, beg, beg);
  764. }
  765. else
  766. {
  767. curLayer = getInRange(centralHex, beg, end);
  768. readingFirst = true;
  769. }
  770. //adding abtained hexes
  771. for(auto & curLayer_it : curLayer)
  772. {
  773. ret.push_back(curLayer_it);
  774. }
  775. }
  776. else if(elem == '-') //dash
  777. {
  778. beg = atoi(number1.c_str());
  779. number1 = "";
  780. readingFirst = false;
  781. }
  782. }
  783. }
  784. //remove duplicates (TODO check if actually needed)
  785. range::unique(ret);
  786. return ret;
  787. }
  788. std::set<const CStack *> DefaultSpellMechanics::getAffectedStacks(SpellTargetingContext & ctx) const
  789. {
  790. std::set<const CStack* > attackedCres;//std::set to exclude multiple occurrences of two hex creatures
  791. const ui8 attackerSide = ctx.cb->playerToSide(ctx.casterColor) == 1;
  792. const auto attackedHexes = rangeInHexes(ctx.destination, ctx.schoolLvl, attackerSide);
  793. const CSpell::TargetInfo ti(owner, ctx.schoolLvl, ctx.mode);
  794. //TODO: more generic solution for mass spells
  795. if(owner->getLevelInfo(ctx.schoolLvl).range.size() > 1) //custom many-hex range
  796. {
  797. for(BattleHex hex : attackedHexes)
  798. {
  799. if(const CStack * st = ctx.cb->battleGetStackByPos(hex, ti.onlyAlive))
  800. {
  801. attackedCres.insert(st);
  802. }
  803. }
  804. }
  805. else if(ti.type == CSpell::CREATURE)
  806. {
  807. auto predicate = [=](const CStack * s){
  808. const bool positiveToAlly = owner->isPositive() && s->owner == ctx.casterColor;
  809. const bool negativeToEnemy = owner->isNegative() && s->owner != ctx.casterColor;
  810. const bool validTarget = s->isValidTarget(!ti.onlyAlive); //todo: this should be handled by spell class
  811. //for single target spells select stacks covering destination tile
  812. const bool rangeCovers = ti.massive || s->coversPos(ctx.destination);
  813. //handle smart targeting
  814. const bool positivenessFlag = !ti.smart || owner->isNeutral() || positiveToAlly || negativeToEnemy;
  815. return rangeCovers && positivenessFlag && validTarget;
  816. };
  817. TStacks stacks = ctx.cb->battleGetStacksIf(predicate);
  818. if(ti.massive)
  819. {
  820. //for massive spells add all targets
  821. for (auto stack : stacks)
  822. attackedCres.insert(stack);
  823. }
  824. else
  825. {
  826. //for single target spells we must select one target. Alive stack is preferred (issue #1763)
  827. for(auto stack : stacks)
  828. {
  829. if(stack->alive())
  830. {
  831. attackedCres.insert(stack);
  832. break;
  833. }
  834. }
  835. if(attackedCres.empty() && !stacks.empty())
  836. {
  837. attackedCres.insert(stacks.front());
  838. }
  839. }
  840. }
  841. else //custom range from attackedHexes
  842. {
  843. for(BattleHex hex : attackedHexes)
  844. {
  845. if(const CStack * st = ctx.cb->battleGetStackByPos(hex, ti.onlyAlive))
  846. attackedCres.insert(st);
  847. }
  848. }
  849. return attackedCres;
  850. }
  851. ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
  852. {
  853. //by default use general algorithm
  854. return owner->isImmuneBy(obj);
  855. }
  856. ///ADVENTURE SPELLS
  857. ///AdventureBonusingMechanics
  858. bool AdventureBonusingMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
  859. {
  860. const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
  861. const int subtype = schoolLevel >= 2 ? 1 : 2; //adv or expert
  862. GiveBonus gb;
  863. gb.id = parameters.caster->id.getNum();
  864. gb.bonus = Bonus(Bonus::ONE_DAY, bonusTypeID, Bonus::SPELL_EFFECT, 0, owner->id, subtype);
  865. env->sendAndApply(&gb);
  866. return true;
  867. }
  868. ///SummonBoatMechanics
  869. bool SummonBoatMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
  870. {
  871. const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
  872. //check if spell works at all
  873. if(env->getRandomGenerator().nextInt(99) >= owner->getPower(schoolLevel)) //power is % chance of success
  874. {
  875. InfoWindow iw;
  876. iw.player = parameters.caster->tempOwner;
  877. iw.text.addTxt(MetaString::GENERAL_TXT, 336); //%s tried to summon a boat, but failed.
  878. iw.text.addReplacement(parameters.caster->name);
  879. env->sendAndApply(&iw);
  880. return true;
  881. }
  882. //try to find unoccupied boat to summon
  883. const CGBoat * nearest = nullptr;
  884. double dist = 0;
  885. int3 summonPos = parameters.caster->bestLocation();
  886. if(summonPos.x < 0)
  887. {
  888. env->complain("There is no water tile available!");
  889. return false;
  890. }
  891. for(const CGObjectInstance * obj : env->getMap()->objects)
  892. {
  893. if(obj && obj->ID == Obj::BOAT)
  894. {
  895. const CGBoat *b = static_cast<const CGBoat*>(obj);
  896. if(b->hero)
  897. continue; //we're looking for unoccupied boat
  898. double nDist = b->pos.dist2d(parameters.caster->getPosition());
  899. if(!nearest || nDist < dist) //it's first boat or closer than previous
  900. {
  901. nearest = b;
  902. dist = nDist;
  903. }
  904. }
  905. }
  906. if(nullptr != nearest) //we found boat to summon
  907. {
  908. ChangeObjPos cop;
  909. cop.objid = nearest->id;
  910. cop.nPos = summonPos + int3(1,0,0);;
  911. cop.flags = 1;
  912. env->sendAndApply(&cop);
  913. }
  914. else if(schoolLevel < 2) //none or basic level -> cannot create boat :(
  915. {
  916. InfoWindow iw;
  917. iw.player = parameters.caster->tempOwner;
  918. iw.text.addTxt(MetaString::GENERAL_TXT, 335); //There are no boats to summon.
  919. env->sendAndApply(&iw);
  920. }
  921. else //create boat
  922. {
  923. NewObject no;
  924. no.ID = Obj::BOAT;
  925. no.subID = parameters.caster->getBoatType();
  926. no.pos = summonPos + int3(1,0,0);;
  927. env->sendAndApply(&no);
  928. }
  929. return true;
  930. }
  931. ///ScuttleBoatMechanics
  932. bool ScuttleBoatMechanics::applyAdventureEffects(const SpellCastEnvironment* env, AdventureSpellCastParameters& parameters) const
  933. {
  934. const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
  935. //check if spell works at all
  936. if(env->getRandomGenerator().nextInt(99) >= owner->getPower(schoolLevel)) //power is % chance of success
  937. {
  938. InfoWindow iw;
  939. iw.player = parameters.caster->tempOwner;
  940. iw.text.addTxt(MetaString::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed
  941. iw.text.addReplacement(parameters.caster->name);
  942. env->sendAndApply(&iw);
  943. return true;
  944. }
  945. if(!env->getMap()->isInTheMap(parameters.pos))
  946. {
  947. env->complain("Invalid dst tile for scuttle!");
  948. return false;
  949. }
  950. //TODO: test range, visibility
  951. const TerrainTile *t = &env->getMap()->getTile(parameters.pos);
  952. if(!t->visitableObjects.size() || t->visitableObjects.back()->ID != Obj::BOAT)
  953. {
  954. env->complain("There is no boat to scuttle!");
  955. return false;
  956. }
  957. RemoveObject ro;
  958. ro.id = t->visitableObjects.back()->id;
  959. env->sendAndApply(&ro);
  960. return true;
  961. }
  962. ///DimensionDoorMechanics
  963. bool DimensionDoorMechanics::applyAdventureEffects(const SpellCastEnvironment* env, AdventureSpellCastParameters& parameters) const
  964. {
  965. if(!env->getMap()->isInTheMap(parameters.pos))
  966. {
  967. env->complain("Destination is out of map!");
  968. return false;
  969. }
  970. const TerrainTile * dest = env->getCb()->getTile(parameters.pos);
  971. const TerrainTile * curr = env->getCb()->getTile(parameters.caster->getSightCenter());
  972. if(nullptr == dest)
  973. {
  974. env->complain("Destination tile doesn't exist!");
  975. return false;
  976. }
  977. if(nullptr == curr)
  978. {
  979. env->complain("Source tile doesn't exist!");
  980. return false;
  981. }
  982. if(parameters.caster->movement <= 0)
  983. {
  984. env->complain("Hero needs movement points to cast Dimension Door!");
  985. return false;
  986. }
  987. const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
  988. if(parameters.caster->getBonusesCount(Bonus::SPELL_EFFECT, SpellID::DIMENSION_DOOR) >= owner->getPower(schoolLevel)) //limit casts per turn
  989. {
  990. InfoWindow iw;
  991. iw.player = parameters.caster->tempOwner;
  992. iw.text.addTxt(MetaString::GENERAL_TXT, 338); //%s is not skilled enough to cast this spell again today.
  993. iw.text.addReplacement(parameters.caster->name);
  994. env->sendAndApply(&iw);
  995. return true;
  996. }
  997. GiveBonus gb;
  998. gb.id = parameters.caster->id.getNum();
  999. gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::NONE, Bonus::SPELL_EFFECT, 0, owner->id);
  1000. env->sendAndApply(&gb);
  1001. if(!dest->isClear(curr)) //wrong dest tile
  1002. {
  1003. InfoWindow iw;
  1004. iw.player = parameters.caster->tempOwner;
  1005. iw.text.addTxt(MetaString::GENERAL_TXT, 70); //Dimension Door failed!
  1006. env->sendAndApply(&iw);
  1007. }
  1008. else if(env->moveHero(parameters.caster->id, parameters.pos + parameters.caster->getVisitableOffset(), true))
  1009. {
  1010. SetMovePoints smp;
  1011. smp.hid = parameters.caster->id;
  1012. smp.val = std::max<ui32>(0, parameters.caster->movement - 300);
  1013. env->sendAndApply(&smp);
  1014. }
  1015. return true;
  1016. }
  1017. ///TownPortalMechanics
  1018. bool TownPortalMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters& parameters) const
  1019. {
  1020. if (!env->getMap()->isInTheMap(parameters.pos))
  1021. {
  1022. env->complain("Destination tile not present!");
  1023. return false;
  1024. }
  1025. TerrainTile tile = env->getMap()->getTile(parameters.pos);
  1026. if (tile.visitableObjects.empty() || tile.visitableObjects.back()->ID != Obj::TOWN)
  1027. {
  1028. env->complain("Town not found for Town Portal!");
  1029. return false;
  1030. }
  1031. CGTownInstance * town = static_cast<CGTownInstance*>(tile.visitableObjects.back());
  1032. if (town->tempOwner != parameters.caster->tempOwner)
  1033. {
  1034. env->complain("Can't teleport to another player!");
  1035. return false;
  1036. }
  1037. if (town->visitingHero)
  1038. {
  1039. env->complain("Can't teleport to occupied town!");
  1040. return false;
  1041. }
  1042. if (parameters.caster->getSpellSchoolLevel(owner) < 2)
  1043. {
  1044. si32 dist = town->pos.dist2dSQ(parameters.caster->pos);
  1045. ObjectInstanceID nearest = town->id; //nearest town's ID
  1046. for(const CGTownInstance * currTown : env->getCb()->getPlayer(parameters.caster->tempOwner)->towns)
  1047. {
  1048. si32 currDist = currTown->pos.dist2dSQ(parameters.caster->pos);
  1049. if (currDist < dist)
  1050. {
  1051. nearest = currTown->id;
  1052. dist = currDist;
  1053. }
  1054. }
  1055. if (town->id != nearest)
  1056. {
  1057. env->complain("This hero can only teleport to nearest town!");
  1058. return false;
  1059. }
  1060. }
  1061. env->moveHero(parameters.caster->id, town->visitablePos() + parameters.caster->getVisitableOffset() ,1);
  1062. return true;
  1063. }
  1064. ///BATTLE SPELLS
  1065. ///AcidBreathDamageMechnics
  1066. void AcidBreathDamageMechnics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
  1067. {
  1068. //calculating dmg to display
  1069. ctx.sc.dmgToDisplay = parameters.usedSpellPower;
  1070. for(auto & attackedCre : ctx.attackedCres) //no immunities
  1071. {
  1072. BattleStackAttacked bsa;
  1073. bsa.flags |= BattleStackAttacked::SPELL_EFFECT;
  1074. bsa.spellID = owner->id;
  1075. bsa.damageAmount = parameters.usedSpellPower; //damage times the number of attackers
  1076. bsa.stackAttacked = (attackedCre)->ID;
  1077. bsa.attackerID = -1;
  1078. (attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
  1079. ctx.si.stacks.push_back(bsa);
  1080. }
  1081. }
  1082. ///ChainLightningMechanics
  1083. std::set<const CStack *> ChainLightningMechanics::getAffectedStacks(SpellTargetingContext & ctx) const
  1084. {
  1085. std::set<const CStack* > attackedCres;
  1086. std::set<BattleHex> possibleHexes;
  1087. for(auto stack : ctx.cb->battleGetAllStacks())
  1088. {
  1089. if(stack->isValidTarget())
  1090. {
  1091. for(auto hex : stack->getHexes())
  1092. {
  1093. possibleHexes.insert (hex);
  1094. }
  1095. }
  1096. }
  1097. int targetsOnLevel[4] = {4, 4, 5, 5};
  1098. BattleHex lightningHex = ctx.destination;
  1099. for(int i = 0; i < targetsOnLevel[ctx.schoolLvl]; ++i)
  1100. {
  1101. auto stack = ctx.cb->battleGetStackByPos(lightningHex, true);
  1102. if(!stack)
  1103. break;
  1104. attackedCres.insert (stack);
  1105. for(auto hex : stack->getHexes())
  1106. {
  1107. possibleHexes.erase(hex); //can't hit same place twice
  1108. }
  1109. if(possibleHexes.empty()) //not enough targets
  1110. break;
  1111. lightningHex = BattleHex::getClosestTile(stack->attackerOwned, ctx.destination, possibleHexes);
  1112. }
  1113. return attackedCres;
  1114. }
  1115. ///CloneMechanics
  1116. void CloneMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
  1117. {
  1118. const CStack * clonedStack = nullptr;
  1119. if(ctx.attackedCres.size())
  1120. clonedStack = *ctx.attackedCres.begin();
  1121. if(!clonedStack)
  1122. {
  1123. env->complain ("No target stack to clone!");
  1124. return;
  1125. }
  1126. const int attacker = !(bool)parameters.casterSide;
  1127. BattleStackAdded bsa;
  1128. bsa.creID = clonedStack->type->idNumber;
  1129. bsa.attacker = attacker;
  1130. bsa.summoned = true;
  1131. bsa.pos = parameters.cb->getAvaliableHex(bsa.creID, attacker); //TODO: unify it
  1132. bsa.amount = clonedStack->count;
  1133. env->sendAndApply(&bsa);
  1134. BattleSetStackProperty ssp;
  1135. ssp.stackID = bsa.newStackID;//we know stack ID after apply
  1136. ssp.which = BattleSetStackProperty::CLONED;
  1137. ssp.val = 0;
  1138. ssp.absolute = 1;
  1139. env->sendAndApply(&ssp);
  1140. }
  1141. ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
  1142. {
  1143. //can't clone already cloned creature
  1144. if(vstd::contains(obj->state, EBattleStackState::CLONED))
  1145. return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
  1146. //TODO: how about stacks casting Clone?
  1147. //currently Clone casted by stack is assumed Expert level
  1148. ui8 schoolLevel;
  1149. if(caster)
  1150. {
  1151. schoolLevel = caster->getSpellSchoolLevel(owner);
  1152. }
  1153. else
  1154. {
  1155. schoolLevel = 3;
  1156. }
  1157. if(schoolLevel < 3)
  1158. {
  1159. int maxLevel = (std::max(schoolLevel, (ui8)1) + 4);
  1160. int creLevel = obj->getCreature()->level;
  1161. if(maxLevel < creLevel) //tier 1-5 for basic, 1-6 for advanced, any level for expert
  1162. return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
  1163. }
  1164. //use default algorithm only if there is no mechanics-related problem
  1165. return DefaultSpellMechanics::isImmuneByStack(caster, obj);
  1166. }
  1167. ///CureMechanics
  1168. void CureMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
  1169. {
  1170. DefaultSpellMechanics::applyBattle(battle, packet);
  1171. for(auto stackID : packet->affectedCres)
  1172. {
  1173. if(vstd::contains(packet->resisted, stackID))
  1174. {
  1175. logGlobal->errorStream() << "Resistance to positive spell CURE";
  1176. continue;
  1177. }
  1178. CStack *s = battle->getStack(stackID);
  1179. s->popBonuses([&](const Bonus *b) -> bool
  1180. {
  1181. if(b->source == Bonus::SPELL_EFFECT)
  1182. {
  1183. CSpell * sp = SpellID(b->sid).toSpell();
  1184. return sp->isNegative();
  1185. }
  1186. return false; //not a spell effect
  1187. });
  1188. }
  1189. }
  1190. ///DeathStareMechnics
  1191. void DeathStareMechnics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
  1192. {
  1193. //calculating dmg to display
  1194. ctx.sc.dmgToDisplay = parameters.usedSpellPower;
  1195. if(!ctx.attackedCres.empty())
  1196. vstd::amin(ctx.sc.dmgToDisplay, (*ctx.attackedCres.begin())->count); //stack is already reduced after attack
  1197. for(auto & attackedCre : ctx.attackedCres)
  1198. {
  1199. BattleStackAttacked bsa;
  1200. bsa.flags |= BattleStackAttacked::SPELL_EFFECT;
  1201. bsa.spellID = owner->id;
  1202. bsa.damageAmount = parameters.usedSpellPower * (attackedCre)->valOfBonuses(Bonus::STACK_HEALTH);
  1203. bsa.stackAttacked = (attackedCre)->ID;
  1204. bsa.attackerID = -1;
  1205. (attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
  1206. ctx.si.stacks.push_back(bsa);
  1207. }
  1208. }
  1209. ///DispellHelpfulMechanics
  1210. void DispellHelpfulMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
  1211. {
  1212. DefaultSpellMechanics::applyBattle(battle, packet);
  1213. for(auto stackID : packet->affectedCres)
  1214. {
  1215. if(vstd::contains(packet->resisted, stackID))
  1216. continue;
  1217. CStack *s = battle->getStack(stackID);
  1218. s->popBonuses([&](const Bonus *b) -> bool
  1219. {
  1220. return Selector::positiveSpellEffects(b);
  1221. });
  1222. }
  1223. }
  1224. ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
  1225. {
  1226. TBonusListPtr spellBon = obj->getSpellBonuses();
  1227. bool hasPositiveSpell = false;
  1228. for(const Bonus * b : *spellBon)
  1229. {
  1230. if(SpellID(b->sid).toSpell()->isPositive())
  1231. {
  1232. hasPositiveSpell = true;
  1233. break;
  1234. }
  1235. }
  1236. if(!hasPositiveSpell)
  1237. {
  1238. return ESpellCastProblem::NO_SPELLS_TO_DISPEL;
  1239. }
  1240. //use default algorithm only if there is no mechanics-related problem
  1241. return DefaultSpellMechanics::isImmuneByStack(caster,obj);
  1242. }
  1243. ///DispellMechanics
  1244. void DispellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
  1245. {
  1246. DefaultSpellMechanics::applyBattle(battle, packet);
  1247. for(auto stackID : packet->affectedCres)
  1248. {
  1249. if(vstd::contains(packet->resisted, stackID))
  1250. continue;
  1251. CStack *s = battle->getStack(stackID);
  1252. s->popBonuses([&](const Bonus *b) -> bool
  1253. {
  1254. return Selector::sourceType(Bonus::SPELL_EFFECT)(b);
  1255. });
  1256. }
  1257. }
  1258. ///HypnotizeMechanics
  1259. ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
  1260. {
  1261. if(nullptr != caster) //do not resist hypnotize casted after attack, for example
  1262. {
  1263. //TODO: what with other creatures casting hypnotize, Faerie Dragons style?
  1264. ui64 subjectHealth = (obj->count - 1) * obj->MaxHealth() + obj->firstHPleft;
  1265. //apply 'damage' bonus for hypnotize, including hero specialty
  1266. ui64 maxHealth = owner->calculateBonus(caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER)
  1267. * owner->power + owner->getPower(caster->getSpellSchoolLevel(owner)), caster, obj);
  1268. if (subjectHealth > maxHealth)
  1269. return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
  1270. }
  1271. return DefaultSpellMechanics::isImmuneByStack(caster, obj);
  1272. }
  1273. ///ObstacleMechanics
  1274. void ObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
  1275. {
  1276. auto placeObstacle = [&, this](BattleHex pos)
  1277. {
  1278. static int obstacleIdToGive = parameters.cb->obstacles.size()
  1279. ? (parameters.cb->obstacles.back()->uniqueID+1)
  1280. : 0;
  1281. auto obstacle = make_shared<SpellCreatedObstacle>();
  1282. switch(owner->id) // :/
  1283. {
  1284. case SpellID::QUICKSAND:
  1285. obstacle->obstacleType = CObstacleInstance::QUICKSAND;
  1286. obstacle->turnsRemaining = -1;
  1287. obstacle->visibleForAnotherSide = false;
  1288. break;
  1289. case SpellID::LAND_MINE:
  1290. obstacle->obstacleType = CObstacleInstance::LAND_MINE;
  1291. obstacle->turnsRemaining = -1;
  1292. obstacle->visibleForAnotherSide = false;
  1293. break;
  1294. case SpellID::FIRE_WALL:
  1295. obstacle->obstacleType = CObstacleInstance::FIRE_WALL;
  1296. obstacle->turnsRemaining = 2;
  1297. obstacle->visibleForAnotherSide = true;
  1298. break;
  1299. case SpellID::FORCE_FIELD:
  1300. obstacle->obstacleType = CObstacleInstance::FORCE_FIELD;
  1301. obstacle->turnsRemaining = 2;
  1302. obstacle->visibleForAnotherSide = true;
  1303. break;
  1304. default:
  1305. //this function cannot be used with spells that do not create obstacles
  1306. assert(0);
  1307. }
  1308. obstacle->pos = pos;
  1309. obstacle->casterSide = parameters.casterSide;
  1310. obstacle->ID = owner->id;
  1311. obstacle->spellLevel = parameters.spellLvl;
  1312. obstacle->casterSpellPower = parameters.usedSpellPower;
  1313. obstacle->uniqueID = obstacleIdToGive++;
  1314. BattleObstaclePlaced bop;
  1315. bop.obstacle = obstacle;
  1316. env->sendAndApply(&bop);
  1317. };
  1318. switch(owner->id)
  1319. {
  1320. case SpellID::QUICKSAND:
  1321. case SpellID::LAND_MINE:
  1322. {
  1323. std::vector<BattleHex> availableTiles;
  1324. for(int i = 0; i < GameConstants::BFIELD_SIZE; i += 1)
  1325. {
  1326. BattleHex hex = i;
  1327. if(hex.getX() > 2 && hex.getX() < 14 && !(parameters.cb->battleGetStackByPos(hex, false)) && !(parameters.cb->battleGetObstacleOnPos(hex, false)))
  1328. availableTiles.push_back(hex);
  1329. }
  1330. boost::range::random_shuffle(availableTiles);
  1331. const int patchesForSkill[] = {4, 4, 6, 8};
  1332. const int patchesToPut = std::min<int>(patchesForSkill[parameters.spellLvl], availableTiles.size());
  1333. //land mines or quicksand patches are handled as spell created obstacles
  1334. for (int i = 0; i < patchesToPut; i++)
  1335. placeObstacle(availableTiles.at(i));
  1336. }
  1337. break;
  1338. case SpellID::FORCE_FIELD:
  1339. placeObstacle(parameters.destination);
  1340. break;
  1341. case SpellID::FIRE_WALL:
  1342. {
  1343. //fire wall is build from multiple obstacles - one fire piece for each affected hex
  1344. auto affectedHexes = owner->rangeInHexes(parameters.destination, parameters.spellLvl, parameters.casterSide);
  1345. for(BattleHex hex : affectedHexes)
  1346. placeObstacle(hex);
  1347. }
  1348. break;
  1349. default:
  1350. assert(0);
  1351. }
  1352. }
  1353. ///WallMechanics
  1354. std::vector<BattleHex> WallMechanics::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes) const
  1355. {
  1356. using namespace SRSLPraserHelpers;
  1357. std::vector<BattleHex> ret;
  1358. //Special case - shape of obstacle depends on caster's side
  1359. //TODO make it possible through spell_info config
  1360. BattleHex::EDir firstStep, secondStep;
  1361. if(side)
  1362. {
  1363. firstStep = BattleHex::TOP_LEFT;
  1364. secondStep = BattleHex::TOP_RIGHT;
  1365. }
  1366. else
  1367. {
  1368. firstStep = BattleHex::TOP_RIGHT;
  1369. secondStep = BattleHex::TOP_LEFT;
  1370. }
  1371. //Adds hex to the ret if it's valid. Otherwise sets output arg flag if given.
  1372. auto addIfValid = [&](BattleHex hex)
  1373. {
  1374. if(hex.isValid())
  1375. ret.push_back(hex);
  1376. else if(outDroppedHexes)
  1377. *outDroppedHexes = true;
  1378. };
  1379. ret.push_back(centralHex);
  1380. addIfValid(centralHex.moveInDir(firstStep, false));
  1381. if(schoolLvl >= 2) //advanced versions of fire wall / force field cotnains of 3 hexes
  1382. addIfValid(centralHex.moveInDir(secondStep, false)); //moveInDir function modifies subject hex
  1383. return ret;
  1384. }
  1385. ///RemoveObstacleMechanics
  1386. void RemoveObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
  1387. {
  1388. if(auto obstacleToRemove = parameters.cb->battleGetObstacleOnPos(parameters.destination, false))
  1389. {
  1390. ObstaclesRemoved obr;
  1391. obr.obstacles.insert(obstacleToRemove->uniqueID);
  1392. env->sendAndApply(&obr);
  1393. }
  1394. else
  1395. env->complain("There's no obstacle to remove!");
  1396. }
  1397. ///SpecialRisingSpellMechanics
  1398. void SacrificeMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
  1399. {
  1400. RisingSpellMechanics::applyBattleEffects(env, parameters, ctx);
  1401. if(parameters.selectedStack == parameters.cb->battleActiveStack())
  1402. //set another active stack than the one removed, or bad things will happen
  1403. //TODO: make that part of BattleStacksRemoved? what about client update?
  1404. {
  1405. //makeStackDoNothing(gs->curB->getStack (selectedStack));
  1406. BattleSetActiveStack sas;
  1407. //std::vector<const CStack *> hlp;
  1408. //battleGetStackQueue(hlp, 1, selectedStack); //next after this one
  1409. //if(hlp.size())
  1410. //{
  1411. // sas.stack = hlp[0]->ID;
  1412. //}
  1413. //else
  1414. // complain ("No new stack to activate!");
  1415. sas.stack = parameters.cb->getNextStack()->ID; //why the hell next stack has same ID as current?
  1416. env->sendAndApply(&sas);
  1417. }
  1418. BattleStacksRemoved bsr;
  1419. bsr.stackIDs.insert(parameters.selectedStack->ID); //somehow it works for teleport?
  1420. env->sendAndApply(&bsr);
  1421. }
  1422. ///SpecialRisingSpellMechanics
  1423. ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
  1424. {
  1425. // following does apply to resurrect and animate dead(?) only
  1426. // for sacrifice health calculation and health limit check don't matter
  1427. if(obj->count >= obj->baseAmount)
  1428. return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
  1429. if(caster) //FIXME: Archangels can cast immune stack
  1430. {
  1431. auto maxHealth = calculateHealedHP(caster, obj, nullptr);
  1432. if (maxHealth < obj->MaxHealth()) //must be able to rise at least one full creature
  1433. return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
  1434. }
  1435. return DefaultSpellMechanics::isImmuneByStack(caster,obj);
  1436. }
  1437. ///SummonMechanics
  1438. void SummonMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
  1439. {
  1440. //todo: make configurable
  1441. CreatureID creID = CreatureID::NONE;
  1442. switch(owner->id)
  1443. {
  1444. case SpellID::SUMMON_FIRE_ELEMENTAL:
  1445. creID = CreatureID::FIRE_ELEMENTAL;
  1446. break;
  1447. case SpellID::SUMMON_EARTH_ELEMENTAL:
  1448. creID = CreatureID::EARTH_ELEMENTAL;
  1449. break;
  1450. case SpellID::SUMMON_WATER_ELEMENTAL:
  1451. creID = CreatureID::WATER_ELEMENTAL;
  1452. break;
  1453. case SpellID::SUMMON_AIR_ELEMENTAL:
  1454. creID = CreatureID::AIR_ELEMENTAL;
  1455. break;
  1456. default:
  1457. env->complain("Unable to determine summoned creature");
  1458. return;
  1459. }
  1460. BattleStackAdded bsa;
  1461. bsa.creID = creID;
  1462. bsa.attacker = !(bool)parameters.casterSide;
  1463. bsa.summoned = true;
  1464. bsa.pos = parameters.cb->getAvaliableHex(creID, !(bool)parameters.casterSide); //TODO: unify it
  1465. //TODO stack casting -> probably power will be zero; set the proper number of creatures manually
  1466. int percentBonus = parameters.caster ? parameters.caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, owner->id.toEnum()) : 0;
  1467. bsa.amount = parameters.usedSpellPower
  1468. * owner->getPower(parameters.spellLvl)
  1469. * (100 + percentBonus) / 100.0; //new feature - percentage bonus
  1470. if(bsa.amount)
  1471. env->sendAndApply(&bsa);
  1472. else
  1473. env->complain("Summoning didn't summon any!");
  1474. }
  1475. ///TeleportMechanics
  1476. void TeleportMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
  1477. {
  1478. BattleStackMoved bsm;
  1479. bsm.distance = -1;
  1480. bsm.stack = parameters.selectedStack->ID;
  1481. std::vector<BattleHex> tiles;
  1482. tiles.push_back(parameters.destination);
  1483. bsm.tilesToMove = tiles;
  1484. bsm.teleporting = true;
  1485. env->sendAndApply(&bsm);
  1486. }