CCreatureHandler.cpp 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217
  1. /*
  2. * CCreatureHandler.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 "CCreatureHandler.h"
  12. #include "CGeneralTextHandler.h"
  13. #include "filesystem/Filesystem.h"
  14. #include "VCMI_Lib.h"
  15. #include "CGameState.h"
  16. #include "CTownHandler.h"
  17. #include "CModHandler.h"
  18. #include "StringConstants.h"
  19. #include "mapObjects/CObjectClassesHandler.h"
  20. int CCreature::getQuantityID(const int & quantity)
  21. {
  22. if (quantity<5)
  23. return 1;
  24. if (quantity<10)
  25. return 2;
  26. if (quantity<20)
  27. return 3;
  28. if (quantity<50)
  29. return 4;
  30. if (quantity<100)
  31. return 5;
  32. if (quantity<250)
  33. return 6;
  34. if (quantity<500)
  35. return 7;
  36. if (quantity<1000)
  37. return 8;
  38. return 9;
  39. }
  40. int CCreature::estimateCreatureCount(ui32 countID)
  41. {
  42. static const int creature_count[] = { 0, 3, 8, 15, 35, 75, 175, 375, 750, 2500 };
  43. if(countID > 9)
  44. {
  45. logGlobal->error("Wrong countID %d!", countID);
  46. return 0;
  47. }
  48. else
  49. return creature_count[countID];
  50. }
  51. bool CCreature::isDoubleWide() const
  52. {
  53. return doubleWide;
  54. }
  55. bool CCreature::isFlying() const
  56. {
  57. return hasBonusOfType(Bonus::FLYING);
  58. }
  59. bool CCreature::isShooting() const
  60. {
  61. return hasBonusOfType(Bonus::SHOOTER);
  62. }
  63. bool CCreature::isUndead() const
  64. {
  65. return hasBonusOfType(Bonus::UNDEAD);
  66. }
  67. /**
  68. * Determines if the creature is of a good alignment.
  69. * @return true if the creture is good, false otherwise.
  70. */
  71. bool CCreature::isGood () const
  72. {
  73. return VLC->townh->factions[faction]->alignment == EAlignment::GOOD;
  74. }
  75. /**
  76. * Determines if the creature is of an evil alignment.
  77. * @return true if the creature is evil, false otherwise.
  78. */
  79. bool CCreature::isEvil () const
  80. {
  81. return VLC->townh->factions[faction]->alignment == EAlignment::EVIL;
  82. }
  83. si32 CCreature::maxAmount(const std::vector<si32> &res) const //how many creatures can be bought
  84. {
  85. int ret = 2147483645;
  86. int resAmnt = static_cast<int>(std::min(res.size(),cost.size()));
  87. for(int i=0;i<resAmnt;i++)
  88. if(cost[i])
  89. ret = std::min(ret,(int)(res[i]/cost[i]));
  90. return ret;
  91. }
  92. CCreature::CCreature()
  93. {
  94. setNodeType(CBonusSystemNode::CREATURE);
  95. faction = 0;
  96. level = 0;
  97. fightValue = AIValue = growth = hordeGrowth = ammMin = ammMax = 0;
  98. doubleWide = false;
  99. special = true;
  100. iconIndex = -1;
  101. }
  102. void CCreature::addBonus(int val, Bonus::BonusType type, int subtype)
  103. {
  104. auto added = std::make_shared<Bonus>(Bonus::PERMANENT, type, Bonus::CREATURE_ABILITY, val, idNumber, subtype, Bonus::BASE_NUMBER);
  105. addNewBonus(added);
  106. }
  107. bool CCreature::isMyUpgrade(const CCreature *anotherCre) const
  108. {
  109. //TODO upgrade of upgrade?
  110. return vstd::contains(upgrades, anotherCre->idNumber);
  111. }
  112. bool CCreature::valid() const
  113. {
  114. return this == VLC->creh->creatures[idNumber];
  115. }
  116. std::string CCreature::nodeName() const
  117. {
  118. return "\"" + namePl + "\"";
  119. }
  120. bool CCreature::isItNativeTerrain(int terrain) const
  121. {
  122. return VLC->townh->factions[faction]->nativeTerrain == terrain;
  123. }
  124. void CCreature::setId(CreatureID ID)
  125. {
  126. idNumber = ID;
  127. for(auto bonus : getExportedBonusList())
  128. {
  129. if(bonus->source == Bonus::CREATURE_ABILITY)
  130. bonus->sid = ID;
  131. }
  132. CBonusSystemNode::treeHasChanged();
  133. }
  134. void CCreature::fillWarMachine()
  135. {
  136. switch (idNumber)
  137. {
  138. case CreatureID::CATAPULT: //Catapult
  139. warMachine = ArtifactID::CATAPULT;
  140. break;
  141. case CreatureID::BALLISTA: //Ballista
  142. warMachine = ArtifactID::BALLISTA;
  143. break;
  144. case CreatureID::FIRST_AID_TENT: //First Aid tent
  145. warMachine = ArtifactID::FIRST_AID_TENT;
  146. break;
  147. case CreatureID::AMMO_CART: //Ammo cart
  148. warMachine = ArtifactID::AMMO_CART;
  149. break;
  150. }
  151. warMachine = ArtifactID::NONE; //this creature is not artifact
  152. }
  153. static void AddAbility(CCreature *cre, const JsonVector &ability_vec)
  154. {
  155. auto nsf = std::make_shared<Bonus>();
  156. std::string type = ability_vec[0].String();
  157. auto it = bonusNameMap.find(type);
  158. if (it == bonusNameMap.end()) {
  159. if (type == "DOUBLE_WIDE")
  160. cre->doubleWide = true;
  161. else if (type == "ENEMY_MORALE_DECREASING") {
  162. cre->addBonus(-1, Bonus::MORALE);
  163. cre->getBonusList().back()->effectRange = Bonus::ONLY_ENEMY_ARMY;
  164. }
  165. else if (type == "ENEMY_LUCK_DECREASING") {
  166. cre->addBonus(-1, Bonus::LUCK);
  167. cre->getBonusList().back()->effectRange = Bonus::ONLY_ENEMY_ARMY;
  168. } else
  169. logGlobal->error("Error: invalid ability type %s in creatures config", type);
  170. return;
  171. }
  172. nsf->type = it->second;
  173. JsonUtils::parseTypedBonusShort(ability_vec,nsf);
  174. nsf->source = Bonus::CREATURE_ABILITY;
  175. nsf->sid = cre->idNumber;
  176. cre->addNewBonus(nsf);
  177. }
  178. CCreatureHandler::CCreatureHandler()
  179. : expAfterUpgrade(0)
  180. {
  181. VLC->creh = this;
  182. allCreatures.setDescription("All creatures");
  183. creaturesOfLevel[0].setDescription("Creatures of unnormalized tier");
  184. for(int i = 1; i < ARRAY_COUNT(creaturesOfLevel); i++)
  185. creaturesOfLevel[i].setDescription("Creatures of tier " + boost::lexical_cast<std::string>(i));
  186. loadCommanders();
  187. }
  188. const CCreature * CCreatureHandler::getCreature(const std::string & scope, const std::string & identifier) const
  189. {
  190. boost::optional<si32> index = VLC->modh->identifiers.getIdentifier(scope, "creature", identifier);
  191. if(!index)
  192. throw std::runtime_error("Creature not found "+identifier);
  193. return creatures[*index];
  194. }
  195. void CCreatureHandler::loadCommanders()
  196. {
  197. JsonNode data(ResourceID("config/commanders.json"));
  198. data.setMeta("core"); // assume that commanders are in core mod (for proper bonuses resolution)
  199. const JsonNode & config = data; // switch to const data accessors
  200. for (auto bonus : config["bonusPerLevel"].Vector())
  201. {
  202. commanderLevelPremy.push_back(JsonUtils::parseBonus(bonus.Vector()));
  203. }
  204. int i = 0;
  205. for (auto skill : config["skillLevels"].Vector())
  206. {
  207. skillLevels.push_back (std::vector<ui8>());
  208. for (auto skillLevel : skill["levels"].Vector())
  209. {
  210. skillLevels[i].push_back ((ui8)skillLevel.Float());
  211. }
  212. ++i;
  213. }
  214. for (auto ability : config["abilityRequirements"].Vector())
  215. {
  216. std::pair <std::shared_ptr<Bonus>, std::pair <ui8, ui8> > a;
  217. a.first = JsonUtils::parseBonus (ability["ability"].Vector());
  218. a.second.first = static_cast<ui8>(ability["skills"].Vector()[0].Float());
  219. a.second.second = static_cast<ui8>(ability["skills"].Vector()[1].Float());
  220. skillRequirements.push_back (a);
  221. }
  222. }
  223. void CCreatureHandler::loadBonuses(JsonNode & creature, std::string bonuses)
  224. {
  225. auto makeBonusNode = [&](std::string type) -> JsonNode
  226. {
  227. JsonNode ret;
  228. ret["type"].String() = type;
  229. return ret;
  230. };
  231. static const std::map<std::string, JsonNode> abilityMap =
  232. {
  233. {"FLYING_ARMY", makeBonusNode("FLYING")},
  234. {"SHOOTING_ARMY", makeBonusNode("SHOOTER")},
  235. {"SIEGE_WEAPON", makeBonusNode("SIEGE_WEAPON")},
  236. {"const_free_attack", makeBonusNode("BLOCKS_RETALIATION")},
  237. {"IS_UNDEAD", makeBonusNode("UNDEAD")},
  238. {"const_no_melee_penalty", makeBonusNode("NO_MELEE_PENALTY")},
  239. {"const_jousting", makeBonusNode("JOUSTING")},
  240. {"KING_1", makeBonusNode("KING1")},
  241. {"KING_2", makeBonusNode("KING2")},
  242. {"KING_3", makeBonusNode("KING3")},
  243. {"const_no_wall_penalty", makeBonusNode("NO_WALL_PENALTY")},
  244. {"CATAPULT", makeBonusNode("CATAPULT")},
  245. {"MULTI_HEADED", makeBonusNode("ATTACKS_ALL_ADJACENT")},
  246. {"IMMUNE_TO_MIND_SPELLS", makeBonusNode("MIND_IMMUNITY")},
  247. {"HAS_EXTENDED_ATTACK", makeBonusNode("TWO_HEX_ATTACK_BREATH")}
  248. };
  249. auto hasAbility = [&](const std::string name) -> bool
  250. {
  251. return boost::algorithm::find_first(bonuses, name);
  252. };
  253. for(auto a : abilityMap)
  254. {
  255. if(hasAbility(a.first))
  256. creature["abilities"][a.first] = a.second;
  257. }
  258. if(hasAbility("DOUBLE_WIDE"))
  259. creature["doubleWide"].Bool() = true;
  260. if(hasAbility("const_raises_morale"))
  261. {
  262. JsonNode node = makeBonusNode("MORALE");
  263. node["val"].Float() = 1;
  264. node["propagator"].String() = "HERO";
  265. creature["abilities"]["const_raises_morale"] = node;
  266. }
  267. if(hasAbility("const_lowers_morale"))
  268. {
  269. JsonNode node = makeBonusNode("MORALE");
  270. node["val"].Float() = -1;
  271. node["effectRange"].String() = "ONLY_ENEMY_ARMY";
  272. creature["abilities"]["const_lowers_morale"] = node;
  273. }
  274. }
  275. std::vector<JsonNode> CCreatureHandler::loadLegacyData(size_t dataSize)
  276. {
  277. creatures.resize(dataSize);
  278. std::vector<JsonNode> h3Data;
  279. h3Data.reserve(dataSize);
  280. CLegacyConfigParser parser("DATA/CRTRAITS.TXT");
  281. parser.endLine(); // header
  282. // this file is a bit different in some of Russian localisations:
  283. //ENG: Singular Plural Wood ...
  284. //RUS: Singular Plural Plural2 Wood ...
  285. // Try to detect which version this is by header
  286. // TODO: use 3rd name? Stand for "whose", e.g. pikemans'
  287. size_t namesCount;
  288. {
  289. if ( parser.readString() != "Singular" || parser.readString() != "Plural" )
  290. throw std::runtime_error("Incorrect format of CrTraits.txt");
  291. if (parser.readString() == "Plural2")
  292. namesCount = 3;
  293. else
  294. namesCount = 2;
  295. parser.endLine();
  296. }
  297. for (size_t i=0; i<dataSize; i++)
  298. {
  299. //loop till non-empty line
  300. while (parser.isNextEntryEmpty())
  301. parser.endLine();
  302. JsonNode data;
  303. data["name"]["singular"].String() = parser.readString();
  304. if (namesCount == 3)
  305. parser.readString();
  306. data["name"]["plural"].String() = parser.readString();
  307. for(int v=0; v<7; ++v)
  308. data["cost"][GameConstants::RESOURCE_NAMES[v]].Float() = parser.readNumber();
  309. data["fightValue"].Float() = parser.readNumber();
  310. data["aiValue"].Float() = parser.readNumber();
  311. data["growth"].Float() = parser.readNumber();
  312. data["horde"].Float() = parser.readNumber();
  313. data["hitPoints"].Float() = parser.readNumber();
  314. data["speed"].Float() = parser.readNumber();
  315. data["attack"].Float() = parser.readNumber();
  316. data["defense"].Float() = parser.readNumber();
  317. data["damage"]["min"].Float() = parser.readNumber();
  318. data["damage"]["max"].Float() = parser.readNumber();
  319. if (float shots = parser.readNumber())
  320. data["shots"].Float() = shots;
  321. if (float spells = parser.readNumber())
  322. data["spellPoints"].Float() = spells;
  323. data["advMapAmount"]["min"].Float() = parser.readNumber();
  324. data["advMapAmount"]["max"].Float() = parser.readNumber();
  325. data["abilityText"].String() = parser.readString();
  326. loadBonuses(data, parser.readString()); //Attributes
  327. h3Data.push_back(data);
  328. }
  329. loadAnimationInfo(h3Data);
  330. return h3Data;
  331. }
  332. void CCreatureHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
  333. {
  334. auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
  335. object->setId(CreatureID((si32)creatures.size()));
  336. object->iconIndex = object->idNumber + 2;
  337. creatures.push_back(object);
  338. VLC->modh->identifiers.requestIdentifier(scope, "object", "monster", [=](si32 index)
  339. {
  340. JsonNode conf;
  341. conf.setMeta(scope);
  342. VLC->objtypeh->loadSubObject(object->identifier, conf, Obj::MONSTER, object->idNumber.num);
  343. if (!object->advMapDef.empty())
  344. {
  345. JsonNode templ;
  346. templ["animation"].String() = object->advMapDef;
  347. VLC->objtypeh->getHandlerFor(Obj::MONSTER, object->idNumber.num)->addTemplate(templ);
  348. }
  349. // object does not have any templates - this is not usable object (e.g. pseudo-creature like Arrow Tower)
  350. if (VLC->objtypeh->getHandlerFor(Obj::MONSTER, object->idNumber.num)->getTemplates().empty())
  351. VLC->objtypeh->removeSubObject(Obj::MONSTER, object->idNumber.num);
  352. });
  353. registerObject(scope, "creature", name, object->idNumber);
  354. for(auto node : data["extraNames"].Vector())
  355. {
  356. registerObject(scope, "creature", node.String(), object->idNumber);
  357. }
  358. }
  359. void CCreatureHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
  360. {
  361. auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
  362. object->setId(CreatureID((si32)index));
  363. object->iconIndex = object->idNumber + 2;
  364. if(data["hasDoubleWeek"].Bool()) //
  365. {
  366. doubledCreatures.insert (object->idNumber); //we need to have id (or identifier) before it is inserted
  367. }
  368. assert(creatures[index] == nullptr); // ensure that this id was not loaded before
  369. creatures[index] = object;
  370. VLC->modh->identifiers.requestIdentifier(scope, "object", "monster", [=](si32 index)
  371. {
  372. JsonNode conf;
  373. conf.setMeta(scope);
  374. VLC->objtypeh->loadSubObject(object->identifier, conf, Obj::MONSTER, object->idNumber.num);
  375. if (!object->advMapDef.empty())
  376. {
  377. JsonNode templ;
  378. templ["animation"].String() = object->advMapDef;
  379. VLC->objtypeh->getHandlerFor(Obj::MONSTER, object->idNumber.num)->addTemplate(templ);
  380. }
  381. // object does not have any templates - this is not usable object (e.g. pseudo-creature like Arrow Tower)
  382. if (VLC->objtypeh->getHandlerFor(Obj::MONSTER, object->idNumber.num)->getTemplates().empty())
  383. VLC->objtypeh->removeSubObject(Obj::MONSTER, object->idNumber.num);
  384. });
  385. registerObject(scope, "creature", name, object->idNumber);
  386. for(auto & node : data["extraNames"].Vector())
  387. {
  388. registerObject(scope, "creature", node.String(), object->idNumber);
  389. }
  390. }
  391. std::vector<bool> CCreatureHandler::getDefaultAllowed() const
  392. {
  393. std::vector<bool> ret;
  394. for(const CCreature * crea : creatures)
  395. {
  396. ret.push_back(crea ? !crea->special : false);
  397. }
  398. return ret;
  399. }
  400. void CCreatureHandler::loadCrExpBon()
  401. {
  402. if (VLC->modh->modules.STACK_EXP) //reading default stack experience bonuses
  403. {
  404. CLegacyConfigParser parser("DATA/CREXPBON.TXT");
  405. Bonus b; //prototype with some default properties
  406. b.source = Bonus::STACK_EXPERIENCE;
  407. b.duration = Bonus::PERMANENT;
  408. b.valType = Bonus::ADDITIVE_VALUE;
  409. b.effectRange = Bonus::NO_LIMIT;
  410. b.additionalInfo = 0;
  411. b.turnsRemain = 0;
  412. BonusList bl;
  413. parser.endLine();
  414. parser.readString(); //ignore index
  415. loadStackExp(b, bl, parser);
  416. for(auto b : bl)
  417. addBonusForAllCreatures(b); //health bonus is common for all
  418. parser.endLine();
  419. for (int i = 1; i < 7; ++i)
  420. {
  421. for (int j = 0; j < 4; ++j) //four modifiers common for tiers
  422. {
  423. parser.readString(); //ignore index
  424. bl.clear();
  425. loadStackExp(b, bl, parser);
  426. for(auto b : bl)
  427. addBonusForTier(i, b);
  428. parser.endLine();
  429. }
  430. }
  431. for (int j = 0; j < 4; ++j) //tier 7
  432. {
  433. parser.readString(); //ignore index
  434. bl.clear();
  435. loadStackExp(b, bl, parser);
  436. for(auto b : bl)
  437. {
  438. addBonusForTier(7, b);
  439. creaturesOfLevel[0].addNewBonus(b); //bonuses from level 7 are given to high-level creatures
  440. }
  441. parser.endLine();
  442. }
  443. do //parse everything that's left
  444. {
  445. ui32 sid = static_cast<ui32>(parser.readNumber()); //id = this particular creature ID
  446. b.sid = sid;
  447. bl.clear();
  448. loadStackExp(b, bl, parser);
  449. for(auto b : bl)
  450. {
  451. creatures[sid]->addNewBonus(b); //add directly to CCreature Node
  452. }
  453. }
  454. while (parser.endLine());
  455. //Calculate rank exp values, formula appears complicated bu no parsing needed
  456. expRanks.resize(8);
  457. int dif = 0;
  458. int it = 8000; //ignore name of this variable
  459. expRanks[0].push_back(it);
  460. for (int j = 1; j < 10; ++j) //used for tiers 8-10, and all other probably
  461. {
  462. expRanks[0].push_back(expRanks[0][j-1] + it + dif);
  463. dif += it/5;
  464. }
  465. for (int i = 1; i < 8; ++i)
  466. {
  467. dif = 0;
  468. it = 1000 * i;
  469. expRanks[i].push_back(it);
  470. for (int j = 1; j < 10; ++j)
  471. {
  472. expRanks[i].push_back(expRanks[i][j-1] + it + dif);
  473. dif += it/5;
  474. }
  475. }
  476. CLegacyConfigParser expBonParser("DATA/CREXPMOD.TXT");
  477. expBonParser.endLine(); //header
  478. maxExpPerBattle.resize(8);
  479. for (int i = 1; i < 8; ++i)
  480. {
  481. expBonParser.readString(); //index
  482. expBonParser.readString(); //float multiplier -> hardcoded
  483. expBonParser.readString(); //ignore upgrade mod? ->hardcoded
  484. expBonParser.readString(); //already calculated
  485. maxExpPerBattle[i] = static_cast<ui32>(expBonParser.readNumber());
  486. expRanks[i].push_back(expRanks[i].back() + (ui32)expBonParser.readNumber());
  487. expBonParser.endLine();
  488. }
  489. //skeleton gets exp penalty
  490. creatures[56].get()->addBonus(-50, Bonus::EXP_MULTIPLIER, -1);
  491. creatures[57].get()->addBonus(-50, Bonus::EXP_MULTIPLIER, -1);
  492. //exp for tier >7, rank 11
  493. expRanks[0].push_back(147000);
  494. expAfterUpgrade = 75; //percent
  495. maxExpPerBattle[0] = maxExpPerBattle[7];
  496. }//end of Stack Experience
  497. }
  498. void CCreatureHandler::loadAnimationInfo(std::vector<JsonNode> &h3Data)
  499. {
  500. CLegacyConfigParser parser("DATA/CRANIM.TXT");
  501. parser.endLine(); // header
  502. parser.endLine();
  503. for(int dd=0; dd<VLC->modh->settings.data["textData"]["creature"].Float(); ++dd)
  504. {
  505. while (parser.isNextEntryEmpty() && parser.endLine()) // skip empty lines
  506. ;
  507. loadUnitAnimInfo(h3Data[dd]["graphics"], parser);
  508. parser.endLine();
  509. }
  510. }
  511. void CCreatureHandler::loadUnitAnimInfo(JsonNode & graphics, CLegacyConfigParser & parser)
  512. {
  513. graphics["timeBetweenFidgets"].Float() = parser.readNumber();
  514. JsonNode & animationTime = graphics["animationTime"];
  515. animationTime["walk"].Float() = parser.readNumber();
  516. animationTime["attack"].Float() = parser.readNumber();
  517. animationTime["flight"].Float() = parser.readNumber();
  518. animationTime["idle"].Float() = 10.0;
  519. JsonNode & missile = graphics["missile"];
  520. JsonNode & offsets = missile["offset"];
  521. offsets["upperX"].Float() = parser.readNumber();
  522. offsets["upperY"].Float() = parser.readNumber();
  523. offsets["middleX"].Float() = parser.readNumber();
  524. offsets["middleY"].Float() = parser.readNumber();
  525. offsets["lowerX"].Float() = parser.readNumber();
  526. offsets["lowerY"].Float() = parser.readNumber();
  527. for(int i=0; i<12; i++)
  528. {
  529. JsonNode entry;
  530. entry.Float() = parser.readNumber();
  531. missile["frameAngles"].Vector().push_back(entry);
  532. }
  533. graphics["troopCountLocationOffset"].Float() = parser.readNumber();
  534. missile["attackClimaxFrame"].Float() = parser.readNumber();
  535. // assume that creature is not a shooter and should not have whole missile field
  536. if (missile["frameAngles"].Vector()[0].Float() == 0 &&
  537. missile["attackClimaxFrame"].Float() == 0)
  538. graphics.Struct().erase("missile");
  539. }
  540. CCreature * CCreatureHandler::loadFromJson(const JsonNode & node, const std::string & identifier)
  541. {
  542. auto cre = new CCreature();
  543. const JsonNode & name = node["name"];
  544. cre->identifier = identifier;
  545. cre->nameSing = name["singular"].String();
  546. cre->namePl = name["plural"].String();
  547. cre->cost = Res::ResourceSet(node["cost"]);
  548. cre->fightValue = static_cast<ui32>(node["fightValue"].Float());
  549. cre->AIValue = static_cast<ui32>(node["aiValue"].Float());
  550. cre->growth = static_cast<ui32>(node["growth"].Float());
  551. cre->hordeGrowth = static_cast<ui32>(node["horde"].Float()); // Needed at least until configurable buildings
  552. cre->addBonus((int)node["hitPoints"].Float(), Bonus::STACK_HEALTH);
  553. cre->addBonus((int)node["speed"].Float(), Bonus::STACKS_SPEED);
  554. cre->addBonus((int)node["attack"].Float(), Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK);
  555. cre->addBonus((int)node["defense"].Float(), Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE);
  556. cre->addBonus((int)node["damage"]["min"].Float(), Bonus::CREATURE_DAMAGE, 1);
  557. cre->addBonus((int)node["damage"]["max"].Float(), Bonus::CREATURE_DAMAGE, 2);
  558. assert(node["damage"]["min"].Float() <= node["damage"]["max"].Float());
  559. cre->ammMin = static_cast<ui32>(node["advMapAmount"]["min"].Float());
  560. cre->ammMax = static_cast<ui32>(node["advMapAmount"]["max"].Float());
  561. assert(cre->ammMin <= cre->ammMax);
  562. if (!node["shots"].isNull())
  563. cre->addBonus((int)node["shots"].Float(), Bonus::SHOTS);
  564. if (node["spellPoints"].isNull())
  565. cre->addBonus((int)node["spellPoints"].Float(), Bonus::CASTS);
  566. cre->doubleWide = node["doubleWide"].Bool();
  567. loadStackExperience(cre, node["stackExperience"]);
  568. loadJsonAnimation(cre, node["graphics"]);
  569. loadCreatureJson(cre, node);
  570. return cre;
  571. }
  572. void CCreatureHandler::loadJsonAnimation(CCreature * cre, const JsonNode & graphics)
  573. {
  574. cre->animation.timeBetweenFidgets = graphics["timeBetweenFidgets"].Float();
  575. cre->animation.troopCountLocationOffset = static_cast<int>(graphics["troopCountLocationOffset"].Float());
  576. const JsonNode & animationTime = graphics["animationTime"];
  577. cre->animation.walkAnimationTime = animationTime["walk"].Float();
  578. cre->animation.idleAnimationTime = animationTime["idle"].Float();
  579. cre->animation.attackAnimationTime = animationTime["attack"].Float();
  580. cre->animation.flightAnimationDistance = animationTime["flight"].Float(); //?
  581. const JsonNode & missile = graphics["missile"];
  582. const JsonNode & offsets = missile["offset"];
  583. cre->animation.upperRightMissleOffsetX = static_cast<int>(offsets["upperX"].Float());
  584. cre->animation.upperRightMissleOffsetY = static_cast<int>(offsets["upperY"].Float());
  585. cre->animation.rightMissleOffsetX = static_cast<int>(offsets["middleX"].Float());
  586. cre->animation.rightMissleOffsetY = static_cast<int>(offsets["middleY"].Float());
  587. cre->animation.lowerRightMissleOffsetX = static_cast<int>(offsets["lowerX"].Float());
  588. cre->animation.lowerRightMissleOffsetY = static_cast<int>(offsets["lowerY"].Float());
  589. cre->animation.attackClimaxFrame = static_cast<int>(missile["attackClimaxFrame"].Float());
  590. cre->animation.missleFrameAngles = missile["frameAngles"].convertTo<std::vector<double> >();
  591. cre->advMapDef = graphics["map"].String();
  592. cre->smallIconName = graphics["iconSmall"].String();
  593. cre->largeIconName = graphics["iconLarge"].String();
  594. }
  595. void CCreatureHandler::loadCreatureJson(CCreature * creature, const JsonNode & config)
  596. {
  597. creature->level = static_cast<ui8>(config["level"].Float());
  598. creature->animDefName = config["graphics"]["animation"].String();
  599. //FIXME: MOD COMPATIBILITY
  600. if (config["abilities"].getType() == JsonNode::JsonType::DATA_STRUCT)
  601. {
  602. for(auto &ability : config["abilities"].Struct())
  603. {
  604. if (!ability.second.isNull())
  605. {
  606. auto b = JsonUtils::parseBonus(ability.second);
  607. b->source = Bonus::CREATURE_ABILITY;
  608. b->duration = Bonus::PERMANENT;
  609. creature->addNewBonus(b);
  610. }
  611. }
  612. }
  613. else
  614. {
  615. for(const JsonNode &ability : config["abilities"].Vector())
  616. {
  617. if (ability.getType() == JsonNode::JsonType::DATA_VECTOR)
  618. {
  619. assert(0); // should be unused now
  620. AddAbility(creature, ability.Vector()); // used only for H3 creatures
  621. }
  622. else
  623. {
  624. auto b = JsonUtils::parseBonus(ability);
  625. b->source = Bonus::CREATURE_ABILITY;
  626. b->duration = Bonus::PERMANENT;
  627. creature->addNewBonus(b);
  628. }
  629. }
  630. }
  631. VLC->modh->identifiers.requestIdentifier("faction", config["faction"], [=](si32 faction)
  632. {
  633. creature->faction = faction;
  634. });
  635. for(const JsonNode &value : config["upgrades"].Vector())
  636. {
  637. VLC->modh->identifiers.requestIdentifier("creature", value, [=](si32 identifier)
  638. {
  639. creature->upgrades.insert(CreatureID(identifier));
  640. });
  641. }
  642. creature->animation.projectileImageName = config["graphics"]["missile"]["projectile"].String();
  643. creature->special = config["special"].Bool() || config["disabled"].Bool();
  644. const JsonNode & sounds = config["sound"];
  645. #define GET_SOUND_VALUE(value_name) creature->sounds.value_name = sounds[#value_name].String()
  646. GET_SOUND_VALUE(attack);
  647. GET_SOUND_VALUE(defend);
  648. GET_SOUND_VALUE(killed);
  649. GET_SOUND_VALUE(move);
  650. GET_SOUND_VALUE(shoot);
  651. GET_SOUND_VALUE(wince);
  652. GET_SOUND_VALUE(startMoving);
  653. GET_SOUND_VALUE(endMoving);
  654. #undef GET_SOUND_VALUE
  655. }
  656. void CCreatureHandler::loadStackExperience(CCreature * creature, const JsonNode & input)
  657. {
  658. for (const JsonNode &exp : input.Vector())
  659. {
  660. auto bonus = JsonUtils::parseBonus (exp["bonus"]);
  661. bonus->source = Bonus::STACK_EXPERIENCE;
  662. bonus->duration = Bonus::PERMANENT;
  663. const JsonVector &values = exp["values"].Vector();
  664. int lowerLimit = 1;//, upperLimit = 255;
  665. if (values[0].getType() == JsonNode::JsonType::DATA_BOOL)
  666. {
  667. for (const JsonNode &val : values)
  668. {
  669. if (val.Bool() == true)
  670. {
  671. bonus->limiter = std::make_shared<RankRangeLimiter>(RankRangeLimiter(lowerLimit));
  672. creature->addNewBonus (std::make_shared<Bonus>(*bonus)); //bonuses must be unique objects
  673. break; //TODO: allow bonuses to turn off?
  674. }
  675. ++lowerLimit;
  676. }
  677. }
  678. else
  679. {
  680. int lastVal = 0;
  681. for (const JsonNode &val : values)
  682. {
  683. if (val.Float() != lastVal)
  684. {
  685. bonus->val = (int)val.Float() - lastVal;
  686. bonus->limiter.reset (new RankRangeLimiter(lowerLimit));
  687. creature->addNewBonus (std::make_shared<Bonus>(*bonus));
  688. }
  689. lastVal = static_cast<int>(val.Float());
  690. ++lowerLimit;
  691. }
  692. }
  693. }
  694. }
  695. void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigParser & parser) //help function for parsing CREXPBON.txt
  696. {
  697. bool enable = false; //some bonuses are activated with values 2 or 1
  698. std::string buf = parser.readString();
  699. std::string mod = parser.readString();
  700. switch (buf[0])
  701. {
  702. case 'H':
  703. b.type = Bonus::STACK_HEALTH;
  704. b.valType = Bonus::PERCENT_TO_BASE;
  705. break;
  706. case 'A':
  707. b.type = Bonus::PRIMARY_SKILL;
  708. b.subtype = PrimarySkill::ATTACK;
  709. break;
  710. case 'D':
  711. b.type = Bonus::PRIMARY_SKILL;
  712. b.subtype = PrimarySkill::DEFENSE;
  713. break;
  714. case 'M': //Max damage
  715. b.type = Bonus::CREATURE_DAMAGE;
  716. b.subtype = 2;
  717. break;
  718. case 'm': //Min damage
  719. b.type = Bonus::CREATURE_DAMAGE;
  720. b.subtype = 1;
  721. break;
  722. case 'S':
  723. b.type = Bonus::STACKS_SPEED; break;
  724. case 'O':
  725. b.type = Bonus::SHOTS; break;
  726. case 'b':
  727. b.type = Bonus::ENEMY_DEFENCE_REDUCTION; break;
  728. case 'C':
  729. b.type = Bonus::CHANGES_SPELL_COST_FOR_ALLY; break;
  730. case 'd':
  731. b.type = Bonus::DEFENSIVE_STANCE; break;
  732. case 'e':
  733. b.type = Bonus::DOUBLE_DAMAGE_CHANCE;
  734. b.subtype = 0;
  735. break;
  736. case 'E':
  737. b.type = Bonus::DEATH_STARE;
  738. b.subtype = 0; //Gorgon
  739. break;
  740. case 'F':
  741. b.type = Bonus::FEAR; break;
  742. case 'g':
  743. b.type = Bonus::SPELL_DAMAGE_REDUCTION;
  744. b.subtype = -1; //all magic schools
  745. break;
  746. case 'P':
  747. b.type = Bonus::CASTS; break;
  748. case 'R':
  749. b.type = Bonus::ADDITIONAL_RETALIATION; break;
  750. case 'W':
  751. b.type = Bonus::MAGIC_RESISTANCE;
  752. b.subtype = 0; //otherwise creature window goes crazy
  753. break;
  754. case 'f': //on-off skill
  755. enable = true; //sometimes format is: 2 -> 0, 1 -> 1
  756. switch (mod[0])
  757. {
  758. case 'A':
  759. b.type = Bonus::ATTACKS_ALL_ADJACENT; break;
  760. case 'b':
  761. b.type = Bonus::RETURN_AFTER_STRIKE; break;
  762. case 'B':
  763. b.type = Bonus::TWO_HEX_ATTACK_BREATH; break;
  764. case 'c':
  765. b.type = Bonus::JOUSTING; break;
  766. case 'D':
  767. b.type = Bonus::ADDITIONAL_ATTACK; break;
  768. case 'f':
  769. b.type = Bonus::FEARLESS; break;
  770. case 'F':
  771. b.type = Bonus::FLYING; break;
  772. case 'm':
  773. b.type = Bonus::SELF_MORALE; break;
  774. case 'M':
  775. b.type = Bonus::NO_MORALE; break;
  776. case 'p': //Mind spells
  777. case 'P':
  778. b.type = Bonus::MIND_IMMUNITY; break;
  779. case 'r':
  780. b.type = Bonus::REBIRTH; //on/off? makes sense?
  781. b.subtype = 0;
  782. b.val = 20; //arbitrary value
  783. break;
  784. case 'R':
  785. b.type = Bonus::BLOCKS_RETALIATION; break;
  786. case 's':
  787. b.type = Bonus::FREE_SHOOTING; break;
  788. case 'u':
  789. b.type = Bonus::SPELL_RESISTANCE_AURA; break;
  790. case 'U':
  791. b.type = Bonus::UNDEAD; break;
  792. default:
  793. logGlobal->trace("Not parsed bonus %s %s", buf, mod);
  794. return;
  795. break;
  796. }
  797. break;
  798. case 'w': //specific spell immunities, enabled/disabled
  799. enable = true;
  800. switch (mod[0])
  801. {
  802. case 'B': //Blind
  803. b.type = Bonus::SPELL_IMMUNITY;
  804. b.subtype = SpellID::BLIND;
  805. b.additionalInfo = 0;//normal immunity
  806. break;
  807. case 'H': //Hypnotize
  808. b.type = Bonus::SPELL_IMMUNITY;
  809. b.subtype = SpellID::HYPNOTIZE;
  810. b.additionalInfo = 0;//normal immunity
  811. break;
  812. case 'I': //Implosion
  813. b.type = Bonus::SPELL_IMMUNITY;
  814. b.subtype = SpellID::IMPLOSION;
  815. b.additionalInfo = 0;//normal immunity
  816. break;
  817. case 'K': //Berserk
  818. b.type = Bonus::SPELL_IMMUNITY;
  819. b.subtype = SpellID::BERSERK;
  820. b.additionalInfo = 0;//normal immunity
  821. break;
  822. case 'M': //Meteor Shower
  823. b.type = Bonus::SPELL_IMMUNITY;
  824. b.subtype = SpellID::METEOR_SHOWER;
  825. b.additionalInfo = 0;//normal immunity
  826. break;
  827. case 'N': //dispell beneficial spells
  828. b.type = Bonus::SPELL_IMMUNITY;
  829. b.subtype = SpellID::DISPEL_HELPFUL_SPELLS;
  830. b.additionalInfo = 0;//normal immunity
  831. break;
  832. case 'R': //Armageddon
  833. b.type = Bonus::SPELL_IMMUNITY;
  834. b.subtype = SpellID::ARMAGEDDON;
  835. b.additionalInfo = 0;//normal immunity
  836. break;
  837. case 'S': //Slow
  838. b.type = Bonus::SPELL_IMMUNITY;
  839. b.subtype = SpellID::SLOW;
  840. b.additionalInfo = 0;//normal immunity
  841. break;
  842. case '6':
  843. case '7':
  844. case '8':
  845. case '9':
  846. b.type = Bonus::LEVEL_SPELL_IMMUNITY;
  847. b.val = std::atoi(mod.c_str()) - 5;
  848. break;
  849. case ':':
  850. b.type = Bonus::LEVEL_SPELL_IMMUNITY;
  851. b.val = GameConstants::SPELL_LEVELS; //in case someone adds higher level spells?
  852. break;
  853. case 'F':
  854. b.type = Bonus::FIRE_IMMUNITY;
  855. b.subtype = 1; //not positive
  856. break;
  857. case 'O':
  858. b.type = Bonus::FIRE_IMMUNITY;
  859. b.subtype = 2; //only direct damage
  860. break;
  861. case 'f':
  862. b.type = Bonus::FIRE_IMMUNITY;
  863. b.subtype = 0; //all
  864. break;
  865. case 'C':
  866. b.type = Bonus::WATER_IMMUNITY;
  867. b.subtype = 1; //not positive
  868. break;
  869. case 'W':
  870. b.type = Bonus::WATER_IMMUNITY;
  871. b.subtype = 2; //only direct damage
  872. break;
  873. case 'w':
  874. b.type = Bonus::WATER_IMMUNITY;
  875. b.subtype = 0; //all
  876. break;
  877. case 'E':
  878. b.type = Bonus::EARTH_IMMUNITY;
  879. b.subtype = 2; //only direct damage
  880. break;
  881. case 'e':
  882. b.type = Bonus::EARTH_IMMUNITY;
  883. b.subtype = 0; //all
  884. break;
  885. case 'A':
  886. b.type = Bonus::AIR_IMMUNITY;
  887. b.subtype = 2; //only direct damage
  888. break;
  889. case 'a':
  890. b.type = Bonus::AIR_IMMUNITY;
  891. b.subtype = 0; //all
  892. break;
  893. case 'D':
  894. b.type = Bonus::DIRECT_DAMAGE_IMMUNITY;
  895. break;
  896. case '0':
  897. b.type = Bonus::RECEPTIVE;
  898. break;
  899. case 'm':
  900. b.type = Bonus::MIND_IMMUNITY;
  901. break;
  902. default:
  903. logGlobal->trace("Not parsed bonus %s %s", buf, mod);
  904. return;
  905. }
  906. break;
  907. case 'i':
  908. enable = true;
  909. b.type = Bonus::NO_DISTANCE_PENALTY;
  910. break;
  911. case 'o':
  912. enable = true;
  913. b.type = Bonus::NO_WALL_PENALTY;
  914. break;
  915. case 'a':
  916. case 'c':
  917. case 'K':
  918. case 'k':
  919. b.type = Bonus::SPELL_AFTER_ATTACK;
  920. b.subtype = stringToNumber(mod);
  921. break;
  922. case 'h':
  923. b.type= Bonus::HATE;
  924. b.subtype = stringToNumber(mod);
  925. break;
  926. case 'p':
  927. case 'J':
  928. b.type = Bonus::SPELL_BEFORE_ATTACK;
  929. b.subtype = stringToNumber(mod);
  930. b.additionalInfo = 3; //always expert?
  931. break;
  932. case 'r':
  933. b.type = Bonus::HP_REGENERATION;
  934. b.val = stringToNumber(mod);
  935. break;
  936. case 's':
  937. b.type = Bonus::ENCHANTED;
  938. b.subtype = stringToNumber(mod);
  939. b.valType = Bonus::INDEPENDENT_MAX;
  940. break;
  941. default:
  942. logGlobal->trace("Not parsed bonus %s %s", buf, mod);
  943. return;
  944. break;
  945. }
  946. switch (mod[0])
  947. {
  948. case '+':
  949. case '=': //should we allow percent values to stack or pick highest?
  950. b.valType = Bonus::ADDITIVE_VALUE;
  951. break;
  952. }
  953. //limiters, range
  954. si32 lastVal, curVal, lastLev = 0;
  955. if (enable) //0 and 2 means non-active, 1 - active
  956. {
  957. if (b.type != Bonus::REBIRTH)
  958. b.val = 0; //on-off ability, no value specified
  959. curVal = static_cast<si32>(parser.readNumber());// 0 level is never active
  960. for (int i = 1; i < 11; ++i)
  961. {
  962. curVal = static_cast<si32>(parser.readNumber());
  963. if (curVal == 1)
  964. {
  965. b.limiter.reset (new RankRangeLimiter(i));
  966. bl.push_back(std::make_shared<Bonus>(b));
  967. break; //never turned off it seems
  968. }
  969. }
  970. }
  971. else
  972. {
  973. lastVal = static_cast<si32>(parser.readNumber());
  974. if (b.type == Bonus::HATE)
  975. lastVal *= 10; //odd fix
  976. //FIXME: value for zero level should be stored in our config files (independent of stack exp)
  977. for (int i = 1; i < 11; ++i)
  978. {
  979. curVal = static_cast<si32>(parser.readNumber());
  980. if (b.type == Bonus::HATE)
  981. curVal *= 10; //odd fix
  982. if (curVal > lastVal) //threshold, add new bonus
  983. {
  984. b.val = curVal - lastVal;
  985. lastVal = curVal;
  986. b.limiter.reset (new RankRangeLimiter(i));
  987. bl.push_back(std::make_shared<Bonus>(b));
  988. lastLev = i; //start new range from here, i = previous rank
  989. }
  990. else if (curVal < lastVal)
  991. {
  992. b.val = lastVal;
  993. b.limiter.reset (new RankRangeLimiter(lastLev, i));
  994. }
  995. }
  996. }
  997. }
  998. int CCreatureHandler::stringToNumber(std::string & s)
  999. {
  1000. boost::algorithm::replace_first(s,"#",""); //drop hash character
  1001. return std::atoi(s.c_str());
  1002. }
  1003. CCreatureHandler::~CCreatureHandler()
  1004. {
  1005. for(auto & creature : creatures)
  1006. creature.dellNull();
  1007. for(auto & p : skillRequirements)
  1008. p.first = nullptr;
  1009. }
  1010. CreatureID CCreatureHandler::pickRandomMonster(CRandomGenerator & rand, int tier) const
  1011. {
  1012. int r = 0;
  1013. if(tier == -1) //pick any allowed creature
  1014. {
  1015. do
  1016. {
  1017. r = (*RandomGeneratorUtil::nextItem(creatures, rand))->idNumber;
  1018. } while (VLC->creh->creatures[r] && VLC->creh->creatures[r]->special); // find first "not special" creature
  1019. }
  1020. else
  1021. {
  1022. assert(vstd::iswithin(tier, 1, 7));
  1023. std::vector<CreatureID> allowed;
  1024. for(const CBonusSystemNode *b : creaturesOfLevel[tier].getChildrenNodes())
  1025. {
  1026. assert(b->getNodeType() == CBonusSystemNode::CREATURE);
  1027. const CCreature * crea = dynamic_cast<const CCreature*>(b);
  1028. if(crea && !crea->special)
  1029. allowed.push_back(crea->idNumber);
  1030. }
  1031. if(!allowed.size())
  1032. {
  1033. logGlobal->warn("Cannot pick a random creature of tier %d!", tier);
  1034. return CreatureID::NONE;
  1035. }
  1036. return *RandomGeneratorUtil::nextItem(allowed, rand);
  1037. }
  1038. assert (r >= 0); //should always be, but it crashed
  1039. return CreatureID(r);
  1040. }
  1041. void CCreatureHandler::addBonusForTier(int tier, std::shared_ptr<Bonus> b)
  1042. {
  1043. assert(vstd::iswithin(tier, 1, 7));
  1044. creaturesOfLevel[tier].addNewBonus(b);
  1045. }
  1046. void CCreatureHandler::addBonusForAllCreatures(std::shared_ptr<Bonus> b)
  1047. {
  1048. allCreatures.addNewBonus(b);
  1049. }
  1050. void CCreatureHandler::buildBonusTreeForTiers()
  1051. {
  1052. for(CCreature *c : creatures)
  1053. {
  1054. if(vstd::isbetween(c->level, 0, ARRAY_COUNT(creaturesOfLevel)))
  1055. c->attachTo(&creaturesOfLevel[c->level]);
  1056. else
  1057. c->attachTo(&creaturesOfLevel[0]);
  1058. }
  1059. for(CBonusSystemNode &b : creaturesOfLevel)
  1060. b.attachTo(&allCreatures);
  1061. }
  1062. void CCreatureHandler::afterLoadFinalization()
  1063. {
  1064. }
  1065. void CCreatureHandler::deserializationFix()
  1066. {
  1067. buildBonusTreeForTiers();
  1068. }