2
0

CSpellHandler.cpp 26 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018
  1. #include "StdInc.h"
  2. #include "CSpellHandler.h"
  3. #include "CGeneralTextHandler.h"
  4. #include "filesystem/Filesystem.h"
  5. #include "JsonNode.h"
  6. #include <cctype>
  7. #include "BattleHex.h"
  8. #include "CModHandler.h"
  9. #include "StringConstants.h"
  10. #include "mapObjects/CGHeroInstance.h"
  11. #include "BattleState.h"
  12. #include "CBattleCallback.h"
  13. #include "SpellMechanics.h"
  14. /*
  15. * CSpellHandler.cpp, part of VCMI engine
  16. *
  17. * Authors: listed in file AUTHORS in main folder
  18. *
  19. * License: GNU General Public License v2.0 or later
  20. * Full text of license available in license.txt file, in main folder
  21. *
  22. */
  23. namespace SpellConfig
  24. {
  25. static const std::string LEVEL_NAMES[] = {"none", "basic", "advanced", "expert"};
  26. static const SpellSchoolInfo SCHOOL[4] =
  27. {
  28. {
  29. ESpellSchool::AIR,
  30. Bonus::AIR_SPELL_DMG_PREMY,
  31. Bonus::AIR_IMMUNITY,
  32. "air",
  33. SecondarySkill::AIR_MAGIC,
  34. Bonus::AIR_SPELLS
  35. },
  36. {
  37. ESpellSchool::FIRE,
  38. Bonus::FIRE_SPELL_DMG_PREMY,
  39. Bonus::FIRE_IMMUNITY,
  40. "fire",
  41. SecondarySkill::FIRE_MAGIC,
  42. Bonus::FIRE_SPELLS
  43. },
  44. {
  45. ESpellSchool::WATER,
  46. Bonus::WATER_SPELL_DMG_PREMY,
  47. Bonus::WATER_IMMUNITY,
  48. "water",
  49. SecondarySkill::WATER_MAGIC,
  50. Bonus::WATER_SPELLS
  51. },
  52. {
  53. ESpellSchool::EARTH,
  54. Bonus::EARTH_SPELL_DMG_PREMY,
  55. Bonus::EARTH_IMMUNITY,
  56. "earth",
  57. SecondarySkill::EARTH_MAGIC,
  58. Bonus::EARTH_SPELLS
  59. }
  60. };
  61. }
  62. BattleSpellCastParameters::BattleSpellCastParameters(const BattleInfo* cb)
  63. : spellLvl(0), destination(BattleHex::INVALID), casterSide(0),casterColor(PlayerColor::CANNOT_DETERMINE),caster(nullptr), secHero(nullptr),
  64. usedSpellPower(0),mode(ECastingMode::HERO_CASTING), casterStack(nullptr), selectedStack(nullptr), cb(cb)
  65. {
  66. }
  67. ///CSpell::LevelInfo
  68. CSpell::LevelInfo::LevelInfo()
  69. :description(""),cost(0),power(0),AIValue(0),smartTarget(true), clearTarget(false), clearAffected(false), range("0")
  70. {
  71. }
  72. CSpell::LevelInfo::~LevelInfo()
  73. {
  74. }
  75. ///CSpell
  76. CSpell::CSpell():
  77. id(SpellID::NONE), level(0),
  78. earth(false), water(false), fire(false), air(false),
  79. combatSpell(false), creatureAbility(false),
  80. positiveness(ESpellPositiveness::NEUTRAL),
  81. defaultProbability(0),
  82. isRising(false), isDamage(false), isOffensive(false),
  83. targetType(ETargetType::NO_TARGET),
  84. mechanics(nullptr)
  85. {
  86. levels.resize(GameConstants::SPELL_SCHOOL_LEVELS);
  87. }
  88. CSpell::~CSpell()
  89. {
  90. delete mechanics;
  91. }
  92. void CSpell::afterCast(BattleInfo * battle, const BattleSpellCast * packet) const
  93. {
  94. mechanics->afterCast(battle, packet);
  95. }
  96. void CSpell::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const
  97. {
  98. assert(env);
  99. mechanics->battleCast(env, parameters);
  100. }
  101. bool CSpell::isCastableBy(const IBonusBearer * caster, bool hasSpellBook, const std::set<SpellID> & spellBook) const
  102. {
  103. if(!hasSpellBook)
  104. return false;
  105. const bool inSpellBook = vstd::contains(spellBook, id);
  106. const bool isBonus = caster->hasBonusOfType(Bonus::SPELL, id);
  107. bool inTome = false;
  108. forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
  109. {
  110. if(caster->hasBonusOfType(cnf.knoledgeBonus))
  111. {
  112. inTome = stop = true;
  113. }
  114. });
  115. if (isSpecialSpell())
  116. {
  117. if (inSpellBook)
  118. {//hero has this spell in spellbook
  119. logGlobal->errorStream() << "Special spell in spellbook "<<name;
  120. }
  121. return isBonus;
  122. }
  123. else
  124. {
  125. return inSpellBook || inTome || isBonus || caster->hasBonusOfType(Bonus::SPELLS_OF_LEVEL, level);
  126. }
  127. }
  128. const CSpell::LevelInfo & CSpell::getLevelInfo(const int level) const
  129. {
  130. if(level < 0 || level >= GameConstants::SPELL_SCHOOL_LEVELS)
  131. {
  132. logGlobal->errorStream() << __FUNCTION__ << " invalid school level " << level;
  133. throw new std::runtime_error("Invalid school level");
  134. }
  135. return levels.at(level);
  136. }
  137. ui32 CSpell::calculateBonus(ui32 baseDamage, const CGHeroInstance* caster, const CStack* affectedCreature) const
  138. {
  139. ui32 ret = baseDamage;
  140. //applying sorcery secondary skill
  141. if(caster)
  142. {
  143. ret *= (100.0 + caster->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::SORCERY)) / 100.0;
  144. ret *= (100.0 + caster->valOfBonuses(Bonus::SPELL_DAMAGE) + caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, id.toEnum())) / 100.0;
  145. forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
  146. {
  147. ret *= (100.0 + caster->valOfBonuses(cnf.damagePremyBonus)) / 100.0;
  148. stop = true; //only bonus from one school is used
  149. });
  150. if (affectedCreature && affectedCreature->getCreature()->level) //Hero specials like Solmyr, Deemer
  151. ret *= (100. + ((caster->valOfBonuses(Bonus::SPECIAL_SPELL_LEV, id.toEnum()) * caster->level) / affectedCreature->getCreature()->level)) / 100.0;
  152. }
  153. return ret;
  154. }
  155. ui32 CSpell::calculateDamage(const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const
  156. {
  157. ui32 ret = 0; //value to return
  158. //check if spell really does damage - if not, return 0
  159. if(!isDamageSpell())
  160. return 0;
  161. ret = usedSpellPower * power;
  162. ret += getPower(spellSchoolLevel);
  163. //affected creature-specific part
  164. if(nullptr != affectedCreature)
  165. {
  166. //applying protections - when spell has more then one elements, only one protection should be applied (I think)
  167. forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
  168. {
  169. if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id))
  170. {
  171. ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id);
  172. ret /= 100;
  173. stop = true;//only bonus from one school is used
  174. }
  175. });
  176. //general spell dmg reduction
  177. if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, -1))
  178. {
  179. ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, -1);
  180. ret /= 100;
  181. }
  182. //dmg increasing
  183. if(affectedCreature->hasBonusOfType(Bonus::MORE_DAMAGE_FROM_SPELL, id))
  184. {
  185. ret *= 100 + affectedCreature->valOfBonuses(Bonus::MORE_DAMAGE_FROM_SPELL, id.toEnum());
  186. ret /= 100;
  187. }
  188. }
  189. ret = calculateBonus(ret, caster, affectedCreature);
  190. return ret;
  191. }
  192. ui32 CSpell::calculateHealedHP(const CGHeroInstance* caster, const CStack* stack, const CStack* sacrificedStack) const
  193. {
  194. //todo: use Mechanics class
  195. int healedHealth;
  196. if(!isHealingSpell())
  197. {
  198. logGlobal->errorStream() << "calculateHealedHP called for nonhealing spell "<< name;
  199. return 0;
  200. }
  201. const int spellPowerSkill = caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
  202. const int levelPower = getPower(caster->getSpellSchoolLevel(this));
  203. if (id == SpellID::SACRIFICE && sacrificedStack)
  204. healedHealth = (spellPowerSkill + sacrificedStack->MaxHealth() + levelPower) * sacrificedStack->count;
  205. else
  206. healedHealth = spellPowerSkill * power + levelPower; //???
  207. healedHealth = calculateBonus(healedHealth, caster, stack);
  208. return std::min<ui32>(healedHealth, stack->MaxHealth() - stack->firstHPleft + (isRisingSpell() ? stack->baseAmount * stack->MaxHealth() : 0));
  209. }
  210. std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
  211. {
  212. return mechanics->rangeInHexes(centralHex,schoolLvl,side,outDroppedHexes);
  213. }
  214. std::set<const CStack* > CSpell::getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const CGHeroInstance * caster) const
  215. {
  216. ISpellMechanics::SpellTargetingContext ctx(this, cb,mode,casterColor,spellLvl,destination);
  217. std::set<const CStack* > attackedCres = mechanics->getAffectedStacks(ctx);
  218. //now handle immunities
  219. auto predicate = [&, this](const CStack * s)->bool
  220. {
  221. bool hitDirectly = ctx.ti.alwaysHitDirectly && s->coversPos(destination);
  222. bool notImmune = (ESpellCastProblem::OK == isImmuneByStack(caster, s));
  223. return !(hitDirectly || notImmune);
  224. };
  225. vstd::erase_if(attackedCres, predicate);
  226. return attackedCres;
  227. }
  228. CSpell::ETargetType CSpell::getTargetType() const
  229. {
  230. return targetType;
  231. }
  232. CSpell::TargetInfo CSpell::getTargetInfo(const int level) const
  233. {
  234. TargetInfo info(this, level);
  235. return info;
  236. }
  237. void CSpell::forEachSchool(const std::function<void(const SpellSchoolInfo &, bool &)>& cb) const
  238. {
  239. bool stop = false;
  240. for(const SpellSchoolInfo & cnf : SpellConfig::SCHOOL)
  241. {
  242. if(school.at(cnf.id))
  243. {
  244. cb(cnf, stop);
  245. if(stop)
  246. break;
  247. }
  248. }
  249. }
  250. bool CSpell::isCombatSpell() const
  251. {
  252. return combatSpell;
  253. }
  254. bool CSpell::isAdventureSpell() const
  255. {
  256. return !combatSpell;
  257. }
  258. bool CSpell::isCreatureAbility() const
  259. {
  260. return creatureAbility;
  261. }
  262. bool CSpell::isPositive() const
  263. {
  264. return positiveness == POSITIVE;
  265. }
  266. bool CSpell::isNegative() const
  267. {
  268. return positiveness == NEGATIVE;
  269. }
  270. bool CSpell::isNeutral() const
  271. {
  272. return positiveness == NEUTRAL;
  273. }
  274. bool CSpell::isHealingSpell() const
  275. {
  276. return isRisingSpell() || (id == SpellID::CURE);
  277. }
  278. bool CSpell::isRisingSpell() const
  279. {
  280. return isRising;
  281. }
  282. bool CSpell::isDamageSpell() const
  283. {
  284. return isDamage;
  285. }
  286. bool CSpell::isOffensiveSpell() const
  287. {
  288. return isOffensive;
  289. }
  290. bool CSpell::isSpecialSpell() const
  291. {
  292. return isSpecial;
  293. }
  294. bool CSpell::hasEffects() const
  295. {
  296. return !levels[0].effects.empty();
  297. }
  298. const std::string& CSpell::getIconImmune() const
  299. {
  300. return iconImmune;
  301. }
  302. const std::string& CSpell::getCastSound() const
  303. {
  304. return castSound;
  305. }
  306. si32 CSpell::getCost(const int skillLevel) const
  307. {
  308. return getLevelInfo(skillLevel).cost;
  309. }
  310. si32 CSpell::getPower(const int skillLevel) const
  311. {
  312. return getLevelInfo(skillLevel).power;
  313. }
  314. //si32 CSpell::calculatePower(const int skillLevel) const
  315. //{
  316. // return power + getPower(skillLevel);
  317. //}
  318. si32 CSpell::getProbability(const TFaction factionId) const
  319. {
  320. if(!vstd::contains(probabilities,factionId))
  321. {
  322. return defaultProbability;
  323. }
  324. return probabilities.at(factionId);
  325. }
  326. void CSpell::getEffects(std::vector<Bonus>& lst, const int level) const
  327. {
  328. if(level < 0 || level >= GameConstants::SPELL_SCHOOL_LEVELS)
  329. {
  330. logGlobal->errorStream() << __FUNCTION__ << " invalid school level " << level;
  331. return;
  332. }
  333. const std::vector<Bonus> & effects = levels[level].effects;
  334. if(effects.empty())
  335. {
  336. logGlobal->errorStream() << __FUNCTION__ << " This spell (" + name + ") has no effects for level " << level;
  337. return;
  338. }
  339. lst.reserve(lst.size() + effects.size());
  340. for(const Bonus & b : effects)
  341. {
  342. lst.push_back(Bonus(b));
  343. }
  344. }
  345. ESpellCastProblem::ESpellCastProblem CSpell::isImmuneBy(const IBonusBearer* obj) const
  346. {
  347. //todo: use new bonus API
  348. //1. Check absolute limiters
  349. for(auto b : absoluteLimiters)
  350. {
  351. if (!obj->hasBonusOfType(b))
  352. return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
  353. }
  354. //2. Check absolute immunities
  355. for(auto b : absoluteImmunities)
  356. {
  357. if (obj->hasBonusOfType(b))
  358. return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
  359. }
  360. //check receptivity
  361. if (isPositive() && obj->hasBonusOfType(Bonus::RECEPTIVE)) //accept all positive spells
  362. return ESpellCastProblem::OK;
  363. //3. Check negation
  364. //FIXME: Orb of vulnerability mechanics is not such trivial
  365. if(obj->hasBonusOfType(Bonus::NEGATE_ALL_NATURAL_IMMUNITIES)) //Orb of vulnerability
  366. return ESpellCastProblem::NOT_DECIDED;
  367. //4. Check negatable limit
  368. for(auto b : limiters)
  369. {
  370. if (!obj->hasBonusOfType(b))
  371. return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
  372. }
  373. //5. Check negatable immunities
  374. for(auto b : immunities)
  375. {
  376. if (obj->hasBonusOfType(b))
  377. return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
  378. }
  379. //6. Check elemental immunities
  380. ESpellCastProblem::ESpellCastProblem tmp = ESpellCastProblem::NOT_DECIDED;
  381. forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
  382. {
  383. auto element = cnf.immunityBonus;
  384. if(obj->hasBonusOfType(element, 0)) //always resist if immune to all spells altogether
  385. {
  386. tmp = ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
  387. stop = true;
  388. }
  389. else if(!isPositive()) //negative or indifferent
  390. {
  391. if((isDamageSpell() && obj->hasBonusOfType(element, 2)) || obj->hasBonusOfType(element, 1))
  392. {
  393. tmp = ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
  394. stop = true;
  395. }
  396. }
  397. });
  398. if(tmp != ESpellCastProblem::NOT_DECIDED)
  399. return tmp;
  400. TBonusListPtr levelImmunities = obj->getBonuses(Selector::type(Bonus::LEVEL_SPELL_IMMUNITY));
  401. if(obj->hasBonusOfType(Bonus::SPELL_IMMUNITY, id)
  402. || ( levelImmunities->size() > 0 && levelImmunities->totalValue() >= level && level))
  403. {
  404. return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
  405. }
  406. return ESpellCastProblem::NOT_DECIDED;
  407. }
  408. ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const CGHeroInstance* caster, const CStack* obj) const
  409. {
  410. const auto immuneResult = mechanics->isImmuneByStack(caster,obj);
  411. if (ESpellCastProblem::NOT_DECIDED != immuneResult)
  412. return immuneResult;
  413. return ESpellCastProblem::OK;
  414. }
  415. void CSpell::setIsOffensive(const bool val)
  416. {
  417. isOffensive = val;
  418. if(val)
  419. {
  420. positiveness = CSpell::NEGATIVE;
  421. isDamage = true;
  422. }
  423. }
  424. void CSpell::setIsRising(const bool val)
  425. {
  426. isRising = val;
  427. if(val)
  428. {
  429. positiveness = CSpell::POSITIVE;
  430. }
  431. }
  432. void CSpell::setup()
  433. {
  434. setupMechanics();
  435. air = school[ESpellSchool::AIR];
  436. fire = school[ESpellSchool::FIRE];
  437. water = school[ESpellSchool::WATER];
  438. earth = school[ESpellSchool::EARTH];
  439. }
  440. void CSpell::setupMechanics()
  441. {
  442. if(nullptr != mechanics)
  443. {
  444. logGlobal->errorStream() << "Spell " << this->name << " mechanics already set";
  445. delete mechanics;
  446. }
  447. mechanics = ISpellMechanics::createMechanics(this);
  448. }
  449. ///CSpell::AnimationInfo
  450. CSpell::AnimationInfo::AnimationInfo()
  451. {
  452. }
  453. CSpell::AnimationInfo::~AnimationInfo()
  454. {
  455. }
  456. std::string CSpell::AnimationInfo::selectProjectile(const double angle) const
  457. {
  458. std::string res;
  459. double maximum = 0.0;
  460. for(const auto & info : projectile)
  461. {
  462. if(info.minimumAngle < angle && info.minimumAngle > maximum)
  463. {
  464. maximum = info.minimumAngle;
  465. res = info.resourceName;
  466. }
  467. }
  468. return std::move(res);
  469. }
  470. ///CSpell::TargetInfo
  471. CSpell::TargetInfo::TargetInfo(const CSpell * spell, const int level)
  472. {
  473. init(spell, level);
  474. }
  475. CSpell::TargetInfo::TargetInfo(const CSpell * spell, const int level, ECastingMode::ECastingMode mode)
  476. {
  477. init(spell, level);
  478. if(mode == ECastingMode::ENCHANTER_CASTING)
  479. {
  480. smart = true; //FIXME: not sure about that, this makes all spells smart in this mode
  481. massive = true;
  482. }
  483. else if(mode == ECastingMode::SPELL_LIKE_ATTACK)
  484. {
  485. alwaysHitDirectly = true;
  486. }
  487. }
  488. void CSpell::TargetInfo::init(const CSpell * spell, const int level)
  489. {
  490. auto & levelInfo = spell->getLevelInfo(level);
  491. type = spell->getTargetType();
  492. smart = levelInfo.smartTarget;
  493. massive = levelInfo.range == "X";
  494. onlyAlive = !spell->isRisingSpell();
  495. alwaysHitDirectly = false;
  496. clearAffected = levelInfo.clearAffected;
  497. clearTarget = levelInfo.clearTarget;
  498. }
  499. bool DLL_LINKAGE isInScreenRange(const int3 &center, const int3 &pos)
  500. {
  501. int3 diff = pos - center;
  502. if(diff.x >= -9 && diff.x <= 9 && diff.y >= -8 && diff.y <= 8)
  503. return true;
  504. else
  505. return false;
  506. }
  507. ///CSpellHandler
  508. CSpellHandler::CSpellHandler()
  509. {
  510. }
  511. std::vector<JsonNode> CSpellHandler::loadLegacyData(size_t dataSize)
  512. {
  513. using namespace SpellConfig;
  514. std::vector<JsonNode> legacyData;
  515. CLegacyConfigParser parser("DATA/SPTRAITS.TXT");
  516. auto readSchool = [&](JsonMap& schools, const std::string& name)
  517. {
  518. if (parser.readString() == "x")
  519. {
  520. schools[name].Bool() = true;
  521. }
  522. };
  523. auto read = [&,this](bool combat, bool ability)
  524. {
  525. do
  526. {
  527. JsonNode lineNode(JsonNode::DATA_STRUCT);
  528. const si32 id = legacyData.size();
  529. lineNode["index"].Float() = id;
  530. lineNode["type"].String() = ability ? "ability" : (combat ? "combat" : "adventure");
  531. lineNode["name"].String() = parser.readString();
  532. parser.readString(); //ignored unused abbreviated name
  533. lineNode["level"].Float() = parser.readNumber();
  534. auto& schools = lineNode["school"].Struct();
  535. readSchool(schools, "earth");
  536. readSchool(schools, "water");
  537. readSchool(schools, "fire");
  538. readSchool(schools, "air");
  539. auto& levels = lineNode["levels"].Struct();
  540. auto getLevel = [&](const size_t idx)->JsonMap&
  541. {
  542. assert(idx < GameConstants::SPELL_SCHOOL_LEVELS);
  543. return levels[LEVEL_NAMES[idx]].Struct();
  544. };
  545. auto costs = parser.readNumArray<si32>(GameConstants::SPELL_SCHOOL_LEVELS);
  546. lineNode["power"].Float() = parser.readNumber();
  547. auto powers = parser.readNumArray<si32>(GameConstants::SPELL_SCHOOL_LEVELS);
  548. auto& chances = lineNode["gainChance"].Struct();
  549. for(size_t i = 0; i < GameConstants::F_NUMBER ; i++){
  550. chances[ETownType::names[i]].Float() = parser.readNumber();
  551. }
  552. auto AIVals = parser.readNumArray<si32>(GameConstants::SPELL_SCHOOL_LEVELS);
  553. std::vector<std::string> descriptions;
  554. for(size_t i = 0; i < GameConstants::SPELL_SCHOOL_LEVELS ; i++)
  555. descriptions.push_back(parser.readString());
  556. parser.readString(); //ignore attributes. All data present in JSON
  557. //save parsed level specific data
  558. for(size_t i = 0; i < GameConstants::SPELL_SCHOOL_LEVELS; i++)
  559. {
  560. auto& level = getLevel(i);
  561. level["description"].String() = descriptions[i];
  562. level["cost"].Float() = costs[i];
  563. level["power"].Float() = powers[i];
  564. level["aiValue"].Float() = AIVals[i];
  565. }
  566. legacyData.push_back(lineNode);
  567. }
  568. while (parser.endLine() && !parser.isNextEntryEmpty());
  569. };
  570. auto skip = [&](int cnt)
  571. {
  572. for(int i=0; i<cnt; i++)
  573. parser.endLine();
  574. };
  575. skip(5);// header
  576. read(false,false); //read adventure map spells
  577. skip(3);
  578. read(true,false); //read battle spells
  579. skip(3);
  580. read(true,true);//read creature abilities
  581. //TODO: maybe move to config
  582. //clone Acid Breath attributes for Acid Breath damage effect
  583. JsonNode temp = legacyData[SpellID::ACID_BREATH_DEFENSE];
  584. temp["index"].Float() = SpellID::ACID_BREATH_DAMAGE;
  585. legacyData.push_back(temp);
  586. objects.resize(legacyData.size());
  587. return legacyData;
  588. }
  589. const std::string CSpellHandler::getTypeName() const
  590. {
  591. return "spell";
  592. }
  593. CSpell * CSpellHandler::loadFromJson(const JsonNode& json)
  594. {
  595. using namespace SpellConfig;
  596. CSpell * spell = new CSpell();
  597. const auto type = json["type"].String();
  598. if(type == "ability")
  599. {
  600. spell->creatureAbility = true;
  601. spell->combatSpell = true;
  602. }
  603. else
  604. {
  605. spell->creatureAbility = false;
  606. spell->combatSpell = type == "combat";
  607. }
  608. spell->name = json["name"].String();
  609. logGlobal->traceStream() << __FUNCTION__ << ": loading spell " << spell->name;
  610. const auto schoolNames = json["school"];
  611. for(const SpellSchoolInfo & info : SpellConfig::SCHOOL)
  612. {
  613. spell->school[info.id] = schoolNames[info.jsonName].Bool();
  614. }
  615. spell->level = json["level"].Float();
  616. spell->power = json["power"].Float();
  617. spell->defaultProbability = json["defaultGainChance"].Float();
  618. for(const auto & node : json["gainChance"].Struct())
  619. {
  620. const int chance = node.second.Float();
  621. VLC->modh->identifiers.requestIdentifier(node.second.meta, "faction",node.first, [=](si32 factionID)
  622. {
  623. spell->probabilities[factionID] = chance;
  624. });
  625. }
  626. auto targetType = json["targetType"].String();
  627. if(targetType == "NO_TARGET")
  628. spell->targetType = CSpell::NO_TARGET;
  629. else if(targetType == "CREATURE")
  630. spell->targetType = CSpell::CREATURE;
  631. else if(targetType == "OBSTACLE")
  632. spell->targetType = CSpell::OBSTACLE;
  633. else if(targetType == "LOCATION")
  634. spell->targetType = CSpell::LOCATION;
  635. else
  636. logGlobal->warnStream() << "Spell " << spell->name << ". Target type " << (targetType.empty() ? "empty" : "unknown ("+targetType+")") << ". Assumed NO_TARGET.";
  637. for(const auto & counteredSpell: json["counters"].Struct())
  638. if (counteredSpell.second.Bool())
  639. {
  640. VLC->modh->identifiers.requestIdentifier(json.meta, counteredSpell.first, [=](si32 id)
  641. {
  642. spell->counteredSpells.push_back(SpellID(id));
  643. });
  644. }
  645. //TODO: more error checking - f.e. conflicting flags
  646. const auto flags = json["flags"];
  647. //by default all flags are set to false in constructor
  648. spell->isDamage = flags["damage"].Bool(); //do this before "offensive"
  649. if(flags["offensive"].Bool())
  650. {
  651. spell->setIsOffensive(true);
  652. }
  653. if(flags["rising"].Bool())
  654. {
  655. spell->setIsRising(true);
  656. }
  657. const bool implicitPositiveness = spell->isOffensive || spell->isRising; //(!) "damage" does not mean NEGATIVE --AVS
  658. if(flags["indifferent"].Bool())
  659. {
  660. spell->positiveness = CSpell::NEUTRAL;
  661. }
  662. else if(flags["negative"].Bool())
  663. {
  664. spell->positiveness = CSpell::NEGATIVE;
  665. }
  666. else if(flags["positive"].Bool())
  667. {
  668. spell->positiveness = CSpell::POSITIVE;
  669. }
  670. else if(!implicitPositiveness)
  671. {
  672. spell->positiveness = CSpell::NEUTRAL; //duplicates constructor but, just in case
  673. logGlobal->errorStream() << "No positiveness specified, assumed NEUTRAL";
  674. }
  675. spell->isSpecial = flags["special"].Bool();
  676. auto findBonus = [&](std::string name, std::vector<Bonus::BonusType> &vec)
  677. {
  678. auto it = bonusNameMap.find(name);
  679. if(it == bonusNameMap.end())
  680. {
  681. logGlobal->errorStream() << spell->name << ": invalid bonus name" << name;
  682. }
  683. else
  684. {
  685. vec.push_back((Bonus::BonusType)it->second);
  686. }
  687. };
  688. auto readBonusStruct = [&](std::string name, std::vector<Bonus::BonusType> &vec)
  689. {
  690. for(auto bonusData: json[name].Struct())
  691. {
  692. const std::string bonusId = bonusData.first;
  693. const bool flag = bonusData.second.Bool();
  694. if(flag)
  695. findBonus(bonusId, vec);
  696. }
  697. };
  698. readBonusStruct("immunity", spell->immunities);
  699. readBonusStruct("absoluteImmunity", spell->absoluteImmunities);
  700. readBonusStruct("limit", spell->limiters);
  701. readBonusStruct("absoluteLimit", spell->absoluteLimiters);
  702. const JsonNode & graphicsNode = json["graphics"];
  703. spell->iconImmune = graphicsNode["iconImmune"].String();
  704. spell->iconBook = graphicsNode["iconBook"].String();
  705. spell->iconEffect = graphicsNode["iconEffect"].String();
  706. spell->iconScenarioBonus = graphicsNode["iconScenarioBonus"].String();
  707. spell->iconScroll = graphicsNode["iconScroll"].String();
  708. const JsonNode & animationNode = json["animation"];
  709. auto loadAnimationQueue = [&](const std::string & jsonName, CSpell::TAnimationQueue & q)
  710. {
  711. auto queueNode = animationNode[jsonName].Vector();
  712. for(const JsonNode & item : queueNode)
  713. {
  714. CSpell::TAnimation newItem;
  715. newItem.verticalPosition = VerticalPosition::TOP;
  716. if(item.getType() == JsonNode::DATA_STRING)
  717. newItem.resourceName = item.String();
  718. else if(item.getType() == JsonNode::DATA_STRUCT)
  719. {
  720. newItem.resourceName = item["defName"].String();
  721. auto vPosStr = item["verticalPosition"].String();
  722. if("bottom" == vPosStr)
  723. newItem.verticalPosition = VerticalPosition::BOTTOM;
  724. }
  725. q.push_back(newItem);
  726. }
  727. };
  728. loadAnimationQueue("affect", spell->animationInfo.affect);
  729. loadAnimationQueue("cast", spell->animationInfo.cast);
  730. loadAnimationQueue("hit", spell->animationInfo.hit);
  731. const JsonVector & projectile = animationNode["projectile"].Vector();
  732. for(const JsonNode & item : projectile)
  733. {
  734. CSpell::ProjectileInfo info;
  735. info.resourceName = item["defName"].String();
  736. info.minimumAngle = item["minimumAngle"].Float();
  737. spell->animationInfo.projectile.push_back(info);
  738. }
  739. const JsonNode & soundsNode = json["sounds"];
  740. spell->castSound = soundsNode["cast"].String();
  741. //load level attributes
  742. const int levelsCount = GameConstants::SPELL_SCHOOL_LEVELS;
  743. for(int levelIndex = 0; levelIndex < levelsCount; levelIndex++)
  744. {
  745. const JsonNode & levelNode = json["levels"][LEVEL_NAMES[levelIndex]];
  746. CSpell::LevelInfo & levelObject = spell->levels[levelIndex];
  747. const si32 levelPower = levelObject.power = levelNode["power"].Float();
  748. levelObject.description = levelNode["description"].String();
  749. levelObject.cost = levelNode["cost"].Float();
  750. levelObject.AIValue = levelNode["aiValue"].Float();
  751. levelObject.smartTarget = levelNode["targetModifier"]["smart"].Bool();
  752. levelObject.clearTarget = levelNode["targetModifier"]["clearTarget"].Bool();
  753. levelObject.clearAffected = levelNode["targetModifier"]["clearAffected"].Bool();
  754. levelObject.range = levelNode["range"].String();
  755. for(const auto & elem : levelNode["effects"].Struct())
  756. {
  757. const JsonNode & bonusNode = elem.second;
  758. Bonus * b = JsonUtils::parseBonus(bonusNode);
  759. const bool usePowerAsValue = bonusNode["val"].isNull();
  760. //TODO: make this work. see CSpellHandler::afterLoadFinalization()
  761. //b->sid = spell->id; //for all
  762. b->source = Bonus::SPELL_EFFECT;//for all
  763. if(usePowerAsValue)
  764. b->val = levelPower;
  765. levelObject.effects.push_back(*b);
  766. }
  767. }
  768. return spell;
  769. }
  770. void CSpellHandler::afterLoadFinalization()
  771. {
  772. //FIXME: it is a bad place for this code, should refactor loadFromJson to know object id during loading
  773. for(auto spell: objects)
  774. {
  775. for(auto & level: spell->levels)
  776. for(auto & bonus: level.effects)
  777. bonus.sid = spell->id;
  778. spell->setup();
  779. }
  780. }
  781. void CSpellHandler::beforeValidate(JsonNode & object)
  782. {
  783. //handle "base" level info
  784. JsonNode& levels = object["levels"];
  785. JsonNode& base = levels["base"];
  786. auto inheritNode = [&](const std::string & name){
  787. JsonUtils::inherit(levels[name],base);
  788. };
  789. inheritNode("none");
  790. inheritNode("basic");
  791. inheritNode("advanced");
  792. inheritNode("expert");
  793. }
  794. CSpellHandler::~CSpellHandler()
  795. {
  796. }
  797. std::vector<bool> CSpellHandler::getDefaultAllowed() const
  798. {
  799. std::vector<bool> allowedSpells;
  800. allowedSpells.resize(GameConstants::SPELLS_QUANTITY, true);
  801. return allowedSpells;
  802. }