CCreatureSet.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  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. std::queue<SlotID> CCreatureSet::getFreeSlotsQueue(ui32 slotsAmount) const
  147. {
  148. std::queue<SlotID> freeSlots;
  149. for (ui32 i = 0; i < slotsAmount; i++)
  150. {
  151. auto slot = SlotID(i);
  152. if(!vstd::contains(stacks, slot))
  153. freeSlots.push(slot);
  154. }
  155. return freeSlots;
  156. }
  157. TMapCreatureSlot CCreatureSet::getCreatureMap() const
  158. {
  159. TMapCreatureSlot creatureMap;
  160. TMapCreatureSlot::key_compare keyComp = creatureMap.key_comp();
  161. // https://stackoverflow.com/questions/97050/stdmap-insert-or-stdmap-find
  162. // https://www.cplusplus.com/reference/map/map/key_comp/
  163. for(const auto & pair : stacks)
  164. {
  165. const auto * creature = pair.second->getCreature();
  166. auto slot = pair.first;
  167. auto lb = creatureMap.lower_bound(creature);
  168. if(lb != creatureMap.end() && !(keyComp(creature, lb->first)))
  169. continue;
  170. creatureMap.insert(lb, TMapCreatureSlot::value_type(creature, slot));
  171. }
  172. return creatureMap;
  173. }
  174. TCreatureQueue CCreatureSet::getCreatureQueue(const SlotID & exclude) const
  175. {
  176. TCreatureQueue creatureQueue;
  177. for(const auto & pair : stacks)
  178. {
  179. if(pair.first == exclude)
  180. continue;
  181. creatureQueue.push(std::make_pair(pair.second->getCreature(), pair.first));
  182. }
  183. return creatureQueue;
  184. }
  185. TQuantity CCreatureSet::getStackCount(const SlotID & slot) const
  186. {
  187. if (!hasStackAtSlot(slot))
  188. return 0;
  189. return stacks.at(slot)->getCount();
  190. }
  191. TExpType CCreatureSet::getStackTotalExperience(const SlotID & slot) const
  192. {
  193. return stacks.at(slot)->getTotalExperience();
  194. }
  195. TExpType CCreatureSet::getStackAverageExperience(const SlotID & slot) const
  196. {
  197. return stacks.at(slot)->getAverageExperience();
  198. }
  199. bool CCreatureSet::mergeableStacks(std::pair<SlotID, SlotID> & out, const SlotID & preferable) const /*looks for two same stacks, returns slot positions */
  200. {
  201. //try to match creature to our preferred stack
  202. if(preferable.validSlot() && vstd::contains(stacks, preferable))
  203. {
  204. const CCreature *cr = stacks.find(preferable)->second->getCreature();
  205. for(const auto & elem : stacks)
  206. {
  207. if(cr == elem.second->getType() && elem.first != preferable)
  208. {
  209. out.first = preferable;
  210. out.second = elem.first;
  211. return true;
  212. }
  213. }
  214. }
  215. for(const auto & stack : stacks)
  216. {
  217. for(const auto & elem : stacks)
  218. {
  219. if(stack.second->getType() == elem.second->getType() && stack.first != elem.first)
  220. {
  221. out.first = stack.first;
  222. out.second = elem.first;
  223. return true;
  224. }
  225. }
  226. }
  227. return false;
  228. }
  229. void CCreatureSet::addToSlot(const SlotID & slot, const CreatureID & cre, TQuantity count, bool allowMerging)
  230. {
  231. const CCreature *c = cre.toCreature();
  232. if(!hasStackAtSlot(slot))
  233. {
  234. setCreature(slot, cre, count);
  235. }
  236. else if(getCreature(slot) == c && allowMerging) //that slot was empty or contained same type creature
  237. {
  238. setStackCount(slot, getStackCount(slot) + count);
  239. }
  240. else
  241. {
  242. logGlobal->error("Failed adding to slot!");
  243. }
  244. }
  245. void CCreatureSet::addToSlot(const SlotID & slot, std::unique_ptr<CStackInstance> stack, bool allowMerging)
  246. {
  247. assert(stack->valid(true));
  248. if(!hasStackAtSlot(slot))
  249. {
  250. putStack(slot, std::move(stack));
  251. }
  252. else if(allowMerging && stack->getType() == getCreature(slot))
  253. {
  254. joinStack(slot, std::move(stack));
  255. }
  256. else
  257. {
  258. logGlobal->error("Cannot add to slot %d stack %s", slot.getNum(), stack->nodeName());
  259. }
  260. }
  261. bool CCreatureSet::validTypes(bool allowUnrandomized) const
  262. {
  263. for(const auto & elem : stacks)
  264. {
  265. if(!elem.second->valid(allowUnrandomized))
  266. return false;
  267. }
  268. return true;
  269. }
  270. bool CCreatureSet::slotEmpty(const SlotID & slot) const
  271. {
  272. return !hasStackAtSlot(slot);
  273. }
  274. bool CCreatureSet::needsLastStack() const
  275. {
  276. return false;
  277. }
  278. ui64 CCreatureSet::getArmyStrength(int fortLevel) const
  279. {
  280. ui64 ret = 0;
  281. for (const auto& elem : stacks)
  282. {
  283. ui64 powerToAdd = elem.second->getPower();
  284. if (fortLevel > 0)
  285. {
  286. if (!elem.second->hasBonusOfType(BonusType::FLYING))
  287. {
  288. powerToAdd /= fortLevel;
  289. if (!elem.second->hasBonusOfType(BonusType::SHOOTER))
  290. powerToAdd /= fortLevel;
  291. }
  292. }
  293. ret += powerToAdd;
  294. }
  295. return ret;
  296. }
  297. ui64 CCreatureSet::getArmyCost() const
  298. {
  299. ui64 ret = 0;
  300. for (const auto& elem : stacks)
  301. ret += elem.second->getMarketValue();
  302. return ret;
  303. }
  304. ui64 CCreatureSet::getPower(const SlotID & slot) const
  305. {
  306. return getStack(slot).getPower();
  307. }
  308. std::string CCreatureSet::getRoughAmount(const SlotID & slot, int mode) const
  309. {
  310. /// Mode represent return string format
  311. /// "Pack" - 0, "A pack of" - 1, "a pack of" - 2
  312. CCreature::CreatureQuantityId quantity = CCreature::getQuantityID(getStackCount(slot));
  313. if((int)quantity)
  314. {
  315. if(settings["gameTweaks"]["numericCreaturesQuantities"].Bool())
  316. return CCreature::getQuantityRangeStringForId(quantity);
  317. return LIBRARY->generaltexth->arraytxt[(174 + mode) + 3*(int)quantity];
  318. }
  319. return "";
  320. }
  321. std::string CCreatureSet::getArmyDescription() const
  322. {
  323. std::string text;
  324. std::vector<std::string> guards;
  325. for(const auto & elem : stacks)
  326. {
  327. auto str = boost::str(boost::format("%s %s") % getRoughAmount(elem.first, 2) % getCreature(elem.first)->getNamePluralTranslated());
  328. guards.push_back(str);
  329. }
  330. if(!guards.empty())
  331. {
  332. for(int i = 0; i < guards.size(); i++)
  333. {
  334. text += guards[i];
  335. if(i + 2 < guards.size())
  336. text += ", ";
  337. else if(i + 2 == guards.size())
  338. text += LIBRARY->generaltexth->allTexts[237];
  339. }
  340. }
  341. return text;
  342. }
  343. int CCreatureSet::stacksCount() const
  344. {
  345. return static_cast<int>(stacks.size());
  346. }
  347. void CCreatureSet::setFormation(EArmyFormation mode)
  348. {
  349. formation = mode;
  350. }
  351. void CCreatureSet::setStackCount(const SlotID & slot, TQuantity count)
  352. {
  353. stacks.at(slot)->setCount(count);
  354. armyChanged();
  355. }
  356. void CCreatureSet::giveAverageStackExperience(TExpType exp)
  357. {
  358. for(const auto & stack : stacks)
  359. {
  360. stack.second->giveAverageStackExperience(exp);
  361. stack.second->nodeHasChanged();
  362. }
  363. }
  364. void CCreatureSet::giveTotalStackExperience(const SlotID & slot, TExpType exp)
  365. {
  366. assert(hasStackAtSlot(slot));
  367. stacks[slot]->giveTotalStackExperience(exp);
  368. stacks[slot]->nodeHasChanged();
  369. }
  370. void CCreatureSet::clearSlots()
  371. {
  372. while(!stacks.empty())
  373. {
  374. eraseStack(stacks.begin()->first);
  375. }
  376. }
  377. const CStackInstance & CCreatureSet::getStack(const SlotID & slot) const
  378. {
  379. assert(hasStackAtSlot(slot));
  380. return *getStackPtr(slot);
  381. }
  382. CStackInstance * CCreatureSet::getStackPtr(const SlotID & slot) const
  383. {
  384. if(hasStackAtSlot(slot))
  385. return stacks.find(slot)->second.get();
  386. else return nullptr;
  387. }
  388. void CCreatureSet::eraseStack(const SlotID & slot)
  389. {
  390. assert(hasStackAtSlot(slot));
  391. detachStack(slot);
  392. }
  393. bool CCreatureSet::contains(const CStackInstance *stack) const
  394. {
  395. if(!stack)
  396. return false;
  397. for(const auto & elem : stacks)
  398. if(elem.second.get() == stack)
  399. return true;
  400. return false;
  401. }
  402. SlotID CCreatureSet::findStack(const CStackInstance *stack) const
  403. {
  404. const auto * h = dynamic_cast<const CGHeroInstance *>(this);
  405. if (h && h->getCommander() == stack)
  406. return SlotID::COMMANDER_SLOT_PLACEHOLDER;
  407. if(!stack)
  408. return SlotID();
  409. for(const auto & elem : stacks)
  410. if(elem.second.get() == stack)
  411. return elem.first;
  412. return SlotID();
  413. }
  414. void CCreatureSet::putStack(const SlotID & slot, std::unique_ptr<CStackInstance> stack)
  415. {
  416. assert(slot.getNum() < GameConstants::ARMY_SIZE);
  417. assert(!hasStackAtSlot(slot));
  418. stacks[slot] = std::move(stack);
  419. stacks[slot]->setArmy(getArmy());
  420. armyChanged();
  421. }
  422. void CCreatureSet::joinStack(const SlotID & slot, std::unique_ptr<CStackInstance> stack)
  423. {
  424. [[maybe_unused]] const CCreature *c = getCreature(slot);
  425. assert(c == stack->getType());
  426. assert(c);
  427. //TODO move stuff
  428. changeStackCount(slot, stack->getCount());
  429. giveTotalStackExperience(slot, stack->getTotalExperience());
  430. }
  431. std::unique_ptr<CStackInstance> CCreatureSet::splitStack(const SlotID & slot, TQuantity toSplit)
  432. {
  433. auto & currentStack = stacks.at(slot);
  434. assert(currentStack->getCount() > toSplit);
  435. TExpType experienceBefore = currentStack->getTotalExperience();
  436. currentStack->setCount(currentStack->getCount() - toSplit);
  437. TExpType experienceAfter = currentStack->getTotalExperience();
  438. auto newStack = std::make_unique<CStackInstance>(currentStack->cb, currentStack->getCreatureID(), toSplit);
  439. newStack->giveTotalStackExperience(experienceBefore - experienceAfter);
  440. return newStack;
  441. }
  442. void CCreatureSet::changeStackCount(const SlotID & slot, TQuantity toAdd)
  443. {
  444. setStackCount(slot, getStackCount(slot) + toAdd);
  445. }
  446. CCreatureSet::~CCreatureSet() = default;
  447. void CCreatureSet::setToArmy(CSimpleArmy &src)
  448. {
  449. clearSlots();
  450. while(src)
  451. {
  452. auto i = src.army.begin();
  453. putStack(i->first, std::make_unique<CStackInstance>(getArmy()->cb, i->second.first, i->second.second));
  454. src.army.erase(i);
  455. }
  456. }
  457. std::unique_ptr<CStackInstance> CCreatureSet::detachStack(const SlotID & slot)
  458. {
  459. assert(hasStackAtSlot(slot));
  460. std::unique_ptr<CStackInstance> ret = std::move(stacks[slot]);
  461. //if(CArmedInstance *armedObj = castToArmyObj())
  462. if(ret)
  463. {
  464. ret->setArmy(nullptr); //detaches from current armyobj
  465. assert(!ret->getArmy()); //we failed detaching?
  466. }
  467. stacks.erase(slot);
  468. armyChanged();
  469. return ret;
  470. }
  471. void CCreatureSet::setStackType(const SlotID & slot, const CreatureID & type)
  472. {
  473. assert(hasStackAtSlot(slot));
  474. stacks[slot]->setType(type);
  475. armyChanged();
  476. }
  477. bool CCreatureSet::canBeMergedWith(const CCreatureSet &cs, bool allowMergingStacks) const
  478. {
  479. if(!allowMergingStacks)
  480. {
  481. int freeSlots = stacksCount() - GameConstants::ARMY_SIZE;
  482. std::set<const CCreature*> cresToAdd;
  483. for(const auto & elem : cs.stacks)
  484. {
  485. SlotID dest = getSlotFor(elem.second->getCreature());
  486. if(!dest.validSlot() || hasStackAtSlot(dest))
  487. cresToAdd.insert(elem.second->getCreature());
  488. }
  489. return cresToAdd.size() <= freeSlots;
  490. }
  491. else
  492. {
  493. CCreatureSet cres;
  494. SlotID j;
  495. //get types of creatures that need their own slot
  496. for(const auto & elem : cs.stacks)
  497. if ((j = cres.getSlotFor(elem.second->getCreature())).validSlot())
  498. cres.addToSlot(j, elem.second->getId(), 1, true); //merge if possible
  499. //cres.addToSlot(elem.first, elem.second->type->getId(), 1, true);
  500. for(const auto & elem : stacks)
  501. {
  502. if ((j = cres.getSlotFor(elem.second->getCreature())).validSlot())
  503. cres.addToSlot(j, elem.second->getId(), 1, true); //merge if possible
  504. else
  505. return false; //no place found
  506. }
  507. return true; //all stacks found their slots
  508. }
  509. }
  510. bool CCreatureSet::hasUnits(const std::vector<CStackBasicDescriptor> & units, bool requireLastStack) const
  511. {
  512. bool foundExtraCreatures = false;
  513. int testedSlots = 0;
  514. for(const auto & reqStack : units)
  515. {
  516. size_t count = 0;
  517. for(const auto & slot : Slots())
  518. {
  519. const auto & heroStack = slot.second;
  520. if (heroStack->getType() == reqStack.getType())
  521. {
  522. count += heroStack->getCount();
  523. testedSlots += 1;
  524. }
  525. }
  526. if (count > reqStack.getCount())
  527. foundExtraCreatures = true;
  528. if (count < reqStack.getCount()) //not enough creatures of this kind
  529. return false;
  530. }
  531. if (requireLastStack)
  532. {
  533. if (!foundExtraCreatures && testedSlots >= Slots().size())
  534. return false;
  535. }
  536. return true;
  537. }
  538. bool CCreatureSet::hasStackAtSlot(const SlotID & slot) const
  539. {
  540. return vstd::contains(stacks, slot);
  541. }
  542. CCreatureSet & CCreatureSet::operator=(const CCreatureSet&cs)
  543. {
  544. assert(0);
  545. return *this;
  546. }
  547. void CCreatureSet::armyChanged()
  548. {
  549. }
  550. void CCreatureSet::serializeJson(JsonSerializeFormat & handler, const std::string & armyFieldName, const std::optional<int> fixedSize)
  551. {
  552. if(handler.saving && stacks.empty())
  553. return;
  554. handler.serializeEnum("formation", formation, NArmyFormation::names);
  555. auto a = handler.enterArray(armyFieldName);
  556. if(handler.saving)
  557. {
  558. size_t sz = 0;
  559. for(const auto & p : stacks)
  560. vstd::amax(sz, p.first.getNum()+1);
  561. if(fixedSize)
  562. vstd::amax(sz, fixedSize.value());
  563. a.resize(sz, JsonNode::JsonType::DATA_STRUCT);
  564. for(const auto & p : stacks)
  565. {
  566. auto s = a.enterStruct(p.first.getNum());
  567. p.second->serializeJson(handler);
  568. }
  569. }
  570. else
  571. {
  572. for(size_t idx = 0; idx < a.size(); idx++)
  573. {
  574. auto s = a.enterStruct(idx);
  575. TQuantity amount = 0;
  576. handler.serializeInt("amount", amount);
  577. if(amount > 0)
  578. {
  579. auto newStack = std::make_unique<CStackInstance>(getArmy()->cb);
  580. newStack->serializeJson(handler);
  581. putStack(SlotID(static_cast<si32>(idx)), std::move(newStack));
  582. }
  583. }
  584. }
  585. }
  586. VCMI_LIB_NAMESPACE_END