mirror of
https://github.com/vcmi/vcmi.git
synced 2025-01-12 02:28:11 +02:00
2ad9038709
When the AI cannot buy units in a city because all slots are blocked and the units in the slot are cheaper than the units it wants to buy, the AI will now get rid of the units that block that slot in order to be able to buy the better units.
476 lines
12 KiB
C++
476 lines
12 KiB
C++
/*
|
|
* AINodeStorage.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 "Actors.h"
|
|
#include "../AIGateway.h"
|
|
#include "../Engine/Nullkiller.h"
|
|
#include "../../../CCallback.h"
|
|
#include "../../../lib/mapObjects/MapObjects.h"
|
|
#include "../../../lib/pathfinder/TurnInfo.h"
|
|
#include "Actions/BuyArmyAction.h"
|
|
|
|
using namespace NKAI;
|
|
|
|
const CCreatureSet emptyArmy;
|
|
|
|
bool HeroExchangeArmy::needsLastStack() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
std::shared_ptr<SpecialAction> HeroExchangeArmy::getActorAction() const
|
|
{
|
|
std::shared_ptr<SpecialAction> result;
|
|
|
|
if(requireBuyArmy)
|
|
{
|
|
result.reset(new AIPathfinding::BuyArmyAction());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
ChainActor::ChainActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t chainMask)
|
|
:hero(hero), heroRole(heroRole), isMovable(true), chainMask(chainMask), creatureSet(hero),
|
|
baseActor(this), carrierParent(nullptr), otherParent(nullptr), actorExchangeCount(1), armyCost(), actorAction()
|
|
{
|
|
initialPosition = hero->visitablePos();
|
|
layer = hero->boat ? hero->boat->layer : EPathfindingLayer::LAND;
|
|
initialMovement = hero->movementPointsRemaining();
|
|
initialTurn = 0;
|
|
armyValue = getHeroArmyStrengthWithCommander(hero, hero);
|
|
heroFightingStrength = hero->getHeroStrength();
|
|
tiCache.reset(new TurnInfo(hero));
|
|
}
|
|
|
|
ChainActor::ChainActor(const ChainActor * carrier, const ChainActor * other, const CCreatureSet * heroArmy)
|
|
:hero(carrier->hero), tiCache(carrier->tiCache), heroRole(carrier->heroRole), isMovable(true), creatureSet(heroArmy), chainMask(carrier->chainMask | other->chainMask),
|
|
baseActor(this), carrierParent(carrier), otherParent(other), heroFightingStrength(carrier->heroFightingStrength),
|
|
actorExchangeCount(carrier->actorExchangeCount + other->actorExchangeCount), armyCost(carrier->armyCost + other->armyCost), actorAction()
|
|
{
|
|
armyValue = getHeroArmyStrengthWithCommander(hero, heroArmy);
|
|
}
|
|
|
|
ChainActor::ChainActor(const CGObjectInstance * obj, const CCreatureSet * creatureSet, uint64_t chainMask, int initialTurn)
|
|
:hero(nullptr), heroRole(HeroRole::MAIN), isMovable(false), creatureSet(creatureSet), chainMask(chainMask),
|
|
baseActor(this), carrierParent(nullptr), otherParent(nullptr), initialTurn(initialTurn), initialMovement(0),
|
|
heroFightingStrength(0), actorExchangeCount(1), armyCost(), actorAction()
|
|
{
|
|
initialPosition = obj->visitablePos();
|
|
layer = EPathfindingLayer::LAND;
|
|
armyValue = creatureSet->getArmyStrength();
|
|
}
|
|
|
|
int ChainActor::maxMovePoints(CGPathNode::ELayer layer)
|
|
{
|
|
#if NKAI_TRACE_LEVEL > 0
|
|
if(!hero)
|
|
throw std::logic_error("Asking movement points for static actor");
|
|
#endif
|
|
|
|
return hero->movementPointsLimitCached(layer, tiCache.get());
|
|
}
|
|
|
|
std::string ChainActor::toString() const
|
|
{
|
|
return hero->getNameTranslated();
|
|
}
|
|
|
|
ObjectActor::ObjectActor(const CGObjectInstance * obj, const CCreatureSet * army, uint64_t chainMask, int initialTurn)
|
|
:ChainActor(obj, army, chainMask, initialTurn), object(obj)
|
|
{
|
|
}
|
|
|
|
const CGObjectInstance * ObjectActor::getActorObject() const
|
|
{
|
|
return object;
|
|
}
|
|
|
|
std::string ObjectActor::toString() const
|
|
{
|
|
return object->getObjectName() + " at " + object->visitablePos().toString();
|
|
}
|
|
|
|
HeroActor::HeroActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t chainMask, const Nullkiller * ai)
|
|
:ChainActor(hero, heroRole, chainMask)
|
|
{
|
|
exchangeMap.reset(new HeroExchangeMap(this, ai));
|
|
setupSpecialActors();
|
|
}
|
|
|
|
HeroActor::HeroActor(
|
|
const ChainActor * carrier,
|
|
const ChainActor * other,
|
|
const HeroExchangeArmy * army,
|
|
const Nullkiller * ai)
|
|
:ChainActor(carrier, other, army)
|
|
{
|
|
exchangeMap.reset(new HeroExchangeMap(this, ai));
|
|
armyCost += army->armyCost;
|
|
actorAction = army->getActorAction();
|
|
setupSpecialActors();
|
|
}
|
|
|
|
void ChainActor::setBaseActor(HeroActor * base)
|
|
{
|
|
baseActor = base;
|
|
hero = base->hero;
|
|
heroRole = base->heroRole;
|
|
layer = base->layer;
|
|
initialMovement = base->initialMovement;
|
|
initialTurn = base->initialTurn;
|
|
armyValue = base->armyValue;
|
|
chainMask = base->chainMask;
|
|
creatureSet = base->creatureSet;
|
|
isMovable = base->isMovable;
|
|
heroFightingStrength = base->heroFightingStrength;
|
|
armyCost = base->armyCost;
|
|
actorAction = base->actorAction;
|
|
tiCache = base->tiCache;
|
|
actorExchangeCount = base->actorExchangeCount;
|
|
}
|
|
|
|
void HeroActor::setupSpecialActors()
|
|
{
|
|
auto allActors = std::vector<ChainActor *>{this};
|
|
|
|
for(ChainActor & specialActor : specialActors)
|
|
{
|
|
specialActor.setBaseActor(this);
|
|
allActors.push_back(&specialActor);
|
|
}
|
|
|
|
for(int i = 0; i <= SPECIAL_ACTORS_COUNT; i++)
|
|
{
|
|
ChainActor * actor = allActors[i];
|
|
|
|
actor->allowBattle = (i & 1) > 0;
|
|
actor->allowSpellCast = (i & 2) > 0;
|
|
actor->allowUseResources = (i & 4) > 0;
|
|
actor->battleActor = allActors[i | 1];
|
|
actor->castActor = allActors[i | 2];
|
|
actor->resourceActor = allActors[i | 4];
|
|
}
|
|
}
|
|
|
|
ExchangeResult ChainActor::tryExchangeNoLock(const ChainActor * specialActor, const ChainActor * other) const
|
|
{
|
|
if(!isMovable) return ExchangeResult();
|
|
|
|
return baseActor->tryExchangeNoLock(specialActor, other);
|
|
}
|
|
|
|
ExchangeResult HeroActor::tryExchangeNoLock(const ChainActor * specialActor, const ChainActor * other) const
|
|
{
|
|
const ChainActor * otherBase = other->baseActor;
|
|
ExchangeResult result = exchangeMap->tryExchangeNoLock(otherBase);
|
|
|
|
if(!result.actor || !result.lockAcquired) return result;
|
|
|
|
if(specialActor == this)
|
|
return result;
|
|
|
|
int index = vstd::find_pos_if(specialActors, [specialActor](const ChainActor & actor) -> bool
|
|
{
|
|
return &actor == specialActor;
|
|
});
|
|
|
|
result.actor = &(dynamic_cast<HeroActor *>(result.actor)->specialActors.at(index));
|
|
|
|
return result;
|
|
}
|
|
|
|
HeroExchangeMap::HeroExchangeMap(const HeroActor * actor, const Nullkiller * ai)
|
|
:actor(actor), ai(ai), sync()
|
|
{
|
|
}
|
|
|
|
HeroExchangeMap::~HeroExchangeMap()
|
|
{
|
|
for(auto & exchange : exchangeMap)
|
|
{
|
|
if(!exchange.second) continue;
|
|
|
|
delete exchange.second->creatureSet;
|
|
}
|
|
|
|
for(auto & exchange : exchangeMap)
|
|
{
|
|
if(!exchange.second) continue;
|
|
|
|
delete exchange.second;
|
|
}
|
|
|
|
exchangeMap.clear();
|
|
|
|
}
|
|
|
|
ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other)
|
|
{
|
|
ExchangeResult result;
|
|
|
|
{
|
|
boost::shared_lock lock(sync, boost::try_to_lock);
|
|
|
|
if(!lock.owns_lock())
|
|
{
|
|
result.lockAcquired = false;
|
|
|
|
return result;
|
|
}
|
|
|
|
auto position = exchangeMap.find(other);
|
|
|
|
if(position != exchangeMap.end())
|
|
{
|
|
result.actor = position->second;
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
{
|
|
boost::unique_lock uniqueLock(sync, boost::try_to_lock);
|
|
|
|
if(!uniqueLock.owns_lock())
|
|
{
|
|
result.lockAcquired = false;
|
|
|
|
return result;
|
|
}
|
|
|
|
auto inserted = exchangeMap.insert(std::pair<const ChainActor *, HeroActor *>(other, nullptr));
|
|
|
|
if(!inserted.second)
|
|
{
|
|
result.actor = inserted.first->second;
|
|
|
|
return result; // already inserted
|
|
}
|
|
|
|
auto differentMasks = (actor->chainMask & other->chainMask) == 0;
|
|
|
|
if(!differentMasks) return result;
|
|
|
|
if(actor->allowSpellCast || other->allowSpellCast)
|
|
return result;
|
|
|
|
TResources resources = ai->cb->getResourceAmount();
|
|
|
|
if(!resources.canAfford(actor->armyCost + other->armyCost))
|
|
{
|
|
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
|
|
logAi->trace(
|
|
"Can not afford exchange because of total cost %s but we have %s",
|
|
(actor->armyCost + other->armyCost).toString(),
|
|
resources.toString());
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
if(other->isMovable && other->armyValue <= actor->armyValue / 10 && other->armyValue < MIN_ARMY_STRENGTH_FOR_CHAIN)
|
|
return result;
|
|
|
|
TResources availableResources = resources - actor->armyCost - other->armyCost;
|
|
HeroExchangeArmy * upgradedInitialArmy = tryUpgrade(actor->creatureSet, other->getActorObject(), availableResources);
|
|
HeroExchangeArmy * newArmy;
|
|
|
|
if(other->creatureSet->Slots().size())
|
|
{
|
|
if(upgradedInitialArmy)
|
|
{
|
|
newArmy = pickBestCreatures(upgradedInitialArmy, other->creatureSet);
|
|
newArmy->armyCost = upgradedInitialArmy->armyCost;
|
|
newArmy->requireBuyArmy = upgradedInitialArmy->requireBuyArmy;
|
|
|
|
delete upgradedInitialArmy;
|
|
}
|
|
else
|
|
{
|
|
newArmy = pickBestCreatures(actor->creatureSet, other->creatureSet);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
newArmy = upgradedInitialArmy;
|
|
}
|
|
|
|
if(!newArmy) return result;
|
|
|
|
auto newArmyStrength = newArmy->getArmyStrength();
|
|
auto oldArmyStrength = actor->creatureSet->getArmyStrength();
|
|
|
|
if(newArmyStrength <= oldArmyStrength) return result;
|
|
|
|
auto reinforcement = newArmyStrength - oldArmyStrength;
|
|
|
|
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
|
|
logAi->trace(
|
|
"Exchange %s->%s reinforcement: %d, %f%%",
|
|
actor->toString(),
|
|
other->toString(),
|
|
reinforcement,
|
|
100.0f * reinforcement / actor->armyValue);
|
|
#endif
|
|
|
|
if(reinforcement <= actor->armyValue / 10 && reinforcement < MIN_ARMY_STRENGTH_FOR_CHAIN)
|
|
{
|
|
delete newArmy;
|
|
|
|
return result;
|
|
}
|
|
|
|
auto * exchanged = new HeroActor(actor, other, newArmy, ai);
|
|
|
|
exchanged->armyCost += newArmy->armyCost;
|
|
result.actor = exchanged;
|
|
exchangeMap[other] = exchanged;
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
|
|
const CCreatureSet * army,
|
|
const CGObjectInstance * upgrader,
|
|
TResources resources) const
|
|
{
|
|
auto * target = new HeroExchangeArmy();
|
|
auto upgradeInfo = ai->armyManager->calculateCreaturesUpgrade(army, upgrader, resources);
|
|
|
|
if(upgradeInfo.upgradeValue)
|
|
{
|
|
for(auto & slotInfo : upgradeInfo.resultingArmy)
|
|
{
|
|
auto targetSlot = target->getFreeSlot();
|
|
|
|
target->addToSlot(targetSlot, slotInfo.creature->getId(), TQuantity(slotInfo.count));
|
|
}
|
|
|
|
resources -= upgradeInfo.upgradeCost;
|
|
target->armyCost += upgradeInfo.upgradeCost;
|
|
}
|
|
else
|
|
{
|
|
for(auto slot : army->Slots())
|
|
{
|
|
auto targetSlot = target->getSlotFor(slot.second->getCreatureID());
|
|
|
|
target->addToSlot(targetSlot, slot.second->getCreatureID(), slot.second->count);
|
|
}
|
|
}
|
|
|
|
if(upgrader->ID == Obj::TOWN)
|
|
{
|
|
auto buyArmy = ai->armyManager->getArmyAvailableToBuy(target, ai->cb->getTown(upgrader->id), resources);
|
|
|
|
for(auto & creatureToBuy : buyArmy)
|
|
{
|
|
auto targetSlot = target->getSlotFor(creatureToBuy.creID.toCreature());
|
|
if (targetSlot.validSlot())
|
|
{
|
|
target->addToSlot(targetSlot, creatureToBuy.creID, creatureToBuy.count);
|
|
target->armyCost += creatureToBuy.creID.toCreature()->getFullRecruitCost() * creatureToBuy.count;
|
|
target->requireBuyArmy = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(target->getArmyStrength() <= army->getArmyStrength())
|
|
{
|
|
delete target;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
HeroExchangeArmy * HeroExchangeMap::pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const
|
|
{
|
|
auto * target = new HeroExchangeArmy();
|
|
auto bestArmy = ai->armyManager->getBestArmy(actor->hero, army1, army2);
|
|
|
|
for(auto & slotInfo : bestArmy)
|
|
{
|
|
auto targetSlot = target->getFreeSlot();
|
|
|
|
target->addToSlot(targetSlot, slotInfo.creature->getId(), TQuantity(slotInfo.count));
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
HillFortActor::HillFortActor(const CGObjectInstance * hillFort, uint64_t chainMask)
|
|
:ObjectActor(hillFort, &emptyArmy, chainMask, 0)
|
|
{
|
|
}
|
|
|
|
DwellingActor::DwellingActor(const CGDwelling * dwelling, uint64_t chainMask, bool waitForGrowth, int dayOfWeek)
|
|
: ObjectActor(
|
|
dwelling,
|
|
getDwellingCreatures(dwelling, waitForGrowth),
|
|
chainMask,
|
|
getInitialTurn(waitForGrowth, dayOfWeek)),
|
|
dwelling(dwelling)
|
|
{
|
|
for(auto & slot : creatureSet->Slots())
|
|
{
|
|
armyCost += slot.second->getCreatureID().toCreature()->getFullRecruitCost() * slot.second->count;
|
|
}
|
|
}
|
|
|
|
DwellingActor::~DwellingActor()
|
|
{
|
|
delete creatureSet;
|
|
}
|
|
|
|
int DwellingActor::getInitialTurn(bool waitForGrowth, int dayOfWeek)
|
|
{
|
|
if(!waitForGrowth)
|
|
return 0;
|
|
|
|
return 8 - dayOfWeek;
|
|
}
|
|
|
|
std::string DwellingActor::toString() const
|
|
{
|
|
return dwelling->getTypeName() + dwelling->visitablePos().toString();
|
|
}
|
|
|
|
CCreatureSet * DwellingActor::getDwellingCreatures(const CGDwelling * dwelling, bool waitForGrowth)
|
|
{
|
|
auto * dwellingCreatures = new CCreatureSet();
|
|
|
|
for(auto & creatureInfo : dwelling->creatures)
|
|
{
|
|
if(!creatureInfo.second.size())
|
|
continue;
|
|
|
|
auto creature = creatureInfo.second.back().toCreature();
|
|
dwellingCreatures->addToSlot(
|
|
dwellingCreatures->getSlotFor(creature),
|
|
creature->getId(),
|
|
TQuantity(creatureInfo.first));
|
|
}
|
|
|
|
return dwellingCreatures;
|
|
}
|
|
|
|
TownGarrisonActor::TownGarrisonActor(const CGTownInstance * town, uint64_t chainMask)
|
|
:ObjectActor(town, town->getUpperArmy(), chainMask, 0), town(town)
|
|
{
|
|
}
|
|
|
|
std::string TownGarrisonActor::toString() const
|
|
{
|
|
return town->getNameTranslated();
|
|
}
|