mirror of
https://github.com/vcmi/vcmi.git
synced 2025-04-13 11:40:38 +02:00
Nullkiller AI: further stabilisation, implement staged hero chain (first with limit 0 turns then 1 turn)
This commit is contained in:
parent
6bebb766a6
commit
eea5cb7f0b
@ -99,7 +99,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks()
|
||||
auto hero = path.targetHero;
|
||||
auto danger = path.getTotalDanger();
|
||||
|
||||
if(danger == 0 && path.exchangeCount > 1)
|
||||
if(ai->ah->getHeroRole(hero) == HeroRole::SCOUT && danger == 0 && path.exchangeCount > 1)
|
||||
continue;
|
||||
|
||||
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger);
|
||||
@ -131,13 +131,13 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks()
|
||||
|
||||
for(auto way : waysToVisitObj)
|
||||
{
|
||||
if(ai->nullkiller->arePathHeroesLocked(way->getPath()))
|
||||
continue;
|
||||
|
||||
way->evaluationContext.closestWayRatio
|
||||
= way->evaluationContext.movementCost / closestWay->evaluationContext.movementCost;
|
||||
|
||||
if(way->hero && ai->nullkiller->canMove(way->hero.h))
|
||||
{
|
||||
tasks.push_back(sptr(*way));
|
||||
}
|
||||
tasks.push_back(sptr(*way));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -227,13 +227,25 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
priority);
|
||||
#endif
|
||||
|
||||
tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, town->visitingHero.get()).setpriority(priority)));
|
||||
tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, town->visitingHero.get(), HeroLockedReason::DEFENCE).setpriority(priority)));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if(path.turn() <= treat.turn && path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= treat.danger)
|
||||
{
|
||||
if(ai->nullkiller->arePathHeroesLocked(path))
|
||||
{
|
||||
#if AI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Can not move %s to defend town %s with priority %f. Path is locked.",
|
||||
path.targetHero->name,
|
||||
town->name,
|
||||
priority);
|
||||
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
#if AI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Move %s to defend town %s with priority %f",
|
||||
path.targetHero->name,
|
||||
@ -242,8 +254,6 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
#endif
|
||||
|
||||
tasks.push_back(Goals::sptr(Goals::ExecuteHeroChain(path, town).setpriority(priority)));
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "lib/mapping/CMap.h" //for victory conditions
|
||||
#include "lib/mapObjects/MapObjects.h" //for victory conditions
|
||||
#include "lib/CPathfinder.h"
|
||||
#include "../Engine/Nullkiller.h"
|
||||
|
||||
extern boost::thread_specific_ptr<CCallback> cb;
|
||||
extern boost::thread_specific_ptr<VCAI> ai;
|
||||
@ -147,17 +148,17 @@ Goals::TGoalVec StartupBehavior::getTasks()
|
||||
{
|
||||
if(canRecruitHero || ai->ah->howManyReinforcementsCanGet(visitingHero, garrisonHero) > 200)
|
||||
{
|
||||
tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero).setpriority(100)));
|
||||
tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero, HeroLockedReason::STARTUP).setpriority(100)));
|
||||
}
|
||||
}
|
||||
else if(ai->ah->howManyReinforcementsCanGet(garrisonHero, visitingHero) > 200)
|
||||
{
|
||||
tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, garrisonHero).setpriority(100)));
|
||||
tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, garrisonHero, HeroLockedReason::STARTUP).setpriority(100)));
|
||||
}
|
||||
}
|
||||
else if(canRecruitHero)
|
||||
{
|
||||
tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero).setpriority(100)));
|
||||
tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero, HeroLockedReason::STARTUP).setpriority(100)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -171,7 +172,7 @@ Goals::TGoalVec StartupBehavior::getTasks()
|
||||
{
|
||||
for(const CGTownInstance * town : towns)
|
||||
{
|
||||
if(town->garrisonHero && town->garrisonHero->movement)
|
||||
if(town->garrisonHero && town->garrisonHero->movement && ai->nullkiller->getHeroLockedReason(town->garrisonHero) != HeroLockedReason::DEFENCE)
|
||||
tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(town, nullptr).setpriority(0.0001f)));
|
||||
}
|
||||
}
|
||||
|
@ -79,13 +79,26 @@ void Nullkiller::updateAiState()
|
||||
auto activeHeroes = ai->getMyHeroes();
|
||||
|
||||
vstd::erase_if(activeHeroes, [this](const HeroPtr & hero) -> bool{
|
||||
return isHeroLocked(hero.h);
|
||||
auto lockedReason = getHeroLockedReason(hero.h);
|
||||
|
||||
return lockedReason == HeroLockedReason::DEFENCE || lockedReason == HeroLockedReason::STARTUP;
|
||||
});
|
||||
|
||||
ai->ah->updatePaths(activeHeroes, true);
|
||||
ai->ah->updateHeroRoles();
|
||||
}
|
||||
|
||||
bool Nullkiller::arePathHeroesLocked(const AIPath & path) const
|
||||
{
|
||||
for(auto & node : path.nodes)
|
||||
{
|
||||
if(isHeroLocked(node.targetHero))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Nullkiller::makeTurn()
|
||||
{
|
||||
resetAiState();
|
||||
@ -97,17 +110,14 @@ void Nullkiller::makeTurn()
|
||||
Goals::TGoalVec bestTasks = {
|
||||
choseBestTask(std::make_shared<BuyArmyBehavior>()),
|
||||
choseBestTask(std::make_shared<CaptureObjectsBehavior>()),
|
||||
choseBestTask(std::make_shared<RecruitHeroBehavior>())
|
||||
choseBestTask(std::make_shared<RecruitHeroBehavior>()),
|
||||
choseBestTask(std::make_shared<DefenceBehavior>())
|
||||
};
|
||||
|
||||
if(cb->getDate(Date::DAY) == 1)
|
||||
{
|
||||
bestTasks.push_back(choseBestTask(std::make_shared<StartupBehavior>()));
|
||||
}
|
||||
else
|
||||
{
|
||||
bestTasks.push_back(choseBestTask(std::make_shared<DefenceBehavior>()));
|
||||
}
|
||||
|
||||
Goals::TSubgoal bestTask = choseBestTask(bestTasks);
|
||||
|
||||
|
@ -14,12 +14,23 @@
|
||||
#include "../Goals/AbstractGoal.h"
|
||||
#include "../Behaviors/Behavior.h"
|
||||
|
||||
enum class HeroLockedReason
|
||||
{
|
||||
NOT_LOCKED = 0,
|
||||
|
||||
STARTUP = 1,
|
||||
|
||||
DEFENCE = 2,
|
||||
|
||||
HERO_CHAIN = 3
|
||||
};
|
||||
|
||||
class Nullkiller
|
||||
{
|
||||
private:
|
||||
std::unique_ptr<PriorityEvaluator> priorityEvaluator;
|
||||
const CGHeroInstance * activeHero;
|
||||
std::set<const CGHeroInstance *> lockedHeroes;
|
||||
std::map<const CGHeroInstance *, HeroLockedReason> lockedHeroes;
|
||||
|
||||
public:
|
||||
std::unique_ptr<DangerHitMapAnalyzer> dangerHitMap;
|
||||
@ -28,10 +39,11 @@ public:
|
||||
void makeTurn();
|
||||
bool isActive(const CGHeroInstance * hero) const { return activeHero == hero; }
|
||||
bool isHeroLocked(const CGHeroInstance * hero) const { return vstd::contains(lockedHeroes, hero); }
|
||||
HeroLockedReason getHeroLockedReason(const CGHeroInstance * hero) const { return isHeroLocked(hero) ? lockedHeroes.at(hero) : HeroLockedReason::NOT_LOCKED; }
|
||||
void setActive(const CGHeroInstance * hero) { activeHero = hero; }
|
||||
void lockHero(const CGHeroInstance * hero) { lockedHeroes.insert(hero); }
|
||||
void lockHero(const CGHeroInstance * hero, HeroLockedReason lockReason) { lockedHeroes[hero] = lockReason; }
|
||||
void unlockHero(const CGHeroInstance * hero) { lockedHeroes.erase(hero); }
|
||||
bool canMove(const CGHeroInstance * hero) { return hero->movement; }
|
||||
bool arePathHeroesLocked(const AIPath & path) const;
|
||||
|
||||
private:
|
||||
void resetAiState();
|
||||
|
@ -201,10 +201,10 @@ float getEnemyHeroStrategicalValue(const CGHeroInstance * enemy)
|
||||
|
||||
for(auto obj : objectsUnderTreat)
|
||||
{
|
||||
objectValue += getStrategicalValue(obj);
|
||||
vstd::amax(objectValue, getStrategicalValue(obj));
|
||||
}
|
||||
|
||||
return objectValue + enemy->level / 15.0f;
|
||||
return objectValue / 2.0f + enemy->level / 15.0f;
|
||||
}
|
||||
|
||||
float getStrategicalValue(const CGObjectInstance * target)
|
||||
@ -400,7 +400,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
|
||||
assert(result >= 0);
|
||||
|
||||
#ifdef VCMI_TRACE_PATHFINDER
|
||||
logAi->trace("Evaluated %s, hero %s, loss: %f, turns: %f, gold: %d, army gain: %d, danger: %d, role: %s, result %f",
|
||||
logAi->trace("Evaluated %s, hero %s, loss: %f, turns: %f, gold: %d, army gain: %d, danger: %d, role: %s, strategical value: %f, result %f",
|
||||
task->name(),
|
||||
hero->name,
|
||||
armyLossPersentage,
|
||||
@ -409,6 +409,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
|
||||
armyReward,
|
||||
danger,
|
||||
heroRole ? "scout" : "main",
|
||||
strategicalValue,
|
||||
result);
|
||||
#endif
|
||||
|
||||
|
@ -23,8 +23,11 @@ extern FuzzyHelper * fh;
|
||||
|
||||
using namespace Goals;
|
||||
|
||||
ExchangeSwapTownHeroes::ExchangeSwapTownHeroes(const CGTownInstance * town, const CGHeroInstance * garrisonHero)
|
||||
:CGoal(Goals::EXCHANGE_SWAP_TOWN_HEROES), town(town), garrisonHero(garrisonHero)
|
||||
ExchangeSwapTownHeroes::ExchangeSwapTownHeroes(
|
||||
const CGTownInstance * town,
|
||||
const CGHeroInstance * garrisonHero,
|
||||
HeroLockedReason lockingReason)
|
||||
:CGoal(Goals::EXCHANGE_SWAP_TOWN_HEROES), town(town), garrisonHero(garrisonHero), lockingReason(lockingReason)
|
||||
{
|
||||
}
|
||||
|
||||
@ -90,7 +93,7 @@ void ExchangeSwapTownHeroes::accept(VCAI * ai)
|
||||
cb->swapGarrisonHero(town); // selected hero left in garrison with strongest army
|
||||
}
|
||||
|
||||
ai->nullkiller->lockHero(garrisonHero);
|
||||
ai->nullkiller->lockHero(garrisonHero, lockingReason);
|
||||
|
||||
if(town->visitingHero && town->visitingHero != garrisonHero)
|
||||
{
|
||||
|
@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CGoal.h"
|
||||
#include "..\Engine\Nullkiller.h"
|
||||
|
||||
namespace Goals
|
||||
{
|
||||
@ -18,9 +19,13 @@ namespace Goals
|
||||
private:
|
||||
const CGTownInstance * town;
|
||||
const CGHeroInstance * garrisonHero;
|
||||
HeroLockedReason lockingReason;
|
||||
|
||||
public:
|
||||
ExchangeSwapTownHeroes(const CGTownInstance * town, const CGHeroInstance * garrisonHero = nullptr);
|
||||
ExchangeSwapTownHeroes(
|
||||
const CGTownInstance * town,
|
||||
const CGHeroInstance * garrisonHero = nullptr,
|
||||
HeroLockedReason lockingReason = HeroLockedReason::NOT_LOCKED);
|
||||
|
||||
TGoalVec getAllPossibleSubgoals() override
|
||||
{
|
||||
|
@ -69,7 +69,7 @@ void ExecuteHeroChain::accept(VCAI * ai)
|
||||
if(vstd::contains(blockedIndexes, i))
|
||||
{
|
||||
blockedIndexes.insert(node.parentIndex);
|
||||
ai->nullkiller->lockHero(hero.get());
|
||||
ai->nullkiller->lockHero(hero.get(), HeroLockedReason::HERO_CHAIN);
|
||||
|
||||
continue;
|
||||
}
|
||||
@ -128,7 +128,7 @@ void ExecuteHeroChain::accept(VCAI * ai)
|
||||
{
|
||||
logAi->warn("Hero %s has %d mp which is not enough to continue his way towards %s.", hero.name, hero->movement, node.coord.toString());
|
||||
|
||||
ai->nullkiller->lockHero(hero.get());
|
||||
ai->nullkiller->lockHero(hero.get(), HeroLockedReason::HERO_CHAIN);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -148,13 +148,9 @@ void ExecuteHeroChain::accept(VCAI * ai)
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// do not lock hero if it is simple one hero chain
|
||||
if(chainPath.exchangeCount == 1)
|
||||
return;
|
||||
|
||||
|
||||
// no exception means we were not able to rich the tile
|
||||
ai->nullkiller->lockHero(hero.get());
|
||||
ai->nullkiller->lockHero(hero.get(), HeroLockedReason::HERO_CHAIN);
|
||||
blockedIndexes.insert(node.parentIndex);
|
||||
}
|
||||
catch(goalFulfilledException)
|
||||
|
@ -32,5 +32,6 @@ namespace Goals
|
||||
std::string name() const override;
|
||||
std::string completeMessage() const override;
|
||||
virtual bool operator==(const ExecuteHeroChain & other) const override;
|
||||
const AIPath & getPath() const { return chainPath; }
|
||||
};
|
||||
}
|
||||
|
@ -77,7 +77,8 @@ void AINodeStorage::clear()
|
||||
{
|
||||
actors.clear();
|
||||
heroChainPass = false;
|
||||
heroChainTurn = 1;
|
||||
heroChainTurn = 0;
|
||||
heroChainMaxTurns = 1;
|
||||
}
|
||||
|
||||
const AIPathNode * AINodeStorage::getAINode(const CGPathNode * node) const
|
||||
@ -251,6 +252,16 @@ std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
|
||||
return neighbours;
|
||||
}
|
||||
|
||||
bool AINodeStorage::increaseHeroChainTurnLimit()
|
||||
{
|
||||
if(heroChainTurn >= heroChainMaxTurns)
|
||||
return false;
|
||||
|
||||
heroChainTurn++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AINodeStorage::calculateHeroChain()
|
||||
{
|
||||
heroChainPass = true;
|
||||
|
@ -102,6 +102,7 @@ private:
|
||||
std::vector<CGPathNode *> heroChain;
|
||||
bool heroChainPass; // true if we need to calculate hero chain
|
||||
int heroChainTurn;
|
||||
int heroChainMaxTurns;
|
||||
PlayerColor playerID;
|
||||
|
||||
public:
|
||||
@ -113,6 +114,8 @@ public:
|
||||
|
||||
void initialize(const PathfinderOptions & options, const CGameState * gs) override;
|
||||
|
||||
bool increaseHeroChainTurnLimit();
|
||||
|
||||
virtual std::vector<CGPathNode *> getInitialNodes() override;
|
||||
|
||||
virtual std::vector<CGPathNode *> calculateNeighbours(
|
||||
|
@ -62,12 +62,25 @@ void AIPathfinder::updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain)
|
||||
}
|
||||
|
||||
auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, storage);
|
||||
bool continueCalculation = false;
|
||||
|
||||
do {
|
||||
do
|
||||
{
|
||||
logAi->trace("Recalculate paths pass %d", pass++);
|
||||
cb->calculatePaths(config);
|
||||
|
||||
logAi->trace("Recalculate chain pass %d", pass);
|
||||
useHeroChain = useHeroChain && storage->calculateHeroChain();
|
||||
} while(useHeroChain);
|
||||
if(useHeroChain)
|
||||
{
|
||||
logAi->trace("Recalculate chain pass %d", pass);
|
||||
|
||||
continueCalculation = storage->calculateHeroChain();
|
||||
|
||||
if(!continueCalculation)
|
||||
{
|
||||
logAi->trace("Increase chain turn limit");
|
||||
|
||||
continueCalculation = storage->increaseHeroChainTurnLimit() && storage->calculateHeroChain();
|
||||
}
|
||||
}
|
||||
} while(continueCalculation);
|
||||
}
|
||||
|
@ -546,15 +546,22 @@ void VCAI::objectPropertyChanged(const SetObjectProperty * sop)
|
||||
NET_EVENT_HANDLER;
|
||||
if(sop->what == ObjProperty::OWNER)
|
||||
{
|
||||
if(myCb->getPlayerRelations(playerID, (PlayerColor)sop->val) == PlayerRelations::ENEMIES)
|
||||
auto relations = myCb->getPlayerRelations(playerID, (PlayerColor)sop->val);
|
||||
auto obj = myCb->getObj(sop->id, false);
|
||||
|
||||
if(obj)
|
||||
{
|
||||
//we want to visit objects owned by oppponents
|
||||
auto obj = myCb->getObj(sop->id, false);
|
||||
if(obj)
|
||||
if(relations == PlayerRelations::ENEMIES)
|
||||
{
|
||||
//we want to visit objects owned by oppponents
|
||||
addVisitableObj(obj); // TODO: Remove once save compatability broken. In past owned objects were removed from this set
|
||||
vstd::erase_if_present(alreadyVisited, obj);
|
||||
}
|
||||
else if(relations == PlayerRelations::SAME_PLAYER && obj->ID == Obj::TOWN && nullkiller)
|
||||
{
|
||||
// reevaluate defence for a new town
|
||||
nullkiller->dangerHitMap->reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user