2021-05-15 21:04:26 +02:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*
|
|
|
|
*/
|
2021-05-15 18:23:05 +02:00
|
|
|
#include "StdInc.h"
|
|
|
|
#include "Nullkiller.h"
|
2021-05-16 14:39:38 +02:00
|
|
|
#include "../AIGateway.h"
|
2021-05-15 18:23:11 +02:00
|
|
|
#include "../Behaviors/CaptureObjectsBehavior.h"
|
|
|
|
#include "../Behaviors/RecruitHeroBehavior.h"
|
2021-05-15 20:57:31 +02:00
|
|
|
#include "../Behaviors/BuyArmyBehavior.h"
|
2021-05-15 21:04:26 +02:00
|
|
|
#include "../Behaviors/StartupBehavior.h"
|
2021-05-16 12:53:32 +02:00
|
|
|
#include "../Behaviors/DefenceBehavior.h"
|
2021-05-16 13:15:03 +02:00
|
|
|
#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"
|
2021-05-15 18:23:11 +02:00
|
|
|
#include "../Goals/Invalid.h"
|
2021-05-16 13:56:42 +02:00
|
|
|
#include "../Goals/Composition.h"
|
2021-05-15 18:23:11 +02:00
|
|
|
|
|
|
|
extern boost::thread_specific_ptr<CCallback> cb;
|
2021-05-16 14:39:38 +02:00
|
|
|
extern boost::thread_specific_ptr<AIGateway> ai;
|
2021-05-15 18:23:11 +02:00
|
|
|
|
2021-05-16 13:38:26 +02:00
|
|
|
using namespace Goals;
|
|
|
|
|
2021-05-16 13:56:21 +02:00
|
|
|
#if AI_TRACE_LEVEL >= 1
|
|
|
|
#define MAXPASS 1000000
|
|
|
|
#else
|
|
|
|
#define MAXPASS 30
|
|
|
|
#endif
|
|
|
|
|
2021-05-15 18:23:38 +02:00
|
|
|
Nullkiller::Nullkiller()
|
|
|
|
{
|
2020-05-04 17:58:43 +02:00
|
|
|
memory.reset(new AIMemory());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Nullkiller::init(std::shared_ptr<CCallback> cb, PlayerColor playerID)
|
|
|
|
{
|
|
|
|
this->cb = cb;
|
|
|
|
this->playerID = playerID;
|
|
|
|
|
|
|
|
priorityEvaluator.reset(new PriorityEvaluator(this));
|
2021-05-16 14:08:56 +02:00
|
|
|
priorityEvaluators.reset(
|
|
|
|
new SharedPool<PriorityEvaluator>(
|
|
|
|
[&]()->std::unique_ptr<PriorityEvaluator>
|
|
|
|
{
|
2021-05-16 14:10:57 +02:00
|
|
|
return make_unique<PriorityEvaluator>(this);
|
2021-05-16 14:08:56 +02:00
|
|
|
}));
|
|
|
|
|
2020-05-04 17:58:43 +02:00
|
|
|
dangerHitMap.reset(new DangerHitMapAnalyzer(this));
|
2021-05-16 13:57:33 +02:00
|
|
|
buildAnalyzer.reset(new BuildAnalyzer(this));
|
2020-05-04 17:58:43 +02:00
|
|
|
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));
|
2021-05-16 13:56:42 +02:00
|
|
|
decomposer.reset(new DeepDecomposer());
|
2021-05-15 18:23:38 +02:00
|
|
|
}
|
|
|
|
|
2021-05-16 13:38:26 +02:00
|
|
|
Goals::TTask Nullkiller::choseBestTask(Goals::TTaskVec & tasks) const
|
2021-05-15 18:23:11 +02:00
|
|
|
{
|
2021-05-16 13:38:26 +02:00
|
|
|
Goals::TTask bestTask = *vstd::maxElementByFun(tasks, [](Goals::TTask task) -> float{
|
|
|
|
return task->priority;
|
2021-05-15 18:23:11 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
return bestTask;
|
|
|
|
}
|
|
|
|
|
2021-05-16 13:56:42 +02:00
|
|
|
Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior, int decompositionMaxDepth) const
|
2021-05-15 18:23:11 +02:00
|
|
|
{
|
2021-05-15 20:57:44 +02:00
|
|
|
logAi->debug("Checking behavior %s", behavior->toString());
|
2021-05-15 20:27:22 +02:00
|
|
|
|
2021-11-23 08:41:03 +02:00
|
|
|
auto start = std::chrono::high_resolution_clock::now();
|
2021-05-16 13:56:42 +02:00
|
|
|
|
|
|
|
Goals::TGoalVec elementarGoals = decomposer->decompose(behavior, decompositionMaxDepth);
|
|
|
|
Goals::TTaskVec tasks;
|
2021-05-16 13:57:33 +02:00
|
|
|
|
|
|
|
boost::this_thread::interruption_point();
|
2021-05-16 13:56:42 +02:00
|
|
|
|
|
|
|
for(auto goal : elementarGoals)
|
2021-05-15 18:23:38 +02:00
|
|
|
{
|
2021-05-16 13:56:42 +02:00
|
|
|
Goals::TTask task = Goals::taskptr(*goal);
|
2021-05-16 13:38:53 +02:00
|
|
|
|
2021-05-16 13:56:42 +02:00
|
|
|
if(task->priority <= 0)
|
|
|
|
task->priority = priorityEvaluator->evaluate(goal);
|
2021-05-16 13:38:26 +02:00
|
|
|
|
2021-05-16 13:56:42 +02:00
|
|
|
tasks.push_back(task);
|
2021-05-15 18:23:38 +02:00
|
|
|
}
|
|
|
|
|
2021-05-16 13:38:53 +02:00
|
|
|
if(tasks.empty())
|
|
|
|
{
|
2021-05-16 13:56:13 +02:00
|
|
|
logAi->debug("Behavior %s found no tasks. Time taken %ld", behavior->toString(), timeElapsed(start));
|
2021-05-16 13:38:53 +02:00
|
|
|
|
|
|
|
return Goals::taskptr(Goals::Invalid());
|
|
|
|
}
|
|
|
|
|
2021-05-15 18:23:38 +02:00
|
|
|
auto task = choseBestTask(tasks);
|
|
|
|
|
2021-05-16 13:56:13 +02:00
|
|
|
logAi->debug(
|
|
|
|
"Behavior %s returns %s, priority %f. Time taken %ld",
|
|
|
|
behavior->toString(),
|
|
|
|
task->toString(),
|
|
|
|
task->priority,
|
|
|
|
timeElapsed(start));
|
2021-05-15 18:23:38 +02:00
|
|
|
|
|
|
|
return task;
|
2021-05-15 18:23:11 +02:00
|
|
|
}
|
2021-05-15 18:23:05 +02:00
|
|
|
|
2021-05-15 20:57:27 +02:00
|
|
|
void Nullkiller::resetAiState()
|
|
|
|
{
|
2021-05-16 14:39:38 +02:00
|
|
|
lockedResources = TResources();
|
2021-05-16 14:01:34 +02:00
|
|
|
scanDepth = ScanDepth::SMALL;
|
2020-05-04 17:58:43 +02:00
|
|
|
playerID = ai->playerID;
|
2021-05-15 20:57:27 +02:00
|
|
|
lockedHeroes.clear();
|
2021-05-16 13:13:35 +02:00
|
|
|
dangerHitMap->reset();
|
2021-05-15 20:57:27 +02:00
|
|
|
}
|
|
|
|
|
2021-05-16 13:56:21 +02:00
|
|
|
void Nullkiller::updateAiState(int pass)
|
2021-05-15 20:57:27 +02:00
|
|
|
{
|
2021-05-16 13:57:33 +02:00
|
|
|
boost::this_thread::interruption_point();
|
|
|
|
|
2021-11-23 08:41:03 +02:00
|
|
|
auto start = std::chrono::high_resolution_clock::now();
|
2021-05-16 13:56:13 +02:00
|
|
|
|
2021-05-16 13:22:41 +02:00
|
|
|
activeHero = nullptr;
|
|
|
|
|
2020-05-04 17:58:43 +02:00
|
|
|
memory->removeInvisibleObjects(cb.get());
|
2021-05-16 13:59:32 +02:00
|
|
|
|
2021-05-16 13:13:35 +02:00
|
|
|
dangerHitMap->updateHitMap();
|
2021-05-15 21:03:58 +02:00
|
|
|
|
2021-05-16 13:57:33 +02:00
|
|
|
boost::this_thread::interruption_point();
|
|
|
|
|
2021-05-16 13:56:13 +02:00
|
|
|
heroManager->update();
|
|
|
|
logAi->trace("Updating paths");
|
|
|
|
|
|
|
|
std::map<const CGHeroInstance *, HeroRole> activeHeroes;
|
2021-05-15 20:57:27 +02:00
|
|
|
|
2021-05-16 13:56:13 +02:00
|
|
|
for(auto hero : cb->getHeroesInfo())
|
2021-05-16 13:15:03 +02:00
|
|
|
{
|
2021-05-16 13:56:13 +02:00
|
|
|
if(getHeroLockedReason(hero) == HeroLockedReason::DEFENCE)
|
|
|
|
continue;
|
2021-05-16 13:13:56 +02:00
|
|
|
|
2021-05-16 13:56:13 +02:00
|
|
|
activeHeroes[hero] = heroManager->getHeroRole(hero);
|
|
|
|
}
|
2021-05-15 20:57:27 +02:00
|
|
|
|
2021-05-16 13:56:21 +02:00
|
|
|
PathfinderSettings cfg;
|
|
|
|
cfg.useHeroChain = true;
|
2021-05-16 13:56:27 +02:00
|
|
|
cfg.scoutTurnDistanceLimit = SCOUT_TURN_DISTANCE_LIMIT;
|
2021-05-16 13:56:21 +02:00
|
|
|
|
2021-05-16 14:01:34 +02:00
|
|
|
if(scanDepth != ScanDepth::FULL)
|
|
|
|
{
|
|
|
|
cfg.mainTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT * ((int)scanDepth + 1);
|
|
|
|
}
|
|
|
|
|
2021-05-16 13:56:21 +02:00
|
|
|
pathfinder->updatePaths(activeHeroes, cfg);
|
2021-05-16 13:56:13 +02:00
|
|
|
|
2020-05-04 17:58:43 +02:00
|
|
|
armyManager->update();
|
2021-05-16 13:15:03 +02:00
|
|
|
|
2021-05-16 13:45:12 +02:00
|
|
|
objectClusterizer->clusterize();
|
2021-05-16 13:15:03 +02:00
|
|
|
buildAnalyzer->update();
|
2021-05-16 13:56:42 +02:00
|
|
|
decomposer->reset();
|
2021-05-16 13:56:13 +02:00
|
|
|
|
|
|
|
logAi->debug("AI state updated in %ld", timeElapsed(start));
|
2021-05-15 20:57:27 +02:00
|
|
|
}
|
|
|
|
|
2021-05-16 13:22:41 +02:00
|
|
|
bool Nullkiller::isHeroLocked(const CGHeroInstance * hero) const
|
|
|
|
{
|
|
|
|
return getHeroLockedReason(hero) != HeroLockedReason::NOT_LOCKED;
|
|
|
|
}
|
|
|
|
|
2021-05-16 13:13:56 +02:00
|
|
|
bool Nullkiller::arePathHeroesLocked(const AIPath & path) const
|
|
|
|
{
|
2021-05-16 13:22:41 +02:00
|
|
|
if(getHeroLockedReason(path.targetHero) == HeroLockedReason::STARTUP)
|
|
|
|
{
|
|
|
|
#if AI_TRACE_LEVEL >= 1
|
|
|
|
logAi->trace("Hero %s is locked by STARTUP. Discarding %s", path.targetHero->name, path.toString());
|
|
|
|
#endif
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-05-16 13:13:56 +02:00
|
|
|
for(auto & node : path.nodes)
|
|
|
|
{
|
2021-05-16 13:22:41 +02:00
|
|
|
auto lockReason = getHeroLockedReason(node.targetHero);
|
|
|
|
|
|
|
|
if(lockReason != HeroLockedReason::NOT_LOCKED)
|
|
|
|
{
|
|
|
|
#if AI_TRACE_LEVEL >= 1
|
|
|
|
logAi->trace("Hero %s is locked by STARTUP. Discarding %s", path.targetHero->name, path.toString());
|
|
|
|
#endif
|
2021-05-16 13:13:56 +02:00
|
|
|
return true;
|
2021-05-16 13:22:41 +02:00
|
|
|
}
|
2021-05-16 13:13:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-05-16 13:22:41 +02:00
|
|
|
HeroLockedReason Nullkiller::getHeroLockedReason(const CGHeroInstance * hero) const
|
|
|
|
{
|
|
|
|
auto found = lockedHeroes.find(hero);
|
|
|
|
|
|
|
|
return found != lockedHeroes.end() ? found->second : HeroLockedReason::NOT_LOCKED;
|
|
|
|
}
|
|
|
|
|
2021-05-15 18:23:05 +02:00
|
|
|
void Nullkiller::makeTurn()
|
|
|
|
{
|
2021-05-16 13:56:42 +02:00
|
|
|
const int MAX_DEPTH = 10;
|
|
|
|
|
2021-05-15 20:57:27 +02:00
|
|
|
resetAiState();
|
|
|
|
|
2021-05-16 13:56:21 +02:00
|
|
|
for(int i = 1; i <= MAXPASS; i++)
|
2021-05-15 18:23:11 +02:00
|
|
|
{
|
2021-05-16 13:56:21 +02:00
|
|
|
updateAiState(i);
|
2021-05-15 18:23:42 +02:00
|
|
|
|
2021-05-16 13:38:26 +02:00
|
|
|
Goals::TTaskVec bestTasks = {
|
2021-05-16 13:56:42 +02:00
|
|
|
choseBestTask(sptr(BuyArmyBehavior()), 1),
|
|
|
|
choseBestTask(sptr(CaptureObjectsBehavior()), 1),
|
|
|
|
choseBestTask(sptr(ClusterBehavior()), MAX_DEPTH),
|
|
|
|
choseBestTask(sptr(RecruitHeroBehavior()), 1),
|
|
|
|
choseBestTask(sptr(DefenceBehavior()), MAX_DEPTH),
|
|
|
|
choseBestTask(sptr(BuildingBehavior()), 1),
|
|
|
|
choseBestTask(sptr(GatherArmyBehavior()), MAX_DEPTH)
|
2021-05-15 18:23:11 +02:00
|
|
|
};
|
|
|
|
|
2021-05-15 21:02:52 +02:00
|
|
|
if(cb->getDate(Date::DAY) == 1)
|
|
|
|
{
|
2021-05-16 13:56:42 +02:00
|
|
|
bestTasks.push_back(choseBestTask(sptr(StartupBehavior()), 1));
|
2021-05-15 21:02:52 +02:00
|
|
|
}
|
|
|
|
|
2021-05-16 13:38:26 +02:00
|
|
|
Goals::TTask bestTask = choseBestTask(bestTasks);
|
2021-05-16 14:01:34 +02:00
|
|
|
HeroPtr hero = bestTask->getHero();
|
|
|
|
|
|
|
|
if(bestTask->priority < NEXT_SCAN_MIN_PRIORITY
|
|
|
|
&& scanDepth != ScanDepth::FULL)
|
|
|
|
{
|
2021-05-16 14:08:56 +02:00
|
|
|
HeroRole heroRole = HeroRole::MAIN;
|
|
|
|
|
|
|
|
if(hero.validAndSet())
|
|
|
|
heroRole = heroManager->getHeroRole(hero);
|
|
|
|
|
2021-05-16 14:39:38 +02:00
|
|
|
if(heroRole == HeroRole::MAIN || bestTask->priority < MIN_PRIORITY)
|
2021-05-16 14:08:56 +02:00
|
|
|
{
|
|
|
|
logAi->trace(
|
|
|
|
"Goal %s has too low priority %f so increasing scan depth",
|
|
|
|
bestTask->toString(),
|
|
|
|
bestTask->priority);
|
|
|
|
scanDepth = (ScanDepth)((int)scanDepth + 1);
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
2021-05-16 14:01:34 +02:00
|
|
|
}
|
2021-05-15 18:23:11 +02:00
|
|
|
|
2021-05-16 13:19:00 +02:00
|
|
|
if(bestTask->priority < MIN_PRIORITY)
|
|
|
|
{
|
2021-05-16 13:38:26 +02:00
|
|
|
logAi->trace("Goal %s has too low priority. It is not worth doing it. Ending turn.", bestTask->toString());
|
2021-05-16 13:19:00 +02:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-05-16 14:08:39 +02:00
|
|
|
std::string taskDescr = bestTask->toString();
|
|
|
|
|
2021-05-16 13:57:33 +02:00
|
|
|
boost::this_thread::interruption_point();
|
2021-05-16 14:08:39 +02:00
|
|
|
logAi->debug("Trying to realize %s (value %2.3f)", taskDescr, bestTask->priority);
|
2021-05-15 18:23:11 +02:00
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
bestTask->accept(ai.get());
|
|
|
|
}
|
|
|
|
catch(goalFulfilledException &)
|
|
|
|
{
|
2021-05-16 13:38:26 +02:00
|
|
|
logAi->trace("Task %s completed", bestTask->toString());
|
2021-05-15 18:23:11 +02:00
|
|
|
}
|
2021-05-16 13:22:37 +02:00
|
|
|
catch(std::exception & e)
|
2021-05-15 18:23:11 +02:00
|
|
|
{
|
2021-05-16 14:08:39 +02:00
|
|
|
logAi->debug("Failed to realize subgoal of type %s, I will stop.", taskDescr);
|
2021-05-15 18:23:11 +02:00
|
|
|
logAi->debug("The error message was: %s", e.what());
|
2021-05-15 18:23:05 +02:00
|
|
|
|
2021-05-15 18:23:11 +02:00
|
|
|
return;
|
2021-05-16 13:22:37 +02:00
|
|
|
}
|
2021-05-15 18:23:11 +02:00
|
|
|
}
|
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
|
|
|
}
|