2021-05-15 22:04:26 +03: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 19:23:05 +03:00
|
|
|
#include "StdInc.h"
|
|
|
|
#include "Nullkiller.h"
|
|
|
|
#include "../VCAI.h"
|
2021-05-15 22:04:53 +03:00
|
|
|
#include "../AIhelper.h"
|
2021-05-15 19:23:11 +03:00
|
|
|
#include "../Behaviors/CaptureObjectsBehavior.h"
|
|
|
|
#include "../Behaviors/RecruitHeroBehavior.h"
|
2021-05-15 21:57:31 +03:00
|
|
|
#include "../Behaviors/BuyArmyBehavior.h"
|
2021-05-15 22:04:26 +03:00
|
|
|
#include "../Behaviors/StartupBehavior.h"
|
2021-05-16 13:53:32 +03:00
|
|
|
#include "../Behaviors/DefenceBehavior.h"
|
2021-05-16 14:15:03 +03:00
|
|
|
#include "../Behaviors/BuildingBehavior.h"
|
2021-05-16 14:19:07 +03:00
|
|
|
#include "../Behaviors/GatherArmyBehavior.h"
|
2021-05-16 14:45:12 +03:00
|
|
|
#include "../Behaviors/ClusterBehavior.h"
|
2021-05-15 19:23:11 +03:00
|
|
|
#include "../Goals/Invalid.h"
|
|
|
|
|
|
|
|
extern boost::thread_specific_ptr<CCallback> cb;
|
|
|
|
extern boost::thread_specific_ptr<VCAI> ai;
|
|
|
|
|
2021-05-16 14:38:26 +03:00
|
|
|
using namespace Goals;
|
|
|
|
|
2021-05-15 19:23:38 +03:00
|
|
|
Nullkiller::Nullkiller()
|
|
|
|
{
|
|
|
|
priorityEvaluator.reset(new PriorityEvaluator());
|
2021-05-15 21:57:36 +03:00
|
|
|
dangerHitMap.reset(new DangerHitMapAnalyzer());
|
2021-05-16 14:15:03 +03:00
|
|
|
buildAnalyzer.reset(new BuildAnalyzer());
|
2021-05-16 14:45:12 +03:00
|
|
|
objectClusterizer.reset(new ObjectClusterizer());
|
2021-05-15 19:23:38 +03:00
|
|
|
}
|
|
|
|
|
2021-05-16 14:38:26 +03:00
|
|
|
Goals::TTask Nullkiller::choseBestTask(Goals::TTaskVec & tasks) const
|
2021-05-15 19:23:11 +03:00
|
|
|
{
|
2021-05-16 14:38:26 +03:00
|
|
|
Goals::TTask bestTask = *vstd::maxElementByFun(tasks, [](Goals::TTask task) -> float{
|
|
|
|
return task->priority;
|
2021-05-15 19:23:11 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
return bestTask;
|
|
|
|
}
|
|
|
|
|
2021-05-16 14:38:26 +03:00
|
|
|
Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior) const
|
2021-05-15 19:23:11 +03:00
|
|
|
{
|
2021-05-15 21:57:44 +03:00
|
|
|
logAi->debug("Checking behavior %s", behavior->toString());
|
2021-05-15 21:27:22 +03:00
|
|
|
|
2021-05-16 14:45:12 +03:00
|
|
|
const int MAX_DEPTH = 10;
|
2021-05-16 14:38:26 +03:00
|
|
|
Goals::TGoalVec goals[MAX_DEPTH + 1];
|
|
|
|
Goals::TTaskVec tasks;
|
2021-05-16 14:44:48 +03:00
|
|
|
std::map<Goals::TSubgoal, Goals::TSubgoal> decompositionMap;
|
2021-05-16 14:38:26 +03:00
|
|
|
|
|
|
|
goals[0] = {behavior};
|
2021-05-15 19:23:11 +03:00
|
|
|
|
2021-05-16 14:38:26 +03:00
|
|
|
int depth = 0;
|
|
|
|
while(goals[0].size())
|
2021-05-15 19:23:38 +03:00
|
|
|
{
|
2021-05-16 14:38:26 +03:00
|
|
|
TSubgoal current = goals[depth].back();
|
2021-05-16 14:38:53 +03:00
|
|
|
|
|
|
|
#if AI_TRACE_LEVEL >= 1
|
|
|
|
logAi->trace("Decomposing %s, level: %d", current->toString(), depth);
|
|
|
|
#endif
|
|
|
|
|
2021-05-16 14:38:26 +03:00
|
|
|
TGoalVec subgoals = current->decompose();
|
|
|
|
|
2021-05-16 14:38:53 +03:00
|
|
|
#if AI_TRACE_LEVEL >= 1
|
|
|
|
logAi->trace("Found %d goals", subgoals.size());
|
|
|
|
#endif
|
|
|
|
|
2021-05-16 14:44:48 +03:00
|
|
|
if(depth < MAX_DEPTH)
|
|
|
|
{
|
|
|
|
goals[depth + 1].clear();
|
|
|
|
}
|
2021-05-16 14:38:26 +03:00
|
|
|
|
|
|
|
for(auto subgoal : subgoals)
|
|
|
|
{
|
2021-05-16 14:38:53 +03:00
|
|
|
if(subgoal->isElementar())
|
2021-05-16 14:38:26 +03:00
|
|
|
{
|
|
|
|
auto task = taskptr(*subgoal);
|
|
|
|
|
2021-05-16 14:38:53 +03:00
|
|
|
#if AI_TRACE_LEVEL >= 1
|
|
|
|
logAi->trace("Found task %s", task->toString());
|
|
|
|
#endif
|
|
|
|
|
2021-05-16 14:38:26 +03:00
|
|
|
if(task->priority <= 0)
|
|
|
|
task->priority = priorityEvaluator->evaluate(subgoal);
|
|
|
|
|
|
|
|
tasks.push_back(task);
|
|
|
|
}
|
2021-05-16 14:44:48 +03:00
|
|
|
else if(depth < MAX_DEPTH)
|
2021-05-16 14:38:26 +03:00
|
|
|
{
|
2021-05-16 14:38:53 +03:00
|
|
|
#if AI_TRACE_LEVEL >= 1
|
|
|
|
logAi->trace("Found abstract goal %s", subgoal->toString());
|
|
|
|
#endif
|
2021-05-16 14:38:26 +03:00
|
|
|
goals[depth + 1].push_back(subgoal);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-16 14:44:48 +03:00
|
|
|
if(depth < MAX_DEPTH && goals[depth + 1].size())
|
2021-05-16 14:38:26 +03:00
|
|
|
{
|
|
|
|
depth++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-05-16 14:38:53 +03:00
|
|
|
goals[depth].pop_back();
|
|
|
|
|
2021-05-16 14:38:26 +03:00
|
|
|
while(depth > 0 && goals[depth].empty())
|
|
|
|
{
|
|
|
|
depth--;
|
2021-05-16 14:38:53 +03:00
|
|
|
goals[depth].pop_back();
|
2021-05-16 14:38:26 +03:00
|
|
|
}
|
|
|
|
}
|
2021-05-15 19:23:38 +03:00
|
|
|
}
|
|
|
|
|
2021-05-16 14:38:53 +03:00
|
|
|
if(tasks.empty())
|
|
|
|
{
|
|
|
|
logAi->debug("Behavior %s found no tasks", behavior->toString());
|
|
|
|
|
|
|
|
return Goals::taskptr(Goals::Invalid());
|
|
|
|
}
|
|
|
|
|
2021-05-15 19:23:38 +03:00
|
|
|
auto task = choseBestTask(tasks);
|
|
|
|
|
2021-05-16 14:38:26 +03:00
|
|
|
logAi->debug("Behavior %s returns %s, priority %f", behavior->toString(), task->toString(), task->priority);
|
2021-05-15 19:23:38 +03:00
|
|
|
|
|
|
|
return task;
|
2021-05-15 19:23:11 +03:00
|
|
|
}
|
2021-05-15 19:23:05 +03:00
|
|
|
|
2021-05-15 21:57:27 +03:00
|
|
|
void Nullkiller::resetAiState()
|
|
|
|
{
|
|
|
|
lockedHeroes.clear();
|
2021-05-16 14:13:35 +03:00
|
|
|
dangerHitMap->reset();
|
2021-05-15 21:57:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void Nullkiller::updateAiState()
|
|
|
|
{
|
2021-05-16 14:22:41 +03:00
|
|
|
activeHero = nullptr;
|
|
|
|
|
2021-05-15 22:03:58 +03:00
|
|
|
ai->validateVisitableObjs();
|
2021-05-16 14:13:35 +03:00
|
|
|
dangerHitMap->updateHitMap();
|
2021-05-15 22:03:58 +03:00
|
|
|
|
2021-05-15 22:02:27 +03:00
|
|
|
// TODO: move to hero manager
|
2021-05-15 21:57:27 +03:00
|
|
|
auto activeHeroes = ai->getMyHeroes();
|
|
|
|
|
2021-05-16 14:15:03 +03:00
|
|
|
vstd::erase_if(activeHeroes, [this](const HeroPtr & hero) -> bool
|
|
|
|
{
|
2021-05-16 14:13:56 +03:00
|
|
|
auto lockedReason = getHeroLockedReason(hero.h);
|
|
|
|
|
2021-05-16 14:19:00 +03:00
|
|
|
return lockedReason == HeroLockedReason::DEFENCE;
|
2021-05-15 21:57:27 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
ai->ah->updatePaths(activeHeroes, true);
|
2021-05-16 14:15:03 +03:00
|
|
|
ai->ah->update();
|
|
|
|
|
2021-05-16 14:45:12 +03:00
|
|
|
objectClusterizer->clusterize();
|
2021-05-16 14:15:03 +03:00
|
|
|
buildAnalyzer->update();
|
2021-05-15 21:57:27 +03:00
|
|
|
}
|
|
|
|
|
2021-05-16 14:22:41 +03:00
|
|
|
bool Nullkiller::isHeroLocked(const CGHeroInstance * hero) const
|
|
|
|
{
|
|
|
|
return getHeroLockedReason(hero) != HeroLockedReason::NOT_LOCKED;
|
|
|
|
}
|
|
|
|
|
2021-05-16 14:13:56 +03:00
|
|
|
bool Nullkiller::arePathHeroesLocked(const AIPath & path) const
|
|
|
|
{
|
2021-05-16 14:22:41 +03: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 14:13:56 +03:00
|
|
|
for(auto & node : path.nodes)
|
|
|
|
{
|
2021-05-16 14:22:41 +03: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 14:13:56 +03:00
|
|
|
return true;
|
2021-05-16 14:22:41 +03:00
|
|
|
}
|
2021-05-16 14:13:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-05-16 14:22:41 +03: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 19:23:05 +03:00
|
|
|
void Nullkiller::makeTurn()
|
|
|
|
{
|
2021-05-15 21:57:27 +03:00
|
|
|
resetAiState();
|
|
|
|
|
2021-05-15 19:23:11 +03:00
|
|
|
while(true)
|
|
|
|
{
|
2021-05-15 21:57:27 +03:00
|
|
|
updateAiState();
|
2021-05-15 19:23:42 +03:00
|
|
|
|
2021-05-16 14:38:26 +03:00
|
|
|
Goals::TTaskVec bestTasks = {
|
|
|
|
choseBestTask(sptr(BuyArmyBehavior())),
|
|
|
|
choseBestTask(sptr(CaptureObjectsBehavior())),
|
2021-05-16 14:45:12 +03:00
|
|
|
choseBestTask(sptr(ClusterBehavior())),
|
2021-05-16 14:38:26 +03:00
|
|
|
choseBestTask(sptr(RecruitHeroBehavior())),
|
|
|
|
choseBestTask(sptr(DefenceBehavior())),
|
|
|
|
choseBestTask(sptr(BuildingBehavior())),
|
|
|
|
choseBestTask(sptr(GatherArmyBehavior()))
|
2021-05-15 19:23:11 +03:00
|
|
|
};
|
|
|
|
|
2021-05-15 22:02:52 +03:00
|
|
|
if(cb->getDate(Date::DAY) == 1)
|
|
|
|
{
|
2021-05-16 14:38:26 +03:00
|
|
|
bestTasks.push_back(choseBestTask(sptr(StartupBehavior())));
|
2021-05-15 22:02:52 +03:00
|
|
|
}
|
|
|
|
|
2021-05-16 14:38:26 +03:00
|
|
|
Goals::TTask bestTask = choseBestTask(bestTasks);
|
2021-05-15 19:23:11 +03:00
|
|
|
|
2021-05-16 14:38:26 +03:00
|
|
|
/*if(bestTask->invalid())
|
2021-05-15 19:23:11 +03:00
|
|
|
{
|
|
|
|
logAi->trace("No goals found. Ending turn.");
|
|
|
|
|
|
|
|
return;
|
2021-05-16 14:38:26 +03:00
|
|
|
}*/
|
2021-05-15 19:23:11 +03:00
|
|
|
|
2021-05-16 14:19:00 +03:00
|
|
|
if(bestTask->priority < MIN_PRIORITY)
|
|
|
|
{
|
2021-05-16 14:38:26 +03:00
|
|
|
logAi->trace("Goal %s has too low priority. It is not worth doing it. Ending turn.", bestTask->toString());
|
2021-05-16 14:19:00 +03:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-05-16 14:38:26 +03:00
|
|
|
logAi->debug("Trying to realize %s (value %2.3f)", bestTask->toString(), bestTask->priority);
|
2021-05-15 19:23:11 +03:00
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
bestTask->accept(ai.get());
|
|
|
|
}
|
|
|
|
catch(goalFulfilledException &)
|
|
|
|
{
|
2021-05-16 14:38:26 +03:00
|
|
|
logAi->trace("Task %s completed", bestTask->toString());
|
2021-05-15 19:23:11 +03:00
|
|
|
}
|
2021-05-16 14:22:37 +03:00
|
|
|
catch(std::exception & e)
|
2021-05-15 19:23:11 +03:00
|
|
|
{
|
2021-05-16 14:38:26 +03:00
|
|
|
logAi->debug("Failed to realize subgoal of type %s, I will stop.", bestTask->toString());
|
2021-05-15 19:23:11 +03:00
|
|
|
logAi->debug("The error message was: %s", e.what());
|
2021-05-15 19:23:05 +03:00
|
|
|
|
2021-05-15 19:23:11 +03:00
|
|
|
return;
|
2021-05-16 14:22:37 +03:00
|
|
|
}
|
2021-05-15 19:23:11 +03:00
|
|
|
}
|
2021-05-15 22:04:26 +03:00
|
|
|
}
|