1
0
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:
Andrii Danylchenko
2021-05-16 14:45:12 +03:00
committed by Andrii Danylchenko
parent 1d4a349c2c
commit 0705ee595a
16 changed files with 744 additions and 202 deletions

View 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
}
}

View 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()
{
}
};

View File

@@ -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;
}

View File

@@ -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;
};
}

View 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;
}

View 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;
};
}

View File

@@ -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
)

View File

@@ -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())),

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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));
}

View File

@@ -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()));
//}

View File

@@ -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

View 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();
}

View 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; }
};
}