1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-02-03 13:01:33 +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
{
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];
return info;

View File

@ -51,6 +51,7 @@ public:
void updateHitMap();
uint64_t enemyCanKillOurHeroesAlongThePath(const AIPath & path) 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;
void reset();
};

View File

@ -92,7 +92,7 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons
{
auto guardPos = ai->cb->getGuardingCreaturePosition(node->coord);
auto blockers = ai->cb->getVisitableObjs(node->coord);
if(guardPos.valid())
{
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())
continue;
@ -113,7 +123,8 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons
|| blocker->ID == Obj::GARRISON2
|| blocker->ID == Obj::BORDERGUARD
|| blocker->ID == Obj::QUEST_GUARD
|| blocker->ID == Obj::BORDER_GATE)
|| blocker->ID == Obj::BORDER_GATE
|| blocker->ID == Obj::SHIPYARD)
{
if(!isObjectPassable(blocker))
return blocker;
@ -201,9 +212,15 @@ void ObjectClusterizer::clusterize()
bool added = false;
bool directlyAccessible = false;
std::set<const CGHeroInstance *> heroesProcessed;
for(auto & path : paths)
{
if(vstd::contains(heroesProcessed, path.targetHero))
continue;
heroesProcessed.insert(path.targetHero);
if(path.nodes.size() > 1)
{
auto blocker = getBlocker(path);
@ -236,10 +253,13 @@ void ObjectClusterizer::clusterize()
if(!added || directlyAccessible)
{
if(paths.front().turn() <= 2)
nearObjects.addObject(obj, paths.front(), 0);
AIPath & shortestPath = paths.front();
float priority = ai->priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(shortestPath, obj)));
if(shortestPath.turn() <= 2 || priority > 0.6f)
nearObjects.addObject(obj, shortestPath, 0);
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
{
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;
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)
|| 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(
"Hero %s can eliminate danger for town %s using path %s.",
@ -194,6 +195,9 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
continue;
}
std::vector<Goals::ExecuteHeroChain> pathsToDefend;
std::map<const CGHeroInstance *, std::vector<AIPath>> defferedPaths;
for(AIPath & path : paths)
{
#if AI_TRACE_LEVEL >= 1
@ -211,11 +215,16 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
town->name,
path.targetHero->name);
defferedPaths[path.targetHero].push_back(path);
continue;
}
float priority = basicPriority
+ std::min(SAFE_ATTACK_CONSTANT, (float)path.getHeroStrength() / treat.danger) / (treat.turn + 1);
float priority = basicPriority * std::min(SAFE_ATTACK_CONSTANT, (float)path.getHeroStrength() / treat.danger)
- treat.turn * 0.2f;
if(treat.turn < path.turn())
priority /= (path.turn() - treat.turn) * 2;
if(path.targetHero == town->visitingHero && path.exchangeCount == 1)
{
@ -231,7 +240,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
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))
{
@ -245,27 +254,33 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
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
logAi->trace("Move %s to defend town %s with priority %f",
path.targetHero->name,
town->name,
priority);
logAi->trace("Move %s to defend town %s with priority %f",
path.targetHero->name,
town->name,
chain.priority);
#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());
/*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 AI_TRACE_LEVEL >= 2
// TODO: decomposition?
// TODO: decomposition
logAi->trace("Ignore path. Action is blocked.");
#endif
continue;

View File

@ -11,6 +11,7 @@
#include "StartupBehavior.h"
#include "../VCAI.h"
#include "../AIUtility.h"
#include "../Goals/BuildThis.h"
#include "../Goals/RecruitHero.h"
#include "../Goals/ExecuteHeroChain.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);
auto closestHero = getNearestHero(startupTown);

View File

@ -43,7 +43,8 @@ EvaluationContext::EvaluationContext(const Nullkiller * ai)
heroRole(HeroRole::SCOUT),
turn(0),
strategicalValue(0),
evaluator(ai)
evaluator(ai),
enemyHeroDangerRatio(0)
{
}
@ -71,6 +72,7 @@ void PriorityEvaluator::initVisitTile()
strategicalValueVariable = engine->getInputVariable("strategicalValue");
goldPreasureVariable = engine->getInputVariable("goldPreasure");
goldCostVariable = engine->getInputVariable("goldCost");
fearVariable = engine->getInputVariable("fear");
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 value = 0;
@ -485,6 +500,7 @@ public:
evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, evaluationContext.heroRole);
evaluationContext.strategicalValue += evaluationContext.evaluator.getStrategicalValue(target);
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());
}
};
@ -659,6 +675,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
goldPreasureVariable->setValue(ai->buildAnalyzer->getGoldPreasure());
goldCostVariable->setValue(evaluationContext.goldCost / ((float)cb->getResourceAmount(Res::GOLD) + (float)ai->buildAnalyzer->getDailyIncome()[Res::GOLD] + 1.0f));
turnVariable->setValue(evaluationContext.turn);
fearVariable->setValue(evaluationContext.enemyHeroDangerRatio);
engine->process();
//engine.process(VISIT_TILE); //TODO: Process only Visit_Tile
@ -671,7 +688,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
assert(result >= 0);
#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(),
evaluationContext.armyLossPersentage,
(int)evaluationContext.turn,
@ -684,6 +701,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout",
evaluationContext.strategicalValue,
evaluationContext.closestWayRatio,
evaluationContext.enemyHeroDangerRatio,
result);
#endif

View File

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

View File

@ -36,32 +36,6 @@ namespace AIPathfinding
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
{
auto hero = source->actor->hero;
@ -90,6 +64,37 @@ namespace AIPathfinding
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
{
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 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 const CGObjectInstance * targetObject() const { return nullptr; }
};