CCreatureSet.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. /*
  2. * CCreatureSet.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 "CCreatureSet.h"
  12. #include "../CGHeroInstance.h"
  13. #include "../../CConfigHandler.h"
  14. #include "../../texts/CGeneralTextHandler.h"
  15. #include "../../serializer/JsonSerializeFormat.h"
  16. VCMI_LIB_NAMESPACE_BEGIN
  17. bool CreatureSlotComparer::operator()(const TPairCreatureSlot & lhs, const TPairCreatureSlot & rhs)
  18. {
  19. return lhs.first->getAIValue() < rhs.first->getAIValue(); // Descendant order sorting
  20. }
  21. const CStackInstance & CCreatureSet::operator[](const SlotID & slot) const
  22. {
  23. auto i = stacks.find(slot);
  24. if(i != stacks.end())
  25. return *i->second;
  26. else
  27. throw std::runtime_error("That slot is empty!");
  28. }
  29. const CCreature * CCreatureSet::getCreature(const SlotID & slot) const
  30. {
  31. auto i = stacks.find(slot);
  32. if(i != stacks.end())
  33. return i->second->getCreature();
  34. else
  35. return nullptr;
  36. }
  37. bool CCreatureSet::setCreature(SlotID slot, CreatureID type, TQuantity quantity) /*slots 0 to 6 */
  38. {
  39. if(!slot.validSlot())
  40. {
  41. logGlobal->error("Cannot set slot %d", slot.getNum());
  42. return false;
  43. }
  44. if(!quantity)
  45. {
  46. logGlobal->warn("Using set creature to delete stack?");
  47. eraseStack(slot);
  48. return true;
  49. }
  50. if(hasStackAtSlot(slot)) //remove old creature
  51. eraseStack(slot);
  52. auto * armyObj = getArmy();
  53. bool isHypotheticArmy = armyObj ? armyObj->isHypothetic() : false;
  54. putStack(slot, std::make_unique<CStackInstance>(armyObj ? armyObj->cb : nullptr, type, quantity, isHypotheticArmy));
  55. return true;
  56. }
  57. SlotID CCreatureSet::getSlotFor(const CreatureID & creature, ui32 slotsAmount) const /*returns -1 if no slot available */
  58. {
  59. return getSlotFor(creature.toCreature(), slotsAmount);
  60. }
  61. SlotID CCreatureSet::getSlotFor(const CCreature * c, ui32 slotsAmount) const
  62. {
  63. assert(c);
  64. for(const auto & elem : stacks)
  65. {
  66. if(elem.second->getType() == c)
  67. {
  68. return elem.first; //if there is already such creature we return its slot id
  69. }
  70. }
  71. return getFreeSlot(slotsAmount);
  72. }
  73. bool CCreatureSet::hasCreatureSlots(const CCreature * c, const SlotID & exclude) const
  74. {
  75. assert(c);
  76. for(const auto & elem : stacks) // elem is const
  77. {
  78. if(elem.first == exclude) // Check slot
  79. continue;
  80. if(!elem.second || !elem.second->getType()) // Check creature
  81. continue;
  82. if(elem.second->getType() == c)
  83. return true;
  84. }
  85. return false;
  86. }
  87. std::vector<SlotID> CCreatureSet::getCreatureSlots(const CCreature * c, const SlotID & exclude, TQuantity ignoreAmount) const
  88. {
  89. assert(c);
  90. std::vector<SlotID> result;
  91. for(const auto & elem : stacks)
  92. {
  93. if(elem.first == exclude)
  94. continue;
  95. if(!elem.second || !elem.second->getType() || elem.second->getType() != c)
  96. continue;
  97. if(elem.second->getCount() == ignoreAmount || elem.second->getCount() < 1)
  98. continue;
  99. result.push_back(elem.first);
  100. }
  101. return result;
  102. }
  103. bool CCreatureSet::isCreatureBalanced(const CCreature * c, TQuantity ignoreAmount) const
  104. {
  105. assert(c);
  106. TQuantity max = 0;
  107. auto min = std::numeric_limits<TQuantity>::max();
  108. for(const auto & elem : stacks)
  109. {
  110. if(!elem.second || !elem.second->getType() || elem.second->getType() != c)
  111. continue;
  112. const auto count = elem.second->getCount();
  113. if(count == ignoreAmount || count < 1)
  114. continue;
  115. if(count > max)
  116. max = count;
  117. if(count < min)
  118. min = count;
  119. if(max - min > 1)
  120. return false;
  121. }
  122. return true;
  123. }
  124. SlotID CCreatureSet::getFreeSlot(ui32 slotsAmount) const
  125. {
  126. for(ui32 i = 0; i < slotsAmount; i++)
  127. {
  128. if(!vstd::contains(stacks, SlotID(i)))
  129. {
  130. return SlotID(i); //return first free slot
  131. }
  132. }
  133. return SlotID(); //no slot available
  134. }
  135. std::vector<SlotID> CCreatureSet::getFreeSlots(ui32 slotsAmount) const
  136. {
  137. std::vector<SlotID> freeSlots;
  138. for(ui32 i = 0; i < slotsAmount; i++)
  139. {
  140. auto slot = SlotID(i);
  141. if(!vstd::contains(stacks, slot))
  142. freeSlots.push_back(slot);
  143. }
  144. return freeSlots;
  145. }
  146. TMapCreatureSlot CCreatureSet::getCreatureMap() const
  147. {
  148. TMapCreatureSlot creatureMap;
  149. TMapCreatureSlot::key_compare keyComp = creatureMap.key_comp();
  150. // https://stackoverflow.com/questions/97050/stdmap-insert-or-stdmap-find
  151. // https://www.cplusplus.com/reference/map/map/key_comp/
  152. for(const auto & pair : stacks)
  153. {
  154. const auto * creature = pair.second->getCreature();
  155. auto slot = pair.first;
  156. auto lb = creatureMap.lower_bound(creature);
  157. if(lb != creatureMap.end() && !(keyComp(creature, lb->first)))
  158. continue;
  159. creatureMap.insert(lb, TMapCreatureSlot::value_type(creature, slot));
  160. }
  161. return creatureMap;
  162. }
  163. TCreatureQueue CCreatureSet::getCreatureQueue(const SlotID & exclude) const
  164. {
  165. TCreatureQueue creatureQueue;
  166. for(const auto & pair : stacks)
  167. {
  168. if(pair.first == exclude)
  169. continue;
  170. creatureQueue.push(std::make_pair(pair.second->getCreature(), pair.first));
  171. }
  172. return creatureQueue;
  173. }
  174. TQuantity CCreatureSet::getStackCount(const SlotID & slot) const
  175. {
  176. if(!hasStackAtSlot(slot))
  177. return 0;
  178. return stacks.at(slot)->getCount();
  179. }
  180. TExpType CCreatureSet::getStackTotalExperience(const SlotID & slot) const
  181. {
  182. return stacks.at(slot)->getTotalExperience();
  183. }
  184. TExpType CCreatureSet::getStackAverageExperience(const SlotID & slot) const
  185. {
  186. return stacks.at(slot)->getAverageExperience();
  187. }
  188. bool CCreatureSet::mergeableStacks(std::pair<SlotID, SlotID> & out, const SlotID & preferable) const /*looks for two same stacks, returns slot positions */
  189. {
  190. //try to match creature to our preferred stack
  191. if(preferable.validSlot() && vstd::contains(stacks, preferable))
  192. {
  193. const CCreature * cr = stacks.find(preferable)->second->getCreature();
  194. for(const auto & elem : stacks)
  195. {
  196. if(cr == elem.second->getType() && elem.first != preferable)
  197. {
  198. out.first = preferable;
  199. out.second = elem.first;
  200. return true;
  201. }
  202. }
  203. }
  204. for(const auto & stack : stacks)
  205. {
  206. for(const auto & elem : stacks)
  207. {
  208. if(stack.second->getType() == elem.second->getType() && stack.first != elem.first)
  209. {
  210. out.first = stack.first;
  211. out.second = elem.first;
  212. return true;
  213. }
  214. }
  215. }
  216. return false;
  217. }
  218. void CCreatureSet::addToSlot(const SlotID & slot, const CreatureID & cre, TQuantity count, bool allowMerging)
  219. {
  220. const CCreature * c = cre.toCreature();
  221. if(!hasStackAtSlot(slot))
  222. {
  223. setCreature(slot, cre, count);
  224. }
  225. else if(getCreature(slot) == c && allowMerging) //that slot was empty or contained same type creature
  226. {
  227. setStackCount(slot, getStackCount(slot) + count);
  228. }
  229. else
  230. {
  231. logGlobal->error("Failed adding to slot!");
  232. }
  233. }
  234. void CCreatureSet::addToSlot(const SlotID & slot, std::unique_ptr<CStackInstance> stack, bool allowMerging)
  235. {
  236. assert(stack->valid(true));
  237. if(!hasStackAtSlot(slot))
  238. {
  239. putStack(slot, std::move(stack));
  240. }
  241. else if(allowMerging && stack->getType() == getCreature(slot))
  242. {
  243. joinStack(slot, std::move(stack));
  244. }
  245. else
  246. {
  247. logGlobal->error("Cannot add to slot %d stack %s", slot.getNum(), stack->nodeName());
  248. }
  249. }
  250. bool CCreatureSet::validTypes(bool allowUnrandomized) const
  251. {
  252. for(const auto & elem : stacks)
  253. {
  254. if(!elem.second->valid(allowUnrandomized))
  255. return false;
  256. }
  257. return true;
  258. }
  259. bool CCreatureSet::slotEmpty(const SlotID & slot) const
  260. {
  261. return !hasStackAtSlot(slot);
  262. }
  263. bool CCreatureSet::needsLastStack() const
  264. {
  265. return false;
  266. }
  267. ui64 CCreatureSet::getArmyStrength(int fortLevel) const
  268. {
  269. ui64 ret = 0;
  270. for(const auto & elem : stacks)
  271. {
  272. ui64 powerToAdd = elem.second->getPower();
  273. if(fortLevel > 0 && !elem.second->hasBonusOfType(BonusType::FLYING))
  274. {
  275. powerToAdd /= fortLevel;
  276. if(!elem.second->hasBonusOfType(BonusType::SHOOTER))
  277. powerToAdd /= fortLevel;
  278. }
  279. ret += powerToAdd;
  280. }
  281. return ret;
  282. }
  283. ui64 CCreatureSet::getArmyCost() const
  284. {
  285. ui64 ret = 0;
  286. for(const auto & elem : stacks)
  287. ret += elem.second->getMarketValue();
  288. return ret;
  289. }
  290. ui64 CCreatureSet::getPower(const SlotID & slot) const
  291. {
  292. return getStack(slot).getPower();
  293. }
  294. std::string CCreatureSet::getRoughAmount(const SlotID & slot, int mode) const
  295. {
  296. /// Mode represent return string format
  297. /// "Pack" - 0, "A pack of" - 1, "a pack of" - 2
  298. CCreature::CreatureQuantityId quantity = CCreature::getQuantityID(getStackCount(slot));
  299. if(static_cast<int>(quantity) != 0)
  300. {
  301. if(settings["gameTweaks"]["numericCreaturesQuantities"].Bool())
  302. return CCreature::getQuantityRangeStringForId(quantity);
  303. return LIBRARY->generaltexth->arraytxt[(174 + mode) + 3 * static_cast<int>(quantity)];
  304. }
  305. return "";
  306. }
  307. std::string CCreatureSet::getArmyDescription() const
  308. {
  309. std::string text;
  310. std::vector<std::string> guards;
  311. for(const auto & elem : stacks)
  312. {
  313. auto str = boost::str(boost::format("%s %s") % getRoughAmount(elem.first, 2) % getCreature(elem.first)->getNamePluralTranslated());
  314. guards.push_back(str);
  315. }
  316. if(!guards.empty())
  317. {
  318. for(int i = 0; i < guards.size(); i++)
  319. {
  320. text += guards[i];
  321. if(i + 2 < guards.size())
  322. text += ", ";
  323. else if(i + 2 == guards.size())
  324. text += LIBRARY->generaltexth->allTexts[237];
  325. }
  326. }
  327. return text;
  328. }
  329. int CCreatureSet::stacksCount() const
  330. {
  331. return static_cast<int>(stacks.size());
  332. }
  333. void CCreatureSet::setFormation(EArmyFormation mode)
  334. {
  335. formation = mode;
  336. }
  337. void CCreatureSet::setStackCount(const SlotID & slot, TQuantity count)
  338. {
  339. stacks.at(slot)->setCount(count);
  340. armyChanged();
  341. }
  342. void CCreatureSet::giveAverageStackExperience(TExpType exp)
  343. {
  344. for(const auto & stack : stacks)
  345. {
  346. stack.second->giveAverageStackExperience(exp);
  347. stack.second->nodeHasChanged();
  348. }
  349. }
  350. void CCreatureSet::giveTotalStackExperience(const SlotID & slot, TExpType exp)
  351. {
  352. assert(hasStackAtSlot(slot));
  353. stacks[slot]->giveTotalStackExperience(exp);
  354. stacks[slot]->nodeHasChanged();
  355. }
  356. void CCreatureSet::clearSlots()
  357. {
  358. while(!stacks.empty())
  359. {
  360. eraseStack(stacks.begin()->first);
  361. }
  362. }
  363. const CStackInstance & CCreatureSet::getStack(const SlotID & slot) const
  364. {
  365. assert(hasStackAtSlot(slot));
  366. return *getStackPtr(slot);
  367. }
  368. CStackInstance * CCreatureSet::getStackPtr(const SlotID & slot) const
  369. {
  370. if(hasStackAtSlot(slot))
  371. return stacks.find(slot)->second.get();
  372. else
  373. return nullptr;
  374. }
  375. void CCreatureSet::eraseStack(const SlotID & slot)
  376. {
  377. assert(hasStackAtSlot(slot));
  378. detachStack(slot);
  379. }
  380. bool CCreatureSet::contains(const CStackInstance * stack) const
  381. {
  382. if(!stack)
  383. return false;
  384. for(const auto & elem : stacks)
  385. if(elem.second.get() == stack)
  386. return true;
  387. return false;
  388. }
  389. SlotID CCreatureSet::findStack(const CStackInstance * stack) const
  390. {
  391. const auto * h = dynamic_cast<const CGHeroInstance *>(this);
  392. if(h && h->getCommander() == stack)
  393. return SlotID::COMMANDER_SLOT_PLACEHOLDER;
  394. if(!stack)
  395. return SlotID();
  396. for(const auto & elem : stacks)
  397. if(elem.second.get() == stack)
  398. return elem.first;
  399. return SlotID();
  400. }
  401. void CCreatureSet::putStack(const SlotID & slot, std::unique_ptr<CStackInstance> stack)
  402. {
  403. assert(slot.getNum() < GameConstants::ARMY_SIZE);
  404. assert(!hasStackAtSlot(slot));
  405. stacks[slot] = std::move(stack);
  406. stacks[slot]->setArmy(getArmy());
  407. armyChanged();
  408. }
  409. void CCreatureSet::joinStack(const SlotID & slot, std::unique_ptr<CStackInstance> stack)
  410. {
  411. [[maybe_unused]] const CCreature * c = getCreature(slot);
  412. assert(c == stack->getType());
  413. assert(c);
  414. //TODO move stuff
  415. changeStackCount(slot, stack->getCount());
  416. giveTotalStackExperience(slot, stack->getTotalExperience());
  417. }
  418. std::unique_ptr<CStackInstance> CCreatureSet::splitStack(const SlotID & slot, TQuantity toSplit)
  419. {
  420. auto & currentStack = stacks.at(slot);
  421. assert(currentStack->getCount() > toSplit);
  422. TExpType experienceBefore = currentStack->getTotalExperience();
  423. currentStack->setCount(currentStack->getCount() - toSplit);
  424. TExpType experienceAfter = currentStack->getTotalExperience();
  425. auto newStack = std::make_unique<CStackInstance>(currentStack->cb, currentStack->getCreatureID(), toSplit);
  426. newStack->giveTotalStackExperience(experienceBefore - experienceAfter);
  427. return newStack;
  428. }
  429. void CCreatureSet::changeStackCount(const SlotID & slot, TQuantity toAdd)
  430. {
  431. setStackCount(slot, getStackCount(slot) + toAdd);
  432. }
  433. CCreatureSet::~CCreatureSet() = default;
  434. void CCreatureSet::setToArmy(CSimpleArmy & src)
  435. {
  436. clearSlots();
  437. while(src)
  438. {
  439. auto i = src.army.begin();
  440. putStack(i->first, std::make_unique<CStackInstance>(getArmy()->cb, i->second.first, i->second.second));
  441. src.army.erase(i);
  442. }
  443. }
  444. std::unique_ptr<CStackInstance> CCreatureSet::detachStack(const SlotID & slot)
  445. {
  446. assert(hasStackAtSlot(slot));
  447. std::unique_ptr<CStackInstance> ret = std::move(stacks[slot]);
  448. if(ret)
  449. {
  450. ret->setArmy(nullptr); //detaches from current armyobj
  451. assert(!ret->getArmy()); //we failed detaching?
  452. }
  453. stacks.erase(slot);
  454. armyChanged();
  455. return ret;
  456. }
  457. void CCreatureSet::setStackType(const SlotID & slot, const CreatureID & type)
  458. {
  459. assert(hasStackAtSlot(slot));
  460. stacks[slot]->setType(type);
  461. armyChanged();
  462. }
  463. bool CCreatureSet::canBeMergedWith(const CCreatureSet & cs, bool allowMergingStacks) const
  464. {
  465. if(!allowMergingStacks)
  466. {
  467. int freeSlots = stacksCount() - GameConstants::ARMY_SIZE;
  468. std::set<const CCreature *> cresToAdd;
  469. for(const auto & elem : cs.stacks)
  470. {
  471. SlotID dest = getSlotFor(elem.second->getCreature());
  472. if(!dest.validSlot() || hasStackAtSlot(dest))
  473. cresToAdd.insert(elem.second->getCreature());
  474. }
  475. return cresToAdd.size() <= freeSlots;
  476. }
  477. else
  478. {
  479. CCreatureSet cres;
  480. SlotID j;
  481. //get types of creatures that need their own slot
  482. for(const auto & elem : cs.stacks)
  483. if((j = cres.getSlotFor(elem.second->getCreature())).validSlot())
  484. cres.addToSlot(j, elem.second->getId(), 1, true); //merge if possible
  485. //cres.addToSlot(elem.first, elem.second->type->getId(), 1, true);
  486. for(const auto & elem : stacks)
  487. {
  488. if((j = cres.getSlotFor(elem.second->getCreature())).validSlot())
  489. cres.addToSlot(j, elem.second->getId(), 1, true); //merge if possible
  490. else
  491. return false; //no place found
  492. }
  493. return true; //all stacks found their slots
  494. }
  495. }
  496. bool CCreatureSet::hasUnits(const std::vector<CStackBasicDescriptor> & units, bool requireLastStack) const
  497. {
  498. bool foundExtraCreatures = false;
  499. int testedSlots = 0;
  500. for(const auto & reqStack : units)
  501. {
  502. size_t count = 0;
  503. for(const auto & slot : Slots())
  504. {
  505. const auto & heroStack = slot.second;
  506. if(heroStack->getType() == reqStack.getType())
  507. {
  508. count += heroStack->getCount();
  509. testedSlots += 1;
  510. }
  511. }
  512. if(count > reqStack.getCount())
  513. foundExtraCreatures = true;
  514. if(count < reqStack.getCount()) //not enough creatures of this kind
  515. return false;
  516. }
  517. if(requireLastStack)
  518. {
  519. if(!foundExtraCreatures && testedSlots >= Slots().size())
  520. return false;
  521. }
  522. return true;
  523. }
  524. bool CCreatureSet::hasStackAtSlot(const SlotID & slot) const
  525. {
  526. return vstd::contains(stacks, slot);
  527. }
  528. void CCreatureSet::armyChanged() {}
  529. void CCreatureSet::serializeJson(JsonSerializeFormat & handler, const std::string & armyFieldName, const std::optional<int> fixedSize)
  530. {
  531. if(handler.saving && stacks.empty())
  532. return;
  533. handler.serializeEnum("formation", formation, NArmyFormation::names);
  534. auto a = handler.enterArray(armyFieldName);
  535. if(handler.saving)
  536. {
  537. size_t sz = 0;
  538. for(const auto & p : stacks)
  539. vstd::amax(sz, p.first.getNum() + 1);
  540. if(fixedSize)
  541. vstd::amax(sz, fixedSize.value());
  542. a.resize(sz, JsonNode::JsonType::DATA_STRUCT);
  543. for(const auto & p : stacks)
  544. {
  545. auto s = a.enterStruct(p.first.getNum());
  546. p.second->serializeJson(handler);
  547. }
  548. }
  549. else
  550. {
  551. for(size_t idx = 0; idx < a.size(); idx++)
  552. {
  553. auto s = a.enterStruct(idx);
  554. TQuantity amount = 0;
  555. handler.serializeInt("amount", amount);
  556. if(amount > 0)
  557. {
  558. auto newStack = std::make_unique<CStackInstance>(getArmy()->cb);
  559. newStack->serializeJson(handler);
  560. putStack(SlotID(static_cast<si32>(idx)), std::move(newStack));
  561. }
  562. }
  563. }
  564. }
  565. VCMI_LIB_NAMESPACE_END