1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-14 10:12:59 +02:00
vcmi/AI/Nullkiller/Engine/Nullkiller.cpp

558 lines
13 KiB
C++
Raw Normal View History

/*
* Nullkiller.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 "Nullkiller.h"
2021-05-16 14:39:38 +02:00
#include "../AIGateway.h"
#include "../Behaviors/CaptureObjectsBehavior.h"
#include "../Behaviors/RecruitHeroBehavior.h"
#include "../Behaviors/BuyArmyBehavior.h"
#include "../Behaviors/StartupBehavior.h"
2021-05-16 12:53:32 +02:00
#include "../Behaviors/DefenceBehavior.h"
#include "../Behaviors/BuildingBehavior.h"
2021-05-16 13:19:07 +02:00
#include "../Behaviors/GatherArmyBehavior.h"
2021-05-16 13:45:12 +02:00
#include "../Behaviors/ClusterBehavior.h"
2023-09-24 12:07:42 +02:00
#include "../Behaviors/StayAtTownBehavior.h"
2024-05-19 09:04:45 +02:00
#include "../Behaviors/ExplorationBehavior.h"
#include "../Goals/Invalid.h"
#include "../Goals/Composition.h"
2024-05-19 09:04:45 +02:00
#include "../../../lib/CPlayerState.h"
#include "../../lib/StartInfo.h"
2022-09-26 20:01:07 +02:00
namespace NKAI
{
using namespace Goals;
// while we play vcmieagles graph can be shared
std::unique_ptr<ObjectGraph> Nullkiller::baseGraph;
Nullkiller::Nullkiller()
2024-02-25 12:39:19 +02:00
:activeHero(nullptr), scanDepth(ScanDepth::MAIN_FULL), useHeroChain(true)
{
2024-02-25 12:39:19 +02:00
memory = std::make_unique<AIMemory>();
settings = std::make_unique<Settings>();
2024-05-19 09:04:45 +02:00
useObjectGraph = settings->isObjectGraphAllowed();
openMap = settings->isOpenMap() || useObjectGraph;
}
bool canUseOpenMap(std::shared_ptr<CCallback> cb, PlayerColor playerID)
{
if(!cb->getStartInfo()->extraOptionsInfo.cheatsAllowed)
{
return false;
}
const TeamState * team = cb->getPlayerTeam(playerID);
auto hasHumanInTeam = vstd::contains_if(team->players, [cb](PlayerColor teamMateID) -> bool
{
return cb->getPlayerState(teamMateID)->isHuman();
});
if(hasHumanInTeam)
{
return false;
}
return cb->getStartInfo()->difficulty >= 3;
}
2024-03-31 17:39:00 +02:00
void Nullkiller::init(std::shared_ptr<CCallback> cb, AIGateway * gateway)
{
this->cb = cb;
2024-03-31 17:39:00 +02:00
this->gateway = gateway;
2024-05-19 09:04:45 +02:00
playerID = gateway->playerID;
if(openMap && !canUseOpenMap(cb, playerID))
{
useObjectGraph = false;
openMap = false;
}
2024-02-03 12:20:59 +02:00
baseGraph.reset();
priorityEvaluator.reset(new PriorityEvaluator(this));
priorityEvaluators.reset(
new SharedPool<PriorityEvaluator>(
[&]()->std::unique_ptr<PriorityEvaluator>
{
2022-12-07 23:36:20 +02:00
return std::make_unique<PriorityEvaluator>(this);
}));
dangerHitMap.reset(new DangerHitMapAnalyzer(this));
2021-05-16 13:57:33 +02:00
buildAnalyzer.reset(new BuildAnalyzer(this));
objectClusterizer.reset(new ObjectClusterizer(this));
dangerEvaluator.reset(new FuzzyHelper(this));
pathfinder.reset(new AIPathfinder(cb.get(), this));
armyManager.reset(new ArmyManager(cb.get(), this));
heroManager.reset(new HeroManager(cb.get(), this));
2024-03-31 17:39:00 +02:00
decomposer.reset(new DeepDecomposer(this));
armyFormation.reset(new ArmyFormation(cb, this));
}
TaskPlanItem::TaskPlanItem(TSubgoal task)
:task(task), affectedObjects(task->asTask()->getAffectedObjects())
{
}
Goals::TTaskVec TaskPlan::getTasks() const
{
Goals::TTaskVec result;
for(auto & item : tasks)
{
result.push_back(taskptr(*item.task));
}
2021-05-15 20:27:22 +02:00
vstd::removeDuplicates(result);
2021-05-16 13:57:33 +02:00
return result;
}
2021-05-16 13:38:53 +02:00
void TaskPlan::merge(TSubgoal task)
{
TGoalVec blockers;
for(auto & item : tasks)
{
for(auto objid : item.affectedObjects)
{
if(task == item.task || task->asTask()->isObjectAffected(objid))
{
if(item.task->asTask()->priority >= task->asTask()->priority)
return;
blockers.push_back(item.task);
break;
}
}
}
vstd::erase_if(tasks, [&](const TaskPlanItem & task)
{
return vstd::contains(blockers, task.task);
});
tasks.emplace_back(task);
}
Goals::TTask Nullkiller::choseBestTask(Goals::TGoalVec & tasks) const
{
2021-05-16 13:38:53 +02:00
if(tasks.empty())
{
return taskptr(Invalid());
}
for(TSubgoal & task : tasks)
{
if(task->asTask()->priority <= 0)
task->asTask()->priority = priorityEvaluator->evaluate(task);
}
2021-05-16 13:38:53 +02:00
auto bestTask = *vstd::maxElementByFun(tasks, [](Goals::TSubgoal task) -> float
{
return task->asTask()->priority;
});
return taskptr(*bestTask);
}
Goals::TTaskVec Nullkiller::buildPlan(TGoalVec & tasks) const
{
TaskPlan taskPlan;
tbb::parallel_for(tbb::blocked_range<size_t>(0, tasks.size()), [this, &tasks](const tbb::blocked_range<size_t> & r)
{
auto evaluator = this->priorityEvaluators->acquire();
for(size_t i = r.begin(); i != r.end(); i++)
{
auto task = tasks[i];
if(task->asTask()->priority <= 0)
task->asTask()->priority = evaluator->evaluate(task);
}
});
std::sort(tasks.begin(), tasks.end(), [](TSubgoal g1, TSubgoal g2) -> bool
{
return g2->asTask()->priority < g1->asTask()->priority;
});
for(TSubgoal & task : tasks)
{
taskPlan.merge(task);
2021-05-16 13:38:53 +02:00
}
return taskPlan.getTasks();
}
void Nullkiller::decompose(Goals::TGoalVec & result, Goals::TSubgoal behavior, int decompositionMaxDepth) const
{
boost::this_thread::interruption_point();
logAi->debug("Checking behavior %s", behavior->toString());
auto start = std::chrono::high_resolution_clock::now();
decomposer->decompose(result, behavior, decompositionMaxDepth);
boost::this_thread::interruption_point();
logAi->debug(
"Behavior %s. Time taken %ld",
behavior->toString(),
timeElapsed(start));
}
void Nullkiller::resetAiState()
{
2023-12-17 10:11:05 +02:00
std::unique_lock<std::mutex> lockGuard(aiStateMutex);
2021-05-16 14:39:38 +02:00
lockedResources = TResources();
2023-07-27 14:58:49 +02:00
scanDepth = ScanDepth::MAIN_FULL;
lockedHeroes.clear();
dangerHitMap->reset();
2022-09-06 20:14:22 +02:00
useHeroChain = true;
objectClusterizer->reset();
2024-05-19 09:04:45 +02:00
if(!baseGraph && isObjectGraphAllowed())
{
baseGraph = std::make_unique<ObjectGraph>();
baseGraph->updateGraph(this);
}
}
2022-09-06 20:14:22 +02:00
void Nullkiller::updateAiState(int pass, bool fast)
{
2021-05-16 13:57:33 +02:00
boost::this_thread::interruption_point();
2023-12-17 10:11:05 +02:00
std::unique_lock<std::mutex> lockGuard(aiStateMutex);
2021-11-23 08:41:03 +02:00
auto start = std::chrono::high_resolution_clock::now();
activeHero = nullptr;
2023-02-28 09:07:59 +02:00
setTargetObject(-1);
decomposer->reset();
buildAnalyzer->update();
2022-09-06 20:14:22 +02:00
if(!fast)
{
memory->removeInvisibleObjects(cb.get());
2022-09-06 20:14:22 +02:00
dangerHitMap->updateHitMap();
dangerHitMap->calculateTileOwners();
2022-09-06 20:14:22 +02:00
boost::this_thread::interruption_point();
2021-05-16 13:57:33 +02:00
2022-09-06 20:14:22 +02:00
heroManager->update();
logAi->trace("Updating paths");
2022-09-06 20:14:22 +02:00
std::map<const CGHeroInstance *, HeroRole> activeHeroes;
2022-09-06 20:14:22 +02:00
for(auto hero : cb->getHeroesInfo())
{
if(getHeroLockedReason(hero) == HeroLockedReason::DEFENCE)
continue;
2022-09-06 20:14:22 +02:00
activeHeroes[hero] = heroManager->getHeroRole(hero);
}
2022-09-06 20:14:22 +02:00
PathfinderSettings cfg;
cfg.useHeroChain = useHeroChain;
cfg.allowBypassObjects = true;
2024-05-19 09:04:45 +02:00
if(scanDepth == ScanDepth::SMALL || isObjectGraphAllowed())
2022-09-06 20:14:22 +02:00
{
2024-03-08 14:39:16 +02:00
cfg.mainTurnDistanceLimit = settings->getMainHeroTurnDistanceLimit();
2023-07-27 14:58:49 +02:00
}
2024-05-19 09:04:45 +02:00
if(scanDepth != ScanDepth::ALL_FULL || isObjectGraphAllowed())
2023-07-27 14:58:49 +02:00
{
2024-03-08 14:39:16 +02:00
cfg.scoutTurnDistanceLimit =settings->getScoutHeroTurnDistanceLimit();
2022-09-06 20:14:22 +02:00
}
2021-05-16 14:01:34 +02:00
boost::this_thread::interruption_point();
2022-09-06 20:14:22 +02:00
pathfinder->updatePaths(activeHeroes, cfg);
2024-03-08 14:39:16 +02:00
2024-05-19 09:04:45 +02:00
if(isObjectGraphAllowed())
2024-03-08 14:39:16 +02:00
{
2024-03-29 20:39:03 +02:00
pathfinder->updateGraphs(
activeHeroes,
scanDepth == ScanDepth::SMALL ? 255 : 10,
scanDepth == ScanDepth::ALL_FULL ? 255 : 3);
2024-03-08 14:39:16 +02:00
}
boost::this_thread::interruption_point();
2022-09-06 20:14:22 +02:00
objectClusterizer->clusterize();
}
2022-09-06 20:14:22 +02:00
armyManager->update();
logAi->debug("AI state updated in %ld", timeElapsed(start));
}
bool Nullkiller::isHeroLocked(const CGHeroInstance * hero) const
{
return getHeroLockedReason(hero) != HeroLockedReason::NOT_LOCKED;
}
bool Nullkiller::arePathHeroesLocked(const AIPath & path) const
{
if(getHeroLockedReason(path.targetHero) == HeroLockedReason::STARTUP)
{
2022-09-26 20:01:07 +02:00
#if NKAI_TRACE_LEVEL >= 1
2023-02-28 09:07:59 +02:00
logAi->trace("Hero %s is locked by STARTUP. Discarding %s", path.targetHero->getObjectName(), path.toString());
#endif
return true;
}
for(auto & node : path.nodes)
{
auto lockReason = getHeroLockedReason(node.targetHero);
if(lockReason != HeroLockedReason::NOT_LOCKED)
{
2022-09-26 20:01:07 +02:00
#if NKAI_TRACE_LEVEL >= 1
2023-02-28 09:07:59 +02:00
logAi->trace("Hero %s is locked by STARTUP. Discarding %s", path.targetHero->getObjectName(), path.toString());
#endif
return true;
}
}
return false;
}
HeroLockedReason Nullkiller::getHeroLockedReason(const CGHeroInstance * hero) const
{
auto found = lockedHeroes.find(hero);
return found != lockedHeroes.end() ? found->second : HeroLockedReason::NOT_LOCKED;
}
void Nullkiller::makeTurn()
{
boost::lock_guard<boost::mutex> sharedStorageLock(AISharedStorage::locker);
const int MAX_DEPTH = 10;
const float FAST_TASK_MINIMAL_PRIORITY = 0.7f;
resetAiState();
Goals::TGoalVec bestTasks;
2024-04-20 09:33:37 +02:00
for(int i = 1; i <= settings->getMaxPass() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME; i++)
{
2024-03-31 17:39:00 +02:00
auto start = std::chrono::high_resolution_clock::now();
updateAiState(i);
2021-05-15 18:23:42 +02:00
2022-09-06 20:14:22 +02:00
Goals::TTask bestTask = taskptr(Goals::Invalid());
2023-07-27 14:58:49 +02:00
2024-02-25 12:39:19 +02:00
for(;i <= settings->getMaxPass(); i++)
2022-09-06 20:14:22 +02:00
{
bestTasks.clear();
decompose(bestTasks, sptr(BuyArmyBehavior()), 1);
decompose(bestTasks, sptr(BuildingBehavior()), 1);
2022-09-06 20:14:22 +02:00
bestTask = choseBestTask(bestTasks);
2022-09-06 20:14:22 +02:00
2023-02-28 09:07:59 +02:00
if(bestTask->priority >= FAST_TASK_MINIMAL_PRIORITY)
2022-09-06 20:14:22 +02:00
{
if(!executeTask(bestTask))
return;
2022-09-06 20:14:22 +02:00
updateAiState(i, true);
}
2023-07-27 14:58:49 +02:00
else
{
break;
}
}
2022-09-06 20:14:22 +02:00
decompose(bestTasks, sptr(RecruitHeroBehavior()), 1);
decompose(bestTasks, sptr(CaptureObjectsBehavior()), 1);
decompose(bestTasks, sptr(ClusterBehavior()), MAX_DEPTH);
decompose(bestTasks, sptr(DefenceBehavior()), MAX_DEPTH);
decompose(bestTasks, sptr(GatherArmyBehavior()), MAX_DEPTH);
decompose(bestTasks, sptr(StayAtTownBehavior()), MAX_DEPTH);
2024-05-19 09:04:45 +02:00
if(!isOpenMap())
decompose(bestTasks, sptr(ExplorationBehavior()), MAX_DEPTH);
2024-05-12 20:10:31 +02:00
if(cb->getDate(Date::DAY) == 1 || heroManager->getHeroRoles().empty())
2021-05-15 21:02:52 +02:00
{
decompose(bestTasks, sptr(StartupBehavior()), 1);
2021-05-15 21:02:52 +02:00
}
auto selectedTasks = buildPlan(bestTasks);
2022-09-06 20:14:22 +02:00
logAi->debug("Decission madel in %ld", timeElapsed(start));
2022-09-06 20:14:22 +02:00
if(selectedTasks.empty())
{
return;
}
2022-09-06 20:14:22 +02:00
bool hasAnySuccess = false;
2022-09-06 20:14:22 +02:00
for(auto bestTask : selectedTasks)
2021-05-16 14:01:34 +02:00
{
2024-04-20 09:33:37 +02:00
if(cb->getPlayerStatus(playerID) != EPlayerStatus::INGAME)
return;
2024-04-27 09:57:30 +02:00
if(!areAffectedObjectsPresent(bestTask))
{
logAi->debug("Affected object not found. Canceling task.");
continue;
}
2024-04-27 09:57:30 +02:00
std::string taskDescription = bestTask->toString();
HeroRole heroRole = getTaskRole(bestTask);
if(heroRole != HeroRole::MAIN || bestTask->getHeroExchangeCount() <= 1)
useHeroChain = false;
2024-03-29 20:39:03 +02:00
// TODO: better to check turn distance here instead of priority
if((heroRole != HeroRole::MAIN || bestTask->priority < SMALL_SCAN_MIN_PRIORITY)
&& scanDepth == ScanDepth::MAIN_FULL)
2023-07-27 14:58:49 +02:00
{
useHeroChain = false;
scanDepth = ScanDepth::SMALL;
2024-03-29 20:39:03 +02:00
logAi->trace(
"Goal %s has low priority %f so decreasing scan depth to gain performance.",
2024-03-29 20:39:03 +02:00
taskDescription,
bestTask->priority);
}
if(bestTask->priority < MIN_PRIORITY)
{
auto heroes = cb->getHeroesInfo();
auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool
{
return h->movementPointsRemaining() > 100;
});
if(hasMp && scanDepth != ScanDepth::ALL_FULL)
{
logAi->trace(
"Goal %s has too low priority %f so increasing scan depth to full.",
taskDescription,
bestTask->priority);
scanDepth = ScanDepth::ALL_FULL;
useHeroChain = false;
hasAnySuccess = true;
break;;
}
logAi->trace("Goal %s has too low priority. It is not worth doing it.", taskDescription);
2023-07-27 14:58:49 +02:00
2024-03-29 20:39:03 +02:00
continue;
2023-07-27 14:58:49 +02:00
}
if(!executeTask(bestTask))
{
if(hasAnySuccess)
break;
else
return;
}
hasAnySuccess = true;
}
if(!hasAnySuccess)
{
logAi->trace("Nothing was done this turn. Ending turn.");
return;
}
2023-08-05 12:49:49 +02:00
2024-02-25 12:39:19 +02:00
if(i == settings->getMaxPass())
2023-08-05 12:49:49 +02:00
{
logAi->warn("Maxpass exceeded. Terminating AI turn.");
2023-08-05 12:49:49 +02:00
}
2022-09-06 20:14:22 +02:00
}
}
2021-05-16 14:08:39 +02:00
2024-04-27 09:57:30 +02:00
bool Nullkiller::areAffectedObjectsPresent(Goals::TTask task) const
{
auto affectedObjs = task->getAffectedObjects();
for(auto oid : affectedObjs)
{
if(!cb->getObj(oid, false))
return false;
}
return true;
}
HeroRole Nullkiller::getTaskRole(Goals::TTask task) const
{
HeroPtr hero = task->getHero();
HeroRole heroRole = HeroRole::MAIN;
if(hero.validAndSet())
heroRole = heroManager->getHeroRole(hero);
return heroRole;
}
bool Nullkiller::executeTask(Goals::TTask task)
2022-09-06 20:14:22 +02:00
{
auto start = std::chrono::high_resolution_clock::now();
2022-09-06 20:14:22 +02:00
std::string taskDescr = task->toString();
2022-09-06 20:14:22 +02:00
boost::this_thread::interruption_point();
logAi->debug("Trying to realize %s (value %2.3f)", taskDescr, task->priority);
2022-09-06 20:14:22 +02:00
try
{
2024-03-31 17:39:00 +02:00
task->accept(gateway);
logAi->trace("Task %s completed in %lld", taskDescr, timeElapsed(start));
2022-09-06 20:14:22 +02:00
}
catch(goalFulfilledException &)
{
logAi->trace("Task %s completed in %lld", taskDescr, timeElapsed(start));
2022-09-06 20:14:22 +02:00
}
2022-11-03 21:16:49 +02:00
catch(cannotFulfillGoalException & e)
2022-09-06 20:14:22 +02:00
{
2023-02-28 09:07:59 +02:00
logAi->error("Failed to realize subgoal of type %s, I will stop.", taskDescr);
logAi->error("The error message was: %s", e.what());
2022-09-06 20:14:22 +02:00
return false;
}
return true;
2021-05-16 14:39:38 +02:00
}
TResources Nullkiller::getFreeResources() const
{
auto freeRes = cb->getResourceAmount() - lockedResources;
freeRes.positive();
return freeRes;
}
void Nullkiller::lockResources(const TResources & res)
{
lockedResources += res;
2021-11-23 08:41:03 +02:00
}
2022-09-26 20:01:07 +02:00
}