1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-04-21 12:06:49 +02:00

Merge remote-tracking branch 'origin/develop' into dimension-door-changes

This commit is contained in:
Dydzio 2024-03-27 23:03:33 +01:00
commit 048fb1867d
698 changed files with 15306 additions and 12192 deletions

View File

@ -124,7 +124,7 @@ jobs:
# also, running it on multiple presets is redundant and slightly increases already long CI built times # also, running it on multiple presets is redundant and slightly increases already long CI built times
if: ${{ startsWith(matrix.preset, 'linux-clang-test') }} if: ${{ startsWith(matrix.preset, 'linux-clang-test') }}
run: | run: |
pip3 install json5 jstyleson pip3 install jstyleson
python3 CI/linux-qt6/validate_json.py python3 CI/linux-qt6/validate_json.py
- name: Dependencies - name: Dependencies
@ -134,7 +134,7 @@ jobs:
# ensure the ccache for each PR is separate so they don't interfere with each other # ensure the ccache for each PR is separate so they don't interfere with each other
# fall back to ccache of the vcmi/vcmi repo if no PR-specific ccache is found # fall back to ccache of the vcmi/vcmi repo if no PR-specific ccache is found
- name: Ccache for PRs - name: ccache for PRs
uses: hendrikmuhs/ccache-action@v1.2 uses: hendrikmuhs/ccache-action@v1.2
if: ${{ github.event.number != '' }} if: ${{ github.event.number != '' }}
with: with:
@ -146,9 +146,9 @@ jobs:
max-size: "5G" max-size: "5G"
verbose: 2 verbose: 2
- name: Ccache for vcmi/vcmi's develop branch - name: ccache for everything but PRs
uses: hendrikmuhs/ccache-action@v1.2 uses: hendrikmuhs/ccache-action@v1.2
if: ${{ github.event.number == '' && github.ref == 'refs/heads/develop' }} if: ${{ (github.repository == 'vcmi/vcmi' && github.event.number == '' && github.ref == 'refs/heads/develop') || github.repository != 'vcmi/vcmi' }}
with: with:
key: ${{ matrix.preset }}-no-PR key: ${{ matrix.preset }}-no-PR
restore-keys: | restore-keys: |

View File

@ -62,16 +62,12 @@ void DamageCache::buildDamageCache(std::shared_ptr<HypotheticBattle> hb, int sid
int64_t DamageCache::getDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb) int64_t DamageCache::getDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb)
{ {
auto damage = damageCache[attacker->unitId()][defender->unitId()] * attacker->getCount(); bool wasComputedBefore = damageCache[attacker->unitId()].count(defender->unitId());
if(damage == 0) if (!wasComputedBefore)
{
cacheDamage(attacker, defender, hb); cacheDamage(attacker, defender, hb);
damage = damageCache[attacker->unitId()][defender->unitId()] * attacker->getCount(); return damageCache[attacker->unitId()][defender->unitId()] * attacker->getCount();
}
return static_cast<int64_t>(damage);
} }
int64_t DamageCache::getOriginalDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb) int64_t DamageCache::getOriginalDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb)

View File

@ -10,7 +10,6 @@
#pragma once #pragma once
#include "../../lib/battle/CUnitState.h" #include "../../lib/battle/CUnitState.h"
#include "../../CCallback.h" #include "../../CCallback.h"
#include "common.h"
#include "StackWithBonuses.h" #include "StackWithBonuses.h"
#define BATTLE_TRACE_LEVEL 0 #define BATTLE_TRACE_LEVEL 0

View File

@ -49,7 +49,6 @@ CBattleAI::~CBattleAI()
void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB) void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB)
{ {
setCbc(CB);
env = ENV; env = ENV;
cb = CB; cb = CB;
playerID = *CB->getPlayerID(); playerID = *CB->getPlayerID();
@ -121,7 +120,6 @@ void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
}; };
BattleAction result = BattleAction::makeDefend(stack); BattleAction result = BattleAction::makeDefend(stack);
setCbc(cb); //TODO: make solid sure that AIs always use their callbacks (need to take care of event handlers too)
auto start = std::chrono::high_resolution_clock::now(); auto start = std::chrono::high_resolution_clock::now();

View File

@ -270,7 +270,7 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
{ {
float score = evaluateExchange(ap, 0, targets, damageCache, hb); float score = evaluateExchange(ap, 0, targets, damageCache, hb);
if(score > result.score || (score == result.score && result.wait)) if(score > result.score || (vstd::isAlmostEqual(score, result.score) && result.wait))
{ {
result.score = score; result.score = score;
result.bestAttack = ap; result.bestAttack = ap;

View File

@ -2,7 +2,6 @@ set(battleAI_SRCS
AttackPossibility.cpp AttackPossibility.cpp
BattleAI.cpp BattleAI.cpp
BattleEvaluator.cpp BattleEvaluator.cpp
common.cpp
EnemyInfo.cpp EnemyInfo.cpp
PossibleSpellcast.cpp PossibleSpellcast.cpp
PotentialTargets.cpp PotentialTargets.cpp
@ -17,7 +16,6 @@ set(battleAI_HEADERS
AttackPossibility.h AttackPossibility.h
BattleAI.h BattleAI.h
BattleEvaluator.h BattleEvaluator.h
common.h
EnemyInfo.h EnemyInfo.h
PotentialTargets.h PotentialTargets.h
PossibleSpellcast.h PossibleSpellcast.h
@ -26,12 +24,12 @@ set(battleAI_HEADERS
BattleExchangeVariant.h BattleExchangeVariant.h
) )
if(NOT ENABLE_STATIC_AI_LIBS) if(NOT ENABLE_STATIC_LIBS)
list(APPEND battleAI_SRCS main.cpp StdInc.cpp) list(APPEND battleAI_SRCS main.cpp StdInc.cpp)
endif() endif()
assign_source_group(${battleAI_SRCS} ${battleAI_HEADERS}) assign_source_group(${battleAI_SRCS} ${battleAI_HEADERS})
if(ENABLE_STATIC_AI_LIBS) if(ENABLE_STATIC_LIBS)
add_library(BattleAI STATIC ${battleAI_SRCS} ${battleAI_HEADERS}) add_library(BattleAI STATIC ${battleAI_SRCS} ${battleAI_HEADERS})
else() else()
add_library(BattleAI SHARED ${battleAI_SRCS} ${battleAI_HEADERS}) add_library(BattleAI SHARED ${battleAI_SRCS} ${battleAI_HEADERS})
@ -39,7 +37,7 @@ else()
endif() endif()
target_include_directories(BattleAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(BattleAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(BattleAI PRIVATE ${VCMI_LIB_TARGET} TBB::tbb) target_link_libraries(BattleAI PRIVATE vcmi TBB::tbb)
vcmi_set_output_dir(BattleAI "AI") vcmi_set_output_dir(BattleAI "AI")
enable_pch(BattleAI) enable_pch(BattleAI)

View File

@ -1,23 +0,0 @@
/*
* common.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 "common.h"
std::shared_ptr<CBattleCallback> cbc;
void setCbc(std::shared_ptr<CBattleCallback> cb)
{
cbc = cb;
}
std::shared_ptr<CBattleCallback> getCbc()
{
return cbc;
}

View File

@ -1,26 +0,0 @@
/*
* common.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
class CBattleCallback;
template<typename Key, typename Val, typename Val2>
const Val getValOr(const std::map<Key, Val> &Map, const Key &key, const Val2 defaultValue)
{
//returning references here won't work: defaultValue must be converted into Val, creating temporary
auto i = Map.find(key);
if(i != Map.end())
return i->second;
else
return defaultValue;
}
void setCbc(std::shared_ptr<CBattleCallback> cb);
std::shared_ptr<CBattleCallback> getCbc();

View File

@ -15,7 +15,7 @@
#define strcpy_s(a, b, c) strncpy(a, c, b) #define strcpy_s(a, b, c) strncpy(a, c, b)
#endif #endif
static const char *g_cszAiName = "Battle AI"; static const char * const g_cszAiName = "Battle AI";
extern "C" DLL_EXPORT int GetGlobalAiVersion() extern "C" DLL_EXPORT int GetGlobalAiVersion()
{ {

View File

@ -19,8 +19,8 @@ class CEmptyAI : public CGlobalAI
std::shared_ptr<CCallback> cb; std::shared_ptr<CCallback> cb;
public: public:
virtual void saveGame(BinarySerializer & h) override; void saveGame(BinarySerializer & h) override;
virtual void loadGame(BinaryDeserializer & h) override; void loadGame(BinaryDeserializer & h) override;
void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override; void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
void yourTurn(QueryID queryID) override; void yourTurn(QueryID queryID) override;

View File

@ -8,12 +8,12 @@ set(emptyAI_HEADERS
CEmptyAI.h CEmptyAI.h
) )
if(NOT ENABLE_STATIC_AI_LIBS) if(NOT ENABLE_STATIC_LIBS)
list(APPEND emptyAI_SRCS main.cpp StdInc.cpp) list(APPEND emptyAI_SRCS main.cpp StdInc.cpp)
endif() endif()
assign_source_group(${emptyAI_SRCS} ${emptyAI_HEADERS}) assign_source_group(${emptyAI_SRCS} ${emptyAI_HEADERS})
if(ENABLE_STATIC_AI_LIBS) if(ENABLE_STATIC_LIBS)
add_library(EmptyAI STATIC ${emptyAI_SRCS} ${emptyAI_HEADERS}) add_library(EmptyAI STATIC ${emptyAI_SRCS} ${emptyAI_HEADERS})
else() else()
add_library(EmptyAI SHARED ${emptyAI_SRCS} ${emptyAI_HEADERS}) add_library(EmptyAI SHARED ${emptyAI_SRCS} ${emptyAI_HEADERS})
@ -21,7 +21,7 @@ else()
endif() endif()
target_include_directories(EmptyAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(EmptyAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(EmptyAI PRIVATE ${VCMI_LIB_TARGET}) target_link_libraries(EmptyAI PRIVATE vcmi)
vcmi_set_output_dir(EmptyAI "AI") vcmi_set_output_dir(EmptyAI "AI")
enable_pch(EmptyAI) enable_pch(EmptyAI)

View File

@ -11,7 +11,6 @@
#include "CEmptyAI.h" #include "CEmptyAI.h"
std::set<CGlobalAI*> ais;
extern "C" DLL_EXPORT int GetGlobalAiVersion() extern "C" DLL_EXPORT int GetGlobalAiVersion()
{ {
return AI_INTERFACE_VER; return AI_INTERFACE_VER;

View File

@ -374,6 +374,11 @@ void AIGateway::objectRemoved(const CGObjectInstance * obj, const PlayerColor &
nullkiller->memory->removeFromMemory(obj); nullkiller->memory->removeFromMemory(obj);
if(nullkiller->baseGraph && nullkiller->settings->isObjectGraphAllowed())
{
nullkiller->baseGraph->removeObject(obj);
}
if(obj->ID == Obj::HERO && obj->tempOwner == playerID) if(obj->ID == Obj::HERO && obj->tempOwner == playerID)
{ {
lostHero(cb->getHero(obj->id)); //we can promote, since objectRemoved is called just before actual deletion lostHero(cb->getHero(obj->id)); //we can promote, since objectRemoved is called just before actual deletion
@ -632,7 +637,8 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
auto topObj = objects.front()->id == hero->id ? objects.back() : objects.front(); auto topObj = objects.front()->id == hero->id ? objects.back() : objects.front();
auto objType = topObj->ID; // top object should be our hero auto objType = topObj->ID; // top object should be our hero
auto goalObjectID = nullkiller->getTargetObject(); auto goalObjectID = nullkiller->getTargetObject();
auto ratio = (float)nullkiller->dangerEvaluator->evaluateDanger(target, hero.get()) / (float)hero->getTotalStrength(); auto danger = nullkiller->dangerEvaluator->evaluateDanger(target, hero.get());
auto ratio = static_cast<float>(danger) / hero->getTotalStrength();
answer = topObj->id == goalObjectID; // no if we do not aim to visit this object answer = topObj->id == goalObjectID; // no if we do not aim to visit this object
logAi->trace("Query hook: %s(%s) by %s danger ratio %f", target.toString(), topObj->getObjectName(), hero.name, ratio); logAi->trace("Query hook: %s(%s) by %s danger ratio %f", target.toString(), topObj->getObjectName(), hero.name, ratio);
@ -648,7 +654,7 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
} }
else if(objType == Obj::ARTIFACT || objType == Obj::RESOURCE) else if(objType == Obj::ARTIFACT || objType == Obj::RESOURCE)
{ {
bool dangerUnknown = ratio == 0; bool dangerUnknown = danger == 0;
bool dangerTooHigh = ratio > (1 / SAFE_ATTACK_CONSTANT); bool dangerTooHigh = ratio > (1 / SAFE_ATTACK_CONSTANT);
answer = !dangerUnknown && !dangerTooHigh; answer = !dangerUnknown && !dangerTooHigh;
@ -676,9 +682,9 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
&& components.size() == 2 && components.size() == 2
&& components.front().type == ComponentType::RESOURCE && components.front().type == ComponentType::RESOURCE
&& (nullkiller->heroManager->getHeroRole(hero) != HeroRole::MAIN && (nullkiller->heroManager->getHeroRole(hero) != HeroRole::MAIN
|| nullkiller->buildAnalyzer->getGoldPreasure() > MAX_GOLD_PEASURE)) || nullkiller->buildAnalyzer->isGoldPreasureHigh()))
{ {
sel = 1; // for now lets pick gold from a chest. sel = 1;
} }
} }
@ -1406,7 +1412,7 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
int accquiredResources = 0; int accquiredResources = 0;
if(const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(g.objid), false)) if(const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(g.objid), false))
{ {
if(const IMarket * m = IMarket::castFrom(obj, false)) if(const auto * m = dynamic_cast<const IMarket*>(obj))
{ {
auto freeRes = cb->getResourceAmount(); //trade only resources which are not reserved auto freeRes = cb->getResourceAmount(); //trade only resources which are not reserved
for(auto it = ResourceSet::nziterator(freeRes); it.valid(); it++) for(auto it = ResourceSet::nziterator(freeRes); it.valid(); it++)

View File

@ -437,7 +437,7 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
case Obj::MAGIC_WELL: case Obj::MAGIC_WELL:
return h->mana < h->manaLimit(); return h->mana < h->manaLimit();
case Obj::PRISON: case Obj::PRISON:
return ai->cb->getHeroesInfo().size() < VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP); return !ai->heroManager->heroCapReached();
case Obj::TAVERN: case Obj::TAVERN:
case Obj::EYE_OF_MAGI: case Obj::EYE_OF_MAGI:
case Obj::BOAT: case Obj::BOAT:

View File

@ -185,8 +185,8 @@ void foreach_tile_pos(const Func & foo)
} }
} }
template<class Func> template<class Func, class TCallback>
void foreach_tile_pos(CCallback * cbp, const Func & foo) // avoid costly retrieval of thread-specific pointer void foreach_tile_pos(TCallback * cbp, const Func & foo) // avoid costly retrieval of thread-specific pointer
{ {
int3 mapSize = cbp->getMapSize(); int3 mapSize = cbp->getMapSize();
for(int z = 0; z < mapSize.z; z++) for(int z = 0; z < mapSize.z; z++)

View File

@ -120,6 +120,11 @@ TResources BuildAnalyzer::getTotalResourcesRequired() const
return result; return result;
} }
bool BuildAnalyzer::isGoldPreasureHigh() const
{
return goldPreasure > ai->settings->getMaxGoldPreasure();
}
void BuildAnalyzer::update() void BuildAnalyzer::update()
{ {
logAi->trace("Start analysing build"); logAi->trace("Start analysing build");

View File

@ -96,6 +96,7 @@ public:
const std::vector<TownDevelopmentInfo> & getDevelopmentInfo() const { return developmentInfos; } const std::vector<TownDevelopmentInfo> & getDevelopmentInfo() const { return developmentInfos; }
TResources getDailyIncome() const { return dailyIncome; } TResources getDailyIncome() const { return dailyIncome; }
float getGoldPreasure() const { return goldPreasure; } float getGoldPreasure() const { return goldPreasure; }
bool isGoldPreasureHigh() const;
bool hasAnyBuilding(int32_t alignment, BuildingID bid) const; bool hasAnyBuilding(int32_t alignment, BuildingID bid) const;
private: private:

View File

@ -16,7 +16,7 @@
namespace NKAI namespace NKAI
{ {
HitMapInfo HitMapInfo::NoThreat; const HitMapInfo HitMapInfo::NoThreat;
double HitMapInfo::value() const double HitMapInfo::value() const
{ {
@ -75,8 +75,7 @@ void DangerHitMapAnalyzer::updateHitMap()
PathfinderSettings ps; PathfinderSettings ps;
ps.mainTurnDistanceLimit = 10; ps.scoutTurnDistanceLimit = ps.mainTurnDistanceLimit = ai->settings->getMainHeroTurnDistanceLimit();
ps.scoutTurnDistanceLimit = 10;
ps.useHeroChain = false; ps.useHeroChain = false;
ai->pathfinder->updatePaths(pair.second, ps); ai->pathfinder->updatePaths(pair.second, ps);
@ -158,15 +157,13 @@ void DangerHitMapAnalyzer::calculateTileOwners()
if(hitMap.shape()[0] != mapSize.x || hitMap.shape()[1] != mapSize.y || hitMap.shape()[2] != mapSize.z) if(hitMap.shape()[0] != mapSize.x || hitMap.shape()[1] != mapSize.y || hitMap.shape()[2] != mapSize.z)
hitMap.resize(boost::extents[mapSize.x][mapSize.y][mapSize.z]); hitMap.resize(boost::extents[mapSize.x][mapSize.y][mapSize.z]);
std::map<const CGHeroInstance *, HeroRole> townHeroes; std::vector<std::unique_ptr<CGHeroInstance>> temporaryHeroes;
std::map<const CGHeroInstance *, const CGTownInstance *> heroTownMap; std::map<const CGHeroInstance *, const CGTownInstance *> heroTownMap;
PathfinderSettings pathfinderSettings; std::map<const CGHeroInstance *, HeroRole> townHeroes;
pathfinderSettings.mainTurnDistanceLimit = 5;
auto addTownHero = [&](const CGTownInstance * town) auto addTownHero = [&](const CGTownInstance * town)
{ {
auto townHero = new CGHeroInstance(town->cb); auto townHero = temporaryHeroes.emplace_back(std::make_unique<CGHeroInstance>(town->cb)).get();
CRandomGenerator rng; CRandomGenerator rng;
auto visitablePos = town->visitablePos(); auto visitablePos = town->visitablePos();
@ -192,7 +189,10 @@ void DangerHitMapAnalyzer::calculateTileOwners()
addTownHero(town); addTownHero(town);
} }
ai->pathfinder->updatePaths(townHeroes, PathfinderSettings()); PathfinderSettings ps;
ps.mainTurnDistanceLimit = ps.scoutTurnDistanceLimit = ai->settings->getMainHeroTurnDistanceLimit();
ai->pathfinder->updatePaths(townHeroes, ps);
pforeachTilePos(mapSize, [&](const int3 & pos) pforeachTilePos(mapSize, [&](const int3 & pos)
{ {
@ -226,7 +226,7 @@ void DangerHitMapAnalyzer::calculateTileOwners()
} }
} }
if(ourDistance == enemyDistance) if(vstd::isAlmostEqual(ourDistance, enemyDistance))
{ {
hitMap[pos.x][pos.y][pos.z].closestTown = nullptr; hitMap[pos.x][pos.y][pos.z].closestTown = nullptr;
} }
@ -285,8 +285,6 @@ const HitMapNode & DangerHitMapAnalyzer::getTileThreat(const int3 & tile) const
return hitMap[tile.x][tile.y][tile.z]; return hitMap[tile.x][tile.y][tile.z];
} }
const std::set<const CGObjectInstance *> empty = {};
std::set<const CGObjectInstance *> DangerHitMapAnalyzer::getOneTurnAccessibleObjects(const CGHeroInstance * enemy) const std::set<const CGObjectInstance *> DangerHitMapAnalyzer::getOneTurnAccessibleObjects(const CGHeroInstance * enemy) const
{ {
std::set<const CGObjectInstance *> result; std::set<const CGObjectInstance *> result;

View File

@ -18,7 +18,7 @@ struct AIPath;
struct HitMapInfo struct HitMapInfo
{ {
static HitMapInfo NoThreat; static const HitMapInfo NoThreat;
uint64_t danger; uint64_t danger;
uint8_t turn; uint8_t turn;

View File

@ -17,7 +17,7 @@
namespace NKAI namespace NKAI
{ {
SecondarySkillEvaluator HeroManager::wariorSkillsScores = SecondarySkillEvaluator( const SecondarySkillEvaluator HeroManager::wariorSkillsScores = SecondarySkillEvaluator(
{ {
std::make_shared<SecondarySkillScoreMap>( std::make_shared<SecondarySkillScoreMap>(
std::map<SecondarySkill, float> std::map<SecondarySkill, float>
@ -46,7 +46,7 @@ SecondarySkillEvaluator HeroManager::wariorSkillsScores = SecondarySkillEvaluato
std::make_shared<AtLeastOneMagicRule>() std::make_shared<AtLeastOneMagicRule>()
}); });
SecondarySkillEvaluator HeroManager::scountSkillsScores = SecondarySkillEvaluator( const SecondarySkillEvaluator HeroManager::scountSkillsScores = SecondarySkillEvaluator(
{ {
std::make_shared<SecondarySkillScoreMap>( std::make_shared<SecondarySkillScoreMap>(
std::map<SecondarySkill, float> std::map<SecondarySkill, float>
@ -187,6 +187,7 @@ bool HeroManager::heroCapReached() const
int heroCount = cb->getHeroCount(ai->playerID, includeGarnisoned); int heroCount = cb->getHeroCount(ai->playerID, includeGarnisoned);
return heroCount >= ALLOWED_ROAMING_HEROES return heroCount >= ALLOWED_ROAMING_HEROES
|| heroCount >= ai->settings->getMaxRoamingHeroes()
|| heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP) || heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP)
|| heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP); || heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP);
} }
@ -332,7 +333,7 @@ void WisdomRule::evaluateScore(const CGHeroInstance * hero, SecondarySkill skill
score += 1.5; score += 1.5;
} }
std::vector<SecondarySkill> AtLeastOneMagicRule::magicSchools = { const std::vector<SecondarySkill> AtLeastOneMagicRule::magicSchools = {
SecondarySkill::AIR_MAGIC, SecondarySkill::AIR_MAGIC,
SecondarySkill::EARTH_MAGIC, SecondarySkill::EARTH_MAGIC,
SecondarySkill::FIRE_MAGIC, SecondarySkill::FIRE_MAGIC,

View File

@ -58,8 +58,8 @@ public:
class DLL_EXPORT HeroManager : public IHeroManager class DLL_EXPORT HeroManager : public IHeroManager
{ {
private: private:
static SecondarySkillEvaluator wariorSkillsScores; static const SecondarySkillEvaluator wariorSkillsScores;
static SecondarySkillEvaluator scountSkillsScores; static const SecondarySkillEvaluator scountSkillsScores;
CCallback * cb; //this is enough, but we downcast from CCallback CCallback * cb; //this is enough, but we downcast from CCallback
const Nullkiller * ai; const Nullkiller * ai;
@ -114,7 +114,7 @@ public:
class AtLeastOneMagicRule : public ISecondarySkillRule class AtLeastOneMagicRule : public ISecondarySkillRule
{ {
private: private:
static std::vector<SecondarySkill> magicSchools; static const std::vector<SecondarySkill> magicSchools;
public: public:
void evaluateScore(const CGHeroInstance * hero, SecondarySkill skill, float & score) const override; void evaluateScore(const CGHeroInstance * hero, SecondarySkill skill, float & score) const override;

View File

@ -189,15 +189,7 @@ bool ObjectClusterizer::shouldVisitObject(const CGObjectInstance * obj) const
return true; //all of the following is met return true; //all of the following is met
} }
void ObjectClusterizer::clusterize() Obj ObjectClusterizer::IgnoredObjectTypes[] = {
{
auto start = std::chrono::high_resolution_clock::now();
nearObjects.reset();
farObjects.reset();
blockedObjects.clear();
Obj ignoreObjects[] = {
Obj::BOAT, Obj::BOAT,
Obj::EYE_OF_MAGI, Obj::EYE_OF_MAGI,
Obj::MONOLITH_ONE_WAY_ENTRANCE, Obj::MONOLITH_ONE_WAY_ENTRANCE,
@ -218,40 +210,75 @@ void ObjectClusterizer::clusterize()
Obj::PILLAR_OF_FIRE Obj::PILLAR_OF_FIRE
}; };
void ObjectClusterizer::clusterize()
{
auto start = std::chrono::high_resolution_clock::now();
nearObjects.reset();
farObjects.reset();
blockedObjects.clear();
logAi->debug("Begin object clusterization"); logAi->debug("Begin object clusterization");
std::vector<const CGObjectInstance *> objs( std::vector<const CGObjectInstance *> objs(
ai->memory->visitableObjs.begin(), ai->memory->visitableObjs.begin(),
ai->memory->visitableObjs.end()); ai->memory->visitableObjs.end());
parallel_for(blocked_range<size_t>(0, objs.size()), [&](const blocked_range<size_t> & r) #if NKAI_TRACE_LEVEL == 0
{ parallel_for(blocked_range<size_t>(0, objs.size()), [&](const blocked_range<size_t> & r) {
#else
blocked_range<size_t> r(0, objs.size());
#endif
auto priorityEvaluator = ai->priorityEvaluators->acquire(); auto priorityEvaluator = ai->priorityEvaluators->acquire();
for(int i = r.begin(); i != r.end(); i++) for(int i = r.begin(); i != r.end(); i++)
{ {
auto obj = objs[i]; clusterizeObject(objs[i], priorityEvaluator.get());
}
#if NKAI_TRACE_LEVEL == 0
});
#endif
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 NKAI_TRACE_LEVEL >= 1
for(auto obj : pair.second->getObjects())
{
logAi->trace("Object %s %s", obj->getObjectName(), obj->visitablePos().toString());
}
#endif
}
logAi->trace("Clusterization complete in %ld", timeElapsed(start));
}
void ObjectClusterizer::clusterizeObject(const CGObjectInstance * obj, PriorityEvaluator * priorityEvaluator)
{
if(!shouldVisitObject(obj)) if(!shouldVisitObject(obj))
{ {
#if NKAI_TRACE_LEVEL >= 2 #if NKAI_TRACE_LEVEL >= 2
logAi->trace("Skip object %s%s.", obj->getObjectName(), obj->visitablePos().toString()); logAi->trace("Skip object %s%s.", obj->getObjectName(), obj->visitablePos().toString());
#endif #endif
continue; return;
} }
#if NKAI_TRACE_LEVEL >= 2 #if NKAI_TRACE_LEVEL >= 2
logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString()); logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString());
#endif #endif
auto paths = ai->pathfinder->getPathInfo(obj->visitablePos()); auto paths = ai->pathfinder->getPathInfo(obj->visitablePos(), true);
if(paths.empty()) if(paths.empty())
{ {
#if NKAI_TRACE_LEVEL >= 2 #if NKAI_TRACE_LEVEL >= 2
logAi->trace("No paths found."); logAi->trace("No paths found.");
#endif #endif
continue; return;
} }
std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool
@ -259,7 +286,7 @@ void ObjectClusterizer::clusterize()
return p1.movementCost() < p2.movementCost(); return p1.movementCost() < p2.movementCost();
}); });
if(vstd::contains(ignoreObjects, obj->ID)) if(vstd::contains(IgnoredObjectTypes, obj->ID))
{ {
farObjects.addObject(obj, paths.front(), 0); farObjects.addObject(obj, paths.front(), 0);
@ -267,7 +294,7 @@ void ObjectClusterizer::clusterize()
logAi->trace("Object ignored. Moved to far objects with path %s", paths.front().toString()); logAi->trace("Object ignored. Moved to far objects with path %s", paths.front().toString());
#endif #endif
continue; return;
} }
std::set<const CGHeroInstance *> heroesProcessed; std::set<const CGHeroInstance *> heroesProcessed;
@ -348,24 +375,5 @@ void ObjectClusterizer::clusterize()
#endif #endif
} }
} }
});
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 NKAI_TRACE_LEVEL >= 1
for(auto obj : pair.second->getObjects())
{
logAi->trace("Object %s %s", obj->getObjectName(), obj->visitablePos().toString());
}
#endif
}
logAi->trace("Clusterization complete in %ld", timeElapsed(start));
}
} }

View File

@ -49,9 +49,13 @@ public:
using ClusterMap = tbb::concurrent_hash_map<const CGObjectInstance *, std::shared_ptr<ObjectCluster>>; using ClusterMap = tbb::concurrent_hash_map<const CGObjectInstance *, std::shared_ptr<ObjectCluster>>;
class PriorityEvaluator;
class ObjectClusterizer class ObjectClusterizer
{ {
private: private:
static Obj IgnoredObjectTypes[];
ObjectCluster nearObjects; ObjectCluster nearObjects;
ObjectCluster farObjects; ObjectCluster farObjects;
ClusterMap blockedObjects; ClusterMap blockedObjects;
@ -68,6 +72,7 @@ public:
private: private:
bool shouldVisitObject(const CGObjectInstance * obj) const; bool shouldVisitObject(const CGObjectInstance * obj) const;
void clusterizeObject(const CGObjectInstance * obj, PriorityEvaluator * priorityEvaluator);
}; };
} }

View File

@ -47,13 +47,13 @@ Goals::TGoalVec BuildingBehavior::decompose() const
totalDevelopmentCost.toString()); totalDevelopmentCost.toString());
auto & developmentInfos = ai->nullkiller->buildAnalyzer->getDevelopmentInfo(); auto & developmentInfos = ai->nullkiller->buildAnalyzer->getDevelopmentInfo();
auto goldPreasure = ai->nullkiller->buildAnalyzer->getGoldPreasure(); auto isGoldPreasureLow = !ai->nullkiller->buildAnalyzer->isGoldPreasureHigh();
for(auto & developmentInfo : developmentInfos) for(auto & developmentInfo : developmentInfos)
{ {
for(auto & buildingInfo : developmentInfo.toBuild) for(auto & buildingInfo : developmentInfo.toBuild)
{ {
if(goldPreasure < MAX_GOLD_PEASURE || buildingInfo.dailyIncome[EGameResID::GOLD] > 0) if(isGoldPreasureLow || buildingInfo.dailyIncome[EGameResID::GOLD] > 0)
{ {
if(buildingInfo.notEnoughRes) if(buildingInfo.notEnoughRes)
{ {

View File

@ -25,9 +25,9 @@ namespace Goals
{ {
} }
virtual Goals::TGoalVec decompose() const override; Goals::TGoalVec decompose() const override;
virtual std::string toString() const override; std::string toString() const override;
virtual bool operator==(const BuildingBehavior & other) const override bool operator==(const BuildingBehavior & other) const override
{ {
return true; return true;
} }

View File

@ -46,8 +46,7 @@ Goals::TGoalVec BuyArmyBehavior::decompose() const
for(const CGHeroInstance * targetHero : heroes) for(const CGHeroInstance * targetHero : heroes)
{ {
if(ai->nullkiller->buildAnalyzer->getGoldPreasure() > MAX_GOLD_PEASURE if(ai->nullkiller->buildAnalyzer->isGoldPreasureHigh() && !town->hasBuilt(BuildingID::CITY_HALL))
&& !town->hasBuilt(BuildingID::CITY_HALL))
{ {
continue; continue;
} }

View File

@ -24,9 +24,9 @@ namespace Goals
{ {
} }
virtual Goals::TGoalVec decompose() const override; Goals::TGoalVec decompose() const override;
virtual std::string toString() const override; std::string toString() const override;
virtual bool operator==(const BuyArmyBehavior & other) const override bool operator==(const BuyArmyBehavior & other) const override
{ {
return true; return true;
} }

View File

@ -73,13 +73,25 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
} }
if(objToVisit && !shouldVisit(ai->nullkiller.get(), path.targetHero, objToVisit)) if(objToVisit && !shouldVisit(ai->nullkiller.get(), path.targetHero, objToVisit))
{
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Ignore path. Hero %s should not visit obj %s", path.targetHero->getNameTranslated(), objToVisit->getObjectName());
#endif
continue; continue;
}
auto hero = path.targetHero; auto hero = path.targetHero;
auto danger = path.getTotalDanger(); auto danger = path.getTotalDanger();
if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::SCOUT && path.exchangeCount > 1) if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::SCOUT
&& (path.getTotalDanger() == 0 || path.turn() > 0)
&& path.exchangeCount > 1)
{
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Ignore path. Hero %s is SCOUT, chain used and no danger", path.targetHero->getNameTranslated());
#endif
continue; continue;
}
auto firstBlockedAction = path.getFirstBlockedAction(); auto firstBlockedAction = path.getFirstBlockedAction();
if(firstBlockedAction) if(firstBlockedAction)
@ -178,8 +190,11 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const
#endif #endif
const int3 pos = objToVisit->visitablePos(); const int3 pos = objToVisit->visitablePos();
bool useObjectGraph = ai->nullkiller->settings->isObjectGraphAllowed()
&& ai->nullkiller->getScanDepth() != ScanDepth::SMALL;
auto paths = ai->nullkiller->pathfinder->getPathInfo(pos, useObjectGraph);
auto paths = ai->nullkiller->pathfinder->getPathInfo(pos);
std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj; std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj;
std::shared_ptr<ExecuteHeroChain> closestWay; std::shared_ptr<ExecuteHeroChain> closestWay;

View File

@ -48,8 +48,8 @@ namespace Goals
specificObjects = true; specificObjects = true;
} }
virtual Goals::TGoalVec decompose() const override; Goals::TGoalVec decompose() const override;
virtual std::string toString() const override; std::string toString() const override;
CaptureObjectsBehavior & ofType(int type) CaptureObjectsBehavior & ofType(int type)
{ {
@ -65,7 +65,7 @@ namespace Goals
return *this; return *this;
} }
virtual bool operator==(const CaptureObjectsBehavior & other) const override; bool operator==(const CaptureObjectsBehavior & other) const override;
static Goals::TGoalVec getVisitGoals(const std::vector<AIPath> & paths, const CGObjectInstance * objToVisit = nullptr); static Goals::TGoalVec getVisitGoals(const std::vector<AIPath> & paths, const CGObjectInstance * objToVisit = nullptr);

View File

@ -42,7 +42,7 @@ Goals::TGoalVec ClusterBehavior::decompose() const
Goals::TGoalVec ClusterBehavior::decomposeCluster(std::shared_ptr<ObjectCluster> cluster) const Goals::TGoalVec ClusterBehavior::decomposeCluster(std::shared_ptr<ObjectCluster> cluster) const
{ {
auto center = cluster->calculateCenter(); auto center = cluster->calculateCenter();
auto paths = ai->nullkiller->pathfinder->getPathInfo(center->visitablePos()); auto paths = ai->nullkiller->pathfinder->getPathInfo(center->visitablePos(), ai->nullkiller->settings->isObjectGraphAllowed());
auto blockerPos = cluster->blocker->visitablePos(); auto blockerPos = cluster->blocker->visitablePos();
std::vector<AIPath> blockerPaths; std::vector<AIPath> blockerPaths;

View File

@ -28,10 +28,10 @@ namespace Goals
{ {
} }
virtual TGoalVec decompose() const override; TGoalVec decompose() const override;
virtual std::string toString() const override; std::string toString() const override;
virtual bool operator==(const ClusterBehavior & other) const override bool operator==(const ClusterBehavior & other) const override
{ {
return true; return true;
} }

View File

@ -443,6 +443,10 @@ void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitM
heroToDismiss = town->garrisonHero.get(); heroToDismiss = town->garrisonHero.get();
} }
} }
// avoid dismissing one weak hero in order to recruit another.
if(heroToDismiss && heroToDismiss->getArmyStrength() + 500 > hero->getArmyStrength())
continue;
} }
else if(ai->nullkiller->heroManager->heroCapReached()) else if(ai->nullkiller->heroManager->heroCapReached())
{ {

View File

@ -29,10 +29,10 @@ namespace Goals
{ {
} }
virtual Goals::TGoalVec decompose() const override; Goals::TGoalVec decompose() const override;
virtual std::string toString() const override; std::string toString() const override;
virtual bool operator==(const DefenceBehavior & other) const override bool operator==(const DefenceBehavior & other) const override
{ {
return true; return true;
} }

View File

@ -246,7 +246,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
{ {
auto heroRole = ai->nullkiller->heroManager->getHeroRole(path.targetHero); auto heroRole = ai->nullkiller->heroManager->getHeroRole(path.targetHero);
if(heroRole == HeroRole::MAIN && path.turn() < SCOUT_TURN_DISTANCE_LIMIT) if(heroRole == HeroRole::MAIN && path.turn() < ai->nullkiller->settings->getScoutHeroTurnDistanceLimit())
hasMainAround = true; hasMainAround = true;
} }
@ -335,7 +335,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
if(!upgrade.upgradeValue if(!upgrade.upgradeValue
&& armyToGetOrBuy.upgradeValue > 20000 && armyToGetOrBuy.upgradeValue > 20000
&& ai->nullkiller->heroManager->canRecruitHero(town) && ai->nullkiller->heroManager->canRecruitHero(town)
&& path.turn() < SCOUT_TURN_DISTANCE_LIMIT) && path.turn() < ai->nullkiller->settings->getScoutHeroTurnDistanceLimit())
{ {
for(auto hero : cb->getAvailableHeroes(town)) for(auto hero : cb->getAvailableHeroes(town))
{ {
@ -344,7 +344,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
if(scoutReinforcement >= armyToGetOrBuy.upgradeValue if(scoutReinforcement >= armyToGetOrBuy.upgradeValue
&& ai->nullkiller->getFreeGold() >20000 && ai->nullkiller->getFreeGold() >20000
&& ai->nullkiller->buildAnalyzer->getGoldPreasure() < MAX_GOLD_PEASURE) && !ai->nullkiller->buildAnalyzer->isGoldPreasureHigh())
{ {
Composition recruitHero; Composition recruitHero;

View File

@ -25,10 +25,10 @@ namespace Goals
{ {
} }
virtual TGoalVec decompose() const override; TGoalVec decompose() const override;
virtual std::string toString() const override; std::string toString() const override;
virtual bool operator==(const GatherArmyBehavior & other) const override bool operator==(const GatherArmyBehavior & other) const override
{ {
return true; return true;
} }

View File

@ -85,8 +85,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const
continue; continue;
if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1 if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1
|| (ai->nullkiller->getFreeResources()[EGameResID::GOLD] > 10000 || (ai->nullkiller->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->nullkiller->buildAnalyzer->isGoldPreasureHigh()))
&& ai->nullkiller->buildAnalyzer->getGoldPreasure() < MAX_GOLD_PEASURE))
{ {
tasks.push_back(Goals::sptr(Goals::RecruitHero(town).setpriority(3))); tasks.push_back(Goals::sptr(Goals::RecruitHero(town).setpriority(3)));
} }

View File

@ -25,10 +25,10 @@ namespace Goals
{ {
} }
virtual TGoalVec decompose() const override; TGoalVec decompose() const override;
virtual std::string toString() const override; std::string toString() const override;
virtual bool operator==(const RecruitHeroBehavior & other) const override bool operator==(const RecruitHeroBehavior & other) const override
{ {
return true; return true;
} }

View File

@ -25,10 +25,10 @@ namespace Goals
{ {
} }
virtual TGoalVec decompose() const override; TGoalVec decompose() const override;
virtual std::string toString() const override; std::string toString() const override;
virtual bool operator==(const StartupBehavior & other) const override bool operator==(const StartupBehavior & other) const override
{ {
return true; return true;
} }

View File

@ -25,10 +25,10 @@ namespace Goals
{ {
} }
virtual TGoalVec decompose() const override; TGoalVec decompose() const override;
virtual std::string toString() const override; std::string toString() const override;
virtual bool operator==(const StayAtTownBehavior & other) const override bool operator==(const StayAtTownBehavior & other) const override
{ {
return true; return true;
} }

View File

@ -14,9 +14,11 @@ set(Nullkiller_SRCS
Pathfinding/Rules/AIMovementAfterDestinationRule.cpp Pathfinding/Rules/AIMovementAfterDestinationRule.cpp
Pathfinding/Rules/AIMovementToDestinationRule.cpp Pathfinding/Rules/AIMovementToDestinationRule.cpp
Pathfinding/Rules/AIPreviousNodeRule.cpp Pathfinding/Rules/AIPreviousNodeRule.cpp
Pathfinding/ObjectGraph.cpp
AIUtility.cpp AIUtility.cpp
Analyzers/ArmyManager.cpp Analyzers/ArmyManager.cpp
Analyzers/HeroManager.cpp Analyzers/HeroManager.cpp
Engine/Settings.cpp
Engine/FuzzyEngines.cpp Engine/FuzzyEngines.cpp
Engine/FuzzyHelper.cpp Engine/FuzzyHelper.cpp
Engine/AIMemory.cpp Engine/AIMemory.cpp
@ -77,9 +79,11 @@ set(Nullkiller_HEADERS
Pathfinding/Rules/AIMovementAfterDestinationRule.h Pathfinding/Rules/AIMovementAfterDestinationRule.h
Pathfinding/Rules/AIMovementToDestinationRule.h Pathfinding/Rules/AIMovementToDestinationRule.h
Pathfinding/Rules/AIPreviousNodeRule.h Pathfinding/Rules/AIPreviousNodeRule.h
Pathfinding/ObjectGraph.h
AIUtility.h AIUtility.h
Analyzers/ArmyManager.h Analyzers/ArmyManager.h
Analyzers/HeroManager.h Analyzers/HeroManager.h
Engine/Settings.h
Engine/FuzzyEngines.h Engine/FuzzyEngines.h
Engine/FuzzyHelper.h Engine/FuzzyHelper.h
Engine/AIMemory.h Engine/AIMemory.h
@ -125,12 +129,12 @@ set(Nullkiller_HEADERS
AIGateway.h AIGateway.h
) )
if(NOT ENABLE_STATIC_AI_LIBS) if(NOT ENABLE_STATIC_LIBS)
list(APPEND Nullkiller_SRCS main.cpp StdInc.cpp) list(APPEND Nullkiller_SRCS main.cpp StdInc.cpp)
endif() endif()
assign_source_group(${Nullkiller_SRCS} ${Nullkiller_HEADERS}) assign_source_group(${Nullkiller_SRCS} ${Nullkiller_HEADERS})
if(ENABLE_STATIC_AI_LIBS) if(ENABLE_STATIC_LIBS)
add_library(Nullkiller STATIC ${Nullkiller_SRCS} ${Nullkiller_HEADERS}) add_library(Nullkiller STATIC ${Nullkiller_SRCS} ${Nullkiller_HEADERS})
else() else()
add_library(Nullkiller SHARED ${Nullkiller_SRCS} ${Nullkiller_HEADERS}) add_library(Nullkiller SHARED ${Nullkiller_SRCS} ${Nullkiller_HEADERS})
@ -138,7 +142,7 @@ else()
endif() endif()
target_include_directories(Nullkiller PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(Nullkiller PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(Nullkiller PUBLIC ${VCMI_LIB_TARGET} fuzzylite::fuzzylite TBB::tbb) target_link_libraries(Nullkiller PUBLIC vcmi fuzzylite::fuzzylite TBB::tbb)
vcmi_set_output_dir(Nullkiller "AI") vcmi_set_output_dir(Nullkiller "AI")
enable_pch(Nullkiller) enable_pch(Nullkiller)

View File

@ -27,15 +27,14 @@ namespace NKAI
using namespace Goals; using namespace Goals;
#if NKAI_TRACE_LEVEL >= 1 // while we play vcmieagles graph can be shared
#define MAXPASS 1000000 std::unique_ptr<ObjectGraph> Nullkiller::baseGraph;
#else
#define MAXPASS 30
#endif
Nullkiller::Nullkiller() Nullkiller::Nullkiller()
:activeHero(nullptr), scanDepth(ScanDepth::MAIN_FULL), useHeroChain(true)
{ {
memory.reset(new AIMemory()); memory = std::make_unique<AIMemory>();
settings = std::make_unique<Settings>();
} }
void Nullkiller::init(std::shared_ptr<CCallback> cb, PlayerColor playerID) void Nullkiller::init(std::shared_ptr<CCallback> cb, PlayerColor playerID)
@ -43,6 +42,8 @@ void Nullkiller::init(std::shared_ptr<CCallback> cb, PlayerColor playerID)
this->cb = cb; this->cb = cb;
this->playerID = playerID; this->playerID = playerID;
baseGraph.reset();
priorityEvaluator.reset(new PriorityEvaluator(this)); priorityEvaluator.reset(new PriorityEvaluator(this));
priorityEvaluators.reset( priorityEvaluators.reset(
new SharedPool<PriorityEvaluator>( new SharedPool<PriorityEvaluator>(
@ -123,6 +124,12 @@ void Nullkiller::resetAiState()
lockedHeroes.clear(); lockedHeroes.clear();
dangerHitMap->reset(); dangerHitMap->reset();
useHeroChain = true; useHeroChain = true;
if(!baseGraph && ai->nullkiller->settings->isObjectGraphAllowed())
{
baseGraph = std::make_unique<ObjectGraph>();
baseGraph->updateGraph(this);
}
} }
void Nullkiller::updateAiState(int pass, bool fast) void Nullkiller::updateAiState(int pass, bool fast)
@ -163,21 +170,27 @@ void Nullkiller::updateAiState(int pass, bool fast)
PathfinderSettings cfg; PathfinderSettings cfg;
cfg.useHeroChain = useHeroChain; cfg.useHeroChain = useHeroChain;
cfg.allowBypassObjects = true;
if(scanDepth == ScanDepth::SMALL) if(scanDepth == ScanDepth::SMALL || settings->isObjectGraphAllowed())
{ {
cfg.mainTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT; cfg.mainTurnDistanceLimit = settings->getMainHeroTurnDistanceLimit();
} }
if(scanDepth != ScanDepth::ALL_FULL) if(scanDepth != ScanDepth::ALL_FULL || settings->isObjectGraphAllowed())
{ {
cfg.scoutTurnDistanceLimit = SCOUT_TURN_DISTANCE_LIMIT; cfg.scoutTurnDistanceLimit =settings->getScoutHeroTurnDistanceLimit();
} }
boost::this_thread::interruption_point(); boost::this_thread::interruption_point();
pathfinder->updatePaths(activeHeroes, cfg); pathfinder->updatePaths(activeHeroes, cfg);
if(settings->isObjectGraphAllowed())
{
pathfinder->updateGraphs(activeHeroes);
}
boost::this_thread::interruption_point(); boost::this_thread::interruption_point();
objectClusterizer->clusterize(); objectClusterizer->clusterize();
@ -235,13 +248,13 @@ void Nullkiller::makeTurn()
resetAiState(); resetAiState();
for(int i = 1; i <= MAXPASS; i++) for(int i = 1; i <= settings->getMaxPass(); i++)
{ {
updateAiState(i); updateAiState(i);
Goals::TTask bestTask = taskptr(Goals::Invalid()); Goals::TTask bestTask = taskptr(Goals::Invalid());
for(;i <= MAXPASS; i++) for(;i <= settings->getMaxPass(); i++)
{ {
Goals::TTaskVec fastTasks = { Goals::TTaskVec fastTasks = {
choseBestTask(sptr(BuyArmyBehavior()), 1), choseBestTask(sptr(BuyArmyBehavior()), 1),
@ -290,7 +303,8 @@ void Nullkiller::makeTurn()
// TODO: better to check turn distance here instead of priority // TODO: better to check turn distance here instead of priority
if((heroRole != HeroRole::MAIN || bestTask->priority < SMALL_SCAN_MIN_PRIORITY) if((heroRole != HeroRole::MAIN || bestTask->priority < SMALL_SCAN_MIN_PRIORITY)
&& scanDepth == ScanDepth::MAIN_FULL) && scanDepth == ScanDepth::MAIN_FULL
&& !settings->isObjectGraphAllowed())
{ {
useHeroChain = false; useHeroChain = false;
scanDepth = ScanDepth::SMALL; scanDepth = ScanDepth::SMALL;
@ -302,6 +316,8 @@ void Nullkiller::makeTurn()
} }
if(bestTask->priority < MIN_PRIORITY) if(bestTask->priority < MIN_PRIORITY)
{
if(!settings->isObjectGraphAllowed())
{ {
auto heroes = cb->getHeroesInfo(); auto heroes = cb->getHeroesInfo();
auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool
@ -320,6 +336,7 @@ void Nullkiller::makeTurn()
useHeroChain = false; useHeroChain = false;
continue; continue;
} }
}
logAi->trace("Goal %s has too low priority. It is not worth doing it. Ending turn.", taskDescription); logAi->trace("Goal %s has too low priority. It is not worth doing it. Ending turn.", taskDescription);
@ -328,9 +345,9 @@ void Nullkiller::makeTurn()
executeTask(bestTask); executeTask(bestTask);
if(i == MAXPASS) if(i == settings->getMaxPass())
{ {
logAi->error("Goal %s exceeded maxpass. Terminating AI turn.", taskDescription); logAi->warn("Goal %s exceeded maxpass. Terminating AI turn.", taskDescription);
} }
} }
} }

View File

@ -11,6 +11,7 @@
#include "PriorityEvaluator.h" #include "PriorityEvaluator.h"
#include "FuzzyHelper.h" #include "FuzzyHelper.h"
#include "Settings.h"
#include "AIMemory.h" #include "AIMemory.h"
#include "DeepDecomposer.h" #include "DeepDecomposer.h"
#include "../Analyzers/DangerHitMapAnalyzer.h" #include "../Analyzers/DangerHitMapAnalyzer.h"
@ -23,7 +24,6 @@
namespace NKAI namespace NKAI
{ {
const float MAX_GOLD_PEASURE = 0.3f;
const float MIN_PRIORITY = 0.01f; const float MIN_PRIORITY = 0.01f;
const float SMALL_SCAN_MIN_PRIORITY = 0.4f; const float SMALL_SCAN_MIN_PRIORITY = 0.4f;
@ -59,6 +59,8 @@ private:
bool useHeroChain; bool useHeroChain;
public: public:
static std::unique_ptr<ObjectGraph> baseGraph;
std::unique_ptr<DangerHitMapAnalyzer> dangerHitMap; std::unique_ptr<DangerHitMapAnalyzer> dangerHitMap;
std::unique_ptr<BuildAnalyzer> buildAnalyzer; std::unique_ptr<BuildAnalyzer> buildAnalyzer;
std::unique_ptr<ObjectClusterizer> objectClusterizer; std::unique_ptr<ObjectClusterizer> objectClusterizer;
@ -71,6 +73,7 @@ public:
std::unique_ptr<FuzzyHelper> dangerEvaluator; std::unique_ptr<FuzzyHelper> dangerEvaluator;
std::unique_ptr<DeepDecomposer> decomposer; std::unique_ptr<DeepDecomposer> decomposer;
std::unique_ptr<ArmyFormation> armyFormation; std::unique_ptr<ArmyFormation> armyFormation;
std::unique_ptr<Settings> settings;
PlayerColor playerID; PlayerColor playerID;
std::shared_ptr<CCallback> cb; std::shared_ptr<CCallback> cb;
std::mutex aiStateMutex; std::mutex aiStateMutex;

View File

@ -69,7 +69,7 @@ PriorityEvaluator::~PriorityEvaluator()
void PriorityEvaluator::initVisitTile() void PriorityEvaluator::initVisitTile()
{ {
auto file = CResourceHandler::get()->load(ResourcePath("config/ai/object-priorities.txt"))->readAll(); auto file = CResourceHandler::get()->load(ResourcePath("config/ai/nkai/object-priorities.txt"))->readAll();
std::string str = std::string((char *)file.first.get(), file.second); std::string str = std::string((char *)file.first.get(), file.second);
engine = fl::FllImporter().fromString(str); engine = fl::FllImporter().fromString(str);
armyLossPersentageVariable = engine->getInputVariable("armyLoss"); armyLossPersentageVariable = engine->getInputVariable("armyLoss");
@ -702,7 +702,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
class HeroExchangeEvaluator : public IEvaluationContextBuilder class HeroExchangeEvaluator : public IEvaluationContextBuilder
{ {
public: public:
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{ {
if(task->goalType != Goals::HERO_EXCHANGE) if(task->goalType != Goals::HERO_EXCHANGE)
return; return;
@ -719,7 +719,7 @@ public:
class ArmyUpgradeEvaluator : public IEvaluationContextBuilder class ArmyUpgradeEvaluator : public IEvaluationContextBuilder
{ {
public: public:
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{ {
if(task->goalType != Goals::ARMY_UPGRADE) if(task->goalType != Goals::ARMY_UPGRADE)
return; return;
@ -736,7 +736,7 @@ public:
class StayAtTownManaRecoveryEvaluator : public IEvaluationContextBuilder class StayAtTownManaRecoveryEvaluator : public IEvaluationContextBuilder
{ {
public: public:
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{ {
if(task->goalType != Goals::STAY_AT_TOWN) if(task->goalType != Goals::STAY_AT_TOWN)
return; return;
@ -771,7 +771,7 @@ void addTileDanger(EvaluationContext & evaluationContext, const int3 & tile, uin
class DefendTownEvaluator : public IEvaluationContextBuilder class DefendTownEvaluator : public IEvaluationContextBuilder
{ {
public: public:
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{ {
if(task->goalType != Goals::DEFEND_TOWN) if(task->goalType != Goals::DEFEND_TOWN)
return; return;
@ -821,7 +821,7 @@ private:
public: public:
ExecuteHeroChainEvaluationContextBuilder(const Nullkiller * ai) : ai(ai) {} ExecuteHeroChainEvaluationContextBuilder(const Nullkiller * ai) : ai(ai) {}
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{ {
if(task->goalType != Goals::EXECUTE_HERO_CHAIN) if(task->goalType != Goals::EXECUTE_HERO_CHAIN)
return; return;
@ -879,7 +879,7 @@ class ClusterEvaluationContextBuilder : public IEvaluationContextBuilder
public: public:
ClusterEvaluationContextBuilder(const Nullkiller * ai) {} ClusterEvaluationContextBuilder(const Nullkiller * ai) {}
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{ {
if(task->goalType != Goals::UNLOCK_CLUSTER) if(task->goalType != Goals::UNLOCK_CLUSTER)
return; return;
@ -926,7 +926,7 @@ public:
class ExchangeSwapTownHeroesContextBuilder : public IEvaluationContextBuilder class ExchangeSwapTownHeroesContextBuilder : public IEvaluationContextBuilder
{ {
public: public:
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{ {
if(task->goalType != Goals::EXCHANGE_SWAP_TOWN_HEROES) if(task->goalType != Goals::EXCHANGE_SWAP_TOWN_HEROES)
return; return;
@ -954,7 +954,7 @@ private:
public: public:
DismissHeroContextBuilder(const Nullkiller * ai) : ai(ai) {} DismissHeroContextBuilder(const Nullkiller * ai) : ai(ai) {}
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{ {
if(task->goalType != Goals::DISMISS_HERO) if(task->goalType != Goals::DISMISS_HERO)
return; return;
@ -974,7 +974,7 @@ public:
class BuildThisEvaluationContextBuilder : public IEvaluationContextBuilder class BuildThisEvaluationContextBuilder : public IEvaluationContextBuilder
{ {
public: public:
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{ {
if(task->goalType != Goals::BUILD_STRUCTURE) if(task->goalType != Goals::BUILD_STRUCTURE)
return; return;

View File

@ -0,0 +1,84 @@
/*
* Settings.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 <limits>
#include "Settings.h"
#include "../../../lib/mapObjectConstructors/AObjectTypeHandler.h"
#include "../../../lib/mapObjectConstructors/CObjectClassesHandler.h"
#include "../../../lib/mapObjectConstructors/CBankInstanceConstructor.h"
#include "../../../lib/mapObjects/MapObjects.h"
#include "../../../lib/modding/CModHandler.h"
#include "../../../lib/VCMI_Lib.h"
#include "../../../lib/filesystem/Filesystem.h"
#include "../../../lib/json/JsonNode.h"
namespace NKAI
{
Settings::Settings()
: maxRoamingHeroes(8),
mainHeroTurnDistanceLimit(10),
scoutHeroTurnDistanceLimit(5),
maxGoldPreasure(0.3f),
maxpass(30),
allowObjectGraph(false)
{
ResourcePath resource("config/ai/nkai/nkai-settings", EResType::JSON);
loadFromMod("core", resource);
for(const auto & modName : VLC->modh->getActiveMods())
{
if(CResourceHandler::get(modName)->existsResource(resource))
loadFromMod(modName, resource);
}
}
void Settings::loadFromMod(const std::string & modName, const ResourcePath & resource)
{
if(!CResourceHandler::get(modName)->existsResource(resource))
{
logGlobal->error("Failed to load font %s from mod %s", resource.getName(), modName);
return;
}
JsonNode node(JsonPath::fromResource(resource), modName);
if(node.Struct()["maxRoamingHeroes"].isNumber())
{
maxRoamingHeroes = node.Struct()["maxRoamingHeroes"].Integer();
}
if(node.Struct()["mainHeroTurnDistanceLimit"].isNumber())
{
mainHeroTurnDistanceLimit = node.Struct()["mainHeroTurnDistanceLimit"].Integer();
}
if(node.Struct()["scoutHeroTurnDistanceLimit"].isNumber())
{
scoutHeroTurnDistanceLimit = node.Struct()["scoutHeroTurnDistanceLimit"].Integer();
}
if(node.Struct()["maxpass"].isNumber())
{
maxpass = node.Struct()["maxpass"].Integer();
}
if(node.Struct()["maxGoldPreasure"].isNumber())
{
maxGoldPreasure = node.Struct()["maxGoldPreasure"].Float();
}
if(!node.Struct()["allowObjectGraph"].isNull())
{
allowObjectGraph = node.Struct()["allowObjectGraph"].Bool();
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Settings.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
VCMI_LIB_NAMESPACE_BEGIN
class JsonNode;
class ResourcePath;
VCMI_LIB_NAMESPACE_END
namespace NKAI
{
class Settings
{
private:
int maxRoamingHeroes;
int mainHeroTurnDistanceLimit;
int scoutHeroTurnDistanceLimit;
int maxpass;
float maxGoldPreasure;
bool allowObjectGraph;
public:
Settings();
int getMaxPass() const { return maxpass; }
float getMaxGoldPreasure() const { return maxGoldPreasure; }
int getMaxRoamingHeroes() const { return maxRoamingHeroes; }
int getMainHeroTurnDistanceLimit() const { return mainHeroTurnDistanceLimit; }
int getScoutHeroTurnDistanceLimit() const { return scoutHeroTurnDistanceLimit; }
bool isObjectGraphAllowed() const { return allowObjectGraph; }
private:
void loadFromMod(const std::string & modName, const ResourcePath & resource);
};
}

View File

@ -180,11 +180,7 @@ public:
{ {
} }
virtual ~cannotFulfillGoalException() throw () const char * what() const noexcept override
{
};
const char * what() const throw () override
{ {
return msg.c_str(); return msg.c_str();
} }
@ -203,11 +199,7 @@ public:
msg = goal->toString(); msg = goal->toString();
} }
virtual ~goalFulfilledException() throw () const char * what() const noexcept override
{
};
const char * what() const throw () override
{ {
return msg.c_str(); return msg.c_str();
} }

View File

@ -39,12 +39,18 @@ void AdventureSpellCast::accept(AIGateway * ai)
if(hero->mana < hero->getSpellCost(spell)) if(hero->mana < hero->getSpellCost(spell))
throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getNameTranslated()); throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getNameTranslated());
if(spellID == SpellID::TOWN_PORTAL && town && town->visitingHero)
throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->getNameTranslated());
if(town && spellID == SpellID::TOWN_PORTAL) if(town && spellID == SpellID::TOWN_PORTAL)
{ {
ai->selectedObject = town->id; ai->selectedObject = town->id;
if(town->visitingHero && town->tempOwner == ai->playerID && !town->getUpperArmy()->stacksCount())
{
ai->myCb->swapGarrisonHero(town);
}
if(town->visitingHero)
throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->getNameTranslated());
} }
auto wait = cb->waitTillRealize; auto wait = cb->waitTillRealize;

View File

@ -35,7 +35,7 @@ namespace Goals
void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;
std::string toString() const override; std::string toString() const override;
virtual bool operator==(const AdventureSpellCast & other) const override; bool operator==(const AdventureSpellCast & other) const override;
}; };
} }

View File

@ -32,7 +32,7 @@ namespace Goals
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override; bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const Build & other) const override bool operator==(const Build & other) const override
{ {
return true; return true;
} }

View File

@ -29,7 +29,7 @@ namespace Goals
void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;
std::string toString() const override; std::string toString() const override;
virtual bool operator==(const BuildBoat & other) const override; bool operator==(const BuildBoat & other) const override;
}; };
} }

View File

@ -39,8 +39,8 @@ namespace Goals
} }
BuildThis(BuildingID Bid, const CGTownInstance * tid); BuildThis(BuildingID Bid, const CGTownInstance * tid);
virtual bool operator==(const BuildThis & other) const override; bool operator==(const BuildThis & other) const override;
virtual std::string toString() const override; std::string toString() const override;
void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;
}; };
} }

View File

@ -36,11 +36,11 @@ namespace Goals
priority = 3;//TODO: evaluate? priority = 3;//TODO: evaluate?
} }
virtual bool operator==(const BuyArmy & other) const override; bool operator==(const BuyArmy & other) const override;
virtual std::string toString() const override; std::string toString() const override;
virtual void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;
}; };
} }

View File

@ -44,7 +44,7 @@ namespace Goals
//h & value & resID & objid & aid & tile & hero & town & bid; //h & value & resID & objid & aid & tile & hero & town & bid;
} }
virtual bool operator==(const AbstractGoal & g) const override bool operator==(const AbstractGoal & g) const override
{ {
if(goalType != g.goalType) if(goalType != g.goalType)
return false; return false;
@ -54,7 +54,7 @@ namespace Goals
virtual bool operator==(const T & other) const = 0; virtual bool operator==(const T & other) const = 0;
virtual TGoalVec decompose() const override TGoalVec decompose() const override
{ {
TSubgoal single = decomposeSingle(); TSubgoal single = decomposeSingle();
@ -90,11 +90,11 @@ namespace Goals
return *((T *)this); return *((T *)this);
} }
virtual bool isElementar() const override { return true; } bool isElementar() const override { return true; }
virtual HeroPtr getHero() const override { return AbstractGoal::hero; } HeroPtr getHero() const override { return AbstractGoal::hero; }
virtual int getHeroExchangeCount() const override { return 0; } int getHeroExchangeCount() const override { return 0; }
}; };
} }

View File

@ -34,11 +34,11 @@ namespace Goals
name = obj->getObjectName(); name = obj->getObjectName();
} }
virtual bool operator==(const CaptureObject & other) const override; bool operator==(const CaptureObject & other) const override;
virtual Goals::TGoalVec decompose() const override; Goals::TGoalVec decompose() const override;
virtual std::string toString() const override; std::string toString() const override;
virtual bool hasHash() const override { return true; } bool hasHash() const override { return true; }
virtual uint64_t getHash() const override; uint64_t getHash() const override;
}; };
} }

View File

@ -29,12 +29,12 @@ namespace Goals
{ {
} }
virtual Goals::TGoalVec decompose() const override; Goals::TGoalVec decompose() const override;
virtual std::string toString() const override; std::string toString() const override;
virtual bool hasHash() const override { return true; } bool hasHash() const override { return true; }
virtual uint64_t getHash() const override; uint64_t getHash() const override;
virtual bool operator==(const CompleteQuest & other) const override; bool operator==(const CompleteQuest & other) const override;
private: private:
TGoalVec tryCompleteQuest() const; TGoalVec tryCompleteQuest() const;

View File

@ -26,15 +26,15 @@ namespace Goals
{ {
} }
virtual bool operator==(const Composition & other) const override; bool operator==(const Composition & other) const override;
virtual std::string toString() const override; std::string toString() const override;
void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;
Composition & addNext(const AbstractGoal & goal); Composition & addNext(const AbstractGoal & goal);
Composition & addNext(TSubgoal goal); Composition & addNext(TSubgoal goal);
Composition & addNextSequence(const TGoalVec & taskSequence); Composition & addNextSequence(const TGoalVec & taskSequence);
virtual TGoalVec decompose() const override; TGoalVec decompose() const override;
virtual bool isElementar() const override; bool isElementar() const override;
virtual int getHeroExchangeCount() const override; int getHeroExchangeCount() const override;
}; };
} }

View File

@ -33,7 +33,7 @@ namespace Goals
{ {
tile = Tile; tile = Tile;
} }
virtual bool operator==(const DigAtTile & other) const override; bool operator==(const DigAtTile & other) const override;
private: private:
//TSubgoal decomposeSingle() const override; //TSubgoal decomposeSingle() const override;

View File

@ -26,7 +26,7 @@ namespace Goals
void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;
std::string toString() const override; std::string toString() const override;
virtual bool operator==(const DismissHero & other) const override; bool operator==(const DismissHero & other) const override;
}; };
} }

View File

@ -31,7 +31,7 @@ namespace Goals
void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;
std::string toString() const override; std::string toString() const override;
virtual bool operator==(const ExchangeSwapTownHeroes & other) const override; bool operator==(const ExchangeSwapTownHeroes & other) const override;
const CGHeroInstance * getGarrisonHero() const { return garrisonHero; } const CGHeroInstance * getGarrisonHero() const { return garrisonHero; }
HeroLockedReason getLockingReason() const { return lockingReason; } HeroLockedReason getLockingReason() const { return lockingReason; }

View File

@ -30,10 +30,10 @@ namespace Goals
void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;
std::string toString() const override; std::string toString() const override;
virtual bool operator==(const ExecuteHeroChain & other) const override; bool operator==(const ExecuteHeroChain & other) const override;
const AIPath & getPath() const { return chainPath; } const AIPath & getPath() const { return chainPath; }
virtual int getHeroExchangeCount() const override { return chainPath.exchangeCount; } int getHeroExchangeCount() const override { return chainPath.exchangeCount; }
private: private:
bool moveHeroToTile(const CGHeroInstance * hero, const int3 & tile); bool moveHeroToTile(const CGHeroInstance * hero, const int3 & tile);

View File

@ -36,7 +36,7 @@ namespace Goals
TGoalVec getAllPossibleSubgoals() override; TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
std::string completeMessage() const override; std::string completeMessage() const override;
virtual bool operator==(const GatherArmy & other) const override; bool operator==(const GatherArmy & other) const override;
}; };
} }

View File

@ -32,17 +32,17 @@ namespace Goals
return TGoalVec(); return TGoalVec();
} }
virtual bool operator==(const Invalid & other) const override bool operator==(const Invalid & other) const override
{ {
return true; return true;
} }
virtual std::string toString() const override std::string toString() const override
{ {
return "Invalid"; return "Invalid";
} }
virtual void accept(AIGateway * ai) override void accept(AIGateway * ai) override
{ {
throw cannotFulfillGoalException("Can not fulfill Invalid goal!"); throw cannotFulfillGoalException("Can not fulfill Invalid goal!");
} }

View File

@ -38,12 +38,12 @@ namespace Goals
{ {
} }
virtual bool operator==(const RecruitHero & other) const override bool operator==(const RecruitHero & other) const override
{ {
return true; return true;
} }
virtual std::string toString() const override; std::string toString() const override;
void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;
}; };
} }

View File

@ -28,7 +28,7 @@ namespace Goals
void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;
std::string toString() const override; std::string toString() const override;
virtual bool operator==(const SaveResources & other) const override; bool operator==(const SaveResources & other) const override;
}; };
} }

View File

@ -26,8 +26,8 @@ namespace Goals
public: public:
StayAtTown(const CGTownInstance * town, AIPath & path); StayAtTown(const CGTownInstance * town, AIPath & path);
virtual bool operator==(const StayAtTown & other) const override; bool operator==(const StayAtTown & other) const override;
virtual std::string toString() const override; std::string toString() const override;
void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;
float getMovementWasted() const { return movementWasted; } float getMovementWasted() const { return movementWasted; }
}; };

View File

@ -34,7 +34,7 @@ namespace Goals
value = val; value = val;
objid = Objid; objid = Objid;
} }
virtual bool operator==(const Trade & other) const override; bool operator==(const Trade & other) const override;
}; };
} }

View File

@ -29,8 +29,8 @@ namespace Goals
ArmyUpgrade(const AIPath & upgradePath, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade); ArmyUpgrade(const AIPath & upgradePath, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade);
ArmyUpgrade(const CGHeroInstance * targetMain, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade); ArmyUpgrade(const CGHeroInstance * targetMain, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade);
virtual bool operator==(const ArmyUpgrade & other) const override; bool operator==(const ArmyUpgrade & other) const override;
virtual std::string toString() const override; std::string toString() const override;
uint64_t getUpgradeValue() const { return upgradeValue; } uint64_t getUpgradeValue() const { return upgradeValue; }
uint64_t getInitialArmyValue() const { return initialValue; } uint64_t getInitialArmyValue() const { return initialValue; }

View File

@ -30,8 +30,8 @@ namespace Goals
DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath, bool isCounterAttack = false); DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath, bool isCounterAttack = false);
DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const CGHeroInstance * defender); DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const CGHeroInstance * defender);
virtual bool operator==(const DefendTown & other) const override; bool operator==(const DefendTown & other) const override;
virtual std::string toString() const override; std::string toString() const override;
const HitMapInfo & getTreat() const { return treat; } const HitMapInfo & getTreat() const { return treat; }

View File

@ -28,8 +28,8 @@ namespace Goals
sethero(targetHero); sethero(targetHero);
} }
virtual bool operator==(const HeroExchange & other) const override; bool operator==(const HeroExchange & other) const override;
virtual std::string toString() const override; std::string toString() const override;
uint64_t getReinforcementArmyStrength() const; uint64_t getReinforcementArmyStrength() const;
}; };

View File

@ -36,8 +36,8 @@ namespace Goals
sethero(pathToCenter.targetHero); sethero(pathToCenter.targetHero);
} }
virtual bool operator==(const UnlockCluster & other) const override; bool operator==(const UnlockCluster & other) const override;
virtual std::string toString() const override; std::string toString() const override;
std::shared_ptr<ObjectCluster> getCluster() const { return cluster; } std::shared_ptr<ObjectCluster> getCluster() const { return cluster; }
const AIPath & getPathToCenter() { return pathToCenter; } const AIPath & getPathToCenter() { return pathToCenter; }
}; };

View File

@ -332,7 +332,7 @@ std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
return neighbours; return neighbours;
} }
EPathfindingLayer phisycalLayers[2] = {EPathfindingLayer::LAND, EPathfindingLayer::SAIL}; constexpr std::array phisycalLayers = {EPathfindingLayer::LAND, EPathfindingLayer::SAIL};
bool AINodeStorage::increaseHeroChainTurnLimit() bool AINodeStorage::increaseHeroChainTurnLimit()
{ {
@ -843,6 +843,7 @@ ExchangeCandidate HeroChainCalculationTask::calculateExchange(
candidate.turns = carrierParentNode->turns; candidate.turns = carrierParentNode->turns;
candidate.setCost(carrierParentNode->getCost() + otherParentNode->getCost() / 1000.0); candidate.setCost(carrierParentNode->getCost() + otherParentNode->getCost() / 1000.0);
candidate.moveRemains = carrierParentNode->moveRemains; candidate.moveRemains = carrierParentNode->moveRemains;
candidate.danger = carrierParentNode->danger;
if(carrierParentNode->turns < otherParentNode->turns) if(carrierParentNode->turns < otherParentNode->turns)
{ {
@ -1122,14 +1123,14 @@ void AINodeStorage::calculateTownPortal(
{ {
for(const CGTownInstance * targetTown : towns) for(const CGTownInstance * targetTown : towns)
{ {
// TODO: allow to hide visiting hero in garrison if(targetTown->visitingHero
if(targetTown->visitingHero && maskMap.find(targetTown->visitingHero.get()) != maskMap.end()) && targetTown->getUpperArmy()->stacksCount()
&& maskMap.find(targetTown->visitingHero.get()) != maskMap.end())
{ {
auto basicMask = maskMap.at(targetTown->visitingHero.get()); auto basicMask = maskMap.at(targetTown->visitingHero.get());
bool heroIsInChain = (actor->chainMask & basicMask) != 0;
bool sameActorInTown = actor->chainMask == basicMask; bool sameActorInTown = actor->chainMask == basicMask;
if(sameActorInTown || !heroIsInChain) if(!sameActorInTown)
continue; continue;
} }
@ -1264,8 +1265,8 @@ bool AINodeStorage::hasBetterChain(
&& nodeActor->heroFightingStrength >= candidateActor->heroFightingStrength && nodeActor->heroFightingStrength >= candidateActor->heroFightingStrength
&& node.getCost() <= candidateNode->getCost()) && node.getCost() <= candidateNode->getCost())
{ {
if(nodeActor->heroFightingStrength == candidateActor->heroFightingStrength if(vstd::isAlmostEqual(nodeActor->heroFightingStrength, candidateActor->heroFightingStrength)
&& node.getCost() == candidateNode->getCost() && vstd::isAlmostEqual(node.getCost(), candidateNode->getCost())
&& &node < candidateNode) && &node < candidateNode)
{ {
continue; continue;

View File

@ -11,6 +11,7 @@
#pragma once #pragma once
#define NKAI_PATHFINDER_TRACE_LEVEL 0 #define NKAI_PATHFINDER_TRACE_LEVEL 0
constexpr int NKAI_GRAPH_TRACE_LEVEL = 0;
#define NKAI_TRACE_LEVEL 0 #define NKAI_TRACE_LEVEL 0
#include "../../../lib/pathfinder/CGPathNode.h" #include "../../../lib/pathfinder/CGPathNode.h"
@ -24,20 +25,11 @@
namespace NKAI namespace NKAI
{ {
const int SCOUT_TURN_DISTANCE_LIMIT = 5;
const int MAIN_TURN_DISTANCE_LIMIT = 10;
namespace AIPathfinding namespace AIPathfinding
{ {
#ifdef ENVIRONMENT64 const int BUCKET_COUNT = 3;
const int BUCKET_COUNT = 7;
#else
const int BUCKET_COUNT = 5;
#endif // ENVIRONMENT64
const int BUCKET_SIZE = 5; const int BUCKET_SIZE = 5;
const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE; const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE;
const int THREAD_COUNT = 8;
const int CHAIN_MAX_DEPTH = 4; const int CHAIN_MAX_DEPTH = 4;
} }
@ -188,7 +180,7 @@ public:
bool selectFirstActor(); bool selectFirstActor();
bool selectNextActor(); bool selectNextActor();
virtual std::vector<CGPathNode *> getInitialNodes() override; std::vector<CGPathNode *> getInitialNodes() override;
virtual std::vector<CGPathNode *> calculateNeighbours( virtual std::vector<CGPathNode *> calculateNeighbours(
const PathNodeInfo & source, const PathNodeInfo & source,
@ -200,7 +192,7 @@ public:
const PathfinderConfig * pathfinderConfig, const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) override; const CPathfinderHelper * pathfinderHelper) override;
virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override; void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override;
void commit( void commit(
AIPathNode * destination, AIPathNode * destination,

View File

@ -33,7 +33,7 @@ bool AIPathfinder::isTileAccessible(const HeroPtr & hero, const int3 & tile) con
|| storage->isTileAccessible(hero, tile, EPathfindingLayer::SAIL); || storage->isTileAccessible(hero, tile, EPathfindingLayer::SAIL);
} }
std::vector<AIPath> AIPathfinder::getPathInfo(const int3 & tile) const std::vector<AIPath> AIPathfinder::getPathInfo(const int3 & tile, bool includeGraph) const
{ {
const TerrainTile * tileInfo = cb->getTile(tile, false); const TerrainTile * tileInfo = cb->getTile(tile, false);
@ -42,10 +42,23 @@ std::vector<AIPath> AIPathfinder::getPathInfo(const int3 & tile) const
return std::vector<AIPath>(); return std::vector<AIPath>();
} }
return storage->getChainInfo(tile, !tileInfo->isWater()); auto info = storage->getChainInfo(tile, !tileInfo->isWater());
if(includeGraph)
{
for(auto hero : cb->getHeroesInfo())
{
auto graph = heroGraphs.find(hero->id);
if(graph != heroGraphs.end())
graph->second.addChainInfo(info, tile, hero, ai);
}
} }
void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes, PathfinderSettings pathfinderSettings) return info;
}
void AIPathfinder::updatePaths(const std::map<const CGHeroInstance *, HeroRole> & heroes, PathfinderSettings pathfinderSettings)
{ {
if(!storage) if(!storage)
{ {
@ -71,7 +84,7 @@ void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes
storage->setTownsAndDwellings(cb->getTownsInfo(), ai->memory->visitableObjs); storage->setTownsAndDwellings(cb->getTownsInfo(), ai->memory->visitableObjs);
} }
auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, storage); auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, storage, pathfinderSettings.allowBypassObjects);
logAi->trace("Recalculate paths pass %d", pass++); logAi->trace("Recalculate paths pass %d", pass++);
cb->calculatePaths(config); cb->calculatePaths(config);
@ -112,4 +125,34 @@ void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes
logAi->trace("Recalculated paths in %ld", timeElapsed(start)); logAi->trace("Recalculated paths in %ld", timeElapsed(start));
} }
void AIPathfinder::updateGraphs(const std::map<const CGHeroInstance *, HeroRole> & heroes)
{
auto start = std::chrono::high_resolution_clock::now();
std::vector<const CGHeroInstance *> heroesVector;
heroGraphs.clear();
for(auto hero : heroes)
{
if(heroGraphs.try_emplace(hero.first->id, GraphPaths()).second)
heroesVector.push_back(hero.first);
}
parallel_for(blocked_range<size_t>(0, heroesVector.size()), [this, &heroesVector](const blocked_range<size_t> & r)
{
for(auto i = r.begin(); i != r.end(); i++)
heroGraphs.at(heroesVector[i]->id).calculatePaths(heroesVector[i], ai);
});
if(NKAI_GRAPH_TRACE_LEVEL >= 1)
{
for(auto hero : heroes)
{
heroGraphs[hero.first->id].dumpToLog();
}
}
logAi->trace("Graph paths updated in %lld", timeElapsed(start));
}
} }

View File

@ -11,6 +11,7 @@
#pragma once #pragma once
#include "AINodeStorage.h" #include "AINodeStorage.h"
#include "ObjectGraph.h"
#include "../AIUtility.h" #include "../AIUtility.h"
namespace NKAI namespace NKAI
@ -23,11 +24,13 @@ struct PathfinderSettings
bool useHeroChain; bool useHeroChain;
uint8_t scoutTurnDistanceLimit; uint8_t scoutTurnDistanceLimit;
uint8_t mainTurnDistanceLimit; uint8_t mainTurnDistanceLimit;
bool allowBypassObjects;
PathfinderSettings() PathfinderSettings()
:useHeroChain(false), :useHeroChain(false),
scoutTurnDistanceLimit(255), scoutTurnDistanceLimit(255),
mainTurnDistanceLimit(255) mainTurnDistanceLimit(255),
allowBypassObjects(true)
{ } { }
}; };
@ -37,13 +40,20 @@ private:
std::shared_ptr<AINodeStorage> storage; std::shared_ptr<AINodeStorage> storage;
CPlayerSpecificInfoCallback * cb; CPlayerSpecificInfoCallback * cb;
Nullkiller * ai; Nullkiller * ai;
std::map<ObjectInstanceID, GraphPaths> heroGraphs;
public: public:
AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * ai); AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * ai);
std::vector<AIPath> getPathInfo(const int3 & tile) const; std::vector<AIPath> getPathInfo(const int3 & tile, bool includeGraph = false) const;
bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const; bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const;
void updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes, PathfinderSettings pathfinderSettings); void updatePaths(const std::map<const CGHeroInstance *, HeroRole> & heroes, PathfinderSettings pathfinderSettings);
void updateGraphs(const std::map<const CGHeroInstance *, HeroRole> & heroes);
void init(); void init();
std::shared_ptr<AINodeStorage>getStorage()
{
return storage;
}
}; };
} }

View File

@ -24,15 +24,16 @@ namespace AIPathfinding
std::vector<std::shared_ptr<IPathfindingRule>> makeRuleset( std::vector<std::shared_ptr<IPathfindingRule>> makeRuleset(
CPlayerSpecificInfoCallback * cb, CPlayerSpecificInfoCallback * cb,
Nullkiller * ai, Nullkiller * ai,
std::shared_ptr<AINodeStorage> nodeStorage) std::shared_ptr<AINodeStorage> nodeStorage,
bool allowBypassObjects)
{ {
std::vector<std::shared_ptr<IPathfindingRule>> rules = { std::vector<std::shared_ptr<IPathfindingRule>> rules = {
std::make_shared<AILayerTransitionRule>(cb, ai, nodeStorage), std::make_shared<AILayerTransitionRule>(cb, ai, nodeStorage),
std::make_shared<DestinationActionRule>(), std::make_shared<DestinationActionRule>(),
std::make_shared<AIMovementToDestinationRule>(nodeStorage), std::make_shared<AIMovementToDestinationRule>(nodeStorage, allowBypassObjects),
std::make_shared<MovementCostRule>(), std::make_shared<MovementCostRule>(),
std::make_shared<AIPreviousNodeRule>(nodeStorage), std::make_shared<AIPreviousNodeRule>(nodeStorage),
std::make_shared<AIMovementAfterDestinationRule>(cb, nodeStorage) std::make_shared<AIMovementAfterDestinationRule>(cb, nodeStorage, allowBypassObjects)
}; };
return rules; return rules;
@ -41,8 +42,9 @@ namespace AIPathfinding
AIPathfinderConfig::AIPathfinderConfig( AIPathfinderConfig::AIPathfinderConfig(
CPlayerSpecificInfoCallback * cb, CPlayerSpecificInfoCallback * cb,
Nullkiller * ai, Nullkiller * ai,
std::shared_ptr<AINodeStorage> nodeStorage) std::shared_ptr<AINodeStorage> nodeStorage,
:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage)), aiNodeStorage(nodeStorage) bool allowBypassObjects)
:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage, allowBypassObjects)), aiNodeStorage(nodeStorage)
{ {
options.canUseCast = true; options.canUseCast = true;
} }

View File

@ -30,11 +30,12 @@ namespace AIPathfinding
AIPathfinderConfig( AIPathfinderConfig(
CPlayerSpecificInfoCallback * cb, CPlayerSpecificInfoCallback * cb,
Nullkiller * ai, Nullkiller * ai,
std::shared_ptr<AINodeStorage> nodeStorage); std::shared_ptr<AINodeStorage> nodeStorage,
bool allowBypassObjects);
~AIPathfinderConfig(); ~AIPathfinderConfig();
virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override; CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;
}; };
} }

View File

@ -29,7 +29,7 @@ namespace AIPathfinding
public: public:
AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero, DayFlags flagsToAdd = DayFlags::NONE); AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero, DayFlags flagsToAdd = DayFlags::NONE);
virtual void execute(const CGHeroInstance * hero) const override; void execute(const CGHeroInstance * hero) const override;
virtual void applyOnDestination( virtual void applyOnDestination(
const CGHeroInstance * hero, const CGHeroInstance * hero,
@ -38,9 +38,9 @@ namespace AIPathfinding
AIPathNode * dstMode, AIPathNode * dstMode,
const AIPathNode * srcNode) const override; const AIPathNode * srcNode) const override;
virtual bool canAct(const AIPathNode * source) const override; bool canAct(const AIPathNode * source) const override;
virtual std::string toString() const override; std::string toString() const override;
}; };
class WaterWalkingAction : public AdventureCastAction class WaterWalkingAction : public AdventureCastAction

View File

@ -28,9 +28,9 @@ namespace AIPathfinding
{ {
} }
virtual void execute(const CGHeroInstance * hero) const override; void execute(const CGHeroInstance * hero) const override;
virtual std::string toString() const override; std::string toString() const override;
}; };
} }

View File

@ -25,7 +25,7 @@ namespace AIPathfinding
class SummonBoatAction : public VirtualBoatAction class SummonBoatAction : public VirtualBoatAction
{ {
public: public:
virtual void execute(const CGHeroInstance * hero) const override; void execute(const CGHeroInstance * hero) const override;
virtual void applyOnDestination( virtual void applyOnDestination(
const CGHeroInstance * hero, const CGHeroInstance * hero,
@ -34,11 +34,11 @@ namespace AIPathfinding
AIPathNode * dstMode, AIPathNode * dstMode,
const AIPathNode * srcNode) const override; const AIPathNode * srcNode) const override;
virtual bool canAct(const AIPathNode * source) const override; bool canAct(const AIPathNode * source) const override;
virtual const ChainActor * getActor(const ChainActor * sourceActor) const override; const ChainActor * getActor(const ChainActor * sourceActor) const override;
virtual std::string toString() const override; std::string toString() const override;
private: private:
int32_t getManaCost(const CGHeroInstance * hero) const; int32_t getManaCost(const CGHeroInstance * hero) const;
@ -56,17 +56,17 @@ namespace AIPathfinding
{ {
} }
virtual bool canAct(const AIPathNode * source) const override; bool canAct(const AIPathNode * source) const override;
virtual void execute(const CGHeroInstance * hero) const override; void execute(const CGHeroInstance * hero) const override;
virtual Goals::TSubgoal decompose(const CGHeroInstance * hero) const override; Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
virtual const ChainActor * getActor(const ChainActor * sourceActor) const override; const ChainActor * getActor(const ChainActor * sourceActor) const override;
virtual std::string toString() const override; std::string toString() const override;
virtual const CGObjectInstance * targetObject() const override; const CGObjectInstance * targetObject() const override;
}; };
} }

View File

@ -20,13 +20,18 @@ namespace AIPathfinding
{ {
bool QuestAction::canAct(const AIPathNode * node) const bool QuestAction::canAct(const AIPathNode * node) const
{ {
if(questInfo.obj->ID == Obj::BORDER_GATE || questInfo.obj->ID == Obj::BORDERGUARD) return canAct(node->actor->hero);
{
return dynamic_cast<const IQuestObject *>(questInfo.obj)->checkQuest(node->actor->hero);
} }
return questInfo.quest->activeForPlayers.count(node->actor->hero->getOwner()) bool QuestAction::canAct(const CGHeroInstance * hero) const
|| questInfo.quest->checkQuest(node->actor->hero); {
if(questInfo.obj->ID == Obj::BORDER_GATE || questInfo.obj->ID == Obj::BORDERGUARD)
{
return dynamic_cast<const IQuestObject *>(questInfo.obj)->checkQuest(hero);
}
return questInfo.quest->activeForPlayers.count(hero->getOwner())
|| questInfo.quest->checkQuest(hero);
} }
Goals::TSubgoal QuestAction::decompose(const CGHeroInstance * hero) const Goals::TSubgoal QuestAction::decompose(const CGHeroInstance * hero) const

View File

@ -28,13 +28,15 @@ namespace AIPathfinding
{ {
} }
virtual bool canAct(const AIPathNode * node) const override; bool canAct(const AIPathNode * node) const override;
virtual Goals::TSubgoal decompose(const CGHeroInstance * hero) const override; bool canAct(const CGHeroInstance * hero) const;
virtual void execute(const CGHeroInstance * hero) const override; Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
virtual std::string toString() const override; void execute(const CGHeroInstance * hero) const override;
std::string toString() const override;
}; };
} }

View File

@ -29,9 +29,9 @@ namespace AIPathfinding
{ {
} }
virtual void execute(const CGHeroInstance * hero) const override; void execute(const CGHeroInstance * hero) const override;
virtual std::string toString() const override; std::string toString() const override;
}; };
} }

View File

@ -18,7 +18,7 @@
using namespace NKAI; using namespace NKAI;
CCreatureSet emptyArmy; const CCreatureSet emptyArmy;
bool HeroExchangeArmy::needsLastStack() const bool HeroExchangeArmy::needsLastStack() const
{ {

View File

@ -28,7 +28,7 @@ class HeroExchangeArmy : public CArmedInstance
public: public:
TResources armyCost; TResources armyCost;
bool requireBuyArmy; bool requireBuyArmy;
virtual bool needsLastStack() const override; bool needsLastStack() const override;
std::shared_ptr<SpecialAction> getActorAction() const; std::shared_ptr<SpecialAction> getActorAction() const;
HeroExchangeArmy(): CArmedInstance(nullptr, true), requireBuyArmy(false) {} HeroExchangeArmy(): CArmedInstance(nullptr, true), requireBuyArmy(false) {}
@ -51,24 +51,24 @@ protected:
public: public:
uint64_t chainMask; uint64_t chainMask;
bool isMovable; bool isMovable = false;
bool allowUseResources; bool allowUseResources = false;
bool allowBattle; bool allowBattle = false;
bool allowSpellCast; bool allowSpellCast = false;
std::shared_ptr<SpecialAction> actorAction; std::shared_ptr<SpecialAction> actorAction;
const CGHeroInstance * hero; const CGHeroInstance * hero;
HeroRole heroRole; HeroRole heroRole;
const CCreatureSet * creatureSet; const CCreatureSet * creatureSet = nullptr;
const ChainActor * battleActor; const ChainActor * battleActor = nullptr;
const ChainActor * castActor; const ChainActor * castActor = nullptr;
const ChainActor * resourceActor; const ChainActor * resourceActor = nullptr;
const ChainActor * carrierParent; const ChainActor * carrierParent = nullptr;
const ChainActor * otherParent; const ChainActor * otherParent = nullptr;
const ChainActor * baseActor; const ChainActor * baseActor = nullptr;
int3 initialPosition; int3 initialPosition;
EPathfindingLayer layer; EPathfindingLayer layer;
uint32_t initialMovement; uint32_t initialMovement = 0;
uint32_t initialTurn; uint32_t initialTurn = 0;
uint64_t armyValue; uint64_t armyValue;
float heroFightingStrength; float heroFightingStrength;
uint8_t actorExchangeCount; uint8_t actorExchangeCount;
@ -126,7 +126,7 @@ public:
HeroActor(const ChainActor * carrier, const ChainActor * other, const HeroExchangeArmy * army, const Nullkiller * ai); HeroActor(const ChainActor * carrier, const ChainActor * other, const HeroExchangeArmy * army, const Nullkiller * ai);
protected: protected:
virtual ExchangeResult tryExchangeNoLock(const ChainActor * specialActor, const ChainActor * other) const override; ExchangeResult tryExchangeNoLock(const ChainActor * specialActor, const ChainActor * other) const override;
}; };
class ObjectActor : public ChainActor class ObjectActor : public ChainActor
@ -136,7 +136,7 @@ private:
public: public:
ObjectActor(const CGObjectInstance * obj, const CCreatureSet * army, uint64_t chainMask, int initialTurn); ObjectActor(const CGObjectInstance * obj, const CCreatureSet * army, uint64_t chainMask, int initialTurn);
virtual std::string toString() const override; std::string toString() const override;
const CGObjectInstance * getActorObject() const override; const CGObjectInstance * getActorObject() const override;
}; };
@ -154,7 +154,7 @@ private:
public: public:
DwellingActor(const CGDwelling * dwelling, uint64_t chainMask, bool waitForGrowth, int dayOfWeek); DwellingActor(const CGDwelling * dwelling, uint64_t chainMask, bool waitForGrowth, int dayOfWeek);
~DwellingActor(); ~DwellingActor();
virtual std::string toString() const override; std::string toString() const override;
protected: protected:
int getInitialTurn(bool waitForGrowth, int dayOfWeek); int getInitialTurn(bool waitForGrowth, int dayOfWeek);
@ -168,7 +168,7 @@ private:
public: public:
TownGarrisonActor(const CGTownInstance * town, uint64_t chainMask); TownGarrisonActor(const CGTownInstance * town, uint64_t chainMask);
virtual std::string toString() const override; std::string toString() const override;
}; };
} }

View File

@ -0,0 +1,671 @@
/*
* ObjectGraph.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 "ObjectGraph.h"
#include "AIPathfinderConfig.h"
#include "../../../lib/CRandomGenerator.h"
#include "../../../CCallback.h"
#include "../../../lib/mapping/CMap.h"
#include "../Engine/Nullkiller.h"
#include "../../../lib/logging/VisualLogger.h"
#include "Actions/QuestAction.h"
namespace NKAI
{
struct ConnectionCostInfo
{
float totalCost = 0;
float avg = 0;
int connectionsCount = 0;
};
class ObjectGraphCalculator
{
private:
ObjectGraph * target;
const Nullkiller * ai;
std::map<const CGHeroInstance *, HeroRole> actors;
std::map<const CGHeroInstance *, const CGObjectInstance *> actorObjectMap;
std::vector<std::unique_ptr<CGBoat>> temporaryBoats;
std::vector<std::unique_ptr<CGHeroInstance>> temporaryActorHeroes;
public:
ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai)
:ai(ai), target(target)
{
}
void setGraphObjects()
{
for(auto obj : ai->memory->visitableObjs)
{
if(obj && obj->isVisitable() && obj->ID != Obj::HERO && obj->ID != Obj::EVENT)
{
addObjectActor(obj);
}
}
for(auto town : ai->cb->getTownsInfo())
{
addObjectActor(town);
}
}
void calculateConnections()
{
updatePaths();
foreach_tile_pos(ai->cb.get(), [this](const CPlayerSpecificInfoCallback * cb, const int3 & pos)
{
calculateConnections(pos);
});
removeExtraConnections();
}
void addMinimalDistanceJunctions()
{
foreach_tile_pos(ai->cb.get(), [this](const CPlayerSpecificInfoCallback * cb, const int3 & pos)
{
if(target->hasNodeAt(pos))
return;
if(ai->cb->getGuardingCreaturePosition(pos).valid())
return;
ConnectionCostInfo currentCost = getConnectionsCost(pos);
if(currentCost.connectionsCount <= 2)
return;
float neighborCost = currentCost.avg + 0.001f;
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
{
logAi->trace("Checking junction %s", pos.toString());
}
foreach_neighbour(
ai->cb.get(),
pos,
[this, &neighborCost](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor)
{
auto costTotal = this->getConnectionsCost(neighbor);
if(costTotal.avg < neighborCost)
{
neighborCost = costTotal.avg;
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
{
logAi->trace("Better node found at %s", neighbor.toString());
}
}
});
if(currentCost.avg < neighborCost)
{
addJunctionActor(pos);
}
});
}
private:
void updatePaths()
{
PathfinderSettings ps;
ps.mainTurnDistanceLimit = 5;
ps.scoutTurnDistanceLimit = 1;
ps.allowBypassObjects = false;
ai->pathfinder->updatePaths(actors, ps);
}
void calculateConnections(const int3 & pos)
{
if(target->hasNodeAt(pos))
{
foreach_neighbour(
ai->cb.get(),
pos,
[this, &pos](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor)
{
if(target->hasNodeAt(neighbor))
{
auto paths = ai->pathfinder->getPathInfo(neighbor);
for(auto & path : paths)
{
if(pos == path.targetHero->visitablePos())
{
target->tryAddConnection(pos, neighbor, path.movementCost(), path.getTotalDanger());
}
}
}
});
return;
}
auto guardPos = ai->cb->getGuardingCreaturePosition(pos);
auto paths = ai->pathfinder->getPathInfo(pos);
for(AIPath & path1 : paths)
{
for(AIPath & path2 : paths)
{
if(path1.targetHero == path2.targetHero)
continue;
auto pos1 = path1.targetHero->visitablePos();
auto pos2 = path2.targetHero->visitablePos();
if(guardPos.valid() && guardPos != pos1 && guardPos != pos2)
continue;
auto obj1 = actorObjectMap[path1.targetHero];
auto obj2 = actorObjectMap[path2.targetHero];
auto tile1 = cb->getTile(pos1);
auto tile2 = cb->getTile(pos2);
if(tile2->isWater() && !tile1->isWater())
{
if(!cb->getTile(pos)->isWater())
continue;
if(obj1 && (obj1->ID != Obj::BOAT || obj1->ID != Obj::SHIPYARD))
continue;
}
auto danger = ai->pathfinder->getStorage()->evaluateDanger(pos2, path1.targetHero, true);
auto updated = target->tryAddConnection(
pos1,
pos2,
path1.movementCost() + path2.movementCost(),
danger);
if(NKAI_GRAPH_TRACE_LEVEL >= 2 && updated)
{
logAi->trace(
"Connected %s[%s] -> %s[%s] through [%s], cost %2f",
obj1 ? obj1->getObjectName() : "J", pos1.toString(),
obj2 ? obj2->getObjectName() : "J", pos2.toString(),
pos.toString(),
path1.movementCost() + path2.movementCost());
}
}
}
}
bool isExtraConnection(float direct, float side1, float side2) const
{
float sideRatio = (side1 + side2) / direct;
return sideRatio < 1.25f && direct > side1 && direct > side2;
}
void removeExtraConnections()
{
std::vector<std::pair<int3, int3>> connectionsToRemove;
for(auto & actor : temporaryActorHeroes)
{
auto pos = actor->visitablePos();
auto & currentNode = target->getNode(pos);
target->iterateConnections(pos, [this, &pos, &connectionsToRemove, &currentNode](int3 n1, ObjectLink o1)
{
target->iterateConnections(n1, [&pos, &o1, &currentNode, &connectionsToRemove, this](int3 n2, ObjectLink o2)
{
auto direct = currentNode.connections.find(n2);
if(direct != currentNode.connections.end() && isExtraConnection(direct->second.cost, o1.cost, o2.cost))
{
connectionsToRemove.push_back({pos, n2});
}
});
});
}
vstd::removeDuplicates(connectionsToRemove);
for(auto & c : connectionsToRemove)
{
target->removeConnection(c.first, c.second);
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
{
logAi->trace("Remove ineffective connection %s->%s", c.first.toString(), c.second.toString());
}
}
}
void addObjectActor(const CGObjectInstance * obj)
{
auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique<CGHeroInstance>(obj->cb)).get();
CRandomGenerator rng;
auto visitablePos = obj->visitablePos();
objectActor->setOwner(ai->playerID); // lets avoid having multiple colors
objectActor->initHero(rng, static_cast<HeroTypeID>(0));
objectActor->pos = objectActor->convertFromVisitablePos(visitablePos);
objectActor->initObj(rng);
if(cb->getTile(visitablePos)->isWater())
{
objectActor->boat = temporaryBoats.emplace_back(std::make_unique<CGBoat>(objectActor->cb)).get();
}
assert(objectActor->visitablePos() == visitablePos);
actorObjectMap[objectActor] = obj;
actors[objectActor] = obj->ID == Obj::TOWN || obj->ID == Obj::SHIPYARD ? HeroRole::MAIN : HeroRole::SCOUT;
target->addObject(obj);
}
void addJunctionActor(const int3 & visitablePos)
{
auto internalCb = temporaryActorHeroes.front()->cb;
auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique<CGHeroInstance>(internalCb)).get();
CRandomGenerator rng;
objectActor->setOwner(ai->playerID); // lets avoid having multiple colors
objectActor->initHero(rng, static_cast<HeroTypeID>(0));
objectActor->pos = objectActor->convertFromVisitablePos(visitablePos);
objectActor->initObj(rng);
if(cb->getTile(visitablePos)->isWater())
{
objectActor->boat = temporaryBoats.emplace_back(std::make_unique<CGBoat>(objectActor->cb)).get();
}
assert(objectActor->visitablePos() == visitablePos);
actorObjectMap[objectActor] = nullptr;
actors[objectActor] = HeroRole::SCOUT;
target->registerJunction(visitablePos);
}
ConnectionCostInfo getConnectionsCost(const int3 & pos) const
{
auto paths = ai->pathfinder->getPathInfo(pos);
std::map<int3, float> costs;
for(auto & path : paths)
{
auto fromPos = path.targetHero->visitablePos();
auto cost = costs.find(fromPos);
if(cost == costs.end())
{
costs.emplace(fromPos, path.movementCost());
}
else
{
if(path.movementCost() < cost->second)
{
costs[fromPos] = path.movementCost();
}
}
}
ConnectionCostInfo result;
for(auto & cost : costs)
{
result.totalCost += cost.second;
result.connectionsCount++;
}
if(result.connectionsCount)
{
result.avg = result.totalCost / result.connectionsCount;
}
return result;
}
};
bool ObjectGraph::tryAddConnection(
const int3 & from,
const int3 & to,
float cost,
uint64_t danger)
{
return nodes[from].connections[to].update(cost, danger);
}
void ObjectGraph::removeConnection(const int3 & from, const int3 & to)
{
nodes[from].connections.erase(to);
}
void ObjectGraph::updateGraph(const Nullkiller * ai)
{
auto cb = ai->cb;
ObjectGraphCalculator calculator(this, ai);
calculator.setGraphObjects();
calculator.calculateConnections();
calculator.addMinimalDistanceJunctions();
calculator.calculateConnections();
if(NKAI_GRAPH_TRACE_LEVEL >= 1)
dumpToLog("graph");
}
void ObjectGraph::addObject(const CGObjectInstance * obj)
{
nodes[obj->visitablePos()].init(obj);
}
void ObjectGraph::registerJunction(const int3 & pos)
{
nodes[pos].initJunction();
}
void ObjectGraph::removeObject(const CGObjectInstance * obj)
{
nodes[obj->visitablePos()].objectExists = false;
if(obj->ID == Obj::BOAT)
{
vstd::erase_if(nodes[obj->visitablePos()].connections, [&](const std::pair<int3, ObjectLink> & link) -> bool
{
auto tile = cb->getTile(link.first, false);
return tile && tile->isWater();
});
}
}
void ObjectGraph::connectHeroes(const Nullkiller * ai)
{
for(auto obj : ai->memory->visitableObjs)
{
if(obj && obj->ID == Obj::HERO)
{
addObject(obj);
}
}
for(auto & node : nodes)
{
auto pos = node.first;
auto paths = ai->pathfinder->getPathInfo(pos);
for(AIPath & path : paths)
{
if(path.getFirstBlockedAction())
continue;
auto heroPos = path.targetHero->visitablePos();
nodes[pos].connections[heroPos].update(
path.movementCost(),
path.getPathDanger());
nodes[heroPos].connections[pos].update(
path.movementCost(),
path.getPathDanger());
}
}
}
void ObjectGraph::dumpToLog(std::string visualKey) const
{
logVisual->updateWithLock(visualKey, [&](IVisualLogBuilder & logBuilder)
{
for(auto & tile : nodes)
{
for(auto & node : tile.second.connections)
{
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
{
logAi->trace(
"%s -> %s: %f !%d",
node.first.toString(),
tile.first.toString(),
node.second.cost,
node.second.danger);
}
logBuilder.addLine(tile.first, node.first);
}
}
});
}
bool GraphNodeComparer::operator()(const GraphPathNodePointer & lhs, const GraphPathNodePointer & rhs) const
{
return pathNodes.at(lhs.coord)[lhs.nodeType].cost > pathNodes.at(rhs.coord)[rhs.nodeType].cost;
}
void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai)
{
graph = *ai->baseGraph;
graph.connectHeroes(ai);
visualKey = std::to_string(ai->playerID) + ":" + targetHero->getNameTranslated();
pathNodes.clear();
GraphNodeComparer cmp(pathNodes);
GraphPathNode::TFibHeap pq(cmp);
pathNodes[targetHero->visitablePos()][GrapthPathNodeType::NORMAL].cost = 0;
pq.emplace(GraphPathNodePointer(targetHero->visitablePos(), GrapthPathNodeType::NORMAL));
while(!pq.empty())
{
GraphPathNodePointer pos = pq.top();
pq.pop();
auto & node = getOrCreateNode(pos);
std::shared_ptr<SpecialAction> transitionAction;
if(node.obj)
{
if(node.obj->ID == Obj::QUEST_GUARD
|| node.obj->ID == Obj::BORDERGUARD
|| node.obj->ID == Obj::BORDER_GATE)
{
auto questObj = dynamic_cast<const IQuestObject *>(node.obj);
auto questInfo = QuestInfo(questObj->quest, node.obj, pos.coord);
if(node.obj->ID == Obj::QUEST_GUARD
&& questObj->quest->mission == Rewardable::Limiter{}
&& questObj->quest->killTarget == ObjectInstanceID::NONE)
{
continue;
}
auto questAction = std::make_shared<AIPathfinding::QuestAction>(questInfo);
if(!questAction->canAct(targetHero))
{
transitionAction = questAction;
}
}
}
node.isInQueue = false;
graph.iterateConnections(pos.coord, [this, ai, &pos, &node, &transitionAction, &pq](int3 target, ObjectLink o)
{
auto targetNodeType = o.danger || transitionAction ? GrapthPathNodeType::BATTLE : pos.nodeType;
auto targetPointer = GraphPathNodePointer(target, targetNodeType);
auto & targetNode = getOrCreateNode(targetPointer);
if(targetNode.tryUpdate(pos, node, o))
{
targetNode.specialAction = transitionAction;
auto targetGraphNode = graph.getNode(target);
if(targetGraphNode.objID.hasValue())
{
targetNode.obj = ai->cb->getObj(targetGraphNode.objID, false);
if(targetNode.obj && targetNode.obj->ID == Obj::HERO)
return;
}
if(targetNode.isInQueue)
{
pq.increase(targetNode.handle);
}
else
{
targetNode.handle = pq.emplace(targetPointer);
targetNode.isInQueue = true;
}
}
});
}
}
void GraphPaths::dumpToLog() const
{
logVisual->updateWithLock(visualKey, [&](IVisualLogBuilder & logBuilder)
{
for(auto & tile : pathNodes)
{
for(auto & node : tile.second)
{
if(!node.previous.valid())
continue;
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
{
logAi->trace(
"%s -> %s: %f !%d",
node.previous.coord.toString(),
tile.first.toString(),
node.cost,
node.danger);
}
logBuilder.addLine(node.previous.coord, tile.first);
}
}
});
}
bool GraphPathNode::tryUpdate(const GraphPathNodePointer & pos, const GraphPathNode & prev, const ObjectLink & link)
{
auto newCost = prev.cost + link.cost;
if(newCost < cost)
{
previous = pos;
danger = prev.danger + link.danger;
cost = newCost;
return true;
}
return false;
}
void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHeroInstance * hero, const Nullkiller * ai) const
{
auto nodes = pathNodes.find(tile);
if(nodes == pathNodes.end())
return;
for(auto & node : nodes->second)
{
if(!node.reachable())
continue;
std::vector<GraphPathNodePointer> tilesToPass;
uint64_t danger = node.danger;
float cost = node.cost;
bool allowBattle = false;
auto current = GraphPathNodePointer(nodes->first, node.nodeType);
while(true)
{
auto currentTile = pathNodes.find(current.coord);
if(currentTile == pathNodes.end())
break;
auto currentNode = currentTile->second[current.nodeType];
if(!currentNode.previous.valid())
break;
allowBattle = allowBattle || currentNode.nodeType == GrapthPathNodeType::BATTLE;
vstd::amax(danger, currentNode.danger);
vstd::amax(cost, currentNode.cost);
tilesToPass.push_back(current);
if(currentNode.cost < 2.0f)
break;
current = currentNode.previous;
}
if(tilesToPass.empty())
continue;
auto entryPaths = ai->pathfinder->getPathInfo(tilesToPass.back().coord);
for(auto & path : entryPaths)
{
if(path.targetHero != hero)
continue;
for(auto graphTile = tilesToPass.rbegin(); graphTile != tilesToPass.rend(); graphTile++)
{
AIPathNodeInfo n;
n.coord = graphTile->coord;
n.cost = cost;
n.turns = static_cast<ui8>(cost) + 1; // just in case lets select worst scenario
n.danger = danger;
n.targetHero = hero;
n.parentIndex = -1;
n.specialAction = getNode(*graphTile).specialAction;
for(auto & node : path.nodes)
{
node.parentIndex++;
}
path.nodes.insert(path.nodes.begin(), n);
}
path.armyLoss += ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), danger);
path.targetObjectDanger = ai->pathfinder->getStorage()->evaluateDanger(tile, path.targetHero, !allowBattle);
path.targetObjectArmyLoss = ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), path.targetObjectDanger);
paths.push_back(path);
}
}
}
}

View File

@ -0,0 +1,192 @@
/*
* ObjectGraph.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 "AINodeStorage.h"
#include "../AIUtility.h"
namespace NKAI
{
class Nullkiller;
struct ObjectLink
{
float cost = 100000; // some big number
uint64_t danger = 0;
bool update(float newCost, uint64_t newDanger)
{
if(cost > newCost)
{
cost = newCost;
danger = newDanger;
return true;
}
return false;
}
};
struct ObjectNode
{
ObjectInstanceID objID;
MapObjectID objTypeID;
bool objectExists;
std::unordered_map<int3, ObjectLink> connections;
void init(const CGObjectInstance * obj)
{
objectExists = true;
objID = obj->id;
objTypeID = obj->ID;
}
void initJunction()
{
objectExists = false;
objID = ObjectInstanceID();
objTypeID = Obj();
}
};
class ObjectGraph
{
std::unordered_map<int3, ObjectNode> nodes;
public:
void updateGraph(const Nullkiller * ai);
void addObject(const CGObjectInstance * obj);
void registerJunction(const int3 & pos);
void connectHeroes(const Nullkiller * ai);
void removeObject(const CGObjectInstance * obj);
bool tryAddConnection(const int3 & from, const int3 & to, float cost, uint64_t danger);
void removeConnection(const int3 & from, const int3 & to);
void dumpToLog(std::string visualKey) const;
template<typename Func>
void iterateConnections(const int3 & pos, Func fn)
{
for(auto & connection : nodes.at(pos).connections)
{
fn(connection.first, connection.second);
}
}
const ObjectNode & getNode(int3 tile) const
{
return nodes.at(tile);
}
bool hasNodeAt(const int3 & tile) const
{
return vstd::contains(nodes, tile);
}
};
struct GraphPathNode;
enum GrapthPathNodeType
{
NORMAL,
BATTLE,
LAST
};
struct GraphPathNodePointer
{
int3 coord = int3(-1);
GrapthPathNodeType nodeType = GrapthPathNodeType::NORMAL;
GraphPathNodePointer() = default;
GraphPathNodePointer(int3 coord, GrapthPathNodeType type)
:coord(coord), nodeType(type)
{ }
bool valid() const
{
return coord.valid();
}
};
typedef std::unordered_map<int3, GraphPathNode[GrapthPathNodeType::LAST]> GraphNodeStorage;
class GraphNodeComparer
{
const GraphNodeStorage & pathNodes;
public:
GraphNodeComparer(const GraphNodeStorage & pathNodes)
:pathNodes(pathNodes)
{
}
bool operator()(const GraphPathNodePointer & lhs, const GraphPathNodePointer & rhs) const;
};
struct GraphPathNode
{
const float BAD_COST = 100000;
GrapthPathNodeType nodeType = GrapthPathNodeType::NORMAL;
GraphPathNodePointer previous;
float cost = BAD_COST;
uint64_t danger = 0;
const CGObjectInstance * obj = nullptr;
std::shared_ptr<SpecialAction> specialAction;
using TFibHeap = boost::heap::fibonacci_heap<GraphPathNodePointer, boost::heap::compare<GraphNodeComparer>>;
TFibHeap::handle_type handle;
bool isInQueue = false;
bool reachable() const
{
return cost < BAD_COST;
}
bool tryUpdate(const GraphPathNodePointer & pos, const GraphPathNode & prev, const ObjectLink & link);
};
class GraphPaths
{
ObjectGraph graph;
GraphNodeStorage pathNodes;
std::string visualKey;
public:
void calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai);
void addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHeroInstance * hero, const Nullkiller * ai) const;
void dumpToLog() const;
private:
GraphPathNode & getOrCreateNode(const GraphPathNodePointer & pos)
{
auto & node = pathNodes[pos.coord][pos.nodeType];
node.nodeType = pos.nodeType;
return node;
}
const GraphPathNode & getNode(const GraphPathNodePointer & pos) const
{
auto & node = pathNodes.at(pos.coord)[pos.nodeType];
return node;
}
};
}

View File

@ -131,7 +131,7 @@ namespace AIPathfinding
{ {
if(obj->ID != Obj::TOWN) //towns were handled in the previous loop if(obj->ID != Obj::TOWN) //towns were handled in the previous loop
{ {
if(const IShipyard * shipyard = IShipyard::castFrom(obj)) if(const auto * shipyard = dynamic_cast<const IShipyard *>(obj))
shipyards.push_back(shipyard); shipyards.push_back(shipyard);
} }
} }

View File

@ -13,6 +13,7 @@
#include "../Actions/QuestAction.h" #include "../Actions/QuestAction.h"
#include "../../Goals/Invalid.h" #include "../../Goals/Invalid.h"
#include "AIPreviousNodeRule.h" #include "AIPreviousNodeRule.h"
#include "../../../../lib/pathfinder/PathfinderOptions.h"
namespace NKAI namespace NKAI
{ {
@ -20,8 +21,9 @@ namespace AIPathfinding
{ {
AIMovementAfterDestinationRule::AIMovementAfterDestinationRule( AIMovementAfterDestinationRule::AIMovementAfterDestinationRule(
CPlayerSpecificInfoCallback * cb, CPlayerSpecificInfoCallback * cb,
std::shared_ptr<AINodeStorage> nodeStorage) std::shared_ptr<AINodeStorage> nodeStorage,
:cb(cb), nodeStorage(nodeStorage) bool allowBypassObjects)
:cb(cb), nodeStorage(nodeStorage), allowBypassObjects(allowBypassObjects)
{ {
} }
@ -40,10 +42,32 @@ namespace AIPathfinding
} }
auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper); auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);
if(blocker == BlockingReason::NONE) if(blocker == BlockingReason::NONE)
{ {
destination.blocked = nodeStorage->isDistanceLimitReached(source, destination); destination.blocked = nodeStorage->isDistanceLimitReached(source, destination);
if(destination.nodeObject
&& !destination.blocked
&& !allowBypassObjects
&& !dynamic_cast<const CGTeleport *>(destination.nodeObject)
&& destination.nodeObject->ID != Obj::EVENT)
{
destination.blocked = true;
destination.node->locked = true;
}
return;
}
if(!allowBypassObjects)
{
if(destination.nodeObject)
{
destination.blocked = true;
destination.node->locked = true;
}
return; return;
} }

View File

@ -25,9 +25,13 @@ namespace AIPathfinding
private: private:
CPlayerSpecificInfoCallback * cb; CPlayerSpecificInfoCallback * cb;
std::shared_ptr<AINodeStorage> nodeStorage; std::shared_ptr<AINodeStorage> nodeStorage;
bool allowBypassObjects;
public: public:
AIMovementAfterDestinationRule(CPlayerSpecificInfoCallback * cb, std::shared_ptr<AINodeStorage> nodeStorage); AIMovementAfterDestinationRule(
CPlayerSpecificInfoCallback * cb,
std::shared_ptr<AINodeStorage> nodeStorage,
bool allowBypassObjects);
virtual void process( virtual void process(
const PathNodeInfo & source, const PathNodeInfo & source,

View File

@ -14,8 +14,10 @@ namespace NKAI
{ {
namespace AIPathfinding namespace AIPathfinding
{ {
AIMovementToDestinationRule::AIMovementToDestinationRule(std::shared_ptr<AINodeStorage> nodeStorage) AIMovementToDestinationRule::AIMovementToDestinationRule(
: nodeStorage(nodeStorage) std::shared_ptr<AINodeStorage> nodeStorage,
bool allowBypassObjects)
: nodeStorage(nodeStorage), allowBypassObjects(allowBypassObjects)
{ {
} }
@ -37,7 +39,21 @@ namespace AIPathfinding
return; return;
} }
if(blocker == BlockingReason::SOURCE_GUARDED && nodeStorage->getAINode(source.node)->actor->allowBattle) if(blocker == BlockingReason::SOURCE_GUARDED)
{
auto actor = nodeStorage->getAINode(source.node)->actor;
if(!allowBypassObjects)
{
if (source.node->getCost() < 0.0001f)
return;
// when actor represents moster graph node, we need to let him escape monster
if(cb->getGuardingCreaturePosition(source.coord) == actor->initialPosition)
return;
}
if(actor->allowBattle)
{ {
#if NKAI_PATHFINDER_TRACE_LEVEL >= 1 #if NKAI_PATHFINDER_TRACE_LEVEL >= 1
logAi->trace( logAi->trace(
@ -47,6 +63,7 @@ namespace AIPathfinding
#endif #endif
return; return;
} }
}
destination.blocked = true; destination.blocked = true;
} }

View File

@ -24,9 +24,10 @@ namespace AIPathfinding
{ {
private: private:
std::shared_ptr<AINodeStorage> nodeStorage; std::shared_ptr<AINodeStorage> nodeStorage;
bool allowBypassObjects;
public: public:
AIMovementToDestinationRule(std::shared_ptr<AINodeStorage> nodeStorage); AIMovementToDestinationRule(std::shared_ptr<AINodeStorage> nodeStorage, bool allowBypassObjects);
virtual void process( virtual void process(
const PathNodeInfo & source, const PathNodeInfo & source,

View File

@ -14,7 +14,7 @@
#define strcpy_s(a, b, c) strncpy(a, c, b) #define strcpy_s(a, b, c) strncpy(a, c, b)
#endif #endif
static const char * g_cszAiName = "Nullkiller"; static const char * const g_cszAiName = "Nullkiller";
extern "C" DLL_EXPORT int GetGlobalAiVersion() extern "C" DLL_EXPORT int GetGlobalAiVersion()
{ {

View File

@ -8,19 +8,19 @@ set(stupidAI_HEADERS
StupidAI.h StupidAI.h
) )
if(NOT ENABLE_STATIC_AI_LIBS) if(NOT ENABLE_STATIC_LIBS)
list(APPEND stupidAI_SRCS main.cpp StdInc.cpp) list(APPEND stupidAI_SRCS main.cpp StdInc.cpp)
endif() endif()
assign_source_group(${stupidAI_SRCS} ${stupidAI_HEADERS}) assign_source_group(${stupidAI_SRCS} ${stupidAI_HEADERS})
if(ENABLE_STATIC_AI_LIBS) if(ENABLE_STATIC_LIBS)
add_library(StupidAI STATIC ${stupidAI_SRCS} ${stupidAI_HEADERS}) add_library(StupidAI STATIC ${stupidAI_SRCS} ${stupidAI_HEADERS})
else() else()
add_library(StupidAI SHARED ${stupidAI_SRCS} ${stupidAI_HEADERS}) add_library(StupidAI SHARED ${stupidAI_SRCS} ${stupidAI_HEADERS})
install(TARGETS StupidAI RUNTIME DESTINATION ${AI_LIB_DIR} LIBRARY DESTINATION ${AI_LIB_DIR}) install(TARGETS StupidAI RUNTIME DESTINATION ${AI_LIB_DIR} LIBRARY DESTINATION ${AI_LIB_DIR})
endif() endif()
target_link_libraries(StupidAI PRIVATE ${VCMI_LIB_TARGET}) target_link_libraries(StupidAI PRIVATE vcmi)
target_include_directories(StupidAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(StupidAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
vcmi_set_output_dir(StupidAI "AI") vcmi_set_output_dir(StupidAI "AI")

View File

@ -16,7 +16,7 @@
#define strcpy_s(a, b, c) strncpy(a, c, b) #define strcpy_s(a, b, c) strncpy(a, c, b)
#endif #endif
static const char *g_cszAiName = "Stupid AI 0.1"; static const char * const g_cszAiName = "Stupid AI 0.1";
extern "C" DLL_EXPORT int GetGlobalAiVersion() extern "C" DLL_EXPORT int GetGlobalAiVersion()
{ {

View File

@ -94,12 +94,12 @@ set(VCAI_HEADERS
VCAI.h VCAI.h
) )
if(NOT ENABLE_STATIC_AI_LIBS) if(NOT ENABLE_STATIC_LIBS)
list(APPEND VCAI_SRCS main.cpp StdInc.cpp) list(APPEND VCAI_SRCS main.cpp StdInc.cpp)
endif() endif()
assign_source_group(${VCAI_SRCS} ${VCAI_HEADERS}) assign_source_group(${VCAI_SRCS} ${VCAI_HEADERS})
if(ENABLE_STATIC_AI_LIBS) if(ENABLE_STATIC_LIBS)
add_library(VCAI STATIC ${VCAI_SRCS} ${VCAI_HEADERS}) add_library(VCAI STATIC ${VCAI_SRCS} ${VCAI_HEADERS})
else() else()
add_library(VCAI SHARED ${VCAI_SRCS} ${VCAI_HEADERS}) add_library(VCAI SHARED ${VCAI_SRCS} ${VCAI_HEADERS})
@ -107,7 +107,7 @@ else()
endif() endif()
target_include_directories(VCAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(VCAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(VCAI PUBLIC ${VCMI_LIB_TARGET} fuzzylite::fuzzylite) target_link_libraries(VCAI PUBLIC vcmi fuzzylite::fuzzylite)
vcmi_set_output_dir(VCAI "AI") vcmi_set_output_dir(VCAI "AI")
enable_pch(VCAI) enable_pch(VCAI)

View File

@ -109,51 +109,52 @@ bool AbstractGoal::operator==(const AbstractGoal & g) const
return false; return false;
} }
bool AbstractGoal::operator<(AbstractGoal & g) //for std::unique // FIXME: unused code?
{ //bool AbstractGoal::operator<(AbstractGoal & g) //for std::unique
//TODO: make sure it gets goals consistent with == operator //{
if (goalType < g.goalType) // //TODO: make sure it gets goals consistent with == operator
return true; // if (goalType < g.goalType)
if (goalType > g.goalType) // return true;
return false; // if (goalType > g.goalType)
if (hero < g.hero) // return false;
return true; // if (hero < g.hero)
if (hero > g.hero) // return true;
return false; // if (hero > g.hero)
if (tile < g.tile) // return false;
return true; // if (tile < g.tile)
if (g.tile < tile) // return true;
return false; // if (g.tile < tile)
if (objid < g.objid) // return false;
return true; // if (objid < g.objid)
if (objid > g.objid) // return true;
return false; // if (objid > g.objid)
if (town < g.town) // return false;
return true; // if (town < g.town)
if (town > g.town) // return true;
return false; // if (town > g.town)
if (value < g.value) // return false;
return true; // if (value < g.value)
if (value > g.value) // return true;
return false; // if (value > g.value)
if (priority < g.priority) // return false;
return true; // if (priority < g.priority)
if (priority > g.priority) // return true;
return false; // if (priority > g.priority)
if (resID < g.resID) // return false;
return true; // if (resID < g.resID)
if (resID > g.resID) // return true;
return false; // if (resID > g.resID)
if (bid < g.bid) // return false;
return true; // if (bid < g.bid)
if (bid > g.bid) // return true;
return false; // if (bid > g.bid)
if (aid < g.aid) // return false;
return true; // if (aid < g.aid)
if (aid > g.aid) // return true;
return false; // if (aid > g.aid)
return false; // return false;
} // return false;
//}
//TODO: find out why the following are not generated automatically on MVS? //TODO: find out why the following are not generated automatically on MVS?
bool TSubgoal::operator==(const TSubgoal & rhs) const bool TSubgoal::operator==(const TSubgoal & rhs) const

View File

@ -165,16 +165,16 @@ namespace Goals
virtual float accept(FuzzyHelper * f); virtual float accept(FuzzyHelper * f);
virtual bool operator==(const AbstractGoal & g) const; virtual bool operator==(const AbstractGoal & g) const;
bool operator<(AbstractGoal & g); //final // bool operator<(AbstractGoal & g); //final
virtual bool fulfillsMe(Goals::TSubgoal goal) //TODO: multimethod instead of type check virtual bool fulfillsMe(Goals::TSubgoal goal) //TODO: multimethod instead of type check
{ {
return false; //use this method to check if goal is fulfilled by another (not equal) goal, operator == is handled spearately return false; //use this method to check if goal is fulfilled by another (not equal) goal, operator == is handled spearately
} }
bool operator!=(const AbstractGoal & g) const // bool operator!=(const AbstractGoal & g) const
{ // {
return !(*this == g); // return !(*this == g);
} // }
template<typename Handler> void serialize(Handler & h) template<typename Handler> void serialize(Handler & h)
{ {

View File

@ -39,6 +39,6 @@ namespace Goals
void accept(VCAI * ai) override; void accept(VCAI * ai) override;
std::string name() const override; std::string name() const override;
std::string completeMessage() const override; std::string completeMessage() const override;
virtual bool operator==(const AdventureSpellCast & other) const override; bool operator==(const AdventureSpellCast & other) const override;
}; };
} }

View File

@ -29,7 +29,7 @@ namespace Goals
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override; bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const Build & other) const override bool operator==(const Build & other) const override
{ {
return true; return true;
} }

View File

@ -32,6 +32,6 @@ namespace Goals
void accept(VCAI * ai) override; void accept(VCAI * ai) override;
std::string name() const override; std::string name() const override;
std::string completeMessage() const override; std::string completeMessage() const override;
virtual bool operator==(const BuildBoat & other) const override; bool operator==(const BuildBoat & other) const override;
}; };
} }

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