1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-15 20:03:15 +02:00

Merge remote-tracking branch 'upstream/develop' into mp-disconnection

# Conflicts:
#	server/CVCMIServer.cpp
This commit is contained in:
nordsoft
2022-10-04 17:41:43 +04:00
271 changed files with 2659 additions and 1747 deletions

View File

@@ -23,7 +23,8 @@
#include "AIGateway.h"
#include "Goals/Goals.h"
class CGVisitableOPW;
namespace NKAI
{
const float SAFE_ATTACK_CONSTANT = 1.2;
@@ -766,10 +767,10 @@ void AIGateway::makeTurn()
logAi->debug("Making turn thread has been interrupted. We'll end without calling endTurn.");
return;
}
/*catch (std::exception & e)
catch (std::exception & e)
{
logAi->debug("Making turn thread has caught an exception: %s", e.what());
}*/
}
endTurn();
}
@@ -1668,3 +1669,5 @@ bool AIStatus::channelProbing()
{
return ongoingChannelProbing;
}
}

View File

@@ -25,11 +25,8 @@
#include "Pathfinding/AIPathfinder.h"
#include "Engine/Nullkiller.h"
VCMI_LIB_NAMESPACE_BEGIN
struct QuestInfo;
VCMI_LIB_NAMESPACE_END
namespace NKAI
{
class AIStatus
{
@@ -222,3 +219,5 @@ public:
h & battlename;
}
};
}

View File

@@ -20,6 +20,9 @@
#include "../../lib/CModHandler.h"
namespace NKAI
{
extern boost::thread_specific_ptr<AIGateway> ai;
//extern static const int3 dirs[8];
@@ -307,6 +310,9 @@ bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2
bool isWeeklyRevisitable(const CGObjectInstance * obj)
{
if(!obj)
return false;
//TODO: allow polling of remaining creatures in dwelling
if(dynamic_cast<const CGVisitableOPW *>(obj)) // ensures future compatibility, unlike IDs
return true;
@@ -443,3 +449,5 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
return true;
}
}

View File

@@ -48,19 +48,19 @@
#include "../../lib/mapObjects/CObjectHandler.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/CPathfinder.h"
#include "../../CCallback.h"
#include <chrono>
using namespace tbb;
class CCallback;
class Nullkiller;
struct creInfo;
typedef const int3 & crint3;
typedef const std::string & crstring;
typedef std::pair<ui32, std::vector<CreatureID>> dwellingContent;
namespace NKAI
{
struct creInfo;
class Nullkiller;
const int GOLD_MINE_PRODUCTION = 1000, WOOD_ORE_MINE_PRODUCTION = 2, RESOURCE_MINE_PRODUCTION = 1;
const int ACTUAL_RESOURCE_COUNT = 7;
const int ALLOWED_ROAMING_HEROES = 8;
@@ -151,39 +151,6 @@ struct ObjectIdRef
}
};
struct TimeCheck
{
CStopWatch time;
std::string txt;
TimeCheck(crstring TXT)
: txt(TXT)
{
}
~TimeCheck()
{
logAi->trace("Time of %s was %d ms.", txt, time.getDiff());
}
};
//TODO: replace with vstd::
struct AtScopeExit
{
std::function<void()> foo;
AtScopeExit(const std::function<void()> & FOO)
: foo(FOO)
{}
~AtScopeExit()
{
foo();
}
};
class ObjsVector : public std::vector<ObjectIdRef>
{
};
template<int id>
bool objWithID(const CGObjectInstance * obj)
{
@@ -278,7 +245,7 @@ uint64_t timeElapsed(std::chrono::time_point<std::chrono::high_resolution_clock>
bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObjectInstance * obj);
template<typename TFunc>
void pforeachTilePos(crint3 mapSize, TFunc fn)
void pforeachTilePos(const int3 & mapSize, TFunc fn)
{
for(int z = 0; z < mapSize.z; ++z)
{
@@ -380,3 +347,5 @@ private:
std::shared_ptr<SharedPool<T> *> instance_tracker;
boost::mutex sync;
};
}

View File

@@ -14,6 +14,8 @@
#include "../../../CCallback.h"
#include "../../../lib/mapObjects/MapObjects.h"
namespace NKAI
{
class StackUpgradeInfo
{
public:
@@ -497,3 +499,5 @@ ArmyUpgradeInfo ArmyManager::calculateCreaturesUpgrade(
return result;
}
}

View File

@@ -17,6 +17,9 @@
#include "../../../lib/CTownHandler.h"
#include "../../../lib/CBuildingHandler.h"
namespace NKAI
{
class Nullkiller;
struct SlotInfo
@@ -102,3 +105,5 @@ private:
std::vector<StackUpgradeInfo> getHillFortUpgrades(const CCreatureSet * army) const;
std::vector<StackUpgradeInfo> getDwellingUpgrades(const CCreatureSet * army, const CGDwelling * dwelling) const;
};
}

View File

@@ -12,6 +12,9 @@
#include "../../../lib/mapping/CMap.h" //for victory conditions
#include "../Engine/Nullkiller.h"
namespace NKAI
{
void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
{
auto townInfo = developmentInfo.town->town;
@@ -205,8 +208,6 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
logAi->trace("checking %s", info.name);
logAi->trace("buildInfo %s", info.toString());
buildPtr = nullptr;
if(!town->hasBuilt(building))
{
auto canBuild = ai->cb->canBuildStructure(town, building);
@@ -239,8 +240,6 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
}
else
{
buildPtr = townInfo->buildings.at(building);
logAi->trace("cant build. Need %d", missingBuildings[0].num);
BuildingInfo prerequisite = getBuildingOrPrerequisite(town, missingBuildings[0], excludeDwellingDependencies);
@@ -398,3 +397,5 @@ std::string BuildingInfo::toString() const
+ " x " + creatureCost.toString()
+ ", daily: " + dailyIncome.toString();
}
}

View File

@@ -12,6 +12,9 @@
#include "../AIUtility.h"
#include "../../../lib/ResourceSet.h"
namespace NKAI
{
class Nullkiller;
class DLL_EXPORT BuildingInfo
@@ -104,3 +107,5 @@ private:
void updateDailyIncome();
void reset();
};
}

View File

@@ -11,6 +11,9 @@
#include "lib/mapping/CMap.h" //for victory conditions
#include "../Engine/Nullkiller.h"
namespace NKAI
{
void DangerHitMapAnalyzer::updateHitMap()
{
if(upToDate)
@@ -137,3 +140,5 @@ void DangerHitMapAnalyzer::reset()
{
upToDate = false;
}
}

View File

@@ -11,6 +11,9 @@
#include "../AIUtility.h"
namespace NKAI
{
struct HitMapInfo
{
uint64_t danger;
@@ -55,3 +58,5 @@ public:
const std::set<const CGObjectInstance *> & getOneTurnAccessibleObjects(const CGHeroInstance * enemy) const;
void reset();
};
}

View File

@@ -13,6 +13,9 @@
#include "../../../lib/mapObjects/MapObjects.h"
#include "../../../lib/CHeroHandler.h"
namespace NKAI
{
SecondarySkillEvaluator HeroManager::wariorSkillsScores = SecondarySkillEvaluator(
{
std::make_shared<SecondarySkillScoreMap>(
@@ -305,3 +308,5 @@ float SecondarySkillEvaluator::evaluateSecSkill(const CGHeroInstance * hero, Sec
return score;
}
}

View File

@@ -17,6 +17,9 @@
#include "../../../lib/CTownHandler.h"
#include "../../../lib/CBuildingHandler.h"
namespace NKAI
{
class DLL_EXPORT IHeroManager //: public: IAbstractManager
{
public:
@@ -104,3 +107,5 @@ private:
public:
void evaluateScore(const CGHeroInstance * hero, SecondarySkill skill, float & score) const override;
};
}

View File

@@ -14,6 +14,9 @@
#include "../Engine/Nullkiller.h"
#include "lib/mapping/CMap.h" //for victory conditions
namespace NKAI
{
void ObjectCluster::addObject(const CGObjectInstance * obj, const AIPath & path, float priority)
{
ClusterObjects::accessor info;
@@ -120,18 +123,23 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons
auto blocker = blockers.front();
if(isObjectPassable(ai, blocker))
continue;
if(blocker->ID == Obj::GARRISON
|| blocker->ID == Obj::MONSTER
|| blocker->ID == Obj::GARRISON2
|| blocker->ID == Obj::BORDERGUARD
|| blocker->ID == Obj::BORDER_GATE
|| blocker->ID == Obj::SHIPYARD)
|| blocker->ID == Obj::GARRISON2)
{
if(!isObjectPassable(ai, blocker))
if(dynamic_cast<const CArmedInstance *>(blocker)->getArmyStrength() == 0)
continue;
else
return blocker;
}
if(blocker->ID == Obj::QUEST_GUARD && node->actionIsBlocked)
if(blocker->ID == Obj::MONSTER
|| blocker->ID == Obj::BORDERGUARD
|| blocker->ID == Obj::BORDER_GATE
|| blocker->ID == Obj::SHIPYARD
|| (blocker->ID == Obj::QUEST_GUARD && node->actionIsBlocked))
{
return blocker;
}
@@ -223,7 +231,7 @@ void ObjectClusterizer::clusterize()
if(!shouldVisitObject(obj))
return;
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString());
#endif
@@ -231,7 +239,7 @@ void ObjectClusterizer::clusterize()
if(paths.empty())
{
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("No paths found.");
#endif
continue;
@@ -246,7 +254,7 @@ void ObjectClusterizer::clusterize()
{
farObjects.addObject(obj, paths.front(), 0);
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Object ignored. Moved to far objects with path %s", paths.front().toString());
#endif
@@ -257,13 +265,13 @@ void ObjectClusterizer::clusterize()
for(auto & path : paths)
{
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Checking path %s", path.toString());
#endif
if(!shouldVisit(ai, path.targetHero, obj))
{
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Hero %s does not need to visit %s", path.targetHero->name, obj->getObjectName());
#endif
continue;
@@ -277,7 +285,7 @@ void ObjectClusterizer::clusterize()
{
if(vstd::contains(heroesProcessed, path.targetHero))
{
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Hero %s is already processed.", path.targetHero->name);
#endif
continue;
@@ -297,7 +305,7 @@ void ObjectClusterizer::clusterize()
cluster->second->addObject(obj, path, priority);
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Path added to cluster %s%s", blocker->getObjectName(), blocker->visitablePos().toString());
#endif
continue;
@@ -322,7 +330,7 @@ void ObjectClusterizer::clusterize()
farObjects.addObject(obj, path, priority);
}
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Path %s added to %s objects. Turn: %d, priority: %f",
path.toString(),
interestingObject ? "near" : "far",
@@ -340,7 +348,7 @@ void ObjectClusterizer::clusterize()
{
logAi->trace("Cluster %s %s count: %i", pair.first->getObjectName(), pair.first->visitablePos().toString(), pair.second->objects.size());
#if AI_TRACE_LEVEL >= 1
#if NKAI_TRACE_LEVEL >= 1
for(auto obj : pair.second->getObjects())
{
logAi->trace("Object %s %s", obj->getObjectName(), obj->visitablePos().toString());
@@ -350,3 +358,5 @@ void ObjectClusterizer::clusterize()
logAi->trace("Clusterization complete in %ld", timeElapsed(start));
}
}

View File

@@ -11,6 +11,9 @@
#include "../Pathfinding/AINodeStorage.h"
namespace NKAI
{
struct ClusterObjectInfo
{
float priority;
@@ -72,3 +75,5 @@ public:
private:
bool shouldVisitObject(const CGObjectInstance * obj) const;
};
}

View File

@@ -1,19 +0,0 @@
/*
* Behavior.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 "../AIGateway.h"
#error REMOVE THIS FILE
class Behavior : public Goals::AbstractGoal
{
public:
virtual std::string toString() const = 0;
};

View File

@@ -19,6 +19,9 @@
#include "lib/CPathfinder.h"
#include "../Engine/Nullkiller.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -77,3 +80,5 @@ Goals::TGoalVec BuildingBehavior::decompose() const
return tasks;
}
}

View File

@@ -13,6 +13,8 @@
#include "../AIUtility.h"
#include "../Goals/CGoal.h"
namespace NKAI
{
namespace Goals
{
class BuildingBehavior : public CGoal<BuildingBehavior>
@@ -32,3 +34,4 @@ namespace Goals
};
}
}

View File

@@ -16,6 +16,9 @@
#include "lib/mapping/CMap.h" //for victory conditions
#include "lib/CPathfinder.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -75,3 +78,5 @@ Goals::TGoalVec BuyArmyBehavior::decompose() const
return tasks;
}
}

View File

@@ -13,6 +13,8 @@
#include "../AIUtility.h"
#include "../Goals/CGoal.h"
namespace NKAI
{
namespace Goals
{
class BuyArmyBehavior : public CGoal<BuyArmyBehavior>
@@ -30,3 +32,5 @@ namespace Goals
}
};
}
}

View File

@@ -12,9 +12,13 @@
#include "../Engine/Nullkiller.h"
#include "../Goals/Composition.h"
#include "../Goals/ExecuteHeroChain.h"
#include "../Goals/Invalid.h"
#include "CaptureObjectsBehavior.h"
#include "../AIUtility.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -59,13 +63,13 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
{
tasks.push_back(sptr(Goals::Invalid()));
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Path found %s", path.toString());
#endif
if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
{
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
#endif
continue;
@@ -85,7 +89,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
{
auto subGoal = firstBlockedAction->decompose(path.targetHero);
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Decomposing special action %s returns %s", firstBlockedAction->toString(), subGoal->toString());
#endif
@@ -104,7 +108,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger);
#if AI_TRACE_LEVEL >= 2
#if NKAI_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",
@@ -133,6 +137,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
}
}
assert(closestWay || waysToVisitObj.empty());
for(auto way : waysToVisitObj)
{
way->closestWayRatio
@@ -160,7 +165,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const
if(!objectMatchesFilter(objToVisit))
continue;
#if AI_TRACE_LEVEL >= 1
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Checking object %s, %s", objToVisit->getObjectName(), objToVisit->visitablePos().toString());
#endif
@@ -170,7 +175,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const
std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj;
std::shared_ptr<ExecuteHeroChain> closestWay;
#if AI_TRACE_LEVEL >= 1
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Found %d paths", paths.size());
#endif
vstd::concatenate(tasks, getVisitGoals(paths, objToVisit));
@@ -218,3 +223,5 @@ bool CaptureObjectsBehavior::objectMatchesFilter(const CGObjectInstance * obj) c
return true;
}
}

View File

@@ -14,6 +14,8 @@
#include "../Goals/CGoal.h"
#include "../Pathfinding/AINodeStorage.h"
namespace NKAI
{
namespace Goals
{
class CaptureObjectsBehavior : public CGoal<CaptureObjectsBehavior>
@@ -72,3 +74,5 @@ namespace Goals
};
}
}

View File

@@ -16,6 +16,9 @@
#include "../Goals/Composition.h"
#include "../Behaviors/CaptureObjectsBehavior.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -50,7 +53,7 @@ Goals::TGoalVec ClusterBehavior::decomposeCluster(std::shared_ptr<ObjectCluster>
TGoalVec goals;
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace(
"Checking cluster %s %s, found %d paths",
cluster->blocker->getObjectName(),
@@ -60,7 +63,7 @@ Goals::TGoalVec ClusterBehavior::decomposeCluster(std::shared_ptr<ObjectCluster>
for(auto path = paths.begin(); path != paths.end();)
{
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Checking path %s", path->toString());
#endif
@@ -89,13 +92,13 @@ Goals::TGoalVec ClusterBehavior::decomposeCluster(std::shared_ptr<ObjectCluster>
for(auto & node : clonedPath.nodes)
node.parentIndex -= path->nodes.size() - clonedPath.nodes.size();
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Unlock path found %s", blockerPaths.back().toString());
#endif
path++;
}
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Decompose unlock paths");
#endif
@@ -114,3 +117,5 @@ Goals::TGoalVec ClusterBehavior::decomposeCluster(std::shared_ptr<ObjectCluster>
return goals;
}
}

View File

@@ -13,6 +13,9 @@
#include "../Goals/CGoal.h"
#include "../AIUtility.h"
namespace NKAI
{
struct ObjectCluster;
namespace Goals
@@ -37,3 +40,5 @@ namespace Goals
Goals::TGoalVec decomposeCluster(std::shared_ptr<ObjectCluster> cluster) const;
};
}
}

View File

@@ -23,6 +23,9 @@
#include "lib/mapping/CMap.h" //for victory conditions
#include "lib/CPathfinder.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -47,14 +50,14 @@ Goals::TGoalVec DefenceBehavior::decompose() const
void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town) const
{
logAi->debug("Evaluating defence for %s", town->name);
logAi->trace("Evaluating defence for %s", town->name);
auto treatNode = ai->nullkiller->dangerHitMap->getObjectTreat(town);
auto treats = { treatNode.fastestDanger, treatNode.maximumDanger };
if(!treatNode.fastestDanger.hero)
{
logAi->debug("No treat found for town %s", town->name);
logAi->trace("No treat found for town %s", town->name);
return;
}
@@ -73,7 +76,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
return;
}
logAi->debug(
logAi->trace(
"Hero %s in garrison of town %s is suposed to defend the town",
town->garrisonHero->name,
town->name);
@@ -85,7 +88,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
if(reinforcement)
{
logAi->debug("Town %s can buy defence army %lld", town->name, reinforcement);
logAi->trace("Town %s can buy defence army %lld", town->name, reinforcement);
tasks.push_back(Goals::sptr(Goals::BuyArmy(town, reinforcement).setpriority(0.5f)));
}
@@ -93,7 +96,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
for(auto & treat : treats)
{
logAi->debug(
logAi->trace(
"Town %s has treat %lld in %s turns, hero: %s",
town->name,
treat.danger,
@@ -104,6 +107,12 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
for(AIPath & path : paths)
{
if(town->visitingHero && path.targetHero != town->visitingHero.get())
continue;
if(town->visitingHero && path.getHeroStrength() < town->visitingHero->getHeroStrength())
continue;
if(path.getHeroStrength() > treat.danger)
{
if((path.turn() <= treat.turn && dayOfWeek + treat.turn < 6 && isSafeToVisit(path.targetHero, path.heroArmy, treat.danger))
@@ -111,11 +120,13 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|| path.turn() < treat.turn - 1
|| (path.turn() < treat.turn && treat.turn >= 2))
{
logAi->debug(
#if NKAI_TRACE_LEVEL >= 1
logAi->trace(
"Hero %s can eliminate danger for town %s using path %s.",
path.targetHero->name,
town->name,
path.toString());
#endif
treatIsUnderControl = true;
break;
@@ -140,7 +151,9 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
if(cb->getHeroesInfo().size() < ALLOWED_ROAMING_HEROES)
{
logAi->debug("Hero %s can be recruited to defend %s", hero->name, town->name);
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Hero %s can be recruited to defend %s", hero->name, town->name);
#endif
tasks.push_back(Goals::sptr(Goals::RecruitHero(town, hero).setpriority(1)));
continue;
}
@@ -174,7 +187,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
if(paths.empty())
{
logAi->debug("No ways to defend town %s", town->name);
logAi->trace("No ways to defend town %s", town->name);
continue;
}
@@ -186,7 +199,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
{
auto & path = paths[i];
#if AI_TRACE_LEVEL >= 1
#if NKAI_TRACE_LEVEL >= 1
logAi->trace(
"Hero %s can defend town with force %lld in %s turns, cost: %f, path: %s",
path.targetHero->name,
@@ -197,9 +210,11 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
#endif
if(path.turn() <= treat.turn - 2)
{
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Deffer defence of %s by %s because he has enough time to rich the town next trun",
town->name,
path.targetHero->name);
#endif
defferedPaths[path.targetHero].push_back(i);
@@ -208,7 +223,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
if(path.targetHero == town->visitingHero && path.exchangeCount == 1)
{
#if AI_TRACE_LEVEL >= 1
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Put %s to garrison of town %s",
path.targetHero->name,
town->name);
@@ -232,7 +247,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
{
if(ai->nullkiller->arePathHeroesLocked(path))
{
#if AI_TRACE_LEVEL >= 1
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Can not move %s to defend town %s. Path is locked.",
path.targetHero->name,
town->name);
@@ -260,7 +275,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
}
}
#if AI_TRACE_LEVEL >= 1
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Move %s to defend town %s",
path.targetHero->name,
town->name);
@@ -274,13 +289,13 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
{
auto subGoal = firstBlockedAction->decompose(path.targetHero);
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Decomposing special action %s returns %s", firstBlockedAction->toString(), subGoal->toString());
#endif
if(subGoal->invalid())
{
#if AI_TRACE_LEVEL >= 1
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Path is invalid. Skipping");
#endif
continue;
@@ -295,3 +310,5 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
logAi->debug("Found %d tasks", tasks.size());
}
}

View File

@@ -13,6 +13,8 @@
#include "../Goals/CGoal.h"
#include "../AIUtility.h"
namespace NKAI
{
namespace Goals
{
class DefenceBehavior : public CGoal<DefenceBehavior>
@@ -36,3 +38,5 @@ namespace Goals
};
}
}

View File

@@ -19,6 +19,9 @@
#include "lib/mapping/CMap.h" //for victory conditions
#include "lib/CPathfinder.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -64,12 +67,12 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
Goals::TGoalVec tasks;
const int3 pos = hero->visitablePos();
#if AI_TRACE_LEVEL >= 1
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Checking ways to gaher army for hero %s, %s", hero->getObjectName(), pos.toString());
#endif
if(ai->nullkiller->isHeroLocked(hero))
{
#if AI_TRACE_LEVEL >= 1
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Skipping locked hero %s, %s", hero->getObjectName(), pos.toString());
#endif
return tasks;
@@ -77,13 +80,13 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
auto paths = ai->nullkiller->pathfinder->getPathInfo(pos);
#if AI_TRACE_LEVEL >= 1
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Gather army found %d paths", paths.size());
#endif
for(const AIPath & path : paths)
{
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Path found %s", path.toString());
#endif
@@ -91,7 +94,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
{
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
#endif
continue;
@@ -99,7 +102,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
if(ai->nullkiller->arePathHeroesLocked(path))
{
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Ignore path because of locked hero");
#endif
continue;
@@ -112,7 +115,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
// avoid transferring very small amount of army
if(armyValue < 0.1f)
{
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Army value is too small.");
#endif
continue;
@@ -121,7 +124,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
// avoid trying to move bigger army to the weaker one.
if(armyValue > 1)
{
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Army value is too large.");
#endif
continue;
@@ -131,7 +134,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger);
#if AI_TRACE_LEVEL >= 2
#if NKAI_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",
@@ -156,7 +159,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
if(blockedAction)
{
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Action is blocked. Considering decomposition.");
#endif
composition.addNext(blockedAction->decompose(path.targetHero));
@@ -175,25 +178,25 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
const int3 pos = upgrader->visitablePos();
TResources availableResources = ai->nullkiller->getFreeResources();
#if AI_TRACE_LEVEL >= 1
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Checking ways to upgrade army in town %s, %s", upgrader->getObjectName(), pos.toString());
#endif
auto paths = ai->nullkiller->pathfinder->getPathInfo(pos);
std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj;
#if AI_TRACE_LEVEL >= 1
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Found %d paths", paths.size());
#endif
for(const AIPath & path : paths)
{
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Path found %s", path.toString());
#endif
if(upgrader->visitingHero != path.targetHero)
{
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Ignore path. Town has visiting hero.");
#endif
continue;
@@ -201,7 +204,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
if(ai->nullkiller->arePathHeroesLocked(path))
{
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Ignore path because of locked hero");
#endif
continue;
@@ -209,7 +212,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
if(path.getFirstBlockedAction())
{
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
// TODO: decomposition?
logAi->trace("Ignore path. Action is blocked.");
#endif
@@ -218,7 +221,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
{
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
#endif
continue;
@@ -234,7 +237,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
auto isSafe = isSafeToVisit(path.targetHero, path.heroArmy, danger);
#if AI_TRACE_LEVEL >= 2
#if NKAI_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",
@@ -257,3 +260,5 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
return tasks;
}
}

View File

@@ -13,6 +13,8 @@
#include "../Goals/CGoal.h"
#include "../AIUtility.h"
namespace NKAI
{
namespace Goals
{
class GatherArmyBehavior : public CGoal<GatherArmyBehavior>
@@ -37,3 +39,5 @@ namespace Goals
};
}
}

View File

@@ -16,6 +16,9 @@
#include "lib/mapping/CMap.h" //for victory conditions
#include "lib/CPathfinder.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -46,3 +49,5 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const
return tasks;
}
}

View File

@@ -13,6 +13,8 @@
#include "../Goals/CGoal.h"
#include "../AIUtility.h"
namespace NKAI
{
namespace Goals
{
class RecruitHeroBehavior : public CGoal<RecruitHeroBehavior>
@@ -32,3 +34,5 @@ namespace Goals
}
};
}
}

View File

@@ -20,6 +20,9 @@
#include "lib/CPathfinder.h"
#include "../Engine/Nullkiller.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -221,3 +224,5 @@ Goals::TGoalVec StartupBehavior::decompose() const
return tasks;
}
}

View File

@@ -13,6 +13,8 @@
#include "../Goals/CGoal.h"
#include "../AIUtility.h"
namespace NKAI
{
namespace Goals
{
class StartupBehavior : public CGoal<StartupBehavior>
@@ -33,3 +35,5 @@ namespace Goals
};
}
}

View File

@@ -11,6 +11,9 @@
#include "AIMemory.h"
#include "../../../CCallback.h"
namespace NKAI
{
void AIMemory::removeFromMemory(const CGObjectInstance * obj)
{
vstd::erase_if_present(visitableObjs, obj);
@@ -102,3 +105,5 @@ void AIMemory::removeInvisibleObjects(CCallback * cb)
vstd::erase_if(visitableObjs, shouldBeErased);
vstd::erase_if(alreadyVisited, shouldBeErased);
}
}

View File

@@ -12,6 +12,9 @@
#include "../AIUtility.h"
#include "../../../lib/mapObjects/MapObjects.h"
namespace NKAI
{
class AIMemory
{
public:
@@ -29,3 +32,5 @@ public:
bool wasVisited(const CGObjectInstance * obj) const;
void removeInvisibleObjects(CCallback * cb);
};
}

View File

@@ -21,6 +21,9 @@
#include "../Goals/Invalid.h"
#include "../Goals/Composition.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -49,7 +52,7 @@ Goals::TGoalVec DeepDecomposer::decompose(TSubgoal behavior, int depthLimit)
TSubgoal current = goals[depth].back();
TGoalVec subgoals = decomposeCached(unwrapComposition(current), fromCache);
#if AI_TRACE_LEVEL >= 1
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Decomposition level %d returned %d goals", depth, subgoals.size());
#endif
@@ -69,7 +72,7 @@ Goals::TGoalVec DeepDecomposer::decompose(TSubgoal behavior, int depthLimit)
// 0 - goals directly from behavior
Goals::TSubgoal task = depth >= 1 ? aggregateGoals(0, subgoal) : subgoal;
#if AI_TRACE_LEVEL >= 1
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Found task %s", task->toString());
#endif
if(!isCompositionLoop(subgoal))
@@ -84,7 +87,7 @@ Goals::TGoalVec DeepDecomposer::decompose(TSubgoal behavior, int depthLimit)
}
else if(depth < depthLimit - 1)
{
#if AI_TRACE_LEVEL >= 1
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Found abstract goal %s", subgoal->toString());
#endif
if(!isCompositionLoop(subgoal))
@@ -176,7 +179,7 @@ bool DeepDecomposer::isCompositionLoop(TSubgoal goal)
TGoalVec DeepDecomposer::decomposeCached(TSubgoal goal, bool & fromCache)
{
#if AI_TRACE_LEVEL >= 1
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Decomposing %s, level %s", goal->toString(), depth);
#endif
@@ -188,7 +191,7 @@ TGoalVec DeepDecomposer::decomposeCached(TSubgoal goal, bool & fromCache)
if(cached != decompositionCache[i].end())
{
#if AI_TRACE_LEVEL >= 1
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Use decomposition cache for %s, level: %d", goal->toString(), depth);
#endif
fromCache = true;
@@ -200,7 +203,7 @@ TGoalVec DeepDecomposer::decomposeCached(TSubgoal goal, bool & fromCache)
decompositionCache[depth][goal] = {}; // if goal decomposition yields no goals we still need it in cache to not decompose again
}
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Calling decompose on %s, level %s", goal->toString(), depth);
#endif
@@ -221,7 +224,7 @@ void DeepDecomposer::addToCache(TSubgoal goal)
{
auto solution = parentDepth < depth ? aggregateGoals(parentDepth + 1, goal) : goal;
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Adding %s to decomosition cache of %s at level %d", solution->toString(), parent->toString(), parentDepth);
#endif
@@ -235,3 +238,5 @@ void DeepDecomposer::addToCache(TSubgoal goal)
}
}
}
}

View File

@@ -11,6 +11,9 @@
#include "../Goals/AbstractGoal.h"
namespace NKAI
{
struct GoalHash
{
uint64_t operator()(const Goals::TSubgoal & goal) const
@@ -39,3 +42,5 @@ private:
Goals::TGoalVec decomposeCached(Goals::TSubgoal goal, bool & fromCache);
void addToCache(Goals::TSubgoal goal);
};
}

View File

@@ -14,6 +14,9 @@
#include "../../../lib/mapObjects/MapObjects.h"
#include "../AIGateway.h"
namespace NKAI
{
#define MIN_AI_STRENGTH (0.5f) //lower when combat AI gets smarter
#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
@@ -239,3 +242,5 @@ float TacticalAdvantageEngine::getTacticalAdvantage(const CArmedInstance * we, c
return output;
}
}

View File

@@ -17,6 +17,9 @@ class CArmedInstance;
VCMI_LIB_NAMESPACE_END
namespace NKAI
{
class engineBase //subclasses create fuzzylite variables with "new" that are not freed - this is desired as fl::Engine wants to destroy these...
{
protected:
@@ -40,3 +43,5 @@ private:
fl::InputVariable * castleWalls;
fl::OutputVariable * threat;
};
}

View File

@@ -14,6 +14,9 @@
#include "../Goals/Goals.h"
#include "Nullkiller.h"
namespace NKAI
{
ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
{
//this one is not fuzzy anymore, just calculate weighted average
@@ -33,7 +36,7 @@ ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
}
ui64 FuzzyHelper::evaluateDanger(crint3 tile, const CGHeroInstance * visitor, bool checkGuards)
ui64 FuzzyHelper::evaluateDanger(const int3 & tile, const CGHeroInstance * visitor, bool checkGuards)
{
auto cb = ai->cb.get();
const TerrainTile * t = cb->getTile(tile, false);
@@ -154,3 +157,5 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
return 0;
}
}
}

View File

@@ -16,6 +16,9 @@ class CBank;
VCMI_LIB_NAMESPACE_END
namespace NKAI
{
class Nullkiller;
class DLL_EXPORT FuzzyHelper
@@ -30,5 +33,7 @@ public:
ui64 estimateBankDanger(const CBank * bank); //TODO: move to another class?
ui64 evaluateDanger(const CGObjectInstance * obj);
ui64 evaluateDanger(crint3 tile, const CGHeroInstance * visitor, bool checkGuards = true);
ui64 evaluateDanger(const int3 & tile, const CGHeroInstance * visitor, bool checkGuards = true);
};
}

View File

@@ -21,12 +21,15 @@
#include "../Goals/Invalid.h"
#include "../Goals/Composition.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
using namespace Goals;
#if AI_TRACE_LEVEL >= 1
#if NKAI_TRACE_LEVEL >= 1
#define MAXPASS 1000000
#else
#define MAXPASS 30
@@ -116,9 +119,10 @@ void Nullkiller::resetAiState()
playerID = ai->playerID;
lockedHeroes.clear();
dangerHitMap->reset();
useHeroChain = true;
}
void Nullkiller::updateAiState(int pass)
void Nullkiller::updateAiState(int pass, bool fast)
{
boost::this_thread::interruption_point();
@@ -126,6 +130,8 @@ void Nullkiller::updateAiState(int pass)
activeHero = nullptr;
if(!fast)
{
memory->removeInvisibleObjects(cb.get());
dangerHitMap->updateHitMap();
@@ -146,7 +152,7 @@ void Nullkiller::updateAiState(int pass)
}
PathfinderSettings cfg;
cfg.useHeroChain = true;
cfg.useHeroChain = useHeroChain;
cfg.scoutTurnDistanceLimit = SCOUT_TURN_DISTANCE_LIMIT;
if(scanDepth != ScanDepth::FULL)
@@ -156,9 +162,10 @@ void Nullkiller::updateAiState(int pass)
pathfinder->updatePaths(activeHeroes, cfg);
armyManager->update();
objectClusterizer->clusterize();
}
armyManager->update();
buildAnalyzer->update();
decomposer->reset();
@@ -174,7 +181,7 @@ bool Nullkiller::arePathHeroesLocked(const AIPath & path) const
{
if(getHeroLockedReason(path.targetHero) == HeroLockedReason::STARTUP)
{
#if AI_TRACE_LEVEL >= 1
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Hero %s is locked by STARTUP. Discarding %s", path.targetHero->name, path.toString());
#endif
return true;
@@ -186,7 +193,7 @@ bool Nullkiller::arePathHeroesLocked(const AIPath & path) const
if(lockReason != HeroLockedReason::NOT_LOCKED)
{
#if AI_TRACE_LEVEL >= 1
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Hero %s is locked by STARTUP. Discarding %s", path.targetHero->name, path.toString());
#endif
return true;
@@ -213,13 +220,30 @@ void Nullkiller::makeTurn()
{
updateAiState(i);
Goals::TTaskVec bestTasks = {
Goals::TTask bestTask = taskptr(Goals::Invalid());
do
{
Goals::TTaskVec fastTasks = {
choseBestTask(sptr(BuyArmyBehavior()), 1),
choseBestTask(sptr(RecruitHeroBehavior()), 1),
choseBestTask(sptr(BuildingBehavior()), 1)
};
bestTask = choseBestTask(fastTasks);
if(bestTask->priority >= 1)
{
executeTask(bestTask);
updateAiState(i, true);
}
} while(bestTask->priority >= 1);
Goals::TTaskVec bestTasks = {
bestTask,
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)
};
@@ -228,19 +252,25 @@ void Nullkiller::makeTurn()
bestTasks.push_back(choseBestTask(sptr(StartupBehavior()), 1));
}
Goals::TTask bestTask = choseBestTask(bestTasks);
bestTask = choseBestTask(bestTasks);
HeroPtr hero = bestTask->getHero();
if(bestTask->priority < NEXT_SCAN_MIN_PRIORITY
&& scanDepth != ScanDepth::FULL)
{
HeroRole heroRole = HeroRole::MAIN;
if(hero.validAndSet())
heroRole = heroManager->getHeroRole(hero);
if(heroRole != HeroRole::MAIN || bestTask->getHeroExchangeCount() <= 1)
useHeroChain = false;
if(bestTask->priority < NEXT_SCAN_MIN_PRIORITY
&& scanDepth != ScanDepth::FULL)
{
if(heroRole == HeroRole::MAIN || bestTask->priority < MIN_PRIORITY)
{
useHeroChain = false;
logAi->trace(
"Goal %s has too low priority %f so increasing scan depth",
bestTask->toString(),
@@ -258,26 +288,31 @@ void Nullkiller::makeTurn()
return;
}
std::string taskDescr = bestTask->toString();
executeTask(bestTask);
}
}
void Nullkiller::executeTask(Goals::TTask task)
{
std::string taskDescr = task->toString();
boost::this_thread::interruption_point();
logAi->debug("Trying to realize %s (value %2.3f)", taskDescr, bestTask->priority);
logAi->debug("Trying to realize %s (value %2.3f)", taskDescr, task->priority);
try
{
bestTask->accept(ai.get());
task->accept(ai.get());
}
catch(goalFulfilledException &)
{
logAi->trace("Task %s completed", bestTask->toString());
logAi->trace("Task %s completed", task->toString());
}
catch(std::exception & e)
{
logAi->debug("Failed to realize subgoal of type %s, I will stop.", taskDescr);
logAi->debug("The error message was: %s", e.what());
return;
}
throw;
}
}
@@ -294,3 +329,5 @@ void Nullkiller::lockResources(const TResources & res)
{
lockedResources += res;
}
}

View File

@@ -19,6 +19,9 @@
#include "../Analyzers/HeroManager.h"
#include "../Analyzers/ObjectClusterizer.h"
namespace NKAI
{
const float MAX_GOLD_PEASURE = 0.3f;
const float MIN_PRIORITY = 0.01f;
const float NEXT_SCAN_MIN_PRIORITY = 0.4f;
@@ -51,6 +54,7 @@ private:
std::map<const CGHeroInstance *, HeroLockedReason> lockedHeroes;
ScanDepth scanDepth;
TResources lockedResources;
bool useHeroChain;
public:
std::unique_ptr<DangerHitMapAnalyzer> dangerHitMap;
@@ -86,7 +90,10 @@ public:
private:
void resetAiState();
void updateAiState(int pass);
void updateAiState(int pass, bool fast = false);
Goals::TTask choseBestTask(Goals::TSubgoal behavior, int decompositionMaxDepth) const;
Goals::TTask choseBestTask(Goals::TTaskVec & tasks) const;
void executeTask(Goals::TTask task);
};
}

View File

@@ -26,6 +26,9 @@
#include "../Markers/ArmyUpgrade.h"
#include "../Markers/DefendTown.h"
namespace NKAI
{
#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
@@ -847,7 +850,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
logAi->error("evaluate VisitTile: %s", fe.getWhat());
}
#if AI_TRACE_LEVEL >= 2
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %d, cost: %d, army gain: %d, danger: %d, role: %s, strategical value: %f, cwr: %f, fear: %f, result %f",
task->toString(),
evaluationContext.armyLossPersentage,
@@ -867,3 +870,5 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
return result;
}
}

View File

@@ -18,6 +18,9 @@ class CGWitchHut;
VCMI_LIB_NAMESPACE_END
namespace NKAI
{
class BuildingInfo;
class Nullkiller;
@@ -105,3 +108,5 @@ private:
EvaluationContext buildEvaluationContext(Goals::TSubgoal goal) const;
};
}

View File

@@ -14,6 +14,9 @@
#include "../../../lib/CPathfinder.h"
#include "../../../lib/StringConstants.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -85,3 +88,5 @@ bool AbstractGoal::invalid() const
{
return goalType == EGoals::INVALID;
}
}

View File

@@ -15,6 +15,9 @@
#include "../../../lib/CTownHandler.h"
#include "../AIUtility.h"
namespace NKAI
{
struct HeroPtr;
class AIGateway;
class FuzzyHelper;
@@ -162,6 +165,7 @@ namespace Goals
virtual std::string toString() const = 0;
virtual HeroPtr getHero() const = 0;
virtual ~ITask() {}
virtual int getHeroExchangeCount() const = 0;
};
}
@@ -170,8 +174,8 @@ class cannotFulfillGoalException : public std::exception
std::string msg;
public:
explicit cannotFulfillGoalException(crstring _Message)
: msg(_Message)
explicit cannotFulfillGoalException(const std::string & message)
: msg(message)
{
}
@@ -207,3 +211,5 @@ public:
return msg.c_str();
}
};
}

View File

@@ -13,6 +13,9 @@
#include "../../../lib/mapping/CMap.h" //for victory conditions
#include "../../../lib/CPathfinder.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -69,3 +72,5 @@ std::string AdventureSpellCast::toString() const
{
return "AdventureSpellCast " + spellID.toSpell()->name;
}
}

View File

@@ -11,6 +11,9 @@
#include "CGoal.h"
namespace NKAI
{
namespace Goals
{
class DLL_EXPORT AdventureSpellCast : public ElementarGoal<AdventureSpellCast>
@@ -35,3 +38,5 @@ namespace Goals
virtual bool operator==(const AdventureSpellCast & other) const override;
};
}
}

View File

@@ -1,3 +1,6 @@
namespace Nullkiller
{
/*
* Build.cpp, part of VCMI engine
*
@@ -91,3 +94,5 @@ bool Build::fulfillsMe(TSubgoal goal)
else
return false;
}
}

View File

@@ -1,3 +1,6 @@
namespace Nullkiller
{
/*
* Build.h, part of VCMI engine
*
@@ -35,3 +38,5 @@ namespace Goals
}
};
}
}

View File

@@ -14,6 +14,9 @@
#include "../../../lib/CPathfinder.h"
#include "../Behaviors/CaptureObjectsBehavior.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -77,3 +80,5 @@ std::string BuildBoat::toString() const
{
return "BuildBoat";
}
}

View File

@@ -11,6 +11,9 @@
#include "CGoal.h"
namespace NKAI
{
namespace Goals
{
class DLL_EXPORT BuildBoat : public ElementarGoal<BuildBoat>
@@ -29,3 +32,5 @@ namespace Goals
virtual bool operator==(const BuildBoat & other) const override;
};
}
}

View File

@@ -16,6 +16,9 @@
#include "../../../lib/StringConstants.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -64,3 +67,5 @@ void BuildThis::accept(AIGateway * ai)
throw cannotFulfillGoalException("Cannot build a given structure!");
}
}

View File

@@ -12,6 +12,9 @@
#include "CGoal.h"
#include "../Analyzers/BuildAnalyzer.h"
namespace NKAI
{
struct HeroPtr;
class AIGateway;
class FuzzyHelper;
@@ -41,3 +44,5 @@ namespace Goals
void accept(AIGateway * ai) override;
};
}
}

View File

@@ -14,6 +14,9 @@
#include "../Engine/Nullkiller.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -73,3 +76,5 @@ void BuyArmy::accept(AIGateway * ai)
ai->moveHeroToTile(town->visitablePos(), town->visitingHero.get());
}
}
}

View File

@@ -11,6 +11,9 @@
#include "CGoal.h"
namespace NKAI
{
struct HeroPtr;
class AIGateway;
class FuzzyHelper;
@@ -40,3 +43,5 @@ namespace Goals
virtual void accept(AIGateway * ai) override;
};
}
}

View File

@@ -11,6 +11,9 @@
#include "AbstractGoal.h"
namespace NKAI
{
struct HeroPtr;
class AIGateway;
@@ -90,34 +93,9 @@ namespace Goals
virtual bool isElementar() const override { return true; }
virtual HeroPtr getHero() const override { return AbstractGoal::hero; }
};
class DLL_EXPORT Invalid : public ElementarGoal<Invalid>
{
public:
Invalid()
: ElementarGoal(Goals::INVALID)
{
priority = -1;
}
TGoalVec decompose() const override
{
return TGoalVec();
}
virtual bool operator==(const Invalid & other) const override
{
return true;
}
virtual std::string toString() const override
{
return "Invalid";
}
virtual void accept(AIGateway * ai) override
{
throw cannotFulfillGoalException("Can not fulfill Invalid goal!");
}
virtual int getHeroExchangeCount() const override { return 0; }
};
}
}

View File

@@ -15,6 +15,9 @@
#include "../Behaviors/CaptureObjectsBehavior.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
using namespace Goals;
@@ -38,3 +41,5 @@ TGoalVec CaptureObject::decompose() const
{
return CaptureObjectsBehavior(cb->getObj(ObjectInstanceID(objid))).decompose();
}
}

View File

@@ -11,6 +11,9 @@
#include "CGoal.h"
namespace NKAI
{
struct HeroPtr;
class AIGateway;
class FuzzyHelper;
@@ -38,3 +41,5 @@ namespace Goals
virtual uint64_t getHash() const override;
};
}
}

View File

@@ -16,6 +16,9 @@
#include "../../../lib/VCMI_Lib.h"
#include "../../../lib/CGeneralTextHandler.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -246,3 +249,5 @@ TGoalVec CompleteQuest::missionDestroyObj() const
return TGoalVec();
}
}

View File

@@ -14,6 +14,8 @@
#include "../Goals/CGoal.h"
#include "../../../lib/CGameState.h"
namespace NKAI
{
namespace Goals
{
class CompleteQuest : public CGoal<CompleteQuest>
@@ -47,3 +49,5 @@ namespace Goals
std::string questToString() const;
};
}
}

View File

@@ -16,6 +16,9 @@
#include "../../../lib/StringConstants.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -73,3 +76,10 @@ bool Composition::isElementar() const
{
return subtasks.back()->isElementar();
}
int Composition::getHeroExchangeCount() const
{
return isElementar() ? taskptr(*subtasks.back())->getHeroExchangeCount() : 0;
}
}

View File

@@ -11,6 +11,8 @@
#include "CGoal.h"
namespace NKAI
{
namespace Goals
{
class DLL_EXPORT Composition : public ElementarGoal<Composition>
@@ -36,5 +38,8 @@ namespace Goals
Composition & addNext(TSubgoal goal);
virtual TGoalVec decompose() const override;
virtual bool isElementar() const override;
virtual int getHeroExchangeCount() const override;
};
}
}

View File

@@ -13,6 +13,9 @@
#include "../AIUtility.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -35,3 +38,5 @@ bool DigAtTile::operator==(const DigAtTile & other) const
//
// return sptr(VisitTile(tile));
//}
}

View File

@@ -11,6 +11,9 @@
#include "CGoal.h"
namespace NKAI
{
struct HeroPtr;
class AIGateway;
class FuzzyHelper;
@@ -36,3 +39,5 @@ namespace Goals
//TSubgoal decomposeSingle() const override;
};
}
}

View File

@@ -13,6 +13,9 @@
#include "../../../lib/mapping/CMap.h" //for victory conditions
#include "../../../lib/CPathfinder.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -37,3 +40,5 @@ std::string DismissHero::toString() const
{
return "DismissHero " + hero.name;
}
}

View File

@@ -11,6 +11,8 @@
#include "CGoal.h"
namespace NKAI
{
namespace Goals
{
class DLL_EXPORT DismissHero : public ElementarGoal<DismissHero>
@@ -27,3 +29,5 @@ namespace Goals
virtual bool operator==(const DismissHero & other) const override;
};
}
}

View File

@@ -15,6 +15,9 @@
#include "../../../lib/CPathfinder.h"
#include "../Engine/Nullkiller.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -90,3 +93,5 @@ void ExchangeSwapTownHeroes::accept(AIGateway * ai)
logAi->debug("Put hero %s to garrison of %s", garrisonHero->name, town->name);
}
}

View File

@@ -12,6 +12,8 @@
#include "CGoal.h"
#include "../Engine/Nullkiller.h"
namespace NKAI
{
namespace Goals
{
class DLL_EXPORT ExchangeSwapTownHeroes : public ElementarGoal<ExchangeSwapTownHeroes>
@@ -32,3 +34,5 @@ namespace Goals
virtual bool operator==(const ExchangeSwapTownHeroes & other) const override;
};
}
}

View File

@@ -14,6 +14,9 @@
#include "../../../lib/CPathfinder.h"
#include "../Engine/Nullkiller.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -198,3 +201,5 @@ bool ExecuteHeroChain::moveHeroToTile(const CGHeroInstance * hero, const int3 &
return ai->moveHeroToTile(tile, hero);
}
}

View File

@@ -12,6 +12,8 @@
#include "CGoal.h"
#include "../Pathfinding/AIPathfinder.h"
namespace NKAI
{
namespace Goals
{
class DLL_EXPORT ExecuteHeroChain : public ElementarGoal<ExecuteHeroChain>
@@ -31,7 +33,11 @@ namespace Goals
virtual bool operator==(const ExecuteHeroChain & other) const override;
const AIPath & getPath() const { return chainPath; }
virtual int getHeroExchangeCount() const override { return chainPath.exchangeCount; }
private:
bool moveHeroToTile(const CGHeroInstance * hero, const int3 & tile);
};
}
}

View File

@@ -1,3 +1,6 @@
namespace Nullkiller
{
/*
* GatherArmy.cpp, part of VCMI engine
*
@@ -208,3 +211,5 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
return ret;
}
}

View File

@@ -1,3 +1,6 @@
namespace Nullkiller
{
/*
* GatherArmy.h, part of VCMI engine
*
@@ -36,3 +39,5 @@ namespace Goals
virtual bool operator==(const GatherArmy & other) const override;
};
}
}

View File

@@ -11,9 +11,42 @@
#include "CGoal.h"
namespace NKAI
{
struct HeroPtr;
class AIGateway;
namespace Goals
{
class DLL_EXPORT Invalid : public ElementarGoal<Invalid>
{
public:
Invalid()
: ElementarGoal(Goals::INVALID)
{
priority = -1;
}
TGoalVec decompose() const override
{
return TGoalVec();
}
virtual bool operator==(const Invalid & other) const override
{
return true;
}
virtual std::string toString() const override
{
return "Invalid";
}
virtual void accept(AIGateway * ai) override
{
throw cannotFulfillGoalException("Can not fulfill Invalid goal!");
}
};
}
}

View File

@@ -16,6 +16,9 @@
#include "../../../lib/StringConstants.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -43,3 +46,5 @@ void RecruitHero::accept(AIGateway * ai)
throw cannotFulfillGoalException("No town to recruit hero!");
}
}
}

View File

@@ -11,6 +11,9 @@
#include "CGoal.h"
namespace NKAI
{
struct HeroPtr;
class AIGateway;
class FuzzyHelper;
@@ -42,3 +45,5 @@ namespace Goals
void accept(AIGateway * ai) override;
};
}
}

View File

@@ -14,6 +14,9 @@
#include "../../../lib/CPathfinder.h"
#include "../Behaviors/CaptureObjectsBehavior.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -37,3 +40,5 @@ std::string SaveResources::toString() const
{
return "SaveResources " + resources.toString();
}
}

View File

@@ -11,6 +11,8 @@
#include "CGoal.h"
namespace NKAI
{
namespace Goals
{
class DLL_EXPORT SaveResources : public ElementarGoal<SaveResources>
@@ -29,3 +31,5 @@ namespace Goals
virtual bool operator==(const SaveResources & other) const override;
};
}
}

View File

@@ -10,9 +10,13 @@
#include "StdInc.h"
#include "Trade.h"
namespace NKAI
{
using namespace Goals;
bool Trade::operator==(const Trade & other) const
{
return resID == other.resID;
}
}

View File

@@ -11,6 +11,9 @@
#include "CGoal.h"
namespace NKAI
{
struct HeroPtr;
class AIGateway;
class FuzzyHelper;
@@ -34,3 +37,5 @@ namespace Goals
virtual bool operator==(const Trade & other) const override;
};
}
}

View File

@@ -1,189 +0,0 @@
/*
* Win.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 "Goals.h"
#include "../AIGateway.h"
#include "../AIUtility.h"
#include "../AIhelper.h"
#include "../FuzzyHelper.h"
#include "../../../lib/mapping/CMap.h" //for victory conditions
#include "../../../lib/CPathfinder.h"
#include "../../../lib/StringConstants.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
extern FuzzyHelper * fh;
using namespace Goals;
TSubgoal Win::whatToDoToAchieve()
{
auto toBool = [=](const EventCondition &)
{
// TODO: proper implementation
// Right now even already fulfilled goals will be included into generated list
// Proper check should test if event condition is already fulfilled
// Easiest way to do this is to call CGameState::checkForVictory but this function should not be
// used on client side or in AI code
return false;
};
std::vector<EventCondition> goals;
for(const TriggeredEvent & event : cb->getMapHeader()->triggeredEvents)
{
//TODO: try to eliminate human player(s) using loss conditions that have isHuman element
if(event.effect.type == EventEffect::VICTORY)
{
boost::range::copy(event.trigger.getFulfillmentCandidates(toBool), std::back_inserter(goals));
}
}
//TODO: instead of returning first encountered goal AI should generate list of possible subgoals
for(const EventCondition & goal : goals)
{
switch(goal.condition)
{
case EventCondition::HAVE_ARTIFACT:
return sptr(GetArtOfType(goal.objectType));
case EventCondition::DESTROY:
{
if(goal.object)
{
auto obj = cb->getObj(goal.object->id);
/*if(obj)
if(obj->getOwner() == ai->playerID) //we can't capture our own object
return sptr(Conquer());*/
return sptr(VisitObj(goal.object->id.getNum()));
}
else
{
// TODO: destroy all objects of type goal.objectType
// This situation represents "kill all creatures" condition from H3
break;
}
}
case EventCondition::HAVE_BUILDING:
{
// TODO build other buildings apart from Grail
// goal.objectType = buidingID to build
// goal.object = optional, town in which building should be built
// Represents "Improve town" condition from H3 (but unlike H3 it consists from 2 separate conditions)
if(goal.objectType == BuildingID::GRAIL)
{
if(auto h = ai->getHeroWithGrail())
{
//hero is in a town that can host Grail
if(h->visitedTown && !vstd::contains(h->visitedTown->forbiddenBuildings, BuildingID::GRAIL))
{
const CGTownInstance * t = h->visitedTown;
return sptr(BuildThis(BuildingID::GRAIL, t).setpriority(10));
}
else
{
auto towns = cb->getTownsInfo();
towns.erase(boost::remove_if(towns,
[](const CGTownInstance * t) -> bool
{
return vstd::contains(t->forbiddenBuildings, BuildingID::GRAIL);
}),
towns.end());
boost::sort(towns, CDistanceSorter(h.get()));
if(towns.size())
{
return sptr(VisitTile(towns.front()->visitablePos()).sethero(h));
}
}
}
double ratio = 0;
// maybe make this check a bit more complex? For example:
// 0.75 -> dig randomly within 3 tiles radius
// 0.85 -> radius now 2 tiles
// 0.95 -> 1 tile radius, position is fully known
// AFAIK H3 AI does something like this
int3 grailPos = cb->getGrailPos(&ratio);
if(ratio > 0.99)
{
return sptr(DigAtTile(grailPos));
} //TODO: use FIND_OBJ
else if(const CGObjectInstance * obj = ai->getUnvisitedObj(objWithID<Obj::OBELISK>)) //there are unvisited Obelisks
return sptr(VisitObj(obj->id.getNum()));
/*else
return sptr(Explore());*/
}
break;
}
case EventCondition::CONTROL:
{
if(goal.object)
{
auto objRelations = cb->getPlayerRelations(ai->playerID, goal.object->tempOwner);
if(objRelations == PlayerRelations::ENEMIES)
{
return sptr(VisitObj(goal.object->id.getNum()));
}
else
{
// TODO: Defance
break;
}
}
else
{
//TODO: control all objects of type "goal.objectType"
// Represents H3 condition "Flag all mines"
break;
}
}
case EventCondition::HAVE_RESOURCES:
//TODO mines? piles? marketplace?
//save?
return sptr(CollectRes(static_cast<Res::ERes>(goal.objectType), goal.value));
case EventCondition::HAVE_CREATURES:
return sptr(GatherTroops(goal.objectType, goal.value));
case EventCondition::TRANSPORT:
{
//TODO. merge with bring Grail to town? So AI will first dig grail, then transport it using this goal and builds it
// Represents "transport artifact" condition:
// goal.objectType = type of artifact
// goal.object = destination-town where artifact should be transported
break;
}
case EventCondition::STANDARD_WIN:
return sptr(Invalid());
// Conditions that likely don't need any implementation
case EventCondition::DAYS_PASSED:
break; // goal.value = number of days for condition to trigger
case EventCondition::DAYS_WITHOUT_TOWN:
break; // goal.value = number of days to trigger this
case EventCondition::IS_HUMAN:
break; // Should be only used in calculation of candidates (see toBool lambda)
case EventCondition::CONST_VALUE:
break;
case EventCondition::HAVE_0:
case EventCondition::HAVE_BUILDING_0:
case EventCondition::DESTROY_0:
//TODO: support new condition format
return sptr(Invalid());
default:
assert(0);
}
}
return sptr(Invalid());
}

View File

@@ -1,39 +0,0 @@
/*
* Win.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"
struct HeroPtr;
class AIGateway;
class FuzzyHelper;
namespace Goals
{
class DLL_EXPORT Win : public CGoal<Win>
{
public:
Win()
: CGoal(Goals::WIN)
{
priority = 100;
}
TGoalVec getAllPossibleSubgoals() override
{
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override;
virtual bool operator==(const Win & other) const override
{
return true;
}
};
}

View File

@@ -13,6 +13,9 @@
#include "../Engine/Nullkiller.h"
#include "../AIUtility.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -34,3 +37,5 @@ std::string ArmyUpgrade::toString() const
{
return "Army upgrade at " + upgrader->getObjectName() + upgrader->visitablePos().toString();
}
}

View File

@@ -13,6 +13,8 @@
#include "../Pathfinding/AINodeStorage.h"
#include "../Analyzers/ArmyManager.h"
namespace NKAI
{
namespace Goals
{
class DLL_EXPORT ArmyUpgrade : public CGoal<ArmyUpgrade>
@@ -33,3 +35,5 @@ namespace Goals
uint64_t getInitialArmyValue() const { return initialValue; }
};
}
}

View File

@@ -13,6 +13,9 @@
#include "../Engine/Nullkiller.h"
#include "../AIUtility.h"
namespace NKAI
{
using namespace Goals;
DefendTown::DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath)
@@ -38,3 +41,5 @@ std::string DefendTown::toString() const
{
return "Defend town " + town->getObjectName();
}
}

View File

@@ -14,6 +14,8 @@
#include "../Analyzers/ArmyManager.h"
#include "../Analyzers/DangerHitMapAnalyzer.h"
namespace NKAI
{
namespace Goals
{
class DLL_EXPORT DefendTown : public CGoal<DefendTown>
@@ -37,3 +39,5 @@ namespace Goals
uint8_t getTurn() const { return turn; }
};
}
}

View File

@@ -14,6 +14,9 @@
#include "../AIUtility.h"
#include "../Analyzers/ArmyManager.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -35,3 +38,5 @@ uint64_t HeroExchange::getReinforcementArmyStrength() const
return armyValue;
}
}

View File

@@ -12,6 +12,8 @@
#include "../Goals/CGoal.h"
#include "../Pathfinding/AINodeStorage.h"
namespace NKAI
{
namespace Goals
{
class DLL_EXPORT HeroExchange : public CGoal<HeroExchange>
@@ -32,3 +34,5 @@ namespace Goals
uint64_t getReinforcementArmyStrength() const;
};
}
}

View File

@@ -13,6 +13,9 @@
#include "../Engine/Nullkiller.h"
#include "../AIUtility.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -27,3 +30,5 @@ std::string UnlockCluster::toString() const
{
return "Unlock Cluster " + cluster->blocker->getObjectName() + tile.toString();
}
}

View File

@@ -13,6 +13,9 @@
#include "../Analyzers/ObjectClusterizer.h"
namespace NKAI
{
struct HeroPtr;
class AIGateway;
class FuzzyHelper;
@@ -39,3 +42,5 @@ namespace Goals
const AIPath & getPathToCenter() { return pathToCenter; }
};
}
}

View File

@@ -19,27 +19,24 @@
#include "../../../lib/PathfinderUtil.h"
#include "../../../lib/CPlayerState.h"
namespace NKAI
{
std::shared_ptr<boost::multi_array<AIPathNode, 5>> AISharedStorage::shared;
std::set<int3> commitedTiles;
std::set<int3> commitedTilesInitial;
#ifdef ENVIRONMENT64
const int BUCKET_COUNT = 11;
#else
const int BUCKET_COUNT = 7;
#endif // ENVIRONMENT64
const uint64_t FirstActorMask = 1;
const int BUCKET_SIZE = GameConstants::MAX_HEROES_PER_PLAYER;
const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE;
const uint64_t MIN_ARMY_STRENGTH_FOR_CHAIN = 5000;
const uint64_t MIN_ARMY_STRENGTH_FOR_NEXT_ACTOR = 1000;
const uint64_t CHAIN_MAX_DEPTH = 4;
AISharedStorage::AISharedStorage(int3 sizes)
{
if(!shared){
shared.reset(new boost::multi_array<AIPathNode, 5>(
boost::extents[EPathfindingLayer::NUM_LAYERS][sizes.z][sizes.x][sizes.y][NUM_CHAINS]));
boost::extents[EPathfindingLayer::NUM_LAYERS][sizes.z][sizes.x][sizes.y][AIPathfinding::NUM_CHAINS]));
}
nodes = shared;
@@ -88,11 +85,11 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
{
for(pos.y = 0; pos.y < sizes.y; ++pos.y)
{
const TerrainTile* tile = &gs->map->getTile(pos);
if (!tile->terType.isPassable())
const TerrainTile & tile = gs->map->getTile(pos);
if (!tile.terType->isPassable())
continue;
if (tile->terType.isWater())
if (tile.terType->isWater())
{
resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
if (useFlying)
@@ -139,16 +136,16 @@ boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
const EPathfindingLayer layer,
const ChainActor * actor)
{
int bucketIndex = ((uintptr_t)actor) % BUCKET_COUNT;
int bucketOffset = bucketIndex * BUCKET_SIZE;
auto chains = nodes.get(pos, layer); //FIXME: chain was the innermost layer
int bucketIndex = ((uintptr_t)actor) % AIPathfinding::BUCKET_COUNT;
int bucketOffset = bucketIndex * AIPathfinding::BUCKET_SIZE;
auto chains = nodes.get(pos, layer);
if(chains[0].blocked())
{
return boost::none;
}
for(auto i = BUCKET_SIZE - 1; i >= 0; i--)
for(auto i = AIPathfinding::BUCKET_SIZE - 1; i >= 0; i--)
{
AIPathNode & node = chains[i + bucketOffset];
@@ -172,6 +169,7 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
{
if(heroChainPass)
{
if(heroChainTurn == 0)
calculateTownPortalTeleportations(heroChain);
return heroChain;
@@ -207,6 +205,7 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
}
}
if(heroChainTurn == 0)
calculateTownPortalTeleportations(initialNodes);
return initialNodes;
@@ -265,7 +264,7 @@ void AINodeStorage::commit(
destination->theNodeBefore = source->theNodeBefore;
destination->chainOther = nullptr;
#if PATHFINDER_TRACE_LEVEL >= 2
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
logAi->trace(
"Commited %s -> %s, cost: %f, turn: %s, mp: %d, hero: %s, mask: %x, army: %lld",
source->coord.toString(),
@@ -406,8 +405,8 @@ public:
AINodeStorage & storage, AISharedStorage & nodes, const std::vector<int3> & tiles, uint64_t chainMask, int heroChainTurn)
:existingChains(), newChains(), delayedWork(), nodes(nodes), storage(storage), chainMask(chainMask), heroChainTurn(heroChainTurn), heroChain(), tiles(tiles)
{
existingChains.reserve(NUM_CHAINS);
newChains.reserve(NUM_CHAINS);
existingChains.reserve(AIPathfinding::NUM_CHAINS);
newChains.reserve(AIPathfinding::NUM_CHAINS);
}
void execute(const blocked_range<size_t>& r)
@@ -593,7 +592,7 @@ void HeroChainCalculationTask::cleanupInefectiveChains(std::vector<ExchangeCandi
auto isNotEffective = storage.hasBetterChain(chainInfo.carrierParent, &chainInfo, chains)
|| storage.hasBetterChain(chainInfo.carrierParent, &chainInfo, result);
#if PATHFINDER_TRACE_LEVEL >= 2
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
if(isNotEffective)
{
logAi->trace(
@@ -623,6 +622,9 @@ void HeroChainCalculationTask::calculateHeroChain(
if((node->actor->chainMask & chainMask) == 0 && (srcNode->actor->chainMask & chainMask) == 0)
continue;
if(node->actor->actorExchangeCount + srcNode->actor->actorExchangeCount > CHAIN_MAX_DEPTH)
continue;
if(node->action == CGPathNode::ENodeAction::BATTLE
|| node->action == CGPathNode::ENodeAction::TELEPORT_BATTLE
|| node->action == CGPathNode::ENodeAction::TELEPORT_NORMAL
@@ -635,7 +637,7 @@ void HeroChainCalculationTask::calculateHeroChain(
|| (node->action == CGPathNode::ENodeAction::UNKNOWN && node->actor->hero)
|| (node->actor->chainMask & srcNode->actor->chainMask) != 0)
{
#if PATHFINDER_TRACE_LEVEL >= 2
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
logAi->trace(
"Skip exchange %s[%x] -> %s[%x] at %s because of %s",
node->actor->toString(),
@@ -652,7 +654,7 @@ void HeroChainCalculationTask::calculateHeroChain(
continue;
}
#if PATHFINDER_TRACE_LEVEL >= 2
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
logAi->trace(
"Thy exchange %s[%x] -> %s[%x] at %s",
node->actor->toString(),
@@ -676,7 +678,7 @@ void HeroChainCalculationTask::calculateHeroChain(
&& carrier->action != CGPathNode::BLOCKING_VISIT
&& (other->armyLoss == 0 || other->armyLoss < other->actor->armyValue))
{
#if PATHFINDER_TRACE_LEVEL >= 2
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
logAi->trace(
"Exchange allowed %s[%x] -> %s[%x] at %s",
other->actor->toString(),
@@ -693,7 +695,7 @@ void HeroChainCalculationTask::calculateHeroChain(
if(hasLessMp && hasLessExperience)
{
#if PATHFINDER_TRACE_LEVEL >= 2
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
logAi->trace("Exchange at %s is ineficient. Blocked.", carrier->coord.toString());
#endif
return;
@@ -718,7 +720,7 @@ void HeroChainCalculationTask::addHeroChain(const std::vector<ExchangeCandidate>
if(!chainNodeOptional)
{
#if PATHFINDER_TRACE_LEVEL >= 2
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
logAi->trace("Exchange at %s can not allocate node. Blocked.", carrier->coord.toString());
#endif
continue;
@@ -728,7 +730,7 @@ void HeroChainCalculationTask::addHeroChain(const std::vector<ExchangeCandidate>
if(exchangeNode->action != CGPathNode::ENodeAction::UNKNOWN)
{
#if PATHFINDER_TRACE_LEVEL >= 2
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
logAi->trace(
"Skip exchange %s[%x] -> %s[%x] at %s because node is in use",
other->actor->toString(),
@@ -742,7 +744,7 @@ void HeroChainCalculationTask::addHeroChain(const std::vector<ExchangeCandidate>
if(exchangeNode->turns != 0xFF && exchangeNode->getCost() < chainInfo.getCost())
{
#if PATHFINDER_TRACE_LEVEL >= 2
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
logAi->trace(
"Skip exchange %s[%x] -> %s[%x] at %s because not effective enough. %f < %f",
other->actor->toString(),
@@ -773,7 +775,7 @@ void HeroChainCalculationTask::addHeroChain(const std::vector<ExchangeCandidate>
exchangeNode->chainOther = other;
exchangeNode->armyLoss = chainInfo.armyLoss;
#if PATHFINDER_TRACE_LEVEL >= 2
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
logAi->trace(
"Chain accepted at %s %s -> %s, mask %x, cost %f, turn: %s, mp: %d, army %i",
exchangeNode->coord.toString(),
@@ -994,8 +996,6 @@ struct TowmPortalFinder
CGPathNode * getBestInitialNodeForTownPortal(const CGTownInstance * targetTown)
{
CGPathNode * bestNode = nullptr;
for(CGPathNode * node : initialNodes)
{
auto aiNode = nodeStorage->getAINode(node);
@@ -1018,11 +1018,10 @@ struct TowmPortalFinder
continue;
}
if(!bestNode || bestNode->getCost() > node->getCost())
bestNode = node;
return node;
}
return bestNode;
return nullptr;
}
boost::optional<AIPathNode *> createTownPortalNode(const CGTownInstance * targetTown)
@@ -1060,30 +1059,13 @@ struct TowmPortalFinder
}
};
void AINodeStorage::calculateTownPortalTeleportations(std::vector<CGPathNode *> & initialNodes)
template<class TVector>
void AINodeStorage::calculateTownPortal(
const ChainActor * actor,
const std::map<const CGHeroInstance *, int> & maskMap,
const std::vector<CGPathNode *> & initialNodes,
TVector & output)
{
std::set<const ChainActor *> actorsOfInitial;
for(const CGPathNode * node : initialNodes)
{
auto aiNode = getAINode(node);
actorsOfInitial.insert(aiNode->actor->baseActor);
}
std::map<const CGHeroInstance *, int> maskMap;
for(std::shared_ptr<ChainActor> basicActor : actors)
{
if(basicActor->hero)
maskMap[basicActor->hero] = basicActor->chainMask;
}
for(const ChainActor * actor : actorsOfInitial)
{
if(!actor->hero)
continue;
auto towns = cb->getTownsInfo(false);
vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool
@@ -1105,7 +1087,7 @@ void AINodeStorage::calculateTownPortalTeleportations(std::vector<CGPathNode *>
// TODO: allow to hide visiting hero in garrison
if(targetTown->visitingHero)
{
auto basicMask = maskMap[targetTown->visitingHero.get()];
auto basicMask = maskMap.at(targetTown->visitingHero.get());
bool heroIsInChain = (actor->chainMask & basicMask) != 0;
bool sameActorInTown = actor->chainMask == basicMask;
@@ -1117,14 +1099,59 @@ void AINodeStorage::calculateTownPortalTeleportations(std::vector<CGPathNode *>
if(nodeOptional)
{
#if PATHFINDER_TRACE_LEVEL >= 1
#if NKAI_PATHFINDER_TRACE_LEVEL >= 1
logAi->trace("Adding town portal node at %s", targetTown->name);
#endif
initialNodes.push_back(nodeOptional.get());
output.push_back(nodeOptional.get());
}
}
}
}
void AINodeStorage::calculateTownPortalTeleportations(std::vector<CGPathNode *> & initialNodes)
{
std::set<const ChainActor *> actorsOfInitial;
for(const CGPathNode * node : initialNodes)
{
auto aiNode = getAINode(node);
if(aiNode->actor->hero)
actorsOfInitial.insert(aiNode->actor->baseActor);
}
std::map<const CGHeroInstance *, int> maskMap;
for(std::shared_ptr<ChainActor> basicActor : actors)
{
if(basicActor->hero)
maskMap[basicActor->hero] = basicActor->chainMask;
}
boost::sort(initialNodes, NodeComparer<CGPathNode>());
std::vector<const ChainActor *> actorsVector(actorsOfInitial.begin(), actorsOfInitial.end());
tbb::concurrent_vector<CGPathNode *> output;
if(actorsVector.size() * initialNodes.size() > 1000)
{
parallel_for(blocked_range<size_t>(0, actorsVector.size()), [&](const blocked_range<size_t> & r)
{
for(int i = r.begin(); i != r.end(); i++)
{
calculateTownPortal(actorsVector[i], maskMap, initialNodes, output);
}
});
std::copy(output.begin(), output.end(), std::back_inserter(initialNodes));
}
else
{
for(auto actor : actorsVector)
{
calculateTownPortal(actor, maskMap, initialNodes, initialNodes);
}
}
}
bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
@@ -1156,7 +1183,7 @@ bool AINodeStorage::hasBetterChain(
{
if(node.getCost() < candidateNode->getCost())
{
#if PATHFINDER_TRACE_LEVEL >= 2
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
logAi->trace(
"Block ineficient battle move %s->%s, hero: %s[%X], army %lld, mp diff: %i",
source->coord.toString(),
@@ -1180,7 +1207,7 @@ bool AINodeStorage::hasBetterChain(
if(nodeArmyValue > candidateArmyValue
&& node.getCost() <= candidateNode->getCost())
{
#if PATHFINDER_TRACE_LEVEL >= 2
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
logAi->trace(
"Block ineficient move because of stronger army %s->%s, hero: %s[%X], army %lld, mp diff: %i",
source->coord.toString(),
@@ -1206,7 +1233,7 @@ bool AINodeStorage::hasBetterChain(
continue;
}
#if AI_TRACE_LEVEL >= 2
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
logAi->trace(
"Block ineficient move because of stronger hero %s->%s, hero: %s[%X], army %lld, mp diff: %i",
source->coord.toString(),
@@ -1244,7 +1271,7 @@ std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand)
{
std::vector<AIPath> paths;
paths.reserve(NUM_CHAINS / 4);
paths.reserve(AIPathfinding::NUM_CHAINS / 4);
auto chains = nodes.get(pos, isOnLand ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL);
@@ -1428,3 +1455,5 @@ std::string AIPath::toString() const
return str.str();
}
}

View File

@@ -10,10 +10,8 @@
#pragma once
#define PATHFINDER_TRACE_LEVEL 0
#define AI_TRACE_LEVEL 0
#define SCOUT_TURN_DISTANCE_LIMIT 3
#define MAIN_TURN_DISTANCE_LIMIT 5
#define NKAI_PATHFINDER_TRACE_LEVEL 0
#define NKAI_TRACE_LEVEL 0
#include "../../../lib/CPathfinder.h"
#include "../../../lib/mapObjects/CGHeroInstance.h"
@@ -23,12 +21,23 @@
#include "Actions/SpecialAction.h"
#include "Actors.h"
namespace NKAI
{
const int SCOUT_TURN_DISTANCE_LIMIT = 3;
const int MAIN_TURN_DISTANCE_LIMIT = 5;
namespace AIPathfinding
{
const int BUCKET_COUNT = 11;
const int BUCKET_SIZE = GameConstants::MAX_HEROES_PER_PLAYER;
#ifdef ENVIRONMENT64
const int BUCKET_COUNT = 7;
#else
const int BUCKET_COUNT = 5;
#endif // ENVIRONMENT64
const int BUCKET_SIZE = 5;
const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE;
const int THREAD_COUNT = 8;
const int CHAIN_MAX_DEPTH = 4;
}
struct AIPathNode : public CGPathNode
@@ -239,7 +248,16 @@ public:
return ((uintptr_t)actor * 395) % AIPathfinding::BUCKET_COUNT;
}
void calculateTownPortalTeleportations(std::vector<CGPathNode *> & neighbours);
void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const;
private:
template<class TVector>
void calculateTownPortal(
const ChainActor * actor,
const std::map<const CGHeroInstance *, int> & maskMap,
const std::vector<CGPathNode *> & initialNodes,
TVector & output);
};
}

View File

@@ -14,6 +14,9 @@
#include "../../../lib/mapping/CMap.h"
#include "../Engine/Nullkiller.h"
namespace NKAI
{
AIPathfinder::AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * ai)
:cb(cb), ai(ai)
{
@@ -99,3 +102,5 @@ void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes
logAi->trace("Recalculated paths in %ld", timeElapsed(start));
}
}

View File

@@ -13,6 +13,9 @@
#include "AINodeStorage.h"
#include "../AIUtility.h"
namespace NKAI
{
class Nullkiller;
struct PathfinderSettings
@@ -42,3 +45,5 @@ public:
void updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes, PathfinderSettings pathfinderSettings);
void init();
};
}

View File

@@ -15,6 +15,8 @@
#include "Rules/AIPreviousNodeRule.h"
#include "../Engine//Nullkiller.h"
namespace NKAI
{
namespace AIPathfinding
{
std::vector<std::shared_ptr<IPathfindingRule>> makeRuleset(
@@ -55,3 +57,5 @@ namespace AIPathfinding
return helper.get();
}
}
}

View File

@@ -12,6 +12,9 @@
#include "AINodeStorage.h"
namespace NKAI
{
class Nullkiller;
namespace AIPathfinding
@@ -31,3 +34,5 @@ namespace AIPathfinding
virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;
};
}
}

View File

@@ -14,6 +14,9 @@
#include "../../Goals/CompleteQuest.h"
#include "../../../../lib/mapping/CMap.h" //for victory conditions
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -29,3 +32,5 @@ namespace AIPathfinding
return "Battle at " + targetTile.toString();
}
}
}

View File

@@ -13,6 +13,9 @@
#include "SpecialAction.h"
#include "../../../../lib/CGameState.h"
namespace NKAI
{
namespace AIPathfinding
{
class BattleAction : public SpecialAction
@@ -31,3 +34,5 @@ namespace AIPathfinding
virtual std::string toString() const override;
};
}
}

View File

@@ -12,11 +12,15 @@
#include "../../AIGateway.h"
#include "../../Goals/AdventureSpellCast.h"
#include "../../Goals/CaptureObject.h"
#include "../../Goals/Invalid.h"
#include "../../Goals/BuildBoat.h"
#include "../../../../lib/mapping/CMap.h"
#include "../../../../lib/mapObjects/MapObjects.h"
#include "BoatActions.h"
namespace NKAI
{
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<AIGateway> ai;
@@ -34,7 +38,7 @@ namespace AIPathfinding
return Goals::sptr(Goals::CaptureObject(shipyard->o));
}
return sptr(Goals::Invalid());
return Goals::sptr(Goals::Invalid());
}
bool BuildBoatAction::canAct(const AIPathNode * source) const
@@ -43,7 +47,7 @@ namespace AIPathfinding
if(cb->getPlayerRelations(hero->tempOwner, shipyard->o->tempOwner) == PlayerRelations::ENEMIES)
{
#if AI_TRACE_LEVEL > 1
#if NKAI_TRACE_LEVEL > 1
logAi->trace("Can not build a boat. Shipyard is enemy.");
#endif
return false;
@@ -55,7 +59,7 @@ namespace AIPathfinding
if(!cb->getResourceAmount().canAfford(source->actor->armyCost + boatCost))
{
#if AI_TRACE_LEVEL > 1
#if NKAI_TRACE_LEVEL > 1
logAi->trace("Can not build a boat. Not enough resources.");
#endif
@@ -129,3 +133,5 @@ namespace AIPathfinding
return hero->getSpellCost(summonBoat.toSpell());
}
}
}

View File

@@ -14,6 +14,9 @@
#include "../../../../lib/mapping/CMap.h"
#include "../../../../lib/mapObjects/MapObjects.h"
namespace NKAI
{
namespace AIPathfinding
{
class VirtualBoatAction : public SpecialAction
@@ -69,3 +72,5 @@ namespace AIPathfinding
virtual const CGObjectInstance * targetObject() const override;
};
}
}

Some files were not shown because too many files have changed in this diff Show More