123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660 |
- /*
- * BuildingManager.cpp, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
- #include "StdInc.h"
- #include "ArmyManager.h"
- #include "../Engine/Nullkiller.h"
- #include "../../../CCallback.h"
- #include "../../../lib/mapObjects/MapObjects.h"
- #include "../../../lib/mapping/CMapDefines.h"
- #include "../../../lib/IGameSettings.h"
- #include "../../../lib/GameConstants.h"
- #include "../../../lib/TerrainHandler.h"
- namespace NKAI
- {
- class StackUpgradeInfo
- {
- public:
- CreatureID initialCreature;
- CreatureID upgradedCreature;
- TResources cost;
- int count;
- uint64_t upgradeValue;
- StackUpgradeInfo(CreatureID initial, CreatureID upgraded, int count)
- :initialCreature(initial), upgradedCreature(upgraded), count(count)
- {
- cost = (upgradedCreature.toCreature()->getFullRecruitCost() - initialCreature.toCreature()->getFullRecruitCost()) * count;
- upgradeValue = (upgradedCreature.toCreature()->getAIValue() - initialCreature.toCreature()->getAIValue()) * count;
- }
- };
- void ArmyUpgradeInfo::addArmyToBuy(std::vector<SlotInfo> army)
- {
- for(auto slot : army)
- {
- resultingArmy.push_back(slot);
- upgradeValue += slot.power;
- upgradeCost += slot.creature->getFullRecruitCost() * slot.count;
- }
- }
- void ArmyUpgradeInfo::addArmyToGet(std::vector<SlotInfo> army)
- {
- for(auto slot : army)
- {
- resultingArmy.push_back(slot);
- upgradeValue += slot.power;
- }
- }
- std::vector<SlotInfo> ArmyManager::toSlotInfo(std::vector<creInfo> army) const
- {
- std::vector<SlotInfo> result;
- for(auto i : army)
- {
- SlotInfo slot;
- slot.creature = i.creID.toCreature();
- slot.count = i.count;
- slot.power = evaluateStackPower(i.creID.toCreature(), i.count);
- result.push_back(slot);
- }
- return result;
- }
- uint64_t ArmyManager::howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const
- {
- return howManyReinforcementsCanGet(hero, hero, source, ai->cb->getTile(hero->visitablePos())->getTerrainID());
- }
- std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const
- {
- const CCreatureSet * armies[] = { target, source };
- //we calculate total strength for each creature type available in armies
- std::map<const CCreature *, SlotInfo> creToPower;
- std::vector<SlotInfo> resultingArmy;
- for(auto armyPtr : armies)
- {
- for(auto & i : armyPtr->Slots())
- {
- auto cre = dynamic_cast<const CCreature*>(i.second->getType());
- auto & slotInfp = creToPower[cre];
- slotInfp.creature = cre;
- slotInfp.power += i.second->getPower();
- slotInfp.count += i.second->count;
- }
- }
- for(auto & pair : creToPower)
- resultingArmy.push_back(pair.second);
- boost::sort(resultingArmy, [](const SlotInfo & left, const SlotInfo & right) -> bool
- {
- return left.power > right.power;
- });
- return resultingArmy;
- }
- std::vector<SlotInfo>::iterator ArmyManager::getBestUnitForScout(std::vector<SlotInfo> & army, const TerrainId & armyTerrain) const
- {
- uint64_t totalPower = 0;
- for (const auto & unit : army)
- totalPower += unit.power;
- int baseMovementCost = cb->getSettings().getInteger(EGameSettings::HEROES_MOVEMENT_COST_BASE);
- bool terrainHasPenalty = armyTerrain.hasValue() && armyTerrain.toEntity(LIBRARY)->moveCost != baseMovementCost;
- // arbitrary threshold - don't give scout more than specified part of total AI value of our army
- uint64_t maxUnitValue = totalPower / 100;
- const auto & movementPointsLimits = cb->getSettings().getVector(EGameSettings::HEROES_MOVEMENT_POINTS_LAND);
- auto fastest = std::min_element(army.begin(), army.end(), [&](const SlotInfo & left, const SlotInfo & right) -> bool
- {
- uint64_t leftUnitPower = left.power / left.count;
- uint64_t rightUnitPower = left.power / left.count;
- bool leftUnitIsWeak = leftUnitPower < maxUnitValue || left.creature->getLevel() < 4;
- bool rightUnitIsWeak = rightUnitPower < maxUnitValue || right.creature->getLevel() < 4;
- if (leftUnitIsWeak != rightUnitIsWeak)
- return leftUnitIsWeak;
- if (terrainHasPenalty)
- {
- auto leftNativeTerrain = left.creature->getFactionID().toFaction()->nativeTerrain;
- auto rightNativeTerrain = right.creature->getFactionID().toFaction()->nativeTerrain;
- if (leftNativeTerrain != rightNativeTerrain)
- {
- if (leftNativeTerrain == armyTerrain)
- return true;
- if (rightNativeTerrain == armyTerrain)
- return false;
- }
- }
- int leftEffectiveMovement = std::min<int>(movementPointsLimits.size() - 1, left.creature->getMovementRange());
- int rightEffectiveMovement = std::min<int>(movementPointsLimits.size() - 1, right.creature->getMovementRange());
- int leftMovementPointsLimit = movementPointsLimits[leftEffectiveMovement];
- int rightMovementPointsLimit = movementPointsLimits[rightEffectiveMovement];
- if (leftMovementPointsLimit != rightMovementPointsLimit)
- return leftMovementPointsLimit > rightMovementPointsLimit;
- return leftUnitPower < rightUnitPower;
- });
- return fastest;
- }
- class TemporaryArmy : public CArmedInstance
- {
- public:
- void armyChanged() override {}
- TemporaryArmy()
- :CArmedInstance(nullptr, true)
- {
- }
- };
- std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const
- {
- auto sortedSlots = getSortedSlots(target, source);
- if(source->stacksCount() == 0)
- return sortedSlots;
- std::map<FactionID, uint64_t> alignmentMap;
- for(auto & slot : sortedSlots)
- {
- alignmentMap[slot.creature->getFactionID()] += slot.power;
- }
- std::set<FactionID> allowedFactions;
- std::vector<SlotInfo> resultingArmy;
- uint64_t armyValue = 0;
- TemporaryArmy newArmyInstance;
- while(allowedFactions.size() < alignmentMap.size())
- {
- auto strongestAlignment = vstd::maxElementByFun(alignmentMap, [&](std::pair<FactionID, uint64_t> pair) -> uint64_t
- {
- return vstd::contains(allowedFactions, pair.first) ? 0 : pair.second;
- });
- allowedFactions.insert(strongestAlignment->first);
- std::vector<SlotInfo> newArmy;
- uint64_t newValue = 0;
- newArmyInstance.clearSlots();
- for(auto & slot : sortedSlots)
- {
- if(vstd::contains(allowedFactions, slot.creature->getFactionID()))
- {
- auto slotID = newArmyInstance.getSlotFor(slot.creature->getId());
- if(slotID.validSlot())
- {
- newArmyInstance.setCreature(slotID, slot.creature->getId(), slot.count);
- newArmy.push_back(slot);
- }
- }
- }
- newArmyInstance.updateMoraleBonusFromArmy();
- for(auto & slot : newArmyInstance.Slots())
- {
- auto morale = slot.second->moraleVal();
- auto multiplier = 1.0f;
- const auto & badMoraleDice = cb->getSettings().getVector(EGameSettings::COMBAT_BAD_MORALE_DICE);
- const auto & highMoraleDice = cb->getSettings().getVector(EGameSettings::COMBAT_GOOD_MORALE_DICE);
- if(morale < 0 && !badMoraleDice.empty())
- {
- size_t diceIndex = std::min<size_t>(badMoraleDice.size(), -morale) - 1;
- multiplier -= 1.0 / badMoraleDice.at(diceIndex);
- }
- else if(morale > 0 && !highMoraleDice.empty())
- {
- size_t diceIndex = std::min<size_t>(highMoraleDice.size(), morale) - 1;
- multiplier += 1.0 / highMoraleDice.at(diceIndex);
- }
- newValue += multiplier * slot.second->getPower();
- }
- if(armyValue >= newValue)
- {
- break;
- }
- resultingArmy = newArmy;
- armyValue = newValue;
- }
- if(resultingArmy.size() <= GameConstants::ARMY_SIZE
- && allowedFactions.size() == alignmentMap.size()
- && source->needsLastStack())
- {
- auto weakest = getBestUnitForScout(resultingArmy, armyTerrain);
- if(weakest->count == 1)
- {
- if (resultingArmy.size() == 1)
- logAi->warn("Unexpected resulting army size!");
- resultingArmy.erase(weakest);
- }
- else
- {
- weakest->power -= weakest->power / weakest->count;
- weakest->count--;
- }
- }
- return resultingArmy;
- }
- ui64 ArmyManager::howManyReinforcementsCanBuy(const CCreatureSet * h, const CGDwelling * t) const
- {
- return howManyReinforcementsCanBuy(h, t, ai->getFreeResources());
- }
- std::shared_ptr<CCreatureSet> ArmyManager::getArmyAvailableToBuyAsCCreatureSet(
- const CGDwelling * dwelling,
- TResources availableRes) const
- {
- std::vector<creInfo> creaturesInDwellings;
- auto army = std::make_shared<TemporaryArmy>();
- for(int i = dwelling->creatures.size() - 1; i >= 0; i--)
- {
- auto ci = infoFromDC(dwelling->creatures[i]);
- if(!ci.count || ci.creID == CreatureID::NONE)
- continue;
- vstd::amin(ci.count, availableRes / ci.creID.toCreature()->getFullRecruitCost()); //max count we can afford
- if(!ci.count)
- continue;
- SlotID dst = army->getFreeSlot();
- if(!dst.validSlot())
- break;
- army->setCreature(dst, ci.creID, ci.count);
- availableRes -= ci.creID.toCreature()->getFullRecruitCost() * ci.count;
- }
- return army;
- }
- ui64 ArmyManager::howManyReinforcementsCanBuy(
- const CCreatureSet * targetArmy,
- const CGDwelling * dwelling,
- const TResources & availableResources,
- uint8_t turn) const
- {
- ui64 aivalue = 0;
- auto army = getArmyAvailableToBuy(targetArmy, dwelling, availableResources);
- for(const creInfo & ci : army)
- {
- aivalue += ci.count * ci.creID.toCreature()->getAIValue();
- }
- return aivalue;
- }
- std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const
- {
- return getArmyAvailableToBuy(hero, dwelling, ai->getFreeResources());
- }
- std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
- const CCreatureSet * hero,
- const CGDwelling * dwelling,
- TResources availableRes,
- uint8_t turn) const
- {
- std::vector<creInfo> creaturesInDwellings;
- int freeHeroSlots = GameConstants::ARMY_SIZE - hero->stacksCount();
- bool countGrowth = (cb->getDate(Date::DAY_OF_WEEK) + turn) > 7;
- const CGTownInstance * town = dwelling->ID == Obj::TOWN
- ? dynamic_cast<const CGTownInstance *>(dwelling)
- : nullptr;
- std::set<SlotID> alreadyDisbanded;
- for(int i = dwelling->creatures.size() - 1; i >= 0; i--)
- {
- auto ci = infoFromDC(dwelling->creatures[i]);
- if(ci.creID == CreatureID::NONE) continue;
- if(i < GameConstants::CREATURES_PER_TOWN && countGrowth)
- {
- ci.count += town ? town->creatureGrowth(i) : ci.creID.toCreature()->getGrowth();
- }
- if(!ci.count) continue;
- // Calculate the market value of the new stack
- TResources newStackValue = ci.creID.toCreature()->getFullRecruitCost() * ci.count;
- SlotID dst = hero->getSlotFor(ci.creID);
- // Keep track of the least valuable slot in the hero's army
- SlotID leastValuableSlot;
- TResources leastValuableStackValue;
- leastValuableStackValue[6] = std::numeric_limits<int>::max();
- bool shouldDisband = false;
- if(!hero->hasStackAtSlot(dst)) //need another new slot for this stack
- {
- if(!freeHeroSlots) // No free slots; consider replacing
- {
- // Check for the least valuable existing stack
- for (auto& slot : hero->Slots())
- {
- if (alreadyDisbanded.find(slot.first) != alreadyDisbanded.end())
- continue;
- if(slot.second->getCreatureID() != CreatureID::NONE)
- {
- TResources currentStackValue = slot.second->getCreatureID().toCreature()->getFullRecruitCost() * slot.second->getCount();
- if (town && slot.second->getCreatureID().toCreature()->getFactionID() == town->getFactionID())
- continue;
- if(currentStackValue.marketValue() < leastValuableStackValue.marketValue())
- {
- leastValuableStackValue = currentStackValue;
- leastValuableSlot = slot.first;
- }
- }
- }
- // Decide whether to replace the least valuable stack
- if(newStackValue.marketValue() <= leastValuableStackValue.marketValue())
- {
- continue; // Skip if the new stack isn't worth replacing
- }
- else
- {
- shouldDisband = true;
- }
- }
- else
- {
- freeHeroSlots--; //new slot will be occupied
- }
- }
- vstd::amin(ci.count, availableRes / ci.creID.toCreature()->getFullRecruitCost()); //max count we can afford
- int disbandMalus = 0;
-
- if (shouldDisband)
- {
- disbandMalus = leastValuableStackValue / ci.creID.toCreature()->getFullRecruitCost();
- alreadyDisbanded.insert(leastValuableSlot);
- }
- ci.count -= disbandMalus;
- if(ci.count <= 0)
- continue;
- ci.level = i; //this is important for Dungeon Summoning Portal
- creaturesInDwellings.push_back(ci);
- availableRes -= ci.creID.toCreature()->getFullRecruitCost() * ci.count;
- }
- return creaturesInDwellings;
- }
- ui64 ArmyManager::howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const
- {
- if(source->stacksCount() == 0)
- {
- return 0;
- }
- auto bestArmy = getBestArmy(armyCarrier, target, source, armyTerrain);
- uint64_t newArmy = 0;
- uint64_t oldArmy = target->getArmyStrength();
- for(auto & slot : bestArmy)
- {
- newArmy += slot.power;
- }
- return newArmy > oldArmy ? newArmy - oldArmy : 0;
- }
- uint64_t ArmyManager::evaluateStackPower(const Creature * creature, int count) const
- {
- return creature->getAIValue() * count;
- }
- SlotInfo ArmyManager::getTotalCreaturesAvailable(CreatureID creatureID) const
- {
- auto creatureInfo = totalArmy.find(creatureID);
- return creatureInfo == totalArmy.end() ? SlotInfo() : creatureInfo->second;
- }
- void ArmyManager::update()
- {
- logAi->trace("Start analysing army");
- std::vector<const CCreatureSet *> total;
- auto heroes = cb->getHeroesInfo();
- auto towns = cb->getTownsInfo();
- std::copy(heroes.begin(), heroes.end(), std::back_inserter(total));
- std::copy(towns.begin(), towns.end(), std::back_inserter(total));
- totalArmy.clear();
- for(auto army : total)
- {
- for(auto slot : army->Slots())
- {
- totalArmy[slot.second->getCreatureID()].count += slot.second->count;
- }
- }
- for(auto & army : totalArmy)
- {
- army.second.creature = army.first.toCreature();
- army.second.power = evaluateStackPower(army.second.creature, army.second.count);
- }
- }
- std::vector<SlotInfo> ArmyManager::convertToSlots(const CCreatureSet * army) const
- {
- std::vector<SlotInfo> result;
- for(auto slot : army->Slots())
- {
- SlotInfo slotInfo;
- slotInfo.creature = slot.second->getCreatureID().toCreature();
- slotInfo.count = slot.second->count;
- slotInfo.power = evaluateStackPower(slotInfo.creature, slotInfo.count);
- result.push_back(slotInfo);
- }
- return result;
- }
- std::vector<StackUpgradeInfo> ArmyManager::getHillFortUpgrades(const CCreatureSet * army) const
- {
- std::vector<StackUpgradeInfo> upgrades;
- for(auto creature : army->Slots())
- {
- CreatureID initial = creature.second->getCreatureID();
- auto possibleUpgrades = initial.toCreature()->upgrades;
- if(possibleUpgrades.empty())
- continue;
- CreatureID strongestUpgrade = *vstd::minElementByFun(possibleUpgrades, [](CreatureID cre) -> uint64_t
- {
- return cre.toCreature()->getAIValue();
- });
- StackUpgradeInfo upgrade = StackUpgradeInfo(initial, strongestUpgrade, creature.second->count);
- if(initial.toCreature()->getLevel() == 1)
- upgrade.cost = TResources();
- upgrades.push_back(upgrade);
- }
- return upgrades;
- }
- std::vector<StackUpgradeInfo> ArmyManager::getDwellingUpgrades(const CCreatureSet * army, const CGDwelling * dwelling) const
- {
- std::vector<StackUpgradeInfo> upgrades;
- for(auto creature : army->Slots())
- {
- CreatureID initial = creature.second->getCreatureID();
- auto possibleUpgrades = initial.toCreature()->upgrades;
- vstd::erase_if(possibleUpgrades, [&](CreatureID creID) -> bool
- {
- for(auto pair : dwelling->creatures)
- {
- if(vstd::contains(pair.second, creID))
- return false;
- }
- return true;
- });
- if(possibleUpgrades.empty())
- continue;
- CreatureID strongestUpgrade = *vstd::minElementByFun(possibleUpgrades, [](CreatureID cre) -> uint64_t
- {
- return cre.toCreature()->getAIValue();
- });
- StackUpgradeInfo upgrade = StackUpgradeInfo(initial, strongestUpgrade, creature.second->count);
- upgrades.push_back(upgrade);
- }
- return upgrades;
- }
- std::vector<StackUpgradeInfo> ArmyManager::getPossibleUpgrades(const CCreatureSet * army, const CGObjectInstance * upgrader) const
- {
- std::vector<StackUpgradeInfo> upgrades;
- if(upgrader->ID == Obj::HILL_FORT)
- {
- upgrades = getHillFortUpgrades(army);
- }
- else
- {
- auto dwelling = dynamic_cast<const CGDwelling *>(upgrader);
- if(dwelling)
- {
- upgrades = getDwellingUpgrades(army, dwelling);
- }
- }
- return upgrades;
- }
- ArmyUpgradeInfo ArmyManager::calculateCreaturesUpgrade(
- const CCreatureSet * army,
- const CGObjectInstance * upgrader,
- const TResources & availableResources) const
- {
- if(!upgrader)
- return ArmyUpgradeInfo();
- std::vector<StackUpgradeInfo> upgrades = getPossibleUpgrades(army, upgrader);
- vstd::erase_if(upgrades, [&](const StackUpgradeInfo & u) -> bool
- {
- return !availableResources.canAfford(u.cost);
- });
- if(upgrades.empty())
- return ArmyUpgradeInfo();
- std::sort(upgrades.begin(), upgrades.end(), [](const StackUpgradeInfo & u1, const StackUpgradeInfo & u2) -> bool
- {
- return u1.upgradeValue > u2.upgradeValue;
- });
- TResources resourcesLeft = availableResources;
- ArmyUpgradeInfo result;
-
- result.resultingArmy = convertToSlots(army);
- for(auto upgrade : upgrades)
- {
- if(resourcesLeft.canAfford(upgrade.cost))
- {
- SlotInfo upgradedArmy;
- upgradedArmy.creature = upgrade.upgradedCreature.toCreature();
- upgradedArmy.count = upgrade.count;
- upgradedArmy.power = evaluateStackPower(upgradedArmy.creature, upgradedArmy.count);
- auto slotToReplace = std::find_if(result.resultingArmy.begin(), result.resultingArmy.end(), [&](const SlotInfo & slot) -> bool {
- return slot.count == upgradedArmy.count && slot.creature->getId() == upgrade.initialCreature;
- });
- resourcesLeft -= upgrade.cost;
- result.upgradeCost += upgrade.cost;
- result.upgradeValue += upgrade.upgradeValue;
- *slotToReplace = upgradedArmy;
- }
- }
- return result;
- }
- }
|