1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-02-09 13:14:02 +02:00

Nullkiller: stabilization+clasterization improvements+fuzzy fear

This commit is contained in:
Andrii Danylchenko 2021-05-16 14:55:33 +03:00 committed by Andrii Danylchenko
parent 75b8ee74fa
commit 32fb465823
12 changed files with 137 additions and 56 deletions

View File

@ -99,6 +99,12 @@ uint64_t DangerHitMapAnalyzer::enemyCanKillOurHeroesAlongThePath(const AIPath &
const HitMapNode & DangerHitMapAnalyzer::getObjectTreat(const CGObjectInstance * obj) const const HitMapNode & DangerHitMapAnalyzer::getObjectTreat(const CGObjectInstance * obj) const
{ {
auto tile = obj->visitablePos(); auto tile = obj->visitablePos();
return getTileTreat(tile);
}
const HitMapNode & DangerHitMapAnalyzer::getTileTreat(const int3 & tile) const
{
const HitMapNode & info = hitMap[tile.x][tile.y][tile.z]; const HitMapNode & info = hitMap[tile.x][tile.y][tile.z];
return info; return info;

View File

@ -51,6 +51,7 @@ public:
void updateHitMap(); void updateHitMap();
uint64_t enemyCanKillOurHeroesAlongThePath(const AIPath & path) const; uint64_t enemyCanKillOurHeroesAlongThePath(const AIPath & path) const;
const HitMapNode & getObjectTreat(const CGObjectInstance * obj) const; const HitMapNode & getObjectTreat(const CGObjectInstance * obj) const;
const HitMapNode & getTileTreat(const int3 & tile) const;
const std::set<const CGObjectInstance *> & getOneTurnAccessibleObjects(const CGHeroInstance * enemy) const; const std::set<const CGObjectInstance *> & getOneTurnAccessibleObjects(const CGHeroInstance * enemy) const;
void reset(); void reset();
}; };

View File

@ -92,7 +92,7 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons
{ {
auto guardPos = ai->cb->getGuardingCreaturePosition(node->coord); auto guardPos = ai->cb->getGuardingCreaturePosition(node->coord);
auto blockers = ai->cb->getVisitableObjs(node->coord); auto blockers = ai->cb->getVisitableObjs(node->coord);
if(guardPos.valid()) if(guardPos.valid())
{ {
auto guard = ai->cb->getTopObj(ai->cb->getGuardingCreaturePosition(node->coord)); auto guard = ai->cb->getTopObj(ai->cb->getGuardingCreaturePosition(node->coord));
@ -103,6 +103,16 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons
} }
} }
if(node->specialAction && node->actionIsBlocked)
{
auto blockerObject = node->specialAction->targetObject();
if(blockerObject)
{
blockers.push_back(blockerObject);
}
}
if(blockers.empty()) if(blockers.empty())
continue; continue;
@ -113,7 +123,8 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons
|| blocker->ID == Obj::GARRISON2 || blocker->ID == Obj::GARRISON2
|| blocker->ID == Obj::BORDERGUARD || blocker->ID == Obj::BORDERGUARD
|| blocker->ID == Obj::QUEST_GUARD || blocker->ID == Obj::QUEST_GUARD
|| blocker->ID == Obj::BORDER_GATE) || blocker->ID == Obj::BORDER_GATE
|| blocker->ID == Obj::SHIPYARD)
{ {
if(!isObjectPassable(blocker)) if(!isObjectPassable(blocker))
return blocker; return blocker;
@ -201,9 +212,15 @@ void ObjectClusterizer::clusterize()
bool added = false; bool added = false;
bool directlyAccessible = false; bool directlyAccessible = false;
std::set<const CGHeroInstance *> heroesProcessed;
for(auto & path : paths) for(auto & path : paths)
{ {
if(vstd::contains(heroesProcessed, path.targetHero))
continue;
heroesProcessed.insert(path.targetHero);
if(path.nodes.size() > 1) if(path.nodes.size() > 1)
{ {
auto blocker = getBlocker(path); auto blocker = getBlocker(path);
@ -236,10 +253,13 @@ void ObjectClusterizer::clusterize()
if(!added || directlyAccessible) if(!added || directlyAccessible)
{ {
if(paths.front().turn() <= 2) AIPath & shortestPath = paths.front();
nearObjects.addObject(obj, paths.front(), 0); float priority = ai->priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(shortestPath, obj)));
if(shortestPath.turn() <= 2 || priority > 0.6f)
nearObjects.addObject(obj, shortestPath, 0);
else else
farObjects.addObject(obj, paths.front(), 0); farObjects.addObject(obj, shortestPath, 0);
} }
} }

View File

@ -60,7 +60,7 @@ uint64_t townArmyIncome(const CGTownInstance * town)
void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town) const void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town) const
{ {
auto basicPriority = 0.3f + std::sqrt(townArmyIncome(town) / 20000.0f) auto basicPriority = 0.3f + std::sqrt(townArmyIncome(town) / 40000.0f)
+ town->dailyIncome()[Res::GOLD] / 10000.0f; + town->dailyIncome()[Res::GOLD] / 10000.0f;
logAi->debug("Evaluating defence for %s, basic priority %f", town->name, basicPriority); logAi->debug("Evaluating defence for %s, basic priority %f", town->name, basicPriority);
@ -124,7 +124,8 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
{ {
if(path.turn() <= treat.turn && dayOfWeek + treat.turn < 6 && isSafeToVisit(path.targetHero, path.heroArmy, treat.danger) if(path.turn() <= treat.turn && dayOfWeek + treat.turn < 6 && isSafeToVisit(path.targetHero, path.heroArmy, treat.danger)
|| path.exchangeCount == 1 && path.turn() < treat.turn || path.exchangeCount == 1 && path.turn() < treat.turn
|| path.turn() < treat.turn - 1) || path.turn() < treat.turn - 1
|| path.turn() < treat.turn && treat.turn >= 2)
{ {
logAi->debug( logAi->debug(
"Hero %s can eliminate danger for town %s using path %s.", "Hero %s can eliminate danger for town %s using path %s.",
@ -194,6 +195,9 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
continue; continue;
} }
std::vector<Goals::ExecuteHeroChain> pathsToDefend;
std::map<const CGHeroInstance *, std::vector<AIPath>> defferedPaths;
for(AIPath & path : paths) for(AIPath & path : paths)
{ {
#if AI_TRACE_LEVEL >= 1 #if AI_TRACE_LEVEL >= 1
@ -211,11 +215,16 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
town->name, town->name,
path.targetHero->name); path.targetHero->name);
defferedPaths[path.targetHero].push_back(path);
continue; continue;
} }
float priority = basicPriority float priority = basicPriority * std::min(SAFE_ATTACK_CONSTANT, (float)path.getHeroStrength() / treat.danger)
+ std::min(SAFE_ATTACK_CONSTANT, (float)path.getHeroStrength() / treat.danger) / (treat.turn + 1); - treat.turn * 0.2f;
if(treat.turn < path.turn())
priority /= (path.turn() - treat.turn) * 2;
if(path.targetHero == town->visitingHero && path.exchangeCount == 1) if(path.targetHero == town->visitingHero && path.exchangeCount == 1)
{ {
@ -231,7 +240,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
continue; continue;
} }
if(path.turn() <= treat.turn && path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= treat.danger) if(treat.turn == 0 || path.turn() <= treat.turn && path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= treat.danger)
{ {
if(ai->nullkiller->arePathHeroesLocked(path)) if(ai->nullkiller->arePathHeroesLocked(path))
{ {
@ -245,27 +254,33 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
continue; continue;
} }
pathsToDefend.push_back(Goals::ExecuteHeroChain(path, town).setpriority(priority));
}
}
for(Goals::ExecuteHeroChain & chain : pathsToDefend)
{
auto path = chain.getPath();
for(AIPath & defferedPath : defferedPaths[path.targetHero])
{
if(defferedPath.getHeroStrength() >= path.getHeroStrength()
&& defferedPath.turn() <= path.turn())
{
continue;
}
}
#if AI_TRACE_LEVEL >= 1 #if AI_TRACE_LEVEL >= 1
logAi->trace("Move %s to defend town %s with priority %f", logAi->trace("Move %s to defend town %s with priority %f",
path.targetHero->name, path.targetHero->name,
town->name, town->name,
priority); chain.priority);
#endif #endif
tasks.push_back(Goals::sptr(Goals::ExecuteHeroChain(path, town).setpriority(priority))); tasks.push_back(Goals::sptr(chain));
}
} }
} }
logAi->debug("Found %d tasks", tasks.size()); logAi->debug("Found %d tasks", tasks.size());
/*for(auto & treat : treats)
{
auto paths = ai->nullkiller->pathfinder->getPathInfo(treat.hero->visitablePos());
for(AIPath & path : paths)
{
tasks.push_back(Goals::sptr(Goals::ExecuteHeroChain(path)));
}
}*/
} }

View File

@ -90,7 +90,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
if(path.getFirstBlockedAction()) if(path.getFirstBlockedAction())
{ {
#if AI_TRACE_LEVEL >= 2 #if AI_TRACE_LEVEL >= 2
// TODO: decomposition? // TODO: decomposition
logAi->trace("Ignore path. Action is blocked."); logAi->trace("Ignore path. Action is blocked.");
#endif #endif
continue; continue;

View File

@ -11,6 +11,7 @@
#include "StartupBehavior.h" #include "StartupBehavior.h"
#include "../VCAI.h" #include "../VCAI.h"
#include "../AIUtility.h" #include "../AIUtility.h"
#include "../Goals/BuildThis.h"
#include "../Goals/RecruitHero.h" #include "../Goals/RecruitHero.h"
#include "../Goals/ExecuteHeroChain.h" #include "../Goals/ExecuteHeroChain.h"
#include "../Goals/ExchangeSwapTownHeroes.h" #include "../Goals/ExchangeSwapTownHeroes.h"
@ -123,6 +124,14 @@ Goals::TGoalVec StartupBehavior::decompose() const
}); });
} }
if(!startupTown->hasBuilt(BuildingID::TAVERN)
&& cb->canBuildStructure(startupTown, BuildingID::TAVERN) == EBuildingState::ALLOWED)
{
tasks.push_back(Goals::sptr(Goals::BuildThis(BuildingID::TAVERN, startupTown).setpriority(100)));
return tasks;
}
bool canRecruitHero = needToRecruitHero(startupTown); bool canRecruitHero = needToRecruitHero(startupTown);
auto closestHero = getNearestHero(startupTown); auto closestHero = getNearestHero(startupTown);

View File

@ -43,7 +43,8 @@ EvaluationContext::EvaluationContext(const Nullkiller * ai)
heroRole(HeroRole::SCOUT), heroRole(HeroRole::SCOUT),
turn(0), turn(0),
strategicalValue(0), strategicalValue(0),
evaluator(ai) evaluator(ai),
enemyHeroDangerRatio(0)
{ {
} }
@ -71,6 +72,7 @@ void PriorityEvaluator::initVisitTile()
strategicalValueVariable = engine->getInputVariable("strategicalValue"); strategicalValueVariable = engine->getInputVariable("strategicalValue");
goldPreasureVariable = engine->getInputVariable("goldPreasure"); goldPreasureVariable = engine->getInputVariable("goldPreasure");
goldCostVariable = engine->getInputVariable("goldCost"); goldCostVariable = engine->getInputVariable("goldCost");
fearVariable = engine->getInputVariable("fear");
value = engine->getOutputVariable("Value"); value = engine->getOutputVariable("Value");
} }
@ -381,6 +383,19 @@ float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGH
} }
} }
uint64_t RewardEvaluator::getEnemyHeroDanger(const AIPath & path) const
{
auto & treatNode = ai->dangerHitMap->getTileTreat(path.targetTile());
if(treatNode.maximumDanger.danger == 0)
return 0;
if(treatNode.maximumDanger.turn <= path.turn())
return treatNode.maximumDanger.danger;
return treatNode.fastestDanger.turn <= path.turn() ? treatNode.fastestDanger.danger : 0;
}
int32_t getArmyCost(const CArmedInstance * army) int32_t getArmyCost(const CArmedInstance * army)
{ {
int32_t value = 0; int32_t value = 0;
@ -485,6 +500,7 @@ public:
evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, evaluationContext.heroRole); evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, evaluationContext.heroRole);
evaluationContext.strategicalValue += evaluationContext.evaluator.getStrategicalValue(target); evaluationContext.strategicalValue += evaluationContext.evaluator.getStrategicalValue(target);
evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army); evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army);
vstd::amax(evaluationContext.enemyHeroDangerRatio, evaluationContext.evaluator.getEnemyHeroDanger(path) / (double)path.getHeroStrength());
vstd::amax(evaluationContext.turn, path.turn()); vstd::amax(evaluationContext.turn, path.turn());
} }
}; };
@ -659,6 +675,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
goldPreasureVariable->setValue(ai->buildAnalyzer->getGoldPreasure()); goldPreasureVariable->setValue(ai->buildAnalyzer->getGoldPreasure());
goldCostVariable->setValue(evaluationContext.goldCost / ((float)cb->getResourceAmount(Res::GOLD) + (float)ai->buildAnalyzer->getDailyIncome()[Res::GOLD] + 1.0f)); goldCostVariable->setValue(evaluationContext.goldCost / ((float)cb->getResourceAmount(Res::GOLD) + (float)ai->buildAnalyzer->getDailyIncome()[Res::GOLD] + 1.0f));
turnVariable->setValue(evaluationContext.turn); turnVariable->setValue(evaluationContext.turn);
fearVariable->setValue(evaluationContext.enemyHeroDangerRatio);
engine->process(); engine->process();
//engine.process(VISIT_TILE); //TODO: Process only Visit_Tile //engine.process(VISIT_TILE); //TODO: Process only Visit_Tile
@ -671,7 +688,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
assert(result >= 0); assert(result >= 0);
#ifdef AI_TRACE_LEVEL >= 1 #ifdef AI_TRACE_LEVEL >= 1
logAi->trace("Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %d, cost: %d, army gain: %d, danger: %d, role: %s, strategical value: %f, cwr: %f, result %f", logAi->trace("Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %d, cost: %d, army gain: %d, danger: %d, role: %s, strategical value: %f, cwr: %f, fear: %f, result %f",
task->toString(), task->toString(),
evaluationContext.armyLossPersentage, evaluationContext.armyLossPersentage,
(int)evaluationContext.turn, (int)evaluationContext.turn,
@ -684,6 +701,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout", evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout",
evaluationContext.strategicalValue, evaluationContext.strategicalValue,
evaluationContext.closestWayRatio, evaluationContext.closestWayRatio,
evaluationContext.enemyHeroDangerRatio,
result); result);
#endif #endif

View File

@ -30,6 +30,7 @@ public:
float getSkillReward(const CGObjectInstance * target, const CGHeroInstance * hero, HeroRole role) const; float getSkillReward(const CGObjectInstance * target, const CGHeroInstance * hero, HeroRole role) const;
int32_t getGoldReward(const CGObjectInstance * target, const CGHeroInstance * hero) const; int32_t getGoldReward(const CGObjectInstance * target, const CGHeroInstance * hero) const;
uint64_t getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const; uint64_t getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const;
uint64_t getEnemyHeroDanger(const AIPath & path) const;
}; };
struct DLL_EXPORT EvaluationContext struct DLL_EXPORT EvaluationContext
@ -48,6 +49,7 @@ struct DLL_EXPORT EvaluationContext
HeroRole heroRole; HeroRole heroRole;
uint8_t turn; uint8_t turn;
RewardEvaluator evaluator; RewardEvaluator evaluator;
float enemyHeroDangerRatio;
EvaluationContext(const Nullkiller * ai); EvaluationContext(const Nullkiller * ai);
}; };
@ -87,6 +89,7 @@ private:
fl::InputVariable * closestHeroRatioVariable; fl::InputVariable * closestHeroRatioVariable;
fl::InputVariable * goldPreasureVariable; fl::InputVariable * goldPreasureVariable;
fl::InputVariable * goldCostVariable; fl::InputVariable * goldCostVariable;
fl::InputVariable * fearVariable;
fl::OutputVariable * value; fl::OutputVariable * value;
std::vector<std::shared_ptr<IEvaluationContextBuilder>> evaluationContextBuilders; std::vector<std::shared_ptr<IEvaluationContextBuilder>> evaluationContextBuilders;

View File

@ -92,7 +92,7 @@ void ExecuteHeroChain::accept(VCAI * ai)
} }
} }
if(node.turns == 0 && node.coord != hero->visitablePos()) if(node.turns == 0 && node.coord != hero->visitablePos())
{ {
auto targetNode = cb->getPathsInfo(hero)->getPathInfo(node.coord); auto targetNode = cb->getPathsInfo(hero)->getPathInfo(node.coord);

View File

@ -36,32 +36,6 @@ namespace AIPathfinding
return sptr(Goals::Invalid()); return sptr(Goals::Invalid());
} }
const ChainActor * BuildBoatAction::getActor(const ChainActor * sourceActor) const
{
return sourceActor->resourceActor;
}
void SummonBoatAction::execute(const CGHeroInstance * hero) const
{
Goals::AdventureSpellCast(hero, SpellID::SUMMON_BOAT).accept(ai.get());
}
const ChainActor * SummonBoatAction::getActor(const ChainActor * sourceActor) const
{
return sourceActor->castActor;
}
void SummonBoatAction::applyOnDestination(
const CGHeroInstance * hero,
CDestinationNodeInfo & destination,
const PathNodeInfo & source,
AIPathNode * dstMode,
const AIPathNode * srcNode) const
{
dstMode->manaCost = srcNode->manaCost + getManaCost(hero);
dstMode->theNodeBefore = source.node;
}
bool BuildBoatAction::canAct(const AIPathNode * source) const bool BuildBoatAction::canAct(const AIPathNode * source) const
{ {
auto hero = source->actor->hero; auto hero = source->actor->hero;
@ -90,6 +64,37 @@ namespace AIPathfinding
return true; return true;
} }
const CGObjectInstance * BuildBoatAction::targetObject() const
{
return shipyard->o;
}
const ChainActor * BuildBoatAction::getActor(const ChainActor * sourceActor) const
{
return sourceActor->resourceActor;
}
void SummonBoatAction::execute(const CGHeroInstance * hero) const
{
Goals::AdventureSpellCast(hero, SpellID::SUMMON_BOAT).accept(ai.get());
}
const ChainActor * SummonBoatAction::getActor(const ChainActor * sourceActor) const
{
return sourceActor->castActor;
}
void SummonBoatAction::applyOnDestination(
const CGHeroInstance * hero,
CDestinationNodeInfo & destination,
const PathNodeInfo & source,
AIPathNode * dstMode,
const AIPathNode * srcNode) const
{
dstMode->manaCost = srcNode->manaCost + getManaCost(hero);
dstMode->theNodeBefore = source.node;
}
std::string BuildBoatAction::toString() const std::string BuildBoatAction::toString() const
{ {
return "Build Boat at " + shipyard->o->getObjectName(); return "Build Boat at " + shipyard->o->getObjectName();

View File

@ -67,5 +67,7 @@ namespace AIPathfinding
virtual const ChainActor * getActor(const ChainActor * sourceActor) const override; virtual const ChainActor * getActor(const ChainActor * sourceActor) const override;
virtual std::string toString() const override; virtual std::string toString() const override;
virtual const CGObjectInstance * targetObject() const override;
}; };
} }

View File

@ -37,4 +37,6 @@ public:
} }
virtual std::string toString() const = 0; virtual std::string toString() const = 0;
virtual const CGObjectInstance * targetObject() const { return nullptr; }
}; };