mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-24 22:14:36 +02:00
Merge remote-tracking branch 'origin/develop' into dimension-door-changes
This commit is contained in:
commit
048fb1867d
8
.github/workflows/github.yml
vendored
8
.github/workflows/github.yml
vendored
@ -124,7 +124,7 @@ jobs:
|
||||
# also, running it on multiple presets is redundant and slightly increases already long CI built times
|
||||
if: ${{ startsWith(matrix.preset, 'linux-clang-test') }}
|
||||
run: |
|
||||
pip3 install json5 jstyleson
|
||||
pip3 install jstyleson
|
||||
python3 CI/linux-qt6/validate_json.py
|
||||
|
||||
- name: Dependencies
|
||||
@ -134,7 +134,7 @@ jobs:
|
||||
|
||||
# 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
|
||||
- name: Ccache for PRs
|
||||
- name: ccache for PRs
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
if: ${{ github.event.number != '' }}
|
||||
with:
|
||||
@ -146,9 +146,9 @@ jobs:
|
||||
max-size: "5G"
|
||||
verbose: 2
|
||||
|
||||
- name: Ccache for vcmi/vcmi's develop branch
|
||||
- name: ccache for everything but PRs
|
||||
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:
|
||||
key: ${{ matrix.preset }}-no-PR
|
||||
restore-keys: |
|
||||
|
@ -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)
|
||||
{
|
||||
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);
|
||||
|
||||
damage = damageCache[attacker->unitId()][defender->unitId()] * attacker->getCount();
|
||||
}
|
||||
|
||||
return static_cast<int64_t>(damage);
|
||||
return damageCache[attacker->unitId()][defender->unitId()] * attacker->getCount();
|
||||
}
|
||||
|
||||
int64_t DamageCache::getOriginalDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb)
|
||||
|
@ -10,7 +10,6 @@
|
||||
#pragma once
|
||||
#include "../../lib/battle/CUnitState.h"
|
||||
#include "../../CCallback.h"
|
||||
#include "common.h"
|
||||
#include "StackWithBonuses.h"
|
||||
|
||||
#define BATTLE_TRACE_LEVEL 0
|
||||
|
@ -49,7 +49,6 @@ CBattleAI::~CBattleAI()
|
||||
|
||||
void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB)
|
||||
{
|
||||
setCbc(CB);
|
||||
env = ENV;
|
||||
cb = CB;
|
||||
playerID = *CB->getPlayerID();
|
||||
@ -121,7 +120,6 @@ void CBattleAI::activeStack(const BattleID & battleID, const CStack * 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();
|
||||
|
||||
|
@ -270,7 +270,7 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
||||
{
|
||||
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.bestAttack = ap;
|
||||
|
@ -2,7 +2,6 @@ set(battleAI_SRCS
|
||||
AttackPossibility.cpp
|
||||
BattleAI.cpp
|
||||
BattleEvaluator.cpp
|
||||
common.cpp
|
||||
EnemyInfo.cpp
|
||||
PossibleSpellcast.cpp
|
||||
PotentialTargets.cpp
|
||||
@ -17,7 +16,6 @@ set(battleAI_HEADERS
|
||||
AttackPossibility.h
|
||||
BattleAI.h
|
||||
BattleEvaluator.h
|
||||
common.h
|
||||
EnemyInfo.h
|
||||
PotentialTargets.h
|
||||
PossibleSpellcast.h
|
||||
@ -26,12 +24,12 @@ set(battleAI_HEADERS
|
||||
BattleExchangeVariant.h
|
||||
)
|
||||
|
||||
if(NOT ENABLE_STATIC_AI_LIBS)
|
||||
if(NOT ENABLE_STATIC_LIBS)
|
||||
list(APPEND battleAI_SRCS main.cpp StdInc.cpp)
|
||||
endif()
|
||||
assign_source_group(${battleAI_SRCS} ${battleAI_HEADERS})
|
||||
|
||||
if(ENABLE_STATIC_AI_LIBS)
|
||||
if(ENABLE_STATIC_LIBS)
|
||||
add_library(BattleAI STATIC ${battleAI_SRCS} ${battleAI_HEADERS})
|
||||
else()
|
||||
add_library(BattleAI SHARED ${battleAI_SRCS} ${battleAI_HEADERS})
|
||||
@ -39,7 +37,7 @@ else()
|
||||
endif()
|
||||
|
||||
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")
|
||||
enable_pch(BattleAI)
|
||||
|
@ -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;
|
||||
}
|
@ -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();
|
@ -15,7 +15,7 @@
|
||||
#define strcpy_s(a, b, c) strncpy(a, c, b)
|
||||
#endif
|
||||
|
||||
static const char *g_cszAiName = "Battle AI";
|
||||
static const char * const g_cszAiName = "Battle AI";
|
||||
|
||||
extern "C" DLL_EXPORT int GetGlobalAiVersion()
|
||||
{
|
||||
|
@ -19,8 +19,8 @@ class CEmptyAI : public CGlobalAI
|
||||
std::shared_ptr<CCallback> cb;
|
||||
|
||||
public:
|
||||
virtual void saveGame(BinarySerializer & h) override;
|
||||
virtual void loadGame(BinaryDeserializer & h) override;
|
||||
void saveGame(BinarySerializer & h) override;
|
||||
void loadGame(BinaryDeserializer & h) override;
|
||||
|
||||
void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
|
||||
void yourTurn(QueryID queryID) override;
|
||||
|
@ -8,12 +8,12 @@ set(emptyAI_HEADERS
|
||||
CEmptyAI.h
|
||||
)
|
||||
|
||||
if(NOT ENABLE_STATIC_AI_LIBS)
|
||||
if(NOT ENABLE_STATIC_LIBS)
|
||||
list(APPEND emptyAI_SRCS main.cpp StdInc.cpp)
|
||||
endif()
|
||||
assign_source_group(${emptyAI_SRCS} ${emptyAI_HEADERS})
|
||||
|
||||
if(ENABLE_STATIC_AI_LIBS)
|
||||
if(ENABLE_STATIC_LIBS)
|
||||
add_library(EmptyAI STATIC ${emptyAI_SRCS} ${emptyAI_HEADERS})
|
||||
else()
|
||||
add_library(EmptyAI SHARED ${emptyAI_SRCS} ${emptyAI_HEADERS})
|
||||
@ -21,7 +21,7 @@ else()
|
||||
endif()
|
||||
|
||||
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")
|
||||
enable_pch(EmptyAI)
|
||||
|
@ -11,7 +11,6 @@
|
||||
|
||||
#include "CEmptyAI.h"
|
||||
|
||||
std::set<CGlobalAI*> ais;
|
||||
extern "C" DLL_EXPORT int GetGlobalAiVersion()
|
||||
{
|
||||
return AI_INTERFACE_VER;
|
||||
|
@ -374,6 +374,11 @@ void AIGateway::objectRemoved(const CGObjectInstance * obj, const PlayerColor &
|
||||
|
||||
nullkiller->memory->removeFromMemory(obj);
|
||||
|
||||
if(nullkiller->baseGraph && nullkiller->settings->isObjectGraphAllowed())
|
||||
{
|
||||
nullkiller->baseGraph->removeObject(obj);
|
||||
}
|
||||
|
||||
if(obj->ID == Obj::HERO && obj->tempOwner == playerID)
|
||||
{
|
||||
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 objType = topObj->ID; // top object should be our hero
|
||||
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
|
||||
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)
|
||||
{
|
||||
bool dangerUnknown = ratio == 0;
|
||||
bool dangerUnknown = danger == 0;
|
||||
bool dangerTooHigh = ratio > (1 / SAFE_ATTACK_CONSTANT);
|
||||
|
||||
answer = !dangerUnknown && !dangerTooHigh;
|
||||
@ -676,9 +682,9 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
|
||||
&& components.size() == 2
|
||||
&& components.front().type == ComponentType::RESOURCE
|
||||
&& (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;
|
||||
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
|
||||
for(auto it = ResourceSet::nziterator(freeRes); it.valid(); it++)
|
||||
|
@ -437,7 +437,7 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
|
||||
case Obj::MAGIC_WELL:
|
||||
return h->mana < h->manaLimit();
|
||||
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::EYE_OF_MAGI:
|
||||
case Obj::BOAT:
|
||||
|
@ -185,8 +185,8 @@ void foreach_tile_pos(const Func & foo)
|
||||
}
|
||||
}
|
||||
|
||||
template<class Func>
|
||||
void foreach_tile_pos(CCallback * cbp, const Func & foo) // avoid costly retrieval of thread-specific pointer
|
||||
template<class Func, class TCallback>
|
||||
void foreach_tile_pos(TCallback * cbp, const Func & foo) // avoid costly retrieval of thread-specific pointer
|
||||
{
|
||||
int3 mapSize = cbp->getMapSize();
|
||||
for(int z = 0; z < mapSize.z; z++)
|
||||
|
@ -120,6 +120,11 @@ TResources BuildAnalyzer::getTotalResourcesRequired() const
|
||||
return result;
|
||||
}
|
||||
|
||||
bool BuildAnalyzer::isGoldPreasureHigh() const
|
||||
{
|
||||
return goldPreasure > ai->settings->getMaxGoldPreasure();
|
||||
}
|
||||
|
||||
void BuildAnalyzer::update()
|
||||
{
|
||||
logAi->trace("Start analysing build");
|
||||
|
@ -96,6 +96,7 @@ public:
|
||||
const std::vector<TownDevelopmentInfo> & getDevelopmentInfo() const { return developmentInfos; }
|
||||
TResources getDailyIncome() const { return dailyIncome; }
|
||||
float getGoldPreasure() const { return goldPreasure; }
|
||||
bool isGoldPreasureHigh() const;
|
||||
bool hasAnyBuilding(int32_t alignment, BuildingID bid) const;
|
||||
|
||||
private:
|
||||
|
@ -16,7 +16,7 @@
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
HitMapInfo HitMapInfo::NoThreat;
|
||||
const HitMapInfo HitMapInfo::NoThreat;
|
||||
|
||||
double HitMapInfo::value() const
|
||||
{
|
||||
@ -75,8 +75,7 @@ void DangerHitMapAnalyzer::updateHitMap()
|
||||
|
||||
PathfinderSettings ps;
|
||||
|
||||
ps.mainTurnDistanceLimit = 10;
|
||||
ps.scoutTurnDistanceLimit = 10;
|
||||
ps.scoutTurnDistanceLimit = ps.mainTurnDistanceLimit = ai->settings->getMainHeroTurnDistanceLimit();
|
||||
ps.useHeroChain = false;
|
||||
|
||||
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)
|
||||
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;
|
||||
PathfinderSettings pathfinderSettings;
|
||||
|
||||
pathfinderSettings.mainTurnDistanceLimit = 5;
|
||||
std::map<const CGHeroInstance *, HeroRole> townHeroes;
|
||||
|
||||
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;
|
||||
auto visitablePos = town->visitablePos();
|
||||
|
||||
@ -192,7 +189,10 @@ void DangerHitMapAnalyzer::calculateTileOwners()
|
||||
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)
|
||||
{
|
||||
@ -226,7 +226,7 @@ void DangerHitMapAnalyzer::calculateTileOwners()
|
||||
}
|
||||
}
|
||||
|
||||
if(ourDistance == enemyDistance)
|
||||
if(vstd::isAlmostEqual(ourDistance, enemyDistance))
|
||||
{
|
||||
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];
|
||||
}
|
||||
|
||||
const std::set<const CGObjectInstance *> empty = {};
|
||||
|
||||
std::set<const CGObjectInstance *> DangerHitMapAnalyzer::getOneTurnAccessibleObjects(const CGHeroInstance * enemy) const
|
||||
{
|
||||
std::set<const CGObjectInstance *> result;
|
||||
|
@ -18,7 +18,7 @@ struct AIPath;
|
||||
|
||||
struct HitMapInfo
|
||||
{
|
||||
static HitMapInfo NoThreat;
|
||||
static const HitMapInfo NoThreat;
|
||||
|
||||
uint64_t danger;
|
||||
uint8_t turn;
|
||||
|
@ -17,7 +17,7 @@
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
SecondarySkillEvaluator HeroManager::wariorSkillsScores = SecondarySkillEvaluator(
|
||||
const SecondarySkillEvaluator HeroManager::wariorSkillsScores = SecondarySkillEvaluator(
|
||||
{
|
||||
std::make_shared<SecondarySkillScoreMap>(
|
||||
std::map<SecondarySkill, float>
|
||||
@ -46,7 +46,7 @@ SecondarySkillEvaluator HeroManager::wariorSkillsScores = SecondarySkillEvaluato
|
||||
std::make_shared<AtLeastOneMagicRule>()
|
||||
});
|
||||
|
||||
SecondarySkillEvaluator HeroManager::scountSkillsScores = SecondarySkillEvaluator(
|
||||
const SecondarySkillEvaluator HeroManager::scountSkillsScores = SecondarySkillEvaluator(
|
||||
{
|
||||
std::make_shared<SecondarySkillScoreMap>(
|
||||
std::map<SecondarySkill, float>
|
||||
@ -187,6 +187,7 @@ bool HeroManager::heroCapReached() const
|
||||
int heroCount = cb->getHeroCount(ai->playerID, includeGarnisoned);
|
||||
|
||||
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_TOTAL_CAP);
|
||||
}
|
||||
@ -332,7 +333,7 @@ void WisdomRule::evaluateScore(const CGHeroInstance * hero, SecondarySkill skill
|
||||
score += 1.5;
|
||||
}
|
||||
|
||||
std::vector<SecondarySkill> AtLeastOneMagicRule::magicSchools = {
|
||||
const std::vector<SecondarySkill> AtLeastOneMagicRule::magicSchools = {
|
||||
SecondarySkill::AIR_MAGIC,
|
||||
SecondarySkill::EARTH_MAGIC,
|
||||
SecondarySkill::FIRE_MAGIC,
|
||||
|
@ -58,8 +58,8 @@ public:
|
||||
class DLL_EXPORT HeroManager : public IHeroManager
|
||||
{
|
||||
private:
|
||||
static SecondarySkillEvaluator wariorSkillsScores;
|
||||
static SecondarySkillEvaluator scountSkillsScores;
|
||||
static const SecondarySkillEvaluator wariorSkillsScores;
|
||||
static const SecondarySkillEvaluator scountSkillsScores;
|
||||
|
||||
CCallback * cb; //this is enough, but we downcast from CCallback
|
||||
const Nullkiller * ai;
|
||||
@ -114,7 +114,7 @@ public:
|
||||
class AtLeastOneMagicRule : public ISecondarySkillRule
|
||||
{
|
||||
private:
|
||||
static std::vector<SecondarySkill> magicSchools;
|
||||
static const std::vector<SecondarySkill> magicSchools;
|
||||
|
||||
public:
|
||||
void evaluateScore(const CGHeroInstance * hero, SecondarySkill skill, float & score) const override;
|
||||
|
@ -189,15 +189,7 @@ bool ObjectClusterizer::shouldVisitObject(const CGObjectInstance * obj) const
|
||||
return true; //all of the following is met
|
||||
}
|
||||
|
||||
void ObjectClusterizer::clusterize()
|
||||
{
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
nearObjects.reset();
|
||||
farObjects.reset();
|
||||
blockedObjects.clear();
|
||||
|
||||
Obj ignoreObjects[] = {
|
||||
Obj ObjectClusterizer::IgnoredObjectTypes[] = {
|
||||
Obj::BOAT,
|
||||
Obj::EYE_OF_MAGI,
|
||||
Obj::MONOLITH_ONE_WAY_ENTRANCE,
|
||||
@ -216,7 +208,15 @@ void ObjectClusterizer::clusterize()
|
||||
Obj::REDWOOD_OBSERVATORY,
|
||||
Obj::CARTOGRAPHER,
|
||||
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");
|
||||
|
||||
@ -224,131 +224,20 @@ void ObjectClusterizer::clusterize()
|
||||
ai->memory->visitableObjs.begin(),
|
||||
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();
|
||||
|
||||
for(int i = r.begin(); i != r.end(); i++)
|
||||
{
|
||||
auto obj = objs[i];
|
||||
|
||||
if(!shouldVisitObject(obj))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Skip object %s%s.", obj->getObjectName(), obj->visitablePos().toString());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString());
|
||||
#endif
|
||||
|
||||
auto paths = ai->pathfinder->getPathInfo(obj->visitablePos());
|
||||
|
||||
if(paths.empty())
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("No paths found.");
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool
|
||||
{
|
||||
return p1.movementCost() < p2.movementCost();
|
||||
});
|
||||
|
||||
if(vstd::contains(ignoreObjects, obj->ID))
|
||||
{
|
||||
farObjects.addObject(obj, paths.front(), 0);
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Object ignored. Moved to far objects with path %s", paths.front().toString());
|
||||
#endif
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
std::set<const CGHeroInstance *> heroesProcessed;
|
||||
|
||||
for(auto & path : paths)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Checking path %s", path.toString());
|
||||
#endif
|
||||
|
||||
if(!shouldVisit(ai, path.targetHero, obj))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Hero %s does not need to visit %s", path.targetHero->getObjectName(), obj->getObjectName());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
if(path.nodes.size() > 1)
|
||||
{
|
||||
auto blocker = getBlocker(path);
|
||||
|
||||
if(blocker)
|
||||
{
|
||||
if(vstd::contains(heroesProcessed, path.targetHero))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Hero %s is already processed.", path.targetHero->getObjectName());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
heroesProcessed.insert(path.targetHero);
|
||||
|
||||
float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)));
|
||||
|
||||
if(priority < MIN_PRIORITY)
|
||||
continue;
|
||||
|
||||
ClusterMap::accessor cluster;
|
||||
blockedObjects.insert(
|
||||
cluster,
|
||||
ClusterMap::value_type(blocker, std::make_shared<ObjectCluster>(blocker)));
|
||||
|
||||
cluster->second->addObject(obj, path, priority);
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Path added to cluster %s%s", blocker->getObjectName(), blocker->visitablePos().toString());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
heroesProcessed.insert(path.targetHero);
|
||||
|
||||
float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)));
|
||||
|
||||
if(priority < MIN_PRIORITY)
|
||||
continue;
|
||||
|
||||
bool interestingObject = path.turn() <= 2 || priority > 0.5f;
|
||||
|
||||
if(interestingObject)
|
||||
{
|
||||
nearObjects.addObject(obj, path, priority);
|
||||
}
|
||||
else
|
||||
{
|
||||
farObjects.addObject(obj, path, priority);
|
||||
}
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Path %s added to %s objects. Turn: %d, priority: %f",
|
||||
path.toString(),
|
||||
interestingObject ? "near" : "far",
|
||||
path.turn(),
|
||||
priority);
|
||||
#endif
|
||||
}
|
||||
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());
|
||||
@ -368,4 +257,123 @@ void ObjectClusterizer::clusterize()
|
||||
logAi->trace("Clusterization complete in %ld", timeElapsed(start));
|
||||
}
|
||||
|
||||
void ObjectClusterizer::clusterizeObject(const CGObjectInstance * obj, PriorityEvaluator * priorityEvaluator)
|
||||
{
|
||||
if(!shouldVisitObject(obj))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Skip object %s%s.", obj->getObjectName(), obj->visitablePos().toString());
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString());
|
||||
#endif
|
||||
|
||||
auto paths = ai->pathfinder->getPathInfo(obj->visitablePos(), true);
|
||||
|
||||
if(paths.empty())
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("No paths found.");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool
|
||||
{
|
||||
return p1.movementCost() < p2.movementCost();
|
||||
});
|
||||
|
||||
if(vstd::contains(IgnoredObjectTypes, obj->ID))
|
||||
{
|
||||
farObjects.addObject(obj, paths.front(), 0);
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Object ignored. Moved to far objects with path %s", paths.front().toString());
|
||||
#endif
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
std::set<const CGHeroInstance *> heroesProcessed;
|
||||
|
||||
for(auto & path : paths)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Checking path %s", path.toString());
|
||||
#endif
|
||||
|
||||
if(!shouldVisit(ai, path.targetHero, obj))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Hero %s does not need to visit %s", path.targetHero->getObjectName(), obj->getObjectName());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
if(path.nodes.size() > 1)
|
||||
{
|
||||
auto blocker = getBlocker(path);
|
||||
|
||||
if(blocker)
|
||||
{
|
||||
if(vstd::contains(heroesProcessed, path.targetHero))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Hero %s is already processed.", path.targetHero->getObjectName());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
heroesProcessed.insert(path.targetHero);
|
||||
|
||||
float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)));
|
||||
|
||||
if(priority < MIN_PRIORITY)
|
||||
continue;
|
||||
|
||||
ClusterMap::accessor cluster;
|
||||
blockedObjects.insert(
|
||||
cluster,
|
||||
ClusterMap::value_type(blocker, std::make_shared<ObjectCluster>(blocker)));
|
||||
|
||||
cluster->second->addObject(obj, path, priority);
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Path added to cluster %s%s", blocker->getObjectName(), blocker->visitablePos().toString());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
heroesProcessed.insert(path.targetHero);
|
||||
|
||||
float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)));
|
||||
|
||||
if(priority < MIN_PRIORITY)
|
||||
continue;
|
||||
|
||||
bool interestingObject = path.turn() <= 2 || priority > 0.5f;
|
||||
|
||||
if(interestingObject)
|
||||
{
|
||||
nearObjects.addObject(obj, path, priority);
|
||||
}
|
||||
else
|
||||
{
|
||||
farObjects.addObject(obj, path, priority);
|
||||
}
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Path %s added to %s objects. Turn: %d, priority: %f",
|
||||
path.toString(),
|
||||
interestingObject ? "near" : "far",
|
||||
path.turn(),
|
||||
priority);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -49,9 +49,13 @@ public:
|
||||
|
||||
using ClusterMap = tbb::concurrent_hash_map<const CGObjectInstance *, std::shared_ptr<ObjectCluster>>;
|
||||
|
||||
class PriorityEvaluator;
|
||||
|
||||
class ObjectClusterizer
|
||||
{
|
||||
private:
|
||||
static Obj IgnoredObjectTypes[];
|
||||
|
||||
ObjectCluster nearObjects;
|
||||
ObjectCluster farObjects;
|
||||
ClusterMap blockedObjects;
|
||||
@ -68,6 +72,7 @@ public:
|
||||
|
||||
private:
|
||||
bool shouldVisitObject(const CGObjectInstance * obj) const;
|
||||
void clusterizeObject(const CGObjectInstance * obj, PriorityEvaluator * priorityEvaluator);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -47,13 +47,13 @@ Goals::TGoalVec BuildingBehavior::decompose() const
|
||||
totalDevelopmentCost.toString());
|
||||
|
||||
auto & developmentInfos = ai->nullkiller->buildAnalyzer->getDevelopmentInfo();
|
||||
auto goldPreasure = ai->nullkiller->buildAnalyzer->getGoldPreasure();
|
||||
auto isGoldPreasureLow = !ai->nullkiller->buildAnalyzer->isGoldPreasureHigh();
|
||||
|
||||
for(auto & developmentInfo : developmentInfos)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
@ -25,9 +25,9 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual Goals::TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
virtual bool operator==(const BuildingBehavior & other) const override
|
||||
Goals::TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
bool operator==(const BuildingBehavior & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -46,8 +46,7 @@ Goals::TGoalVec BuyArmyBehavior::decompose() const
|
||||
|
||||
for(const CGHeroInstance * targetHero : heroes)
|
||||
{
|
||||
if(ai->nullkiller->buildAnalyzer->getGoldPreasure() > MAX_GOLD_PEASURE
|
||||
&& !town->hasBuilt(BuildingID::CITY_HALL))
|
||||
if(ai->nullkiller->buildAnalyzer->isGoldPreasureHigh() && !town->hasBuilt(BuildingID::CITY_HALL))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
@ -24,9 +24,9 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual Goals::TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
virtual bool operator==(const BuyArmyBehavior & other) const override
|
||||
Goals::TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
bool operator==(const BuyArmyBehavior & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -73,13 +73,25 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
auto hero = path.targetHero;
|
||||
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;
|
||||
}
|
||||
|
||||
auto firstBlockedAction = path.getFirstBlockedAction();
|
||||
if(firstBlockedAction)
|
||||
@ -178,8 +190,11 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const
|
||||
#endif
|
||||
|
||||
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::shared_ptr<ExecuteHeroChain> closestWay;
|
||||
|
||||
|
@ -48,8 +48,8 @@ namespace Goals
|
||||
specificObjects = true;
|
||||
}
|
||||
|
||||
virtual Goals::TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
Goals::TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
|
||||
CaptureObjectsBehavior & ofType(int type)
|
||||
{
|
||||
@ -65,7 +65,7 @@ namespace Goals
|
||||
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);
|
||||
|
||||
|
@ -42,7 +42,7 @@ Goals::TGoalVec ClusterBehavior::decompose() const
|
||||
Goals::TGoalVec ClusterBehavior::decomposeCluster(std::shared_ptr<ObjectCluster> cluster) const
|
||||
{
|
||||
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();
|
||||
std::vector<AIPath> blockerPaths;
|
||||
|
||||
|
@ -28,10 +28,10 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
|
||||
virtual bool operator==(const ClusterBehavior & other) const override
|
||||
bool operator==(const ClusterBehavior & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -443,6 +443,10 @@ void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitM
|
||||
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())
|
||||
{
|
||||
|
@ -29,10 +29,10 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual Goals::TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
Goals::TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
|
||||
virtual bool operator==(const DefenceBehavior & other) const override
|
||||
bool operator==(const DefenceBehavior & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -246,7 +246,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@ -335,7 +335,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
|
||||
if(!upgrade.upgradeValue
|
||||
&& armyToGetOrBuy.upgradeValue > 20000
|
||||
&& ai->nullkiller->heroManager->canRecruitHero(town)
|
||||
&& path.turn() < SCOUT_TURN_DISTANCE_LIMIT)
|
||||
&& path.turn() < ai->nullkiller->settings->getScoutHeroTurnDistanceLimit())
|
||||
{
|
||||
for(auto hero : cb->getAvailableHeroes(town))
|
||||
{
|
||||
@ -344,7 +344,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
|
||||
|
||||
if(scoutReinforcement >= armyToGetOrBuy.upgradeValue
|
||||
&& ai->nullkiller->getFreeGold() >20000
|
||||
&& ai->nullkiller->buildAnalyzer->getGoldPreasure() < MAX_GOLD_PEASURE)
|
||||
&& !ai->nullkiller->buildAnalyzer->isGoldPreasureHigh())
|
||||
{
|
||||
Composition recruitHero;
|
||||
|
||||
|
@ -25,10 +25,10 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
|
||||
virtual bool operator==(const GatherArmyBehavior & other) const override
|
||||
bool operator==(const GatherArmyBehavior & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -85,8 +85,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const
|
||||
continue;
|
||||
|
||||
if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1
|
||||
|| (ai->nullkiller->getFreeResources()[EGameResID::GOLD] > 10000
|
||||
&& ai->nullkiller->buildAnalyzer->getGoldPreasure() < MAX_GOLD_PEASURE))
|
||||
|| (ai->nullkiller->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->nullkiller->buildAnalyzer->isGoldPreasureHigh()))
|
||||
{
|
||||
tasks.push_back(Goals::sptr(Goals::RecruitHero(town).setpriority(3)));
|
||||
}
|
||||
|
@ -25,10 +25,10 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
|
||||
virtual bool operator==(const RecruitHeroBehavior & other) const override
|
||||
bool operator==(const RecruitHeroBehavior & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -25,10 +25,10 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
|
||||
virtual bool operator==(const StartupBehavior & other) const override
|
||||
bool operator==(const StartupBehavior & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -25,10 +25,10 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
|
||||
virtual bool operator==(const StayAtTownBehavior & other) const override
|
||||
bool operator==(const StayAtTownBehavior & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -14,9 +14,11 @@ set(Nullkiller_SRCS
|
||||
Pathfinding/Rules/AIMovementAfterDestinationRule.cpp
|
||||
Pathfinding/Rules/AIMovementToDestinationRule.cpp
|
||||
Pathfinding/Rules/AIPreviousNodeRule.cpp
|
||||
Pathfinding/ObjectGraph.cpp
|
||||
AIUtility.cpp
|
||||
Analyzers/ArmyManager.cpp
|
||||
Analyzers/HeroManager.cpp
|
||||
Engine/Settings.cpp
|
||||
Engine/FuzzyEngines.cpp
|
||||
Engine/FuzzyHelper.cpp
|
||||
Engine/AIMemory.cpp
|
||||
@ -77,9 +79,11 @@ set(Nullkiller_HEADERS
|
||||
Pathfinding/Rules/AIMovementAfterDestinationRule.h
|
||||
Pathfinding/Rules/AIMovementToDestinationRule.h
|
||||
Pathfinding/Rules/AIPreviousNodeRule.h
|
||||
Pathfinding/ObjectGraph.h
|
||||
AIUtility.h
|
||||
Analyzers/ArmyManager.h
|
||||
Analyzers/HeroManager.h
|
||||
Engine/Settings.h
|
||||
Engine/FuzzyEngines.h
|
||||
Engine/FuzzyHelper.h
|
||||
Engine/AIMemory.h
|
||||
@ -125,12 +129,12 @@ set(Nullkiller_HEADERS
|
||||
AIGateway.h
|
||||
)
|
||||
|
||||
if(NOT ENABLE_STATIC_AI_LIBS)
|
||||
if(NOT ENABLE_STATIC_LIBS)
|
||||
list(APPEND Nullkiller_SRCS main.cpp StdInc.cpp)
|
||||
endif()
|
||||
assign_source_group(${Nullkiller_SRCS} ${Nullkiller_HEADERS})
|
||||
|
||||
if(ENABLE_STATIC_AI_LIBS)
|
||||
if(ENABLE_STATIC_LIBS)
|
||||
add_library(Nullkiller STATIC ${Nullkiller_SRCS} ${Nullkiller_HEADERS})
|
||||
else()
|
||||
add_library(Nullkiller SHARED ${Nullkiller_SRCS} ${Nullkiller_HEADERS})
|
||||
@ -138,7 +142,7 @@ else()
|
||||
endif()
|
||||
|
||||
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")
|
||||
enable_pch(Nullkiller)
|
||||
|
@ -27,15 +27,14 @@ namespace NKAI
|
||||
|
||||
using namespace Goals;
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
#define MAXPASS 1000000
|
||||
#else
|
||||
#define MAXPASS 30
|
||||
#endif
|
||||
// while we play vcmieagles graph can be shared
|
||||
std::unique_ptr<ObjectGraph> Nullkiller::baseGraph;
|
||||
|
||||
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)
|
||||
@ -43,6 +42,8 @@ void Nullkiller::init(std::shared_ptr<CCallback> cb, PlayerColor playerID)
|
||||
this->cb = cb;
|
||||
this->playerID = playerID;
|
||||
|
||||
baseGraph.reset();
|
||||
|
||||
priorityEvaluator.reset(new PriorityEvaluator(this));
|
||||
priorityEvaluators.reset(
|
||||
new SharedPool<PriorityEvaluator>(
|
||||
@ -123,6 +124,12 @@ void Nullkiller::resetAiState()
|
||||
lockedHeroes.clear();
|
||||
dangerHitMap->reset();
|
||||
useHeroChain = true;
|
||||
|
||||
if(!baseGraph && ai->nullkiller->settings->isObjectGraphAllowed())
|
||||
{
|
||||
baseGraph = std::make_unique<ObjectGraph>();
|
||||
baseGraph->updateGraph(this);
|
||||
}
|
||||
}
|
||||
|
||||
void Nullkiller::updateAiState(int pass, bool fast)
|
||||
@ -163,21 +170,27 @@ void Nullkiller::updateAiState(int pass, bool fast)
|
||||
|
||||
PathfinderSettings cfg;
|
||||
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();
|
||||
|
||||
pathfinder->updatePaths(activeHeroes, cfg);
|
||||
|
||||
if(settings->isObjectGraphAllowed())
|
||||
{
|
||||
pathfinder->updateGraphs(activeHeroes);
|
||||
}
|
||||
|
||||
boost::this_thread::interruption_point();
|
||||
|
||||
objectClusterizer->clusterize();
|
||||
@ -235,13 +248,13 @@ void Nullkiller::makeTurn()
|
||||
|
||||
resetAiState();
|
||||
|
||||
for(int i = 1; i <= MAXPASS; i++)
|
||||
for(int i = 1; i <= settings->getMaxPass(); i++)
|
||||
{
|
||||
updateAiState(i);
|
||||
|
||||
Goals::TTask bestTask = taskptr(Goals::Invalid());
|
||||
|
||||
for(;i <= MAXPASS; i++)
|
||||
for(;i <= settings->getMaxPass(); i++)
|
||||
{
|
||||
Goals::TTaskVec fastTasks = {
|
||||
choseBestTask(sptr(BuyArmyBehavior()), 1),
|
||||
@ -290,7 +303,8 @@ void Nullkiller::makeTurn()
|
||||
|
||||
// TODO: better to check turn distance here instead of priority
|
||||
if((heroRole != HeroRole::MAIN || bestTask->priority < SMALL_SCAN_MIN_PRIORITY)
|
||||
&& scanDepth == ScanDepth::MAIN_FULL)
|
||||
&& scanDepth == ScanDepth::MAIN_FULL
|
||||
&& !settings->isObjectGraphAllowed())
|
||||
{
|
||||
useHeroChain = false;
|
||||
scanDepth = ScanDepth::SMALL;
|
||||
@ -303,22 +317,25 @@ void Nullkiller::makeTurn()
|
||||
|
||||
if(bestTask->priority < MIN_PRIORITY)
|
||||
{
|
||||
auto heroes = cb->getHeroesInfo();
|
||||
auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool
|
||||
{
|
||||
return h->movementPointsRemaining() > 100;
|
||||
});
|
||||
|
||||
if(hasMp && scanDepth != ScanDepth::ALL_FULL)
|
||||
if(!settings->isObjectGraphAllowed())
|
||||
{
|
||||
logAi->trace(
|
||||
"Goal %s has too low priority %f so increasing scan depth to full.",
|
||||
taskDescription,
|
||||
bestTask->priority);
|
||||
auto heroes = cb->getHeroesInfo();
|
||||
auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool
|
||||
{
|
||||
return h->movementPointsRemaining() > 100;
|
||||
});
|
||||
|
||||
scanDepth = ScanDepth::ALL_FULL;
|
||||
useHeroChain = false;
|
||||
continue;
|
||||
if(hasMp && scanDepth != ScanDepth::ALL_FULL)
|
||||
{
|
||||
logAi->trace(
|
||||
"Goal %s has too low priority %f so increasing scan depth to full.",
|
||||
taskDescription,
|
||||
bestTask->priority);
|
||||
|
||||
scanDepth = ScanDepth::ALL_FULL;
|
||||
useHeroChain = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include "PriorityEvaluator.h"
|
||||
#include "FuzzyHelper.h"
|
||||
#include "Settings.h"
|
||||
#include "AIMemory.h"
|
||||
#include "DeepDecomposer.h"
|
||||
#include "../Analyzers/DangerHitMapAnalyzer.h"
|
||||
@ -23,7 +24,6 @@
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
const float MAX_GOLD_PEASURE = 0.3f;
|
||||
const float MIN_PRIORITY = 0.01f;
|
||||
const float SMALL_SCAN_MIN_PRIORITY = 0.4f;
|
||||
|
||||
@ -59,6 +59,8 @@ private:
|
||||
bool useHeroChain;
|
||||
|
||||
public:
|
||||
static std::unique_ptr<ObjectGraph> baseGraph;
|
||||
|
||||
std::unique_ptr<DangerHitMapAnalyzer> dangerHitMap;
|
||||
std::unique_ptr<BuildAnalyzer> buildAnalyzer;
|
||||
std::unique_ptr<ObjectClusterizer> objectClusterizer;
|
||||
@ -71,6 +73,7 @@ public:
|
||||
std::unique_ptr<FuzzyHelper> dangerEvaluator;
|
||||
std::unique_ptr<DeepDecomposer> decomposer;
|
||||
std::unique_ptr<ArmyFormation> armyFormation;
|
||||
std::unique_ptr<Settings> settings;
|
||||
PlayerColor playerID;
|
||||
std::shared_ptr<CCallback> cb;
|
||||
std::mutex aiStateMutex;
|
||||
|
@ -69,7 +69,7 @@ PriorityEvaluator::~PriorityEvaluator()
|
||||
|
||||
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);
|
||||
engine = fl::FllImporter().fromString(str);
|
||||
armyLossPersentageVariable = engine->getInputVariable("armyLoss");
|
||||
@ -702,7 +702,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
|
||||
class HeroExchangeEvaluator : public IEvaluationContextBuilder
|
||||
{
|
||||
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)
|
||||
return;
|
||||
@ -719,7 +719,7 @@ public:
|
||||
class ArmyUpgradeEvaluator : public IEvaluationContextBuilder
|
||||
{
|
||||
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)
|
||||
return;
|
||||
@ -736,7 +736,7 @@ public:
|
||||
class StayAtTownManaRecoveryEvaluator : public IEvaluationContextBuilder
|
||||
{
|
||||
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)
|
||||
return;
|
||||
@ -771,7 +771,7 @@ void addTileDanger(EvaluationContext & evaluationContext, const int3 & tile, uin
|
||||
class DefendTownEvaluator : public IEvaluationContextBuilder
|
||||
{
|
||||
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)
|
||||
return;
|
||||
@ -821,7 +821,7 @@ private:
|
||||
public:
|
||||
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)
|
||||
return;
|
||||
@ -879,7 +879,7 @@ class ClusterEvaluationContextBuilder : public IEvaluationContextBuilder
|
||||
public:
|
||||
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)
|
||||
return;
|
||||
@ -926,7 +926,7 @@ public:
|
||||
class ExchangeSwapTownHeroesContextBuilder : public IEvaluationContextBuilder
|
||||
{
|
||||
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)
|
||||
return;
|
||||
@ -954,7 +954,7 @@ private:
|
||||
public:
|
||||
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)
|
||||
return;
|
||||
@ -974,7 +974,7 @@ public:
|
||||
class BuildThisEvaluationContextBuilder : public IEvaluationContextBuilder
|
||||
{
|
||||
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)
|
||||
return;
|
||||
|
84
AI/Nullkiller/Engine/Settings.cpp
Normal file
84
AI/Nullkiller/Engine/Settings.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
44
AI/Nullkiller/Engine/Settings.h
Normal file
44
AI/Nullkiller/Engine/Settings.h
Normal 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);
|
||||
};
|
||||
}
|
@ -180,11 +180,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~cannotFulfillGoalException() throw ()
|
||||
{
|
||||
};
|
||||
|
||||
const char * what() const throw () override
|
||||
const char * what() const noexcept override
|
||||
{
|
||||
return msg.c_str();
|
||||
}
|
||||
@ -203,11 +199,7 @@ public:
|
||||
msg = goal->toString();
|
||||
}
|
||||
|
||||
virtual ~goalFulfilledException() throw ()
|
||||
{
|
||||
};
|
||||
|
||||
const char * what() const throw () override
|
||||
const char * what() const noexcept override
|
||||
{
|
||||
return msg.c_str();
|
||||
}
|
||||
|
@ -39,12 +39,18 @@ void AdventureSpellCast::accept(AIGateway * ai)
|
||||
if(hero->mana < hero->getSpellCost(spell))
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
@ -35,7 +35,7 @@ namespace Goals
|
||||
|
||||
void accept(AIGateway * ai) override;
|
||||
std::string toString() const override;
|
||||
virtual bool operator==(const AdventureSpellCast & other) const override;
|
||||
bool operator==(const AdventureSpellCast & other) const override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ namespace Goals
|
||||
TSubgoal whatToDoToAchieve() override;
|
||||
bool fulfillsMe(TSubgoal goal) override;
|
||||
|
||||
virtual bool operator==(const Build & other) const override
|
||||
bool operator==(const Build & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ namespace Goals
|
||||
|
||||
void accept(AIGateway * ai) override;
|
||||
std::string toString() const override;
|
||||
virtual bool operator==(const BuildBoat & other) const override;
|
||||
bool operator==(const BuildBoat & other) const override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -39,8 +39,8 @@ namespace Goals
|
||||
}
|
||||
BuildThis(BuildingID Bid, const CGTownInstance * tid);
|
||||
|
||||
virtual bool operator==(const BuildThis & other) const override;
|
||||
virtual std::string toString() const override;
|
||||
bool operator==(const BuildThis & other) const override;
|
||||
std::string toString() const override;
|
||||
void accept(AIGateway * ai) override;
|
||||
};
|
||||
}
|
||||
|
@ -36,11 +36,11 @@ namespace Goals
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ namespace Goals
|
||||
//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)
|
||||
return false;
|
||||
@ -54,7 +54,7 @@ namespace Goals
|
||||
|
||||
virtual bool operator==(const T & other) const = 0;
|
||||
|
||||
virtual TGoalVec decompose() const override
|
||||
TGoalVec decompose() const override
|
||||
{
|
||||
TSubgoal single = decomposeSingle();
|
||||
|
||||
@ -90,11 +90,11 @@ namespace Goals
|
||||
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; }
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -34,11 +34,11 @@ namespace Goals
|
||||
name = obj->getObjectName();
|
||||
}
|
||||
|
||||
virtual bool operator==(const CaptureObject & other) const override;
|
||||
virtual Goals::TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
virtual bool hasHash() const override { return true; }
|
||||
virtual uint64_t getHash() const override;
|
||||
bool operator==(const CaptureObject & other) const override;
|
||||
Goals::TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
bool hasHash() const override { return true; }
|
||||
uint64_t getHash() const override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -29,12 +29,12 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual Goals::TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
virtual bool hasHash() const override { return true; }
|
||||
virtual uint64_t getHash() const override;
|
||||
Goals::TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
bool hasHash() const override { return true; }
|
||||
uint64_t getHash() const override;
|
||||
|
||||
virtual bool operator==(const CompleteQuest & other) const override;
|
||||
bool operator==(const CompleteQuest & other) const override;
|
||||
|
||||
private:
|
||||
TGoalVec tryCompleteQuest() const;
|
||||
|
@ -26,15 +26,15 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool operator==(const Composition & other) const override;
|
||||
virtual std::string toString() const override;
|
||||
bool operator==(const Composition & other) const override;
|
||||
std::string toString() const override;
|
||||
void accept(AIGateway * ai) override;
|
||||
Composition & addNext(const AbstractGoal & goal);
|
||||
Composition & addNext(TSubgoal goal);
|
||||
Composition & addNextSequence(const TGoalVec & taskSequence);
|
||||
virtual TGoalVec decompose() const override;
|
||||
virtual bool isElementar() const override;
|
||||
virtual int getHeroExchangeCount() const override;
|
||||
TGoalVec decompose() const override;
|
||||
bool isElementar() const override;
|
||||
int getHeroExchangeCount() const override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ namespace Goals
|
||||
{
|
||||
tile = Tile;
|
||||
}
|
||||
virtual bool operator==(const DigAtTile & other) const override;
|
||||
bool operator==(const DigAtTile & other) const override;
|
||||
|
||||
private:
|
||||
//TSubgoal decomposeSingle() const override;
|
||||
|
@ -26,7 +26,7 @@ namespace Goals
|
||||
|
||||
void accept(AIGateway * ai) override;
|
||||
std::string toString() const override;
|
||||
virtual bool operator==(const DismissHero & other) const override;
|
||||
bool operator==(const DismissHero & other) const override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ namespace Goals
|
||||
|
||||
void accept(AIGateway * ai) 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; }
|
||||
HeroLockedReason getLockingReason() const { return lockingReason; }
|
||||
|
@ -30,10 +30,10 @@ namespace Goals
|
||||
|
||||
void accept(AIGateway * ai) 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; }
|
||||
|
||||
virtual int getHeroExchangeCount() const override { return chainPath.exchangeCount; }
|
||||
int getHeroExchangeCount() const override { return chainPath.exchangeCount; }
|
||||
|
||||
private:
|
||||
bool moveHeroToTile(const CGHeroInstance * hero, const int3 & tile);
|
||||
|
@ -36,7 +36,7 @@ namespace Goals
|
||||
TGoalVec getAllPossibleSubgoals() override;
|
||||
TSubgoal whatToDoToAchieve() override;
|
||||
std::string completeMessage() const override;
|
||||
virtual bool operator==(const GatherArmy & other) const override;
|
||||
bool operator==(const GatherArmy & other) const override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -32,17 +32,17 @@ namespace Goals
|
||||
return TGoalVec();
|
||||
}
|
||||
|
||||
virtual bool operator==(const Invalid & other) const override
|
||||
bool operator==(const Invalid & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual std::string toString() const override
|
||||
std::string toString() const override
|
||||
{
|
||||
return "Invalid";
|
||||
}
|
||||
|
||||
virtual void accept(AIGateway * ai) override
|
||||
void accept(AIGateway * ai) override
|
||||
{
|
||||
throw cannotFulfillGoalException("Can not fulfill Invalid goal!");
|
||||
}
|
||||
|
@ -38,12 +38,12 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool operator==(const RecruitHero & other) const override
|
||||
bool operator==(const RecruitHero & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual std::string toString() const override;
|
||||
std::string toString() const override;
|
||||
void accept(AIGateway * ai) override;
|
||||
};
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ namespace Goals
|
||||
|
||||
void accept(AIGateway * ai) override;
|
||||
std::string toString() const override;
|
||||
virtual bool operator==(const SaveResources & other) const override;
|
||||
bool operator==(const SaveResources & other) const override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -26,8 +26,8 @@ namespace Goals
|
||||
public:
|
||||
StayAtTown(const CGTownInstance * town, AIPath & path);
|
||||
|
||||
virtual bool operator==(const StayAtTown & other) const override;
|
||||
virtual std::string toString() const override;
|
||||
bool operator==(const StayAtTown & other) const override;
|
||||
std::string toString() const override;
|
||||
void accept(AIGateway * ai) override;
|
||||
float getMovementWasted() const { return movementWasted; }
|
||||
};
|
||||
|
@ -34,7 +34,7 @@ namespace Goals
|
||||
value = val;
|
||||
objid = Objid;
|
||||
}
|
||||
virtual bool operator==(const Trade & other) const override;
|
||||
bool operator==(const Trade & other) const override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -29,8 +29,8 @@ namespace Goals
|
||||
ArmyUpgrade(const AIPath & upgradePath, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade);
|
||||
ArmyUpgrade(const CGHeroInstance * targetMain, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade);
|
||||
|
||||
virtual bool operator==(const ArmyUpgrade & other) const override;
|
||||
virtual std::string toString() const override;
|
||||
bool operator==(const ArmyUpgrade & other) const override;
|
||||
std::string toString() const override;
|
||||
|
||||
uint64_t getUpgradeValue() const { return upgradeValue; }
|
||||
uint64_t getInitialArmyValue() const { return initialValue; }
|
||||
|
@ -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 CGHeroInstance * defender);
|
||||
|
||||
virtual bool operator==(const DefendTown & other) const override;
|
||||
virtual std::string toString() const override;
|
||||
bool operator==(const DefendTown & other) const override;
|
||||
std::string toString() const override;
|
||||
|
||||
const HitMapInfo & getTreat() const { return treat; }
|
||||
|
||||
|
@ -28,8 +28,8 @@ namespace Goals
|
||||
sethero(targetHero);
|
||||
}
|
||||
|
||||
virtual bool operator==(const HeroExchange & other) const override;
|
||||
virtual std::string toString() const override;
|
||||
bool operator==(const HeroExchange & other) const override;
|
||||
std::string toString() const override;
|
||||
|
||||
uint64_t getReinforcementArmyStrength() const;
|
||||
};
|
||||
|
@ -36,8 +36,8 @@ namespace Goals
|
||||
sethero(pathToCenter.targetHero);
|
||||
}
|
||||
|
||||
virtual bool operator==(const UnlockCluster & other) const override;
|
||||
virtual std::string toString() const override;
|
||||
bool operator==(const UnlockCluster & other) const override;
|
||||
std::string toString() const override;
|
||||
std::shared_ptr<ObjectCluster> getCluster() const { return cluster; }
|
||||
const AIPath & getPathToCenter() { return pathToCenter; }
|
||||
};
|
||||
|
@ -332,7 +332,7 @@ std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
|
||||
return neighbours;
|
||||
}
|
||||
|
||||
EPathfindingLayer phisycalLayers[2] = {EPathfindingLayer::LAND, EPathfindingLayer::SAIL};
|
||||
constexpr std::array phisycalLayers = {EPathfindingLayer::LAND, EPathfindingLayer::SAIL};
|
||||
|
||||
bool AINodeStorage::increaseHeroChainTurnLimit()
|
||||
{
|
||||
@ -843,6 +843,7 @@ ExchangeCandidate HeroChainCalculationTask::calculateExchange(
|
||||
candidate.turns = carrierParentNode->turns;
|
||||
candidate.setCost(carrierParentNode->getCost() + otherParentNode->getCost() / 1000.0);
|
||||
candidate.moveRemains = carrierParentNode->moveRemains;
|
||||
candidate.danger = carrierParentNode->danger;
|
||||
|
||||
if(carrierParentNode->turns < otherParentNode->turns)
|
||||
{
|
||||
@ -1122,14 +1123,14 @@ void AINodeStorage::calculateTownPortal(
|
||||
{
|
||||
for(const CGTownInstance * targetTown : towns)
|
||||
{
|
||||
// TODO: allow to hide visiting hero in garrison
|
||||
if(targetTown->visitingHero && maskMap.find(targetTown->visitingHero.get()) != maskMap.end())
|
||||
if(targetTown->visitingHero
|
||||
&& targetTown->getUpperArmy()->stacksCount()
|
||||
&& maskMap.find(targetTown->visitingHero.get()) != maskMap.end())
|
||||
{
|
||||
auto basicMask = maskMap.at(targetTown->visitingHero.get());
|
||||
bool heroIsInChain = (actor->chainMask & basicMask) != 0;
|
||||
bool sameActorInTown = actor->chainMask == basicMask;
|
||||
|
||||
if(sameActorInTown || !heroIsInChain)
|
||||
if(!sameActorInTown)
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1264,8 +1265,8 @@ bool AINodeStorage::hasBetterChain(
|
||||
&& nodeActor->heroFightingStrength >= candidateActor->heroFightingStrength
|
||||
&& node.getCost() <= candidateNode->getCost())
|
||||
{
|
||||
if(nodeActor->heroFightingStrength == candidateActor->heroFightingStrength
|
||||
&& node.getCost() == candidateNode->getCost()
|
||||
if(vstd::isAlmostEqual(nodeActor->heroFightingStrength, candidateActor->heroFightingStrength)
|
||||
&& vstd::isAlmostEqual(node.getCost(), candidateNode->getCost())
|
||||
&& &node < candidateNode)
|
||||
{
|
||||
continue;
|
||||
|
@ -11,6 +11,7 @@
|
||||
#pragma once
|
||||
|
||||
#define NKAI_PATHFINDER_TRACE_LEVEL 0
|
||||
constexpr int NKAI_GRAPH_TRACE_LEVEL = 0;
|
||||
#define NKAI_TRACE_LEVEL 0
|
||||
|
||||
#include "../../../lib/pathfinder/CGPathNode.h"
|
||||
@ -24,20 +25,11 @@
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
const int SCOUT_TURN_DISTANCE_LIMIT = 5;
|
||||
const int MAIN_TURN_DISTANCE_LIMIT = 10;
|
||||
|
||||
namespace AIPathfinding
|
||||
{
|
||||
#ifdef ENVIRONMENT64
|
||||
const int BUCKET_COUNT = 7;
|
||||
#else
|
||||
const int BUCKET_COUNT = 5;
|
||||
#endif // ENVIRONMENT64
|
||||
|
||||
const int BUCKET_COUNT = 3;
|
||||
const int BUCKET_SIZE = 5;
|
||||
const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE;
|
||||
const int THREAD_COUNT = 8;
|
||||
const int CHAIN_MAX_DEPTH = 4;
|
||||
}
|
||||
|
||||
@ -188,7 +180,7 @@ public:
|
||||
bool selectFirstActor();
|
||||
bool selectNextActor();
|
||||
|
||||
virtual std::vector<CGPathNode *> getInitialNodes() override;
|
||||
std::vector<CGPathNode *> getInitialNodes() override;
|
||||
|
||||
virtual std::vector<CGPathNode *> calculateNeighbours(
|
||||
const PathNodeInfo & source,
|
||||
@ -200,7 +192,7 @@ public:
|
||||
const PathfinderConfig * pathfinderConfig,
|
||||
const CPathfinderHelper * pathfinderHelper) override;
|
||||
|
||||
virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override;
|
||||
void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override;
|
||||
|
||||
void commit(
|
||||
AIPathNode * destination,
|
||||
|
@ -33,7 +33,7 @@ bool AIPathfinder::isTileAccessible(const HeroPtr & hero, const int3 & tile) con
|
||||
|| 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);
|
||||
|
||||
@ -42,10 +42,23 @@ std::vector<AIPath> AIPathfinder::getPathInfo(const int3 & tile) const
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes, PathfinderSettings pathfinderSettings)
|
||||
void AIPathfinder::updatePaths(const std::map<const CGHeroInstance *, HeroRole> & heroes, PathfinderSettings pathfinderSettings)
|
||||
{
|
||||
if(!storage)
|
||||
{
|
||||
@ -71,7 +84,7 @@ void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes
|
||||
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++);
|
||||
cb->calculatePaths(config);
|
||||
@ -112,4 +125,34 @@ void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "AINodeStorage.h"
|
||||
#include "ObjectGraph.h"
|
||||
#include "../AIUtility.h"
|
||||
|
||||
namespace NKAI
|
||||
@ -23,11 +24,13 @@ struct PathfinderSettings
|
||||
bool useHeroChain;
|
||||
uint8_t scoutTurnDistanceLimit;
|
||||
uint8_t mainTurnDistanceLimit;
|
||||
bool allowBypassObjects;
|
||||
|
||||
PathfinderSettings()
|
||||
:useHeroChain(false),
|
||||
scoutTurnDistanceLimit(255),
|
||||
mainTurnDistanceLimit(255)
|
||||
mainTurnDistanceLimit(255),
|
||||
allowBypassObjects(true)
|
||||
{ }
|
||||
};
|
||||
|
||||
@ -37,13 +40,20 @@ private:
|
||||
std::shared_ptr<AINodeStorage> storage;
|
||||
CPlayerSpecificInfoCallback * cb;
|
||||
Nullkiller * ai;
|
||||
std::map<ObjectInstanceID, GraphPaths> heroGraphs;
|
||||
|
||||
public:
|
||||
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;
|
||||
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();
|
||||
|
||||
std::shared_ptr<AINodeStorage>getStorage()
|
||||
{
|
||||
return storage;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -24,16 +24,17 @@ namespace AIPathfinding
|
||||
std::vector<std::shared_ptr<IPathfindingRule>> makeRuleset(
|
||||
CPlayerSpecificInfoCallback * cb,
|
||||
Nullkiller * ai,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage)
|
||||
std::shared_ptr<AINodeStorage> nodeStorage,
|
||||
bool allowBypassObjects)
|
||||
{
|
||||
std::vector<std::shared_ptr<IPathfindingRule>> rules = {
|
||||
std::make_shared<AILayerTransitionRule>(cb, ai, nodeStorage),
|
||||
std::make_shared<DestinationActionRule>(),
|
||||
std::make_shared<AIMovementToDestinationRule>(nodeStorage),
|
||||
std::make_shared<MovementCostRule>(),
|
||||
std::make_shared<AIPreviousNodeRule>(nodeStorage),
|
||||
std::make_shared<AIMovementAfterDestinationRule>(cb, nodeStorage)
|
||||
};
|
||||
std::vector<std::shared_ptr<IPathfindingRule>> rules = {
|
||||
std::make_shared<AILayerTransitionRule>(cb, ai, nodeStorage),
|
||||
std::make_shared<DestinationActionRule>(),
|
||||
std::make_shared<AIMovementToDestinationRule>(nodeStorage, allowBypassObjects),
|
||||
std::make_shared<MovementCostRule>(),
|
||||
std::make_shared<AIPreviousNodeRule>(nodeStorage),
|
||||
std::make_shared<AIMovementAfterDestinationRule>(cb, nodeStorage, allowBypassObjects)
|
||||
};
|
||||
|
||||
return rules;
|
||||
}
|
||||
@ -41,8 +42,9 @@ namespace AIPathfinding
|
||||
AIPathfinderConfig::AIPathfinderConfig(
|
||||
CPlayerSpecificInfoCallback * cb,
|
||||
Nullkiller * ai,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage)
|
||||
:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage)), aiNodeStorage(nodeStorage)
|
||||
std::shared_ptr<AINodeStorage> nodeStorage,
|
||||
bool allowBypassObjects)
|
||||
:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage, allowBypassObjects)), aiNodeStorage(nodeStorage)
|
||||
{
|
||||
options.canUseCast = true;
|
||||
}
|
||||
|
@ -30,11 +30,12 @@ namespace AIPathfinding
|
||||
AIPathfinderConfig(
|
||||
CPlayerSpecificInfoCallback * cb,
|
||||
Nullkiller * ai,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage);
|
||||
std::shared_ptr<AINodeStorage> nodeStorage,
|
||||
bool allowBypassObjects);
|
||||
|
||||
~AIPathfinderConfig();
|
||||
|
||||
virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;
|
||||
CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ namespace AIPathfinding
|
||||
public:
|
||||
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(
|
||||
const CGHeroInstance * hero,
|
||||
@ -38,9 +38,9 @@ namespace AIPathfinding
|
||||
AIPathNode * dstMode,
|
||||
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
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ namespace AIPathfinding
|
||||
class SummonBoatAction : public VirtualBoatAction
|
||||
{
|
||||
public:
|
||||
virtual void execute(const CGHeroInstance * hero) const override;
|
||||
void execute(const CGHeroInstance * hero) const override;
|
||||
|
||||
virtual void applyOnDestination(
|
||||
const CGHeroInstance * hero,
|
||||
@ -34,11 +34,11 @@ namespace AIPathfinding
|
||||
AIPathNode * dstMode,
|
||||
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:
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -19,14 +19,19 @@ namespace NKAI
|
||||
namespace AIPathfinding
|
||||
{
|
||||
bool QuestAction::canAct(const AIPathNode * node) const
|
||||
{
|
||||
return canAct(node->actor->hero);
|
||||
}
|
||||
|
||||
bool QuestAction::canAct(const CGHeroInstance * hero) const
|
||||
{
|
||||
if(questInfo.obj->ID == Obj::BORDER_GATE || questInfo.obj->ID == Obj::BORDERGUARD)
|
||||
{
|
||||
return dynamic_cast<const IQuestObject *>(questInfo.obj)->checkQuest(node->actor->hero);
|
||||
return dynamic_cast<const IQuestObject *>(questInfo.obj)->checkQuest(hero);
|
||||
}
|
||||
|
||||
return questInfo.quest->activeForPlayers.count(node->actor->hero->getOwner())
|
||||
|| questInfo.quest->checkQuest(node->actor->hero);
|
||||
return questInfo.quest->activeForPlayers.count(hero->getOwner())
|
||||
|| questInfo.quest->checkQuest(hero);
|
||||
}
|
||||
|
||||
Goals::TSubgoal QuestAction::decompose(const CGHeroInstance * hero) const
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
|
||||
using namespace NKAI;
|
||||
|
||||
CCreatureSet emptyArmy;
|
||||
const CCreatureSet emptyArmy;
|
||||
|
||||
bool HeroExchangeArmy::needsLastStack() const
|
||||
{
|
||||
|
@ -28,7 +28,7 @@ class HeroExchangeArmy : public CArmedInstance
|
||||
public:
|
||||
TResources armyCost;
|
||||
bool requireBuyArmy;
|
||||
virtual bool needsLastStack() const override;
|
||||
bool needsLastStack() const override;
|
||||
std::shared_ptr<SpecialAction> getActorAction() const;
|
||||
|
||||
HeroExchangeArmy(): CArmedInstance(nullptr, true), requireBuyArmy(false) {}
|
||||
@ -51,24 +51,24 @@ protected:
|
||||
|
||||
public:
|
||||
uint64_t chainMask;
|
||||
bool isMovable;
|
||||
bool allowUseResources;
|
||||
bool allowBattle;
|
||||
bool allowSpellCast;
|
||||
bool isMovable = false;
|
||||
bool allowUseResources = false;
|
||||
bool allowBattle = false;
|
||||
bool allowSpellCast = false;
|
||||
std::shared_ptr<SpecialAction> actorAction;
|
||||
const CGHeroInstance * hero;
|
||||
HeroRole heroRole;
|
||||
const CCreatureSet * creatureSet;
|
||||
const ChainActor * battleActor;
|
||||
const ChainActor * castActor;
|
||||
const ChainActor * resourceActor;
|
||||
const ChainActor * carrierParent;
|
||||
const ChainActor * otherParent;
|
||||
const ChainActor * baseActor;
|
||||
const CCreatureSet * creatureSet = nullptr;
|
||||
const ChainActor * battleActor = nullptr;
|
||||
const ChainActor * castActor = nullptr;
|
||||
const ChainActor * resourceActor = nullptr;
|
||||
const ChainActor * carrierParent = nullptr;
|
||||
const ChainActor * otherParent = nullptr;
|
||||
const ChainActor * baseActor = nullptr;
|
||||
int3 initialPosition;
|
||||
EPathfindingLayer layer;
|
||||
uint32_t initialMovement;
|
||||
uint32_t initialTurn;
|
||||
uint32_t initialMovement = 0;
|
||||
uint32_t initialTurn = 0;
|
||||
uint64_t armyValue;
|
||||
float heroFightingStrength;
|
||||
uint8_t actorExchangeCount;
|
||||
@ -126,7 +126,7 @@ public:
|
||||
HeroActor(const ChainActor * carrier, const ChainActor * other, const HeroExchangeArmy * army, const Nullkiller * ai);
|
||||
|
||||
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
|
||||
@ -136,7 +136,7 @@ private:
|
||||
|
||||
public:
|
||||
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;
|
||||
};
|
||||
|
||||
@ -154,7 +154,7 @@ private:
|
||||
public:
|
||||
DwellingActor(const CGDwelling * dwelling, uint64_t chainMask, bool waitForGrowth, int dayOfWeek);
|
||||
~DwellingActor();
|
||||
virtual std::string toString() const override;
|
||||
std::string toString() const override;
|
||||
|
||||
protected:
|
||||
int getInitialTurn(bool waitForGrowth, int dayOfWeek);
|
||||
@ -168,7 +168,7 @@ private:
|
||||
|
||||
public:
|
||||
TownGarrisonActor(const CGTownInstance * town, uint64_t chainMask);
|
||||
virtual std::string toString() const override;
|
||||
std::string toString() const override;
|
||||
};
|
||||
|
||||
}
|
||||
|
671
AI/Nullkiller/Pathfinding/ObjectGraph.cpp
Normal file
671
AI/Nullkiller/Pathfinding/ObjectGraph.cpp
Normal 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, ¤tNode](int3 n1, ObjectLink o1)
|
||||
{
|
||||
target->iterateConnections(n1, [&pos, &o1, ¤tNode, &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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
192
AI/Nullkiller/Pathfinding/ObjectGraph.h
Normal file
192
AI/Nullkiller/Pathfinding/ObjectGraph.h
Normal 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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
@ -131,7 +131,7 @@ namespace AIPathfinding
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "../Actions/QuestAction.h"
|
||||
#include "../../Goals/Invalid.h"
|
||||
#include "AIPreviousNodeRule.h"
|
||||
#include "../../../../lib/pathfinder/PathfinderOptions.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
@ -20,8 +21,9 @@ namespace AIPathfinding
|
||||
{
|
||||
AIMovementAfterDestinationRule::AIMovementAfterDestinationRule(
|
||||
CPlayerSpecificInfoCallback * cb,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage)
|
||||
:cb(cb), nodeStorage(nodeStorage)
|
||||
std::shared_ptr<AINodeStorage> nodeStorage,
|
||||
bool allowBypassObjects)
|
||||
:cb(cb), nodeStorage(nodeStorage), allowBypassObjects(allowBypassObjects)
|
||||
{
|
||||
}
|
||||
|
||||
@ -40,10 +42,32 @@ namespace AIPathfinding
|
||||
}
|
||||
|
||||
auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);
|
||||
|
||||
if(blocker == BlockingReason::NONE)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -25,9 +25,13 @@ namespace AIPathfinding
|
||||
private:
|
||||
CPlayerSpecificInfoCallback * cb;
|
||||
std::shared_ptr<AINodeStorage> nodeStorage;
|
||||
bool allowBypassObjects;
|
||||
|
||||
public:
|
||||
AIMovementAfterDestinationRule(CPlayerSpecificInfoCallback * cb, std::shared_ptr<AINodeStorage> nodeStorage);
|
||||
AIMovementAfterDestinationRule(
|
||||
CPlayerSpecificInfoCallback * cb,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage,
|
||||
bool allowBypassObjects);
|
||||
|
||||
virtual void process(
|
||||
const PathNodeInfo & source,
|
||||
|
@ -14,8 +14,10 @@ namespace NKAI
|
||||
{
|
||||
namespace AIPathfinding
|
||||
{
|
||||
AIMovementToDestinationRule::AIMovementToDestinationRule(std::shared_ptr<AINodeStorage> nodeStorage)
|
||||
: nodeStorage(nodeStorage)
|
||||
AIMovementToDestinationRule::AIMovementToDestinationRule(
|
||||
std::shared_ptr<AINodeStorage> nodeStorage,
|
||||
bool allowBypassObjects)
|
||||
: nodeStorage(nodeStorage), allowBypassObjects(allowBypassObjects)
|
||||
{
|
||||
}
|
||||
|
||||
@ -37,15 +39,30 @@ namespace AIPathfinding
|
||||
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
|
||||
logAi->trace(
|
||||
"Bypass src guard while moving from %s to %s",
|
||||
source.coord.toString(),
|
||||
destination.coord.toString());
|
||||
logAi->trace(
|
||||
"Bypass src guard while moving from %s to %s",
|
||||
source.coord.toString(),
|
||||
destination.coord.toString());
|
||||
#endif
|
||||
return;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
destination.blocked = true;
|
||||
|
@ -24,9 +24,10 @@ namespace AIPathfinding
|
||||
{
|
||||
private:
|
||||
std::shared_ptr<AINodeStorage> nodeStorage;
|
||||
bool allowBypassObjects;
|
||||
|
||||
public:
|
||||
AIMovementToDestinationRule(std::shared_ptr<AINodeStorage> nodeStorage);
|
||||
AIMovementToDestinationRule(std::shared_ptr<AINodeStorage> nodeStorage, bool allowBypassObjects);
|
||||
|
||||
virtual void process(
|
||||
const PathNodeInfo & source,
|
||||
|
@ -14,7 +14,7 @@
|
||||
#define strcpy_s(a, b, c) strncpy(a, c, b)
|
||||
#endif
|
||||
|
||||
static const char * g_cszAiName = "Nullkiller";
|
||||
static const char * const g_cszAiName = "Nullkiller";
|
||||
|
||||
extern "C" DLL_EXPORT int GetGlobalAiVersion()
|
||||
{
|
||||
|
@ -8,19 +8,19 @@ set(stupidAI_HEADERS
|
||||
StupidAI.h
|
||||
)
|
||||
|
||||
if(NOT ENABLE_STATIC_AI_LIBS)
|
||||
if(NOT ENABLE_STATIC_LIBS)
|
||||
list(APPEND stupidAI_SRCS main.cpp StdInc.cpp)
|
||||
endif()
|
||||
assign_source_group(${stupidAI_SRCS} ${stupidAI_HEADERS})
|
||||
|
||||
if(ENABLE_STATIC_AI_LIBS)
|
||||
if(ENABLE_STATIC_LIBS)
|
||||
add_library(StupidAI STATIC ${stupidAI_SRCS} ${stupidAI_HEADERS})
|
||||
else()
|
||||
add_library(StupidAI SHARED ${stupidAI_SRCS} ${stupidAI_HEADERS})
|
||||
install(TARGETS StupidAI RUNTIME DESTINATION ${AI_LIB_DIR} LIBRARY DESTINATION ${AI_LIB_DIR})
|
||||
endif()
|
||||
|
||||
target_link_libraries(StupidAI PRIVATE ${VCMI_LIB_TARGET})
|
||||
target_link_libraries(StupidAI PRIVATE vcmi)
|
||||
target_include_directories(StupidAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
vcmi_set_output_dir(StupidAI "AI")
|
||||
|
@ -16,7 +16,7 @@
|
||||
#define strcpy_s(a, b, c) strncpy(a, c, b)
|
||||
#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()
|
||||
{
|
||||
|
@ -148,7 +148,7 @@ BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_
|
||||
static const std::vector<BuildingID> unitGrowth = { BuildingID::HORDE_1, BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR };
|
||||
static const std::vector<BuildingID> _spells = { BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3,
|
||||
BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5 };
|
||||
static const std::vector<BuildingID> extra = { BuildingID::MARKETPLACE, BuildingID::BLACKSMITH, BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2,
|
||||
static const std::vector<BuildingID> extra = { BuildingID::MARKETPLACE, BuildingID::BLACKSMITH, BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2,
|
||||
BuildingID::SPECIAL_3, BuildingID::SPECIAL_4, BuildingID::SHIPYARD }; // all remaining buildings
|
||||
|
||||
bool BuildingManager::getBuildingOptions(const CGTownInstance * t)
|
||||
|
@ -94,12 +94,12 @@ set(VCAI_HEADERS
|
||||
VCAI.h
|
||||
)
|
||||
|
||||
if(NOT ENABLE_STATIC_AI_LIBS)
|
||||
if(NOT ENABLE_STATIC_LIBS)
|
||||
list(APPEND VCAI_SRCS main.cpp StdInc.cpp)
|
||||
endif()
|
||||
assign_source_group(${VCAI_SRCS} ${VCAI_HEADERS})
|
||||
|
||||
if(ENABLE_STATIC_AI_LIBS)
|
||||
if(ENABLE_STATIC_LIBS)
|
||||
add_library(VCAI STATIC ${VCAI_SRCS} ${VCAI_HEADERS})
|
||||
else()
|
||||
add_library(VCAI SHARED ${VCAI_SRCS} ${VCAI_HEADERS})
|
||||
@ -107,7 +107,7 @@ else()
|
||||
endif()
|
||||
|
||||
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")
|
||||
enable_pch(VCAI)
|
||||
|
@ -109,51 +109,52 @@ bool AbstractGoal::operator==(const AbstractGoal & g) const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AbstractGoal::operator<(AbstractGoal & g) //for std::unique
|
||||
{
|
||||
//TODO: make sure it gets goals consistent with == operator
|
||||
if (goalType < g.goalType)
|
||||
return true;
|
||||
if (goalType > g.goalType)
|
||||
return false;
|
||||
if (hero < g.hero)
|
||||
return true;
|
||||
if (hero > g.hero)
|
||||
return false;
|
||||
if (tile < g.tile)
|
||||
return true;
|
||||
if (g.tile < tile)
|
||||
return false;
|
||||
if (objid < g.objid)
|
||||
return true;
|
||||
if (objid > g.objid)
|
||||
return false;
|
||||
if (town < g.town)
|
||||
return true;
|
||||
if (town > g.town)
|
||||
return false;
|
||||
if (value < g.value)
|
||||
return true;
|
||||
if (value > g.value)
|
||||
return false;
|
||||
if (priority < g.priority)
|
||||
return true;
|
||||
if (priority > g.priority)
|
||||
return false;
|
||||
if (resID < g.resID)
|
||||
return true;
|
||||
if (resID > g.resID)
|
||||
return false;
|
||||
if (bid < g.bid)
|
||||
return true;
|
||||
if (bid > g.bid)
|
||||
return false;
|
||||
if (aid < g.aid)
|
||||
return true;
|
||||
if (aid > g.aid)
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
// FIXME: unused code?
|
||||
//bool AbstractGoal::operator<(AbstractGoal & g) //for std::unique
|
||||
//{
|
||||
// //TODO: make sure it gets goals consistent with == operator
|
||||
// if (goalType < g.goalType)
|
||||
// return true;
|
||||
// if (goalType > g.goalType)
|
||||
// return false;
|
||||
// if (hero < g.hero)
|
||||
// return true;
|
||||
// if (hero > g.hero)
|
||||
// return false;
|
||||
// if (tile < g.tile)
|
||||
// return true;
|
||||
// if (g.tile < tile)
|
||||
// return false;
|
||||
// if (objid < g.objid)
|
||||
// return true;
|
||||
// if (objid > g.objid)
|
||||
// return false;
|
||||
// if (town < g.town)
|
||||
// return true;
|
||||
// if (town > g.town)
|
||||
// return false;
|
||||
// if (value < g.value)
|
||||
// return true;
|
||||
// if (value > g.value)
|
||||
// return false;
|
||||
// if (priority < g.priority)
|
||||
// return true;
|
||||
// if (priority > g.priority)
|
||||
// return false;
|
||||
// if (resID < g.resID)
|
||||
// return true;
|
||||
// if (resID > g.resID)
|
||||
// return false;
|
||||
// if (bid < g.bid)
|
||||
// return true;
|
||||
// if (bid > g.bid)
|
||||
// return false;
|
||||
// if (aid < g.aid)
|
||||
// return true;
|
||||
// if (aid > g.aid)
|
||||
// return false;
|
||||
// return false;
|
||||
//}
|
||||
|
||||
//TODO: find out why the following are not generated automatically on MVS?
|
||||
bool TSubgoal::operator==(const TSubgoal & rhs) const
|
||||
|
@ -165,16 +165,16 @@ namespace Goals
|
||||
virtual float accept(FuzzyHelper * f);
|
||||
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
return !(*this == g);
|
||||
}
|
||||
// bool operator!=(const AbstractGoal & g) const
|
||||
// {
|
||||
// return !(*this == g);
|
||||
// }
|
||||
|
||||
template<typename Handler> void serialize(Handler & h)
|
||||
{
|
||||
|
@ -39,6 +39,6 @@ namespace Goals
|
||||
void accept(VCAI * ai) override;
|
||||
std::string name() const override;
|
||||
std::string completeMessage() const override;
|
||||
virtual bool operator==(const AdventureSpellCast & other) const override;
|
||||
bool operator==(const AdventureSpellCast & other) const override;
|
||||
};
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ namespace Goals
|
||||
TSubgoal whatToDoToAchieve() override;
|
||||
bool fulfillsMe(TSubgoal goal) override;
|
||||
|
||||
virtual bool operator==(const Build & other) const override
|
||||
bool operator==(const Build & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user