CDefaultSpellMechanics.cpp 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  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 "CSpellHandler.h"
  13. #include "../CStack.h"
  14. #include "../battle/BattleInfo.h"
  15. #include "../CGeneralTextHandler.h"
  16. namespace spells
  17. {
  18. namespace SRSLPraserHelpers
  19. {
  20. static int XYToHex(int x, int y)
  21. {
  22. return x + GameConstants::BFIELD_WIDTH * y;
  23. }
  24. static int XYToHex(std::pair<int, int> xy)
  25. {
  26. return XYToHex(xy.first, xy.second);
  27. }
  28. static int hexToY(int battleFieldPosition)
  29. {
  30. return battleFieldPosition/GameConstants::BFIELD_WIDTH;
  31. }
  32. static int hexToX(int battleFieldPosition)
  33. {
  34. int pos = battleFieldPosition - hexToY(battleFieldPosition) * GameConstants::BFIELD_WIDTH;
  35. return pos;
  36. }
  37. static std::pair<int, int> hexToPair(int battleFieldPosition)
  38. {
  39. return std::make_pair(hexToX(battleFieldPosition), hexToY(battleFieldPosition));
  40. }
  41. //moves hex by one hex in given direction
  42. //0 - left top, 1 - right top, 2 - right, 3 - right bottom, 4 - left bottom, 5 - left
  43. static std::pair<int, int> gotoDir(int x, int y, int direction)
  44. {
  45. switch(direction)
  46. {
  47. case 0: //top left
  48. return std::make_pair((y%2) ? x-1 : x, y-1);
  49. case 1: //top right
  50. return std::make_pair((y%2) ? x : x+1, y-1);
  51. case 2: //right
  52. return std::make_pair(x+1, y);
  53. case 3: //right bottom
  54. return std::make_pair((y%2) ? x : x+1, y+1);
  55. case 4: //left bottom
  56. return std::make_pair((y%2) ? x-1 : x, y+1);
  57. case 5: //left
  58. return std::make_pair(x-1, y);
  59. default:
  60. throw std::runtime_error("Disaster: wrong direction in SRSLPraserHelpers::gotoDir!\n");
  61. }
  62. }
  63. static std::pair<int, int> gotoDir(std::pair<int, int> xy, int direction)
  64. {
  65. return gotoDir(xy.first, xy.second, direction);
  66. }
  67. static bool isGoodHex(std::pair<int, int> xy)
  68. {
  69. return xy.first >=0 && xy.first < GameConstants::BFIELD_WIDTH && xy.second >= 0 && xy.second < GameConstants::BFIELD_HEIGHT;
  70. }
  71. //helper function for rangeInHexes
  72. static std::set<ui16> getInRange(unsigned int center, int low, int high)
  73. {
  74. std::set<ui16> ret;
  75. if(low == 0)
  76. {
  77. ret.insert(center);
  78. }
  79. std::pair<int, int> mainPointForLayer[6]; //A, B, C, D, E, F points
  80. for(auto & elem : mainPointForLayer)
  81. elem = hexToPair(center);
  82. for(int it=1; it<=high; ++it) //it - distance to the center
  83. {
  84. for(int b=0; b<6; ++b)
  85. mainPointForLayer[b] = gotoDir(mainPointForLayer[b], b);
  86. if(it>=low)
  87. {
  88. std::pair<int, int> curHex;
  89. //adding lines (A-b, B-c, C-d, etc)
  90. for(int v=0; v<6; ++v)
  91. {
  92. curHex = mainPointForLayer[v];
  93. for(int h=0; h<it; ++h)
  94. {
  95. if(isGoodHex(curHex))
  96. ret.insert(XYToHex(curHex));
  97. curHex = gotoDir(curHex, (v+2)%6);
  98. }
  99. }
  100. } //if(it>=low)
  101. }
  102. return ret;
  103. }
  104. }
  105. ///DefaultSpellMechanics
  106. DefaultSpellMechanics::DefaultSpellMechanics(const IBattleCast * event)
  107. : BaseMechanics(event)
  108. {
  109. };
  110. std::vector<BattleHex> DefaultSpellMechanics::rangeInHexes(BattleHex centralHex, bool * outDroppedHexes) const
  111. {
  112. auto spellRange = spellRangeInHexes(centralHex);
  113. std::vector<BattleHex> ret;
  114. ret.reserve(spellRange.size());
  115. std::copy(spellRange.begin(), spellRange.end(), std::back_inserter(ret));
  116. return ret;
  117. }
  118. std::set<BattleHex> DefaultSpellMechanics::spellRangeInHexes(BattleHex centralHex) const
  119. {
  120. using namespace SRSLPraserHelpers;
  121. std::set<BattleHex> ret;
  122. std::string rng = owner->getLevelInfo(getRangeLevel()).range + ','; //copy + artificial comma for easier handling
  123. if(rng.size() >= 2 && rng[0] != 'X') //there is at least one hex in range (+artificial comma)
  124. {
  125. std::string number1, number2;
  126. int beg, end;
  127. bool readingFirst = true;
  128. for(auto & elem : rng)
  129. {
  130. if(std::isdigit(elem) ) //reading number
  131. {
  132. if(readingFirst)
  133. number1 += elem;
  134. else
  135. number2 += elem;
  136. }
  137. else if(elem == ',') //comma
  138. {
  139. //calculating variables
  140. if(readingFirst)
  141. {
  142. beg = atoi(number1.c_str());
  143. number1 = "";
  144. }
  145. else
  146. {
  147. end = atoi(number2.c_str());
  148. number2 = "";
  149. }
  150. //obtaining new hexes
  151. std::set<ui16> curLayer;
  152. if(readingFirst)
  153. {
  154. curLayer = getInRange(centralHex, beg, beg);
  155. }
  156. else
  157. {
  158. curLayer = getInRange(centralHex, beg, end);
  159. readingFirst = true;
  160. }
  161. //adding obtained hexes
  162. for(auto & curLayer_it : curLayer)
  163. {
  164. ret.insert(curLayer_it);
  165. }
  166. }
  167. else if(elem == '-') //dash
  168. {
  169. beg = atoi(number1.c_str());
  170. number1 = "";
  171. readingFirst = false;
  172. }
  173. }
  174. }
  175. return ret;
  176. }
  177. void DefaultSpellMechanics::addBattleLog(MetaString && line)
  178. {
  179. sc.battleLog.push_back(line);
  180. }
  181. void DefaultSpellMechanics::addCustomEffect(const battle::Unit * target, ui32 effect)
  182. {
  183. CustomEffectInfo customEffect;
  184. customEffect.effect = effect;
  185. customEffect.stack = target->unitId();
  186. sc.customEffects.push_back(customEffect);
  187. }
  188. void DefaultSpellMechanics::afterCast() const
  189. {
  190. }
  191. void DefaultSpellMechanics::cast(const SpellCastEnvironment * env, const Target & target, std::vector<const CStack *> & reflected)
  192. {
  193. sc.side = casterSide;
  194. sc.spellID = getSpellId();
  195. sc.tile = target.at(0).hexValue;
  196. sc.castByHero = mode == Mode::HERO;
  197. sc.casterStack = (casterUnit ? casterUnit->unitId() : -1);
  198. sc.manaGained = 0;
  199. sc.activeCast = false;
  200. affectedUnits.clear();
  201. const CGHeroInstance * otherHero = nullptr;
  202. {
  203. //check it there is opponent hero
  204. const ui8 otherSide = cb->otherSide(casterSide);
  205. if(cb->battleHasHero(otherSide))
  206. otherHero = cb->battleGetFightingHero(otherSide);
  207. }
  208. //calculate spell cost
  209. if(mode == Mode::HERO)
  210. {
  211. auto casterHero = dynamic_cast<const CGHeroInstance *>(caster);
  212. spellCost = cb->battleGetSpellCost(owner, casterHero);
  213. if(nullptr != otherHero) //handle mana channel
  214. {
  215. int manaChannel = 0;
  216. for(const CStack * stack : cb->battleGetAllStacks(true)) //TODO: shouldn't bonus system handle it somehow?
  217. {
  218. if(stack->owner == otherHero->tempOwner)
  219. {
  220. vstd::amax(manaChannel, stack->valOfBonuses(Bonus::MANA_CHANNELING));
  221. }
  222. }
  223. sc.manaGained = (manaChannel * spellCost) / 100;
  224. }
  225. sc.activeCast = true;
  226. }
  227. else if(mode == Mode::CREATURE_ACTIVE || mode == Mode::ENCHANTER)
  228. {
  229. spellCost = 1;
  230. sc.activeCast = true;
  231. }
  232. beforeCast(env->getRandomGenerator(), target, reflected);
  233. switch (mode)
  234. {
  235. case Mode::CREATURE_ACTIVE:
  236. case Mode::ENCHANTER:
  237. case Mode::HERO:
  238. case Mode::PASSIVE:
  239. {
  240. MetaString line;
  241. caster->getCastDescription(owner, affectedUnits, line);
  242. addBattleLog(std::move(line));
  243. }
  244. break;
  245. default:
  246. break;
  247. }
  248. doRemoveEffects(env, affectedUnits, std::bind(&DefaultSpellMechanics::counteringSelector, this, _1));
  249. for(auto & unit : affectedUnits)
  250. sc.affectedCres.insert(unit->unitId());
  251. env->sendAndApply(&sc);
  252. applyCastEffects(env, target);
  253. afterCast();
  254. if(sc.activeCast)
  255. {
  256. caster->spendMana(mode, owner, env, spellCost);
  257. if(sc.manaGained > 0)
  258. {
  259. assert(otherHero);
  260. otherHero->spendMana(Mode::HERO, owner, env, -sc.manaGained);
  261. }
  262. }
  263. }
  264. bool DefaultSpellMechanics::counteringSelector(const Bonus * bonus) const
  265. {
  266. if(bonus->source != Bonus::SPELL_EFFECT)
  267. return false;
  268. for(const SpellID & id : owner->counteredSpells)
  269. {
  270. if(bonus->sid == id.toEnum())
  271. return true;
  272. }
  273. return false;
  274. }
  275. void DefaultSpellMechanics::doRemoveEffects(const SpellCastEnvironment * env, const std::vector<const battle::Unit *> & targets, const CSelector & selector)
  276. {
  277. SetStackEffect sse;
  278. for(auto unit : targets)
  279. {
  280. std::vector<Bonus> buffer;
  281. auto bl = unit->getBonuses(selector);
  282. for(auto item : *bl)
  283. buffer.emplace_back(*item);
  284. if(!buffer.empty())
  285. sse.toRemove.push_back(std::make_pair(unit->unitId(), buffer));
  286. }
  287. if(!sse.toRemove.empty())
  288. env->sendAndApply(&sse);
  289. }
  290. } // namespace spells