ArmyManager.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. /*
  2. * BuildingManager.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 "ArmyManager.h"
  12. #include "../Engine/Nullkiller.h"
  13. #include "../../../CCallback.h"
  14. #include "../../../lib/mapObjects/MapObjects.h"
  15. #include "../../../lib/mapping/CMapDefines.h"
  16. #include "../../../lib/IGameSettings.h"
  17. #include "../../../lib/GameConstants.h"
  18. #include "../../../lib/TerrainHandler.h"
  19. namespace NKAI
  20. {
  21. class StackUpgradeInfo
  22. {
  23. public:
  24. CreatureID initialCreature;
  25. CreatureID upgradedCreature;
  26. TResources cost;
  27. int count;
  28. uint64_t upgradeValue;
  29. StackUpgradeInfo(CreatureID initial, CreatureID upgraded, int count)
  30. :initialCreature(initial), upgradedCreature(upgraded), count(count)
  31. {
  32. cost = (upgradedCreature.toCreature()->getFullRecruitCost() - initialCreature.toCreature()->getFullRecruitCost()) * count;
  33. upgradeValue = (upgradedCreature.toCreature()->getAIValue() - initialCreature.toCreature()->getAIValue()) * count;
  34. }
  35. };
  36. void ArmyUpgradeInfo::addArmyToBuy(std::vector<SlotInfo> army)
  37. {
  38. for(auto slot : army)
  39. {
  40. resultingArmy.push_back(slot);
  41. upgradeValue += slot.power;
  42. upgradeCost += slot.creature->getFullRecruitCost() * slot.count;
  43. }
  44. }
  45. void ArmyUpgradeInfo::addArmyToGet(std::vector<SlotInfo> army)
  46. {
  47. for(auto slot : army)
  48. {
  49. resultingArmy.push_back(slot);
  50. upgradeValue += slot.power;
  51. }
  52. }
  53. std::vector<SlotInfo> ArmyManager::toSlotInfo(std::vector<creInfo> army) const
  54. {
  55. std::vector<SlotInfo> result;
  56. for(auto i : army)
  57. {
  58. SlotInfo slot;
  59. slot.creature = i.creID.toCreature();
  60. slot.count = i.count;
  61. slot.power = evaluateStackPower(i.creID.toCreature(), i.count);
  62. result.push_back(slot);
  63. }
  64. return result;
  65. }
  66. uint64_t ArmyManager::howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const
  67. {
  68. return howManyReinforcementsCanGet(hero, hero, source, ai->cb->getTile(hero->visitablePos())->getTerrainID());
  69. }
  70. std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const
  71. {
  72. const CCreatureSet * armies[] = { target, source };
  73. //we calculate total strength for each creature type available in armies
  74. std::map<const CCreature *, SlotInfo> creToPower;
  75. std::vector<SlotInfo> resultingArmy;
  76. for(auto armyPtr : armies)
  77. {
  78. for(auto & i : armyPtr->Slots())
  79. {
  80. auto cre = dynamic_cast<const CCreature*>(i.second->getType());
  81. auto & slotInfp = creToPower[cre];
  82. slotInfp.creature = cre;
  83. slotInfp.power += i.second->getPower();
  84. slotInfp.count += i.second->count;
  85. }
  86. }
  87. for(auto & pair : creToPower)
  88. resultingArmy.push_back(pair.second);
  89. boost::sort(resultingArmy, [](const SlotInfo & left, const SlotInfo & right) -> bool
  90. {
  91. return left.power > right.power;
  92. });
  93. return resultingArmy;
  94. }
  95. std::vector<SlotInfo>::iterator ArmyManager::getBestUnitForScout(std::vector<SlotInfo> & army, const TerrainId & armyTerrain) const
  96. {
  97. uint64_t totalPower = 0;
  98. for (const auto & unit : army)
  99. totalPower += unit.power;
  100. int baseMovementCost = cb->getSettings().getInteger(EGameSettings::HEROES_MOVEMENT_COST_BASE);
  101. bool terrainHasPenalty = armyTerrain.hasValue() && armyTerrain.toEntity(LIBRARY)->moveCost != baseMovementCost;
  102. // arbitrary threshold - don't give scout more than specified part of total AI value of our army
  103. uint64_t maxUnitValue = totalPower / 100;
  104. const auto & movementPointsLimits = cb->getSettings().getVector(EGameSettings::HEROES_MOVEMENT_POINTS_LAND);
  105. auto fastest = boost::min_element(army, [&](const SlotInfo & left, const SlotInfo & right) -> bool
  106. {
  107. uint64_t leftUnitPower = left.power / left.count;
  108. uint64_t rightUnitPower = left.power / left.count;
  109. bool leftUnitIsWeak = leftUnitPower < maxUnitValue || left.creature->getLevel() < 4;
  110. bool rightUnitIsWeak = rightUnitPower < maxUnitValue || right.creature->getLevel() < 4;
  111. if (leftUnitIsWeak != rightUnitIsWeak)
  112. return leftUnitIsWeak;
  113. if (terrainHasPenalty)
  114. {
  115. auto leftNativeTerrain = left.creature->getFactionID().toFaction()->nativeTerrain;
  116. auto rightNativeTerrain = right.creature->getFactionID().toFaction()->nativeTerrain;
  117. if (leftNativeTerrain != rightNativeTerrain)
  118. {
  119. if (leftNativeTerrain == armyTerrain)
  120. return true;
  121. if (rightNativeTerrain == armyTerrain)
  122. return false;
  123. }
  124. }
  125. int leftEffectiveMovement = std::min<int>(movementPointsLimits.size() - 1, left.creature->getMovementRange());
  126. int rightEffectiveMovement = std::min<int>(movementPointsLimits.size() - 1, right.creature->getMovementRange());
  127. int leftMovementPointsLimit = movementPointsLimits[leftEffectiveMovement];
  128. int rightMovementPointsLimit = movementPointsLimits[rightEffectiveMovement];
  129. if (leftMovementPointsLimit != rightMovementPointsLimit)
  130. return leftMovementPointsLimit > rightMovementPointsLimit;
  131. return leftUnitPower < rightUnitPower;
  132. });
  133. return fastest;
  134. }
  135. class TemporaryArmy : public CArmedInstance
  136. {
  137. public:
  138. void armyChanged() override {}
  139. TemporaryArmy()
  140. :CArmedInstance(nullptr, true)
  141. {
  142. }
  143. };
  144. std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const
  145. {
  146. auto sortedSlots = getSortedSlots(target, source);
  147. if(source->stacksCount() == 0)
  148. return sortedSlots;
  149. std::map<FactionID, uint64_t> alignmentMap;
  150. for(auto & slot : sortedSlots)
  151. {
  152. alignmentMap[slot.creature->getFactionID()] += slot.power;
  153. }
  154. std::set<FactionID> allowedFactions;
  155. std::vector<SlotInfo> resultingArmy;
  156. uint64_t armyValue = 0;
  157. TemporaryArmy newArmyInstance;
  158. while(allowedFactions.size() < alignmentMap.size())
  159. {
  160. auto strongestAlignment = vstd::maxElementByFun(alignmentMap, [&](std::pair<FactionID, uint64_t> pair) -> uint64_t
  161. {
  162. return vstd::contains(allowedFactions, pair.first) ? 0 : pair.second;
  163. });
  164. allowedFactions.insert(strongestAlignment->first);
  165. std::vector<SlotInfo> newArmy;
  166. uint64_t newValue = 0;
  167. newArmyInstance.clearSlots();
  168. for(auto & slot : sortedSlots)
  169. {
  170. if(vstd::contains(allowedFactions, slot.creature->getFactionID()))
  171. {
  172. auto slotID = newArmyInstance.getSlotFor(slot.creature->getId());
  173. if(slotID.validSlot())
  174. {
  175. newArmyInstance.setCreature(slotID, slot.creature->getId(), slot.count);
  176. newArmy.push_back(slot);
  177. }
  178. }
  179. }
  180. newArmyInstance.updateMoraleBonusFromArmy();
  181. for(auto & slot : newArmyInstance.Slots())
  182. {
  183. auto morale = slot.second->moraleVal();
  184. auto multiplier = 1.0f;
  185. const auto & badMoraleChance = cb->getSettings().getVector(EGameSettings::COMBAT_BAD_MORALE_CHANCE);
  186. const auto & highMoraleChance = cb->getSettings().getVector(EGameSettings::COMBAT_GOOD_MORALE_CHANCE);
  187. int moraleDiceSize = cb->getSettings().getInteger(EGameSettings::COMBAT_MORALE_DICE_SIZE);
  188. if(morale < 0 && !badMoraleChance.empty())
  189. {
  190. size_t chanceIndex = std::min<size_t>(badMoraleChance.size(), -morale) - 1;
  191. multiplier -= 1.0 / moraleDiceSize * badMoraleChance.at(chanceIndex);
  192. }
  193. else if(morale > 0 && !highMoraleChance.empty())
  194. {
  195. size_t chanceIndex = std::min<size_t>(highMoraleChance.size(), morale) - 1;
  196. multiplier += 1.0 / moraleDiceSize * highMoraleChance.at(chanceIndex);
  197. }
  198. newValue += multiplier * slot.second->getPower();
  199. }
  200. if(armyValue >= newValue)
  201. {
  202. break;
  203. }
  204. resultingArmy = newArmy;
  205. armyValue = newValue;
  206. }
  207. if(resultingArmy.size() <= GameConstants::ARMY_SIZE
  208. && allowedFactions.size() == alignmentMap.size()
  209. && source->needsLastStack())
  210. {
  211. auto weakest = getBestUnitForScout(resultingArmy, armyTerrain);
  212. if(weakest->count == 1)
  213. {
  214. if (resultingArmy.size() == 1)
  215. logAi->warn("Unexpected resulting army size!");
  216. resultingArmy.erase(weakest);
  217. }
  218. else
  219. {
  220. weakest->power -= weakest->power / weakest->count;
  221. weakest->count--;
  222. }
  223. }
  224. return resultingArmy;
  225. }
  226. ui64 ArmyManager::howManyReinforcementsCanBuy(const CCreatureSet * h, const CGDwelling * t) const
  227. {
  228. return howManyReinforcementsCanBuy(h, t, ai->getFreeResources());
  229. }
  230. std::shared_ptr<CCreatureSet> ArmyManager::getArmyAvailableToBuyAsCCreatureSet(
  231. const CGDwelling * dwelling,
  232. TResources availableRes) const
  233. {
  234. std::vector<creInfo> creaturesInDwellings;
  235. auto army = std::make_shared<TemporaryArmy>();
  236. for(int i = dwelling->creatures.size() - 1; i >= 0; i--)
  237. {
  238. auto ci = infoFromDC(dwelling->creatures[i]);
  239. if(!ci.count || ci.creID == CreatureID::NONE)
  240. continue;
  241. vstd::amin(ci.count, availableRes / ci.creID.toCreature()->getFullRecruitCost()); //max count we can afford
  242. if(!ci.count)
  243. continue;
  244. SlotID dst = army->getFreeSlot();
  245. if(!dst.validSlot())
  246. break;
  247. army->setCreature(dst, ci.creID, ci.count);
  248. availableRes -= ci.creID.toCreature()->getFullRecruitCost() * ci.count;
  249. }
  250. return army;
  251. }
  252. ui64 ArmyManager::howManyReinforcementsCanBuy(
  253. const CCreatureSet * targetArmy,
  254. const CGDwelling * dwelling,
  255. const TResources & availableResources,
  256. uint8_t turn) const
  257. {
  258. ui64 aivalue = 0;
  259. auto army = getArmyAvailableToBuy(targetArmy, dwelling, availableResources);
  260. for(const creInfo & ci : army)
  261. {
  262. aivalue += ci.count * ci.creID.toCreature()->getAIValue();
  263. }
  264. return aivalue;
  265. }
  266. std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const
  267. {
  268. return getArmyAvailableToBuy(hero, dwelling, ai->getFreeResources());
  269. }
  270. std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
  271. const CCreatureSet * hero,
  272. const CGDwelling * dwelling,
  273. TResources availableRes,
  274. uint8_t turn) const
  275. {
  276. std::vector<creInfo> creaturesInDwellings;
  277. int freeHeroSlots = GameConstants::ARMY_SIZE - hero->stacksCount();
  278. bool countGrowth = (cb->getDate(Date::DAY_OF_WEEK) + turn) > 7;
  279. const CGTownInstance * town = dwelling->ID == Obj::TOWN
  280. ? dynamic_cast<const CGTownInstance *>(dwelling)
  281. : nullptr;
  282. std::set<SlotID> alreadyDisbanded;
  283. for(int i = dwelling->creatures.size() - 1; i >= 0; i--)
  284. {
  285. auto ci = infoFromDC(dwelling->creatures[i]);
  286. if(ci.creID == CreatureID::NONE) continue;
  287. if(i < GameConstants::CREATURES_PER_TOWN && countGrowth)
  288. {
  289. ci.count += town ? town->creatureGrowth(i) : ci.creID.toCreature()->getGrowth();
  290. }
  291. if(!ci.count) continue;
  292. // Calculate the market value of the new stack
  293. TResources newStackValue = ci.creID.toCreature()->getFullRecruitCost() * ci.count;
  294. SlotID dst = hero->getSlotFor(ci.creID);
  295. // Keep track of the least valuable slot in the hero's army
  296. SlotID leastValuableSlot;
  297. TResources leastValuableStackValue;
  298. leastValuableStackValue[6] = std::numeric_limits<int>::max();
  299. bool shouldDisband = false;
  300. if(!hero->hasStackAtSlot(dst)) //need another new slot for this stack
  301. {
  302. if(!freeHeroSlots) // No free slots; consider replacing
  303. {
  304. // Check for the least valuable existing stack
  305. for (auto& slot : hero->Slots())
  306. {
  307. if (alreadyDisbanded.find(slot.first) != alreadyDisbanded.end())
  308. continue;
  309. if(slot.second->getCreatureID() != CreatureID::NONE)
  310. {
  311. TResources currentStackValue = slot.second->getCreatureID().toCreature()->getFullRecruitCost() * slot.second->getCount();
  312. if (town && slot.second->getCreatureID().toCreature()->getFactionID() == town->getFactionID())
  313. continue;
  314. if(currentStackValue.marketValue() < leastValuableStackValue.marketValue())
  315. {
  316. leastValuableStackValue = currentStackValue;
  317. leastValuableSlot = slot.first;
  318. }
  319. }
  320. }
  321. // Decide whether to replace the least valuable stack
  322. if(newStackValue.marketValue() <= leastValuableStackValue.marketValue())
  323. {
  324. continue; // Skip if the new stack isn't worth replacing
  325. }
  326. else
  327. {
  328. shouldDisband = true;
  329. }
  330. }
  331. else
  332. {
  333. freeHeroSlots--; //new slot will be occupied
  334. }
  335. }
  336. vstd::amin(ci.count, availableRes / ci.creID.toCreature()->getFullRecruitCost()); //max count we can afford
  337. int disbandMalus = 0;
  338. if (shouldDisband)
  339. {
  340. disbandMalus = leastValuableStackValue / ci.creID.toCreature()->getFullRecruitCost();
  341. alreadyDisbanded.insert(leastValuableSlot);
  342. }
  343. ci.count -= disbandMalus;
  344. if(ci.count <= 0)
  345. continue;
  346. ci.level = i; //this is important for Dungeon Summoning Portal
  347. creaturesInDwellings.push_back(ci);
  348. availableRes -= ci.creID.toCreature()->getFullRecruitCost() * ci.count;
  349. }
  350. return creaturesInDwellings;
  351. }
  352. ui64 ArmyManager::howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const
  353. {
  354. if(source->stacksCount() == 0)
  355. {
  356. return 0;
  357. }
  358. auto bestArmy = getBestArmy(armyCarrier, target, source, armyTerrain);
  359. uint64_t newArmy = 0;
  360. uint64_t oldArmy = target->getArmyStrength();
  361. for(auto & slot : bestArmy)
  362. {
  363. newArmy += slot.power;
  364. }
  365. return newArmy > oldArmy ? newArmy - oldArmy : 0;
  366. }
  367. uint64_t ArmyManager::evaluateStackPower(const Creature * creature, int count) const
  368. {
  369. return creature->getAIValue() * count;
  370. }
  371. SlotInfo ArmyManager::getTotalCreaturesAvailable(CreatureID creatureID) const
  372. {
  373. auto creatureInfo = totalArmy.find(creatureID);
  374. return creatureInfo == totalArmy.end() ? SlotInfo() : creatureInfo->second;
  375. }
  376. void ArmyManager::update()
  377. {
  378. logAi->trace("Start analysing army");
  379. std::vector<const CCreatureSet *> total;
  380. auto heroes = cb->getHeroesInfo();
  381. auto towns = cb->getTownsInfo();
  382. std::copy(heroes.begin(), heroes.end(), std::back_inserter(total));
  383. std::copy(towns.begin(), towns.end(), std::back_inserter(total));
  384. totalArmy.clear();
  385. for(auto army : total)
  386. {
  387. for(const auto & slot : army->Slots())
  388. {
  389. totalArmy[slot.second->getCreatureID()].count += slot.second->count;
  390. }
  391. }
  392. for(auto & army : totalArmy)
  393. {
  394. army.second.creature = army.first.toCreature();
  395. army.second.power = evaluateStackPower(army.second.creature, army.second.count);
  396. }
  397. }
  398. std::vector<SlotInfo> ArmyManager::convertToSlots(const CCreatureSet * army) const
  399. {
  400. std::vector<SlotInfo> result;
  401. for(const auto & slot : army->Slots())
  402. {
  403. SlotInfo slotInfo;
  404. slotInfo.creature = slot.second->getCreatureID().toCreature();
  405. slotInfo.count = slot.second->count;
  406. slotInfo.power = evaluateStackPower(slotInfo.creature, slotInfo.count);
  407. result.push_back(slotInfo);
  408. }
  409. return result;
  410. }
  411. std::vector<StackUpgradeInfo> ArmyManager::getHillFortUpgrades(const CCreatureSet * army) const
  412. {
  413. std::vector<StackUpgradeInfo> upgrades;
  414. for(const auto & creature : army->Slots())
  415. {
  416. CreatureID initial = creature.second->getCreatureID();
  417. auto possibleUpgrades = initial.toCreature()->upgrades;
  418. if(possibleUpgrades.empty())
  419. continue;
  420. CreatureID strongestUpgrade = *vstd::minElementByFun(possibleUpgrades, [](CreatureID cre) -> uint64_t
  421. {
  422. return cre.toCreature()->getAIValue();
  423. });
  424. StackUpgradeInfo upgrade = StackUpgradeInfo(initial, strongestUpgrade, creature.second->count);
  425. if(initial.toCreature()->getLevel() == 1)
  426. upgrade.cost = TResources();
  427. upgrades.push_back(upgrade);
  428. }
  429. return upgrades;
  430. }
  431. std::vector<StackUpgradeInfo> ArmyManager::getDwellingUpgrades(const CCreatureSet * army, const CGDwelling * dwelling) const
  432. {
  433. std::vector<StackUpgradeInfo> upgrades;
  434. for(const auto & creature : army->Slots())
  435. {
  436. CreatureID initial = creature.second->getCreatureID();
  437. auto possibleUpgrades = initial.toCreature()->upgrades;
  438. vstd::erase_if(possibleUpgrades, [&](CreatureID creID) -> bool
  439. {
  440. for(auto pair : dwelling->creatures)
  441. {
  442. if(vstd::contains(pair.second, creID))
  443. return false;
  444. }
  445. return true;
  446. });
  447. if(possibleUpgrades.empty())
  448. continue;
  449. CreatureID strongestUpgrade = *vstd::minElementByFun(possibleUpgrades, [](CreatureID cre) -> uint64_t
  450. {
  451. return cre.toCreature()->getAIValue();
  452. });
  453. StackUpgradeInfo upgrade = StackUpgradeInfo(initial, strongestUpgrade, creature.second->count);
  454. upgrades.push_back(upgrade);
  455. }
  456. return upgrades;
  457. }
  458. std::vector<StackUpgradeInfo> ArmyManager::getPossibleUpgrades(const CCreatureSet * army, const CGObjectInstance * upgrader) const
  459. {
  460. std::vector<StackUpgradeInfo> upgrades;
  461. if(upgrader->ID == Obj::HILL_FORT)
  462. {
  463. upgrades = getHillFortUpgrades(army);
  464. }
  465. else
  466. {
  467. auto dwelling = dynamic_cast<const CGDwelling *>(upgrader);
  468. if(dwelling)
  469. {
  470. upgrades = getDwellingUpgrades(army, dwelling);
  471. }
  472. }
  473. return upgrades;
  474. }
  475. ArmyUpgradeInfo ArmyManager::calculateCreaturesUpgrade(
  476. const CCreatureSet * army,
  477. const CGObjectInstance * upgrader,
  478. const TResources & availableResources) const
  479. {
  480. if(!upgrader)
  481. return ArmyUpgradeInfo();
  482. std::vector<StackUpgradeInfo> upgrades = getPossibleUpgrades(army, upgrader);
  483. vstd::erase_if(upgrades, [&](const StackUpgradeInfo & u) -> bool
  484. {
  485. return !availableResources.canAfford(u.cost);
  486. });
  487. if(upgrades.empty())
  488. return ArmyUpgradeInfo();
  489. std::sort(upgrades.begin(), upgrades.end(), [](const StackUpgradeInfo & u1, const StackUpgradeInfo & u2) -> bool
  490. {
  491. return u1.upgradeValue > u2.upgradeValue;
  492. });
  493. TResources resourcesLeft = availableResources;
  494. ArmyUpgradeInfo result;
  495. result.resultingArmy = convertToSlots(army);
  496. for(auto upgrade : upgrades)
  497. {
  498. if(resourcesLeft.canAfford(upgrade.cost))
  499. {
  500. SlotInfo upgradedArmy;
  501. upgradedArmy.creature = upgrade.upgradedCreature.toCreature();
  502. upgradedArmy.count = upgrade.count;
  503. upgradedArmy.power = evaluateStackPower(upgradedArmy.creature, upgradedArmy.count);
  504. auto slotToReplace = std::find_if(result.resultingArmy.begin(), result.resultingArmy.end(), [&](const SlotInfo & slot) -> bool {
  505. return slot.count == upgradedArmy.count && slot.creature->getId() == upgrade.initialCreature;
  506. });
  507. resourcesLeft -= upgrade.cost;
  508. result.upgradeCost += upgrade.cost;
  509. result.upgradeValue += upgrade.upgradeValue;
  510. *slotToReplace = upgradedArmy;
  511. }
  512. }
  513. return result;
  514. }
  515. }