| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558 | /** 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/GameConstants.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);}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->type);			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::getWeakestCreature(std::vector<SlotInfo> & army) const{	auto weakest = boost::min_element(army, [](const SlotInfo & left, const SlotInfo & right) -> bool	{		if(left.creature->getLevel() != right.creature->getLevel())			return left.creature->getLevel() < right.creature->getLevel();				return left.creature->getMovementRange() > right.creature->getMovementRange();	});	return weakest;}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{	auto sortedSlots = getSortedSlots(target, source);	if(source->stacksCount() == 0)		return sortedSlots;	std::map<FactionID, uint64_t> alignmentMap;	for(auto & slot : sortedSlots)	{		alignmentMap[slot.creature->getFaction()] += 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->getFaction()))			{				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 float BadMoraleChance = 0.083f;			const float HighMoraleChance = 0.04f;			if(morale < 0)			{				multiplier += morale * BadMoraleChance;			}			else if(morale > 0)			{				multiplier += morale * HighMoraleChance;			}			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 = getWeakestCreature(resultingArmy);		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;	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;		SlotID dst = hero->getSlotFor(ci.creID);		if(!hero->hasStackAtSlot(dst)) //need another new slot for this stack		{			if(!freeHeroSlots) //no more place for stacks				continue;			else				freeHeroSlots--; //new slot will be occupied		}		vstd::amin(ci.count, availableRes / ci.creID.toCreature()->getFullRecruitCost()); //max count we can afford		if(!ci.count) 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{	if(source->stacksCount() == 0)	{		return 0;	}	auto bestArmy = getBestArmy(armyCarrier, target, source);	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;}}
 |