mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-06 09:09:40 +02:00
Nullkiller: object clusterizer
This commit is contained in:
committed by
Andrii Danylchenko
parent
1d4a349c2c
commit
0705ee595a
256
AI/Nullkiller/Analyzers/ObjectClusterizer.cpp
Normal file
256
AI/Nullkiller/Analyzers/ObjectClusterizer.cpp
Normal file
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* DangerHitMapAnalyzer.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 "ObjectClusterizer.h"
|
||||
#include "../Goals/ExecuteHeroChain.h"
|
||||
#include "../VCAI.h"
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "lib/mapping/CMap.h" //for victory conditions
|
||||
|
||||
extern boost::thread_specific_ptr<CCallback> cb;
|
||||
extern boost::thread_specific_ptr<VCAI> ai;
|
||||
|
||||
void ObjectCluster::addObject(const CGObjectInstance * obj, const AIPath & path, float priority)
|
||||
{
|
||||
auto & info = objects[obj];
|
||||
|
||||
if(info.priority < priority)
|
||||
{
|
||||
info.priority = priority;
|
||||
info.movementCost = path.movementCost() - path.firstNode().cost;
|
||||
info.danger = path.targetObjectDanger;
|
||||
info.turn = path.turn();
|
||||
}
|
||||
}
|
||||
|
||||
const CGObjectInstance * ObjectCluster::calculateCenter() const
|
||||
{
|
||||
auto v = getObjects();
|
||||
auto tile = int3(0);
|
||||
float priority = 0;
|
||||
|
||||
for(auto pair : objects)
|
||||
{
|
||||
auto newPoint = pair.first->visitablePos();
|
||||
float newPriority = std::pow(pair.second.priority, 4); // lets make high priority targets more weghtful
|
||||
int3 direction = newPoint - tile;
|
||||
float priorityRatio = newPriority / (priority + newPriority);
|
||||
|
||||
tile += direction * priorityRatio;
|
||||
priority += newPriority;
|
||||
}
|
||||
|
||||
auto closestPair = *vstd::minElementByFun(objects, [&](const std::pair<const CGObjectInstance *, ObjectInfo> & pair) -> int
|
||||
{
|
||||
return pair.first->visitablePos().dist2dSQ(tile);
|
||||
});
|
||||
|
||||
return closestPair.first;
|
||||
}
|
||||
|
||||
std::vector<const CGObjectInstance *> ObjectCluster::getObjects() const
|
||||
{
|
||||
std::vector<const CGObjectInstance *> result;
|
||||
|
||||
for(auto pair : objects)
|
||||
{
|
||||
result.push_back(pair.first);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<const CGObjectInstance *> ObjectClusterizer::getNearbyObjects() const
|
||||
{
|
||||
return nearObjects.getObjects();
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<ObjectCluster>> ObjectClusterizer::getLockedClusters() const
|
||||
{
|
||||
std::vector<std::shared_ptr<ObjectCluster>> result;
|
||||
|
||||
for(auto pair : blockedObjects)
|
||||
{
|
||||
result.push_back(pair.second);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) const
|
||||
{
|
||||
for(auto node = path.nodes.rbegin(); node != path.nodes.rend(); node++)
|
||||
{
|
||||
auto guardPos = cb->getGuardingCreaturePosition(node->coord);
|
||||
auto blockers = cb->getVisitableObjs(node->coord);
|
||||
|
||||
if(guardPos.valid())
|
||||
{
|
||||
auto guard = cb->getTopObj(cb->getGuardingCreaturePosition(node->coord));
|
||||
|
||||
if(guard)
|
||||
{
|
||||
blockers.insert(blockers.begin(), guard);
|
||||
}
|
||||
}
|
||||
|
||||
if(blockers.empty())
|
||||
continue;
|
||||
|
||||
auto blocker = blockers.front();
|
||||
|
||||
if(blocker->ID == Obj::GARRISON
|
||||
|| blocker->ID == Obj::MONSTER
|
||||
|| blocker->ID == Obj::GARRISON2
|
||||
|| blocker->ID == Obj::BORDERGUARD
|
||||
|| blocker->ID == Obj::QUEST_GUARD
|
||||
|| blocker->ID == Obj::BORDER_GATE)
|
||||
{
|
||||
return blocker;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool shouldVisitObject(const CGObjectInstance * obj)
|
||||
{
|
||||
if(isObjectRemovable(obj))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const int3 pos = obj->visitablePos();
|
||||
|
||||
if(obj->ID != Obj::CREATURE_GENERATOR1 && vstd::contains(ai->alreadyVisited, obj)
|
||||
|| obj->wasVisited(ai->playerID))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto playerRelations = cb->getPlayerRelations(ai->playerID, obj->tempOwner);
|
||||
|
||||
if(playerRelations != PlayerRelations::ENEMIES && !isWeeklyRevisitable(obj))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//it may be hero visiting this obj
|
||||
//we don't try visiting object on which allied or owned hero stands
|
||||
// -> it will just trigger exchange windows and AI will be confused that obj behind doesn't get visited
|
||||
const CGObjectInstance * topObj = cb->getTopObj(pos);
|
||||
|
||||
if(!topObj)
|
||||
return false; // partly visible obj but its visitable pos is not visible.
|
||||
|
||||
if(topObj->ID == Obj::HERO && cb->getPlayerRelations(ai->playerID, topObj->tempOwner) != PlayerRelations::ENEMIES)
|
||||
return false;
|
||||
else
|
||||
return true; //all of the following is met
|
||||
}
|
||||
|
||||
void ObjectClusterizer::clusterize()
|
||||
{
|
||||
nearObjects.reset();
|
||||
farObjects.reset();
|
||||
blockedObjects.clear();
|
||||
|
||||
Obj ignoreObjects[] = {
|
||||
Obj::MONSTER,
|
||||
Obj::SIGN,
|
||||
Obj::REDWOOD_OBSERVATORY,
|
||||
Obj::MONOLITH_TWO_WAY,
|
||||
Obj::MONOLITH_ONE_WAY_ENTRANCE,
|
||||
Obj::MONOLITH_ONE_WAY_EXIT,
|
||||
Obj::BUOY
|
||||
};
|
||||
|
||||
logAi->debug("Begin object clusterization");
|
||||
|
||||
for(const CGObjectInstance * obj : ai->visitableObjs)
|
||||
{
|
||||
if(!shouldVisitObject(obj))
|
||||
continue;
|
||||
|
||||
auto paths = ai->ah->getPathsToTile(obj->visitablePos());
|
||||
|
||||
if(paths.empty())
|
||||
continue;
|
||||
|
||||
std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool
|
||||
{
|
||||
return p1.movementCost() < p2.movementCost();
|
||||
});
|
||||
|
||||
if(vstd::contains(ignoreObjects, obj->ID))
|
||||
{
|
||||
farObjects.addObject(obj, paths.front(), 0);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
bool added = false;
|
||||
bool directlyAccessible = false;
|
||||
|
||||
for(auto & path : paths)
|
||||
{
|
||||
if(path.nodes.size() > 1)
|
||||
{
|
||||
auto blocker = getBlocker(path);
|
||||
|
||||
if(blocker)
|
||||
{
|
||||
auto cluster = blockedObjects[blocker];
|
||||
|
||||
if(!cluster)
|
||||
{
|
||||
cluster.reset(new ObjectCluster(blocker));
|
||||
blockedObjects[blocker] = cluster;
|
||||
}
|
||||
|
||||
if(!vstd::contains(cluster->objects, obj))
|
||||
{
|
||||
float priority = ai->nullkiller->priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)));
|
||||
|
||||
cluster->addObject(obj, path, priority);
|
||||
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
directlyAccessible = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!added || directlyAccessible)
|
||||
{
|
||||
if(paths.front().turn() <= 2)
|
||||
nearObjects.addObject(obj, paths.front(), 0);
|
||||
else
|
||||
farObjects.addObject(obj, paths.front(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
logAi->trace("Near objects count: %i", nearObjects.objects.size());
|
||||
logAi->trace("Far objects count: %i", farObjects.objects.size());
|
||||
for(auto pair : blockedObjects)
|
||||
{
|
||||
logAi->trace("Cluster %s %s count: %i", pair.first->getObjectName(), pair.first->visitablePos().toString(), pair.second->objects.size());
|
||||
|
||||
#if AI_TRACE_LEVEL >= 1
|
||||
for(auto obj : pair.second->getObjects())
|
||||
{
|
||||
logAi->trace("Object %s %s", obj->getObjectName(), obj->visitablePos().toString());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
65
AI/Nullkiller/Analyzers/ObjectClusterizer.h
Normal file
65
AI/Nullkiller/Analyzers/ObjectClusterizer.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* DangerHitMapAnalyzer.h, 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
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../Pathfinding/AINodeStorage.h"
|
||||
|
||||
struct ObjectInfo
|
||||
{
|
||||
float priority;
|
||||
float movementCost;
|
||||
uint64_t danger;
|
||||
uint8_t turn;
|
||||
};
|
||||
|
||||
struct ObjectCluster
|
||||
{
|
||||
public:
|
||||
std::map<const CGObjectInstance *, ObjectInfo> objects;
|
||||
const CGObjectInstance * blocker;
|
||||
|
||||
void reset()
|
||||
{
|
||||
objects.clear();
|
||||
}
|
||||
|
||||
void addObject(const CGObjectInstance * object, const AIPath & path, float priority);
|
||||
|
||||
ObjectCluster(const CGObjectInstance * blocker)
|
||||
:objects(), blocker(blocker)
|
||||
{
|
||||
}
|
||||
|
||||
ObjectCluster() : ObjectCluster(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<const CGObjectInstance *> getObjects() const;
|
||||
const CGObjectInstance * calculateCenter() const;
|
||||
};
|
||||
|
||||
class ObjectClusterizer
|
||||
{
|
||||
private:
|
||||
ObjectCluster nearObjects;
|
||||
ObjectCluster farObjects;
|
||||
std::map<const CGObjectInstance *, std::shared_ptr<ObjectCluster>> blockedObjects;
|
||||
|
||||
public:
|
||||
void clusterize();
|
||||
std::vector<const CGObjectInstance *> getNearbyObjects() const;
|
||||
std::vector<std::shared_ptr<ObjectCluster>> getLockedClusters() const;
|
||||
const CGObjectInstance * getBlocker(const AIPath & path) const;
|
||||
|
||||
ObjectClusterizer()
|
||||
:nearObjects(), farObjects(), blockedObjects()
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -50,6 +50,102 @@ bool CaptureObjectsBehavior::operator==(const CaptureObjectsBehavior & other) co
|
||||
&& vectorEquals(objectSubTypes, other.objectSubTypes);
|
||||
}
|
||||
|
||||
Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath> & paths, const CGObjectInstance * objToVisit)
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
|
||||
tasks.reserve(paths.size());
|
||||
|
||||
const AIPath * closestWay = nullptr;
|
||||
std::vector<ExecuteHeroChain *> waysToVisitObj;
|
||||
|
||||
for(auto & path : paths)
|
||||
{
|
||||
tasks.push_back(sptr(Goals::Invalid()));
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Path found %s", path.toString());
|
||||
#endif
|
||||
|
||||
if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
|
||||
{
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
if(objToVisit && !shouldVisit(path.targetHero, objToVisit))
|
||||
continue;
|
||||
|
||||
auto hero = path.targetHero;
|
||||
auto danger = path.getTotalDanger();
|
||||
|
||||
if(ai->ah->getHeroRole(hero) == HeroRole::SCOUT && danger == 0 && path.exchangeCount > 1)
|
||||
continue;
|
||||
|
||||
auto firstBlockedAction = path.getFirstBlockedAction();
|
||||
if(firstBlockedAction)
|
||||
{
|
||||
auto subGoal = firstBlockedAction->decompose(path.targetHero);
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Decomposing special action %s returns %s", firstBlockedAction->toString(), subGoal->toString());
|
||||
#endif
|
||||
|
||||
if(!subGoal->invalid())
|
||||
{
|
||||
Composition composition;
|
||||
|
||||
composition.addNext(ExecuteHeroChain(path, objToVisit));
|
||||
composition.addNext(subGoal);
|
||||
|
||||
tasks[tasks.size() - 1] = sptr(composition);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger);
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"It is %s to visit %s by %s with army %lld, danger %lld and army loss %lld",
|
||||
isSafe ? "safe" : "not safe",
|
||||
objToVisit ? objToVisit->getObjectName() : path.targetTile().toString(),
|
||||
hero->name,
|
||||
path.getHeroStrength(),
|
||||
danger,
|
||||
path.getTotalArmyLoss());
|
||||
#endif
|
||||
|
||||
if(isSafe)
|
||||
{
|
||||
auto newWay = new ExecuteHeroChain(path, objToVisit);
|
||||
TSubgoal sharedPtr;
|
||||
|
||||
sharedPtr.reset(newWay);
|
||||
|
||||
if(!closestWay || closestWay->movementCost() > path.movementCost())
|
||||
closestWay = &path;
|
||||
|
||||
if(!ai->nullkiller->arePathHeroesLocked(path))
|
||||
{
|
||||
waysToVisitObj.push_back(newWay);
|
||||
tasks[tasks.size() - 1] = sharedPtr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(auto way : waysToVisitObj)
|
||||
{
|
||||
way->closestWayRatio
|
||||
= closestWay->movementCost() / way->getPath().movementCost();
|
||||
}
|
||||
|
||||
return tasks;
|
||||
}
|
||||
|
||||
Goals::TGoalVec CaptureObjectsBehavior::decompose() const
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
@@ -80,89 +176,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const
|
||||
#if AI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Found %d paths", paths.size());
|
||||
#endif
|
||||
|
||||
for(auto & path : paths)
|
||||
{
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Path found %s", path.toString());
|
||||
#endif
|
||||
|
||||
if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
|
||||
{
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!shouldVisit(path.targetHero, objToVisit))
|
||||
continue;
|
||||
|
||||
auto hero = path.targetHero;
|
||||
auto danger = path.getTotalDanger();
|
||||
|
||||
if(ai->ah->getHeroRole(hero) == HeroRole::SCOUT && danger == 0 && path.exchangeCount > 1)
|
||||
continue;
|
||||
|
||||
auto firstBlockedAction = path.getFirstBlockedAction();
|
||||
if(firstBlockedAction)
|
||||
{
|
||||
auto subGoal = firstBlockedAction->decompose(path.targetHero);
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Decomposing special action %s returns %s", firstBlockedAction->toString(), subGoal->toString());
|
||||
#endif
|
||||
|
||||
if(!subGoal->invalid())
|
||||
{
|
||||
Composition composition;
|
||||
|
||||
composition.addNext(ExecuteHeroChain(path, objToVisit));
|
||||
composition.addNext(subGoal);
|
||||
|
||||
tasks.push_back(sptr(composition));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger);
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"It is %s to visit %s by %s with army %lld, danger %lld and army loss %lld",
|
||||
isSafe ? "safe" : "not safe",
|
||||
objToVisit->getObjectName(),
|
||||
hero->name,
|
||||
path.getHeroStrength(),
|
||||
danger,
|
||||
path.getTotalArmyLoss());
|
||||
#endif
|
||||
|
||||
if(isSafe)
|
||||
{
|
||||
auto newWay = std::make_shared<ExecuteHeroChain>(path, objToVisit);
|
||||
|
||||
waysToVisitObj.push_back(newWay);
|
||||
|
||||
if(!closestWay || closestWay->getPath().movementCost() > newWay->getPath().movementCost())
|
||||
closestWay = newWay;
|
||||
}
|
||||
}
|
||||
|
||||
if(waysToVisitObj.empty())
|
||||
continue;
|
||||
|
||||
for(auto way : waysToVisitObj)
|
||||
{
|
||||
if(ai->nullkiller->arePathHeroesLocked(way->getPath()))
|
||||
continue;
|
||||
|
||||
way->closestWayRatio
|
||||
= closestWay->getPath().movementCost() / way->getPath().movementCost();
|
||||
|
||||
tasks.push_back(sptr(*way));
|
||||
}
|
||||
vstd::concatenate(tasks, getVisitGoals(paths, objToVisit));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -172,57 +186,28 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const
|
||||
}
|
||||
else
|
||||
{
|
||||
captureObjects(std::vector<const CGObjectInstance*>(ai->visitableObjs.begin(), ai->visitableObjs.end()));
|
||||
captureObjects(ai->nullkiller->objectClusterizer->getNearbyObjects());
|
||||
}
|
||||
|
||||
vstd::erase_if(tasks, [](TSubgoal task) -> bool
|
||||
{
|
||||
return task->invalid();
|
||||
});
|
||||
|
||||
return tasks;
|
||||
}
|
||||
|
||||
bool CaptureObjectsBehavior::shouldVisitObject(ObjectIdRef obj) const
|
||||
bool CaptureObjectsBehavior::shouldVisitObject(const CGObjectInstance * obj) const
|
||||
{
|
||||
const CGObjectInstance* objInstance = obj;
|
||||
if(!objInstance)
|
||||
return false;
|
||||
|
||||
if(objectTypes.size() && !vstd::contains(objectTypes, objInstance->ID.num))
|
||||
if(objectTypes.size() && !vstd::contains(objectTypes, obj->ID.num))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(objectSubTypes.size() && !vstd::contains(objectSubTypes, objInstance->subID))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(isObjectRemovable(obj))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const int3 pos = objInstance->visitablePos();
|
||||
|
||||
if(objInstance->ID != Obj::CREATURE_GENERATOR1 && vstd::contains(ai->alreadyVisited, objInstance)
|
||||
|| obj->wasVisited(ai->playerID))
|
||||
if(objectSubTypes.size() && !vstd::contains(objectSubTypes, obj->subID))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto playerRelations = cb->getPlayerRelations(ai->playerID, objInstance->tempOwner);
|
||||
if(playerRelations != PlayerRelations::ENEMIES && !isWeeklyRevisitable(objInstance))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//it may be hero visiting this obj
|
||||
//we don't try visiting object on which allied or owned hero stands
|
||||
// -> it will just trigger exchange windows and AI will be confused that obj behind doesn't get visited
|
||||
const CGObjectInstance * topObj = cb->getTopObj(pos);
|
||||
|
||||
if(!topObj)
|
||||
return false; // partly visible obj but its visitable pos is not visible.
|
||||
|
||||
if(topObj->ID == Obj::HERO && cb->getPlayerRelations(ai->playerID, topObj->tempOwner) != PlayerRelations::ENEMIES)
|
||||
return false;
|
||||
else
|
||||
return true; //all of the following is met
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -64,8 +64,10 @@ namespace Goals
|
||||
|
||||
virtual bool operator==(const CaptureObjectsBehavior & other) const override;
|
||||
|
||||
static Goals::TGoalVec getVisitGoals(const std::vector<AIPath> & paths, const CGObjectInstance * objToVisit = nullptr);
|
||||
|
||||
private:
|
||||
bool shouldVisitObject(ObjectIdRef obj) const;
|
||||
bool shouldVisitObject(const CGObjectInstance * obj) const;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
113
AI/Nullkiller/Behaviors/ClusterBehavior.cpp
Normal file
113
AI/Nullkiller/Behaviors/ClusterBehavior.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* RecruitHeroBehavior.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 "ClusterBehavior.h"
|
||||
#include "../VCAI.h"
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../AIhelper.h"
|
||||
#include "../AIUtility.h"
|
||||
#include "../Goals/UnlockCluster.h"
|
||||
#include "../Goals/Composition.h"
|
||||
#include "../Behaviors/CaptureObjectsBehavior.h"
|
||||
|
||||
extern boost::thread_specific_ptr<CCallback> cb;
|
||||
extern boost::thread_specific_ptr<VCAI> ai;
|
||||
extern FuzzyHelper * fh;
|
||||
|
||||
using namespace Goals;
|
||||
|
||||
std::string ClusterBehavior::toString() const
|
||||
{
|
||||
return "Unlock Clusters";
|
||||
}
|
||||
|
||||
Goals::TGoalVec ClusterBehavior::decompose() const
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
auto clusters = ai->nullkiller->objectClusterizer->getLockedClusters();
|
||||
|
||||
for(auto cluster : clusters)
|
||||
{
|
||||
vstd::concatenate(tasks, decomposeCluster(cluster));
|
||||
}
|
||||
|
||||
return tasks;
|
||||
}
|
||||
|
||||
Goals::TGoalVec ClusterBehavior::decomposeCluster(std::shared_ptr<ObjectCluster> cluster) const
|
||||
{
|
||||
auto center = cluster->calculateCenter();
|
||||
auto paths = ai->ah->getPathsToTile(center->visitablePos());
|
||||
auto blockerPos = cluster->blocker->visitablePos();
|
||||
std::vector<AIPath> blockerPaths;
|
||||
|
||||
blockerPaths.reserve(paths.size());
|
||||
|
||||
TGoalVec goals;
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"Checking cluster %s, found %d paths",
|
||||
cluster->blocker->getObjectName(),
|
||||
cluster->blocker->visitablePos().toString(),
|
||||
paths.size());
|
||||
#endif
|
||||
|
||||
for(auto path = paths.begin(); path != paths.end();)
|
||||
{
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Checking path %s", path->toString());
|
||||
#endif
|
||||
|
||||
auto blocker = ai->nullkiller->objectClusterizer->getBlocker(*path);
|
||||
|
||||
if(blocker != cluster->blocker)
|
||||
{
|
||||
path = paths.erase(path);
|
||||
continue;
|
||||
}
|
||||
|
||||
blockerPaths.push_back(*path);
|
||||
|
||||
blockerPaths.back().nodes.clear();
|
||||
|
||||
for(auto node = path->nodes.rbegin(); node != path->nodes.rend(); node++)
|
||||
{
|
||||
blockerPaths.back().nodes.insert(blockerPaths.back().nodes.begin(), *node);
|
||||
|
||||
if(node->coord == blockerPos)
|
||||
break;
|
||||
}
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Unlock path found %s", blockerPaths.back().toString());
|
||||
#endif
|
||||
path++;
|
||||
}
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Decompose unlock paths");
|
||||
#endif
|
||||
|
||||
auto unlockTasks = CaptureObjectsBehavior::getVisitGoals(blockerPaths);
|
||||
|
||||
for(int i = 0; i < paths.size(); i++)
|
||||
{
|
||||
if(unlockTasks[i]->invalid())
|
||||
continue;
|
||||
|
||||
auto path = paths[i];
|
||||
auto elementarUnlock = sptr(UnlockCluster(cluster, path));
|
||||
|
||||
goals.push_back(sptr(Composition().addNext(elementarUnlock).addNext(unlockTasks[i])));
|
||||
}
|
||||
|
||||
return goals;
|
||||
}
|
||||
39
AI/Nullkiller/Behaviors/ClusterBehavior.h
Normal file
39
AI/Nullkiller/Behaviors/ClusterBehavior.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* RecruitHeroBehavior.h, 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
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "lib/VCMI_Lib.h"
|
||||
#include "../Goals/CGoal.h"
|
||||
#include "../AIUtility.h"
|
||||
|
||||
struct ObjectCluster;
|
||||
|
||||
namespace Goals
|
||||
{
|
||||
class ClusterBehavior : public CGoal<ClusterBehavior>
|
||||
{
|
||||
public:
|
||||
ClusterBehavior()
|
||||
:CGoal(CLUSTER_BEHAVIOR)
|
||||
{
|
||||
}
|
||||
|
||||
virtual TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
|
||||
virtual bool operator==(const ClusterBehavior & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
Goals::TGoalVec decomposeCluster(std::shared_ptr<ObjectCluster> cluster) const;
|
||||
};
|
||||
}
|
||||
@@ -32,6 +32,7 @@ set(VCAI_SRCS
|
||||
Goals/CollectRes.cpp
|
||||
Goals/Trade.cpp
|
||||
Goals/RecruitHero.cpp
|
||||
Goals/UnlockCluster.cpp
|
||||
Goals/DigAtTile.cpp
|
||||
Goals/GetArtOfType.cpp
|
||||
Goals/ExecuteHeroChain.cpp
|
||||
@@ -40,6 +41,7 @@ set(VCAI_SRCS
|
||||
Engine/PriorityEvaluator.cpp
|
||||
Analyzers/DangerHitMapAnalyzer.cpp
|
||||
Analyzers/BuildAnalyzer.cpp
|
||||
Analyzers/ObjectClusterizer.cpp
|
||||
Behaviors/CaptureObjectsBehavior.cpp
|
||||
Behaviors/RecruitHeroBehavior.cpp
|
||||
Behaviors/BuyArmyBehavior.cpp
|
||||
@@ -48,6 +50,7 @@ set(VCAI_SRCS
|
||||
Behaviors/BuildingBehavior.cpp
|
||||
Behaviors/GatherArmyBehavior.cpp
|
||||
Behaviors/CompleteQuestBehavior.cpp
|
||||
Behaviors/ClusterBehavior.cpp
|
||||
main.cpp
|
||||
VCAI.cpp
|
||||
)
|
||||
@@ -87,6 +90,7 @@ set(VCAI_HEADERS
|
||||
Goals/AdventureSpellCast.h
|
||||
Goals/CollectRes.h
|
||||
Goals/Trade.h
|
||||
Goals/UnlockCluster.h
|
||||
Goals/RecruitHero.h
|
||||
Goals/DigAtTile.h
|
||||
Goals/GetArtOfType.h
|
||||
@@ -97,6 +101,7 @@ set(VCAI_HEADERS
|
||||
Engine/PriorityEvaluator.h
|
||||
Analyzers/DangerHitMapAnalyzer.h
|
||||
Analyzers/BuildAnalyzer.h
|
||||
Analyzers/ObjectClusterizer.h
|
||||
Behaviors/CaptureObjectsBehavior.h
|
||||
Behaviors/RecruitHeroBehavior.h
|
||||
Behaviors/BuyArmyBehavior.h
|
||||
@@ -105,6 +110,7 @@ set(VCAI_HEADERS
|
||||
Behaviors/BuildingBehavior.h
|
||||
Behaviors/GatherArmyBehavior.h
|
||||
Behaviors/CompleteQuestBehavior.h
|
||||
Behaviors/ClusterBehavior.h
|
||||
VCAI.h
|
||||
)
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "../Behaviors/DefenceBehavior.h"
|
||||
#include "../Behaviors/BuildingBehavior.h"
|
||||
#include "../Behaviors/GatherArmyBehavior.h"
|
||||
#include "../Behaviors/ClusterBehavior.h"
|
||||
#include "../Goals/Invalid.h"
|
||||
|
||||
extern boost::thread_specific_ptr<CCallback> cb;
|
||||
@@ -30,6 +31,7 @@ Nullkiller::Nullkiller()
|
||||
priorityEvaluator.reset(new PriorityEvaluator());
|
||||
dangerHitMap.reset(new DangerHitMapAnalyzer());
|
||||
buildAnalyzer.reset(new BuildAnalyzer());
|
||||
objectClusterizer.reset(new ObjectClusterizer());
|
||||
}
|
||||
|
||||
Goals::TTask Nullkiller::choseBestTask(Goals::TTaskVec & tasks) const
|
||||
@@ -45,7 +47,7 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior) const
|
||||
{
|
||||
logAi->debug("Checking behavior %s", behavior->toString());
|
||||
|
||||
const int MAX_DEPTH = 0;
|
||||
const int MAX_DEPTH = 10;
|
||||
Goals::TGoalVec goals[MAX_DEPTH + 1];
|
||||
Goals::TTaskVec tasks;
|
||||
std::map<Goals::TSubgoal, Goals::TSubgoal> decompositionMap;
|
||||
@@ -152,6 +154,7 @@ void Nullkiller::updateAiState()
|
||||
ai->ah->updatePaths(activeHeroes, true);
|
||||
ai->ah->update();
|
||||
|
||||
objectClusterizer->clusterize();
|
||||
buildAnalyzer->update();
|
||||
}
|
||||
|
||||
@@ -204,6 +207,7 @@ void Nullkiller::makeTurn()
|
||||
Goals::TTaskVec bestTasks = {
|
||||
choseBestTask(sptr(BuyArmyBehavior())),
|
||||
choseBestTask(sptr(CaptureObjectsBehavior())),
|
||||
choseBestTask(sptr(ClusterBehavior())),
|
||||
choseBestTask(sptr(RecruitHeroBehavior())),
|
||||
choseBestTask(sptr(DefenceBehavior())),
|
||||
choseBestTask(sptr(BuildingBehavior())),
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "PriorityEvaluator.h"
|
||||
#include "../Analyzers/DangerHitMapAnalyzer.h"
|
||||
#include "../Analyzers/BuildAnalyzer.h"
|
||||
#include "../Analyzers/ObjectClusterizer.h"
|
||||
#include "../Goals/AbstractGoal.h"
|
||||
|
||||
const float MAX_GOLD_PEASURE = 0.3f;
|
||||
@@ -31,7 +32,6 @@ enum class HeroLockedReason
|
||||
class Nullkiller
|
||||
{
|
||||
private:
|
||||
std::unique_ptr<PriorityEvaluator> priorityEvaluator;
|
||||
const CGHeroInstance * activeHero;
|
||||
int3 targetTile;
|
||||
std::map<const CGHeroInstance *, HeroLockedReason> lockedHeroes;
|
||||
@@ -39,6 +39,8 @@ private:
|
||||
public:
|
||||
std::unique_ptr<DangerHitMapAnalyzer> dangerHitMap;
|
||||
std::unique_ptr<BuildAnalyzer> buildAnalyzer;
|
||||
std::unique_ptr<ObjectClusterizer> objectClusterizer;
|
||||
std::unique_ptr<PriorityEvaluator> priorityEvaluator;
|
||||
|
||||
Nullkiller();
|
||||
void makeTurn();
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "../AIhelper.h"
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../Goals/ExecuteHeroChain.h"
|
||||
#include "../Goals/UnlockCluster.h"
|
||||
|
||||
#define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter
|
||||
#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
|
||||
@@ -459,11 +460,18 @@ public:
|
||||
evaluationContext.movementCost += path.movementCost();
|
||||
evaluationContext.closestWayRatio = chain.closestWayRatio;
|
||||
|
||||
std::map<const CGHeroInstance *, float> costsPerHero;
|
||||
|
||||
for(auto & node : path.nodes)
|
||||
{
|
||||
auto role = ai->ah->getHeroRole(node.targetHero);
|
||||
vstd::amax(costsPerHero[node.targetHero], node.cost);
|
||||
}
|
||||
|
||||
evaluationContext.movementCostByRole[role] += node.cost;
|
||||
for(auto pair : costsPerHero)
|
||||
{
|
||||
auto role = ai->ah->getHeroRole(pair.first);
|
||||
|
||||
evaluationContext.movementCostByRole[role] += pair.second;
|
||||
}
|
||||
|
||||
auto heroPtr = task->hero;
|
||||
@@ -484,6 +492,56 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class ClusterEvaluationContextBuilder : public IEvaluationContextBuilder
|
||||
{
|
||||
public:
|
||||
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
{
|
||||
if(task->goalType != Goals::UNLOCK_CLUSTER)
|
||||
return;
|
||||
|
||||
Goals::UnlockCluster & clusterGoal = dynamic_cast<Goals::UnlockCluster &>(*task);
|
||||
std::shared_ptr<ObjectCluster> cluster = clusterGoal.getCluster();
|
||||
|
||||
auto hero = clusterGoal.hero.get();
|
||||
auto role = ai->ah->getHeroRole(clusterGoal.hero);
|
||||
|
||||
std::vector<std::pair<const CGObjectInstance *, ObjectInfo>> objects(cluster->objects.begin(), cluster->objects.end());
|
||||
|
||||
std::sort(objects.begin(), objects.end(), [](std::pair<const CGObjectInstance *, ObjectInfo> o1, std::pair<const CGObjectInstance *, ObjectInfo> o2) -> bool
|
||||
{
|
||||
return o1.second.priority > o2.second.priority;
|
||||
});
|
||||
|
||||
int boost = 1;
|
||||
|
||||
for(auto objInfo : objects)
|
||||
{
|
||||
auto target = objInfo.first;
|
||||
auto day = cb->getDate(Date::DAY);
|
||||
bool checkGold = objInfo.second.danger == 0;
|
||||
auto army = hero;
|
||||
|
||||
evaluationContext.goldReward += getGoldReward(target, hero) / boost;
|
||||
evaluationContext.armyReward += getArmyReward(target, hero, army, checkGold) / boost;
|
||||
evaluationContext.skillReward += getSkillReward(target, hero, role) / boost;
|
||||
evaluationContext.strategicalValue += getStrategicalValue(target) / boost;
|
||||
evaluationContext.goldCost += getGoldCost(target, hero, army) / boost;
|
||||
evaluationContext.movementCostByRole[role] += objInfo.second.movementCost / boost;
|
||||
evaluationContext.movementCost += objInfo.second.movementCost / boost;
|
||||
|
||||
boost <<= 1;
|
||||
|
||||
if(boost > 8)
|
||||
break;
|
||||
}
|
||||
|
||||
const AIPath & pathToCenter = clusterGoal.getPathToCenter();
|
||||
|
||||
vstd::amax(evaluationContext.turn, pathToCenter.turn());
|
||||
}
|
||||
};
|
||||
|
||||
class BuildThisEvaluationContextBuilder : public IEvaluationContextBuilder
|
||||
{
|
||||
public:
|
||||
@@ -529,6 +587,7 @@ PriorityEvaluator::PriorityEvaluator()
|
||||
initVisitTile();
|
||||
evaluationContextBuilders.push_back(std::make_shared<ExecuteHeroChainEvaluationContextBuilder>());
|
||||
evaluationContextBuilders.push_back(std::make_shared<BuildThisEvaluationContextBuilder>());
|
||||
evaluationContextBuilders.push_back(std::make_shared<ClusterEvaluationContextBuilder>());
|
||||
}
|
||||
|
||||
EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal) const
|
||||
@@ -545,14 +604,14 @@ EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal
|
||||
parts.push_back(goal);
|
||||
}
|
||||
|
||||
for(auto goal : parts)
|
||||
for(auto subgoal : parts)
|
||||
{
|
||||
context.strategicalValue += goal->strategicalValue;
|
||||
context.goldCost += goal->goldCost;
|
||||
context.strategicalValue += subgoal->strategicalValue;
|
||||
context.goldCost += subgoal->goldCost;
|
||||
|
||||
for(auto builder : evaluationContextBuilders)
|
||||
{
|
||||
builder->buildEvaluationContext(context, goal);
|
||||
builder->buildEvaluationContext(context, subgoal);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,9 @@ namespace Goals
|
||||
EXECUTE_HERO_CHAIN,
|
||||
EXCHANGE_SWAP_TOWN_HEROES,
|
||||
DISMISS_HERO,
|
||||
COMPOSITION
|
||||
COMPOSITION,
|
||||
CLUSTER_BEHAVIOR,
|
||||
UNLOCK_CLUSTER
|
||||
};
|
||||
|
||||
class DLL_EXPORT TSubgoal : public std::shared_ptr<AbstractGoal>
|
||||
|
||||
@@ -70,16 +70,16 @@ TGoalVec Composition::decompose() const
|
||||
Composition & other = dynamic_cast<Composition &>(*goal);
|
||||
bool cancel = false;
|
||||
|
||||
for(auto goal : other.subtasks)
|
||||
for(auto subgoal : other.subtasks)
|
||||
{
|
||||
if(goal == last || vstd::contains(tasks, goal))
|
||||
if(subgoal == last || vstd::contains(tasks, subgoal))
|
||||
{
|
||||
cancel = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
newComposition.addNext(goal);
|
||||
newComposition.addNext(subgoal);
|
||||
}
|
||||
|
||||
if(cancel)
|
||||
@@ -90,6 +90,11 @@ TGoalVec Composition::decompose() const
|
||||
newComposition.addNext(goal);
|
||||
}
|
||||
|
||||
if(newComposition.isElementar() && !newComposition.hero.validAndSet())
|
||||
{
|
||||
logAi->error("Bad");
|
||||
}
|
||||
|
||||
result.push_back(sptr(newComposition));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* FindObj.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 "FindObj.h"
|
||||
#include "../VCAI.h"
|
||||
#include "../AIUtility.h"
|
||||
|
||||
extern boost::thread_specific_ptr<CCallback> cb;
|
||||
extern boost::thread_specific_ptr<VCAI> ai;
|
||||
extern FuzzyHelper * fh;
|
||||
|
||||
using namespace Goals;
|
||||
|
||||
bool FindObj::operator==(const FindObj & other) const
|
||||
{
|
||||
return other.hero.h == hero.h && other.objid == objid;
|
||||
}
|
||||
//
|
||||
//TSubgoal FindObj::whatToDoToAchieve()
|
||||
//{
|
||||
// const CGObjectInstance * o = nullptr;
|
||||
// if(resID > -1) //specified
|
||||
// {
|
||||
// for(const CGObjectInstance * obj : ai->visitableObjs)
|
||||
// {
|
||||
// if(obj->ID == objid && obj->subID == resID)
|
||||
// {
|
||||
// o = obj;
|
||||
// break; //TODO: consider multiple objects and choose best
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// for(const CGObjectInstance * obj : ai->visitableObjs)
|
||||
// {
|
||||
// if(obj->ID == objid)
|
||||
// {
|
||||
// o = obj;
|
||||
// break; //TODO: consider multiple objects and choose best
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if(o && ai->isAccessible(o->pos)) //we don't use isAccessibleForHero as we don't know which hero it is
|
||||
// return sptr(VisitObj(o->id.getNum()));
|
||||
//}
|
||||
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* FindObj.h, 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
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "CGoal.h"
|
||||
|
||||
#error not supported
|
||||
30
AI/Nullkiller/Goals/UnlockCluster.cpp
Normal file
30
AI/Nullkiller/Goals/UnlockCluster.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* FindObj.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 "UnlockCluster.h"
|
||||
#include "../VCAI.h"
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../AIUtility.h"
|
||||
|
||||
extern boost::thread_specific_ptr<CCallback> cb;
|
||||
extern boost::thread_specific_ptr<VCAI> ai;
|
||||
extern FuzzyHelper * fh;
|
||||
|
||||
using namespace Goals;
|
||||
|
||||
bool UnlockCluster::operator==(const UnlockCluster & other) const
|
||||
{
|
||||
return other.tile == tile;
|
||||
}
|
||||
|
||||
std::string UnlockCluster::toString() const
|
||||
{
|
||||
return "Unlock Cluster " + cluster->blocker->getObjectName() + tile.toString();
|
||||
}
|
||||
41
AI/Nullkiller/Goals/UnlockCluster.h
Normal file
41
AI/Nullkiller/Goals/UnlockCluster.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* FindObj.h, 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
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "CGoal.h"
|
||||
#include "../Analyzers/ObjectClusterizer.h"
|
||||
|
||||
|
||||
struct HeroPtr;
|
||||
class VCAI;
|
||||
class FuzzyHelper;
|
||||
|
||||
namespace Goals
|
||||
{
|
||||
class DLL_EXPORT UnlockCluster : public CGoal<UnlockCluster>
|
||||
{
|
||||
private:
|
||||
std::shared_ptr<ObjectCluster> cluster;
|
||||
AIPath pathToCenter;
|
||||
|
||||
public:
|
||||
UnlockCluster(std::shared_ptr<ObjectCluster> cluster, const AIPath & pathToCenter)
|
||||
: CGoal(Goals::UNLOCK_CLUSTER), cluster(cluster), pathToCenter(pathToCenter)
|
||||
{
|
||||
tile = cluster->blocker->visitablePos();
|
||||
sethero(pathToCenter.targetHero);
|
||||
}
|
||||
|
||||
virtual bool operator==(const UnlockCluster & other) const override;
|
||||
virtual std::string toString() const override;
|
||||
std::shared_ptr<ObjectCluster> getCluster() const { return cluster; }
|
||||
const AIPath & getPathToCenter() { return pathToCenter; }
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user