1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Nullkiller: town portal fixes

This commit is contained in:
Andrii Danylchenko 2021-05-16 14:07:54 +03:00 committed by Andrii Danylchenko
parent a2ac19e4ec
commit 1096f2e9e6
6 changed files with 161 additions and 98 deletions

View File

@ -62,7 +62,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
if(town->garrisonHero)
{
if(ai->nullkiller->isActive(town->garrisonHero.get()))
if(!ai->nullkiller->isHeroLocked(town->garrisonHero.get()))
{
tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, nullptr).setpriority(5)));
return;
@ -70,8 +70,8 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
logAi->debug(
"Hero %s in garrison of town %s is suposed to defend the town",
town->name,
town->garrisonHero->name);
town->garrisonHero->name,
town->name);
return;
}
@ -107,9 +107,13 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
{
for(auto & treat : treats)
{
if(path.turn() < treat.turn && isSafeToVisit(path.targetHero, path.heroArmy, treat.danger))
if(isSafeToVisit(path.targetHero, path.heroArmy, treat.danger))
{
logAi->debug("Town %s is not in danger.", town->name);
logAi->debug(
"Hero %s can eliminate danger for town %s using path %s.",
path.targetHero->name,
town->name,
path.toString());
return;
}
@ -141,15 +145,18 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
if(path.targetHero == town->visitingHero && path.exchangeCount == 1)
{
#if AI_TRACE_LEVEL >= 1
logAi->trace("Put %s to garrison of town %s with priority %f",
logAi->trace("Put %s to garrison of town %s with priority %f",
path.targetHero->name,
town->name,
priority);
#endif
tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, town->visitingHero.get()).setpriority(priority)));
continue;
}
else if(path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= treat.danger)
if(path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= treat.danger)
{
#if AI_TRACE_LEVEL >= 1
logAi->trace("Move %s to defend town %s with priority %f",
@ -159,6 +166,8 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
#endif
tasks.push_back(Goals::sptr(Goals::ExecuteHeroChain(path, town).setpriority(priority)));
continue;
}
}
}

View File

@ -77,8 +77,8 @@ void Nullkiller::updateAiState()
// TODO: move to hero manager
auto activeHeroes = ai->getMyHeroes();
vstd::erase_if(activeHeroes, [&](const HeroPtr & hero) -> bool{
return vstd::contains(lockedHeroes, hero);
vstd::erase_if(activeHeroes, [this](const HeroPtr & hero) -> bool{
return isHeroLocked(hero.h);
});
ai->ah->updatePaths(activeHeroes, true);
@ -96,14 +96,17 @@ 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<DefenceBehavior>())
choseBestTask(std::make_shared<RecruitHeroBehavior>())
};
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);
@ -118,7 +121,7 @@ void Nullkiller::makeTurn()
try
{
activeHero = bestTask->hero;
activeHero = bestTask->hero.get();
bestTask->accept(ai.get());
}

View File

@ -18,18 +18,19 @@ class Nullkiller
{
private:
std::unique_ptr<PriorityEvaluator> priorityEvaluator;
HeroPtr activeHero;
std::set<HeroPtr> lockedHeroes;
const CGHeroInstance * activeHero;
std::set<const CGHeroInstance *> lockedHeroes;
public:
std::unique_ptr<DangerHitMapAnalyzer> dangerHitMap;
Nullkiller();
void makeTurn();
bool isActive(const CGHeroInstance * hero) const { return activeHero.h == hero; }
void setActive(const HeroPtr & hero) { activeHero = hero; }
void lockHero(const HeroPtr & hero) { lockedHeroes.insert(hero); }
void unlockHero(const HeroPtr & hero) { lockedHeroes.erase(hero); }
bool isActive(const CGHeroInstance * hero) const { return activeHero == hero; }
bool isHeroLocked(const CGHeroInstance * hero) const { return vstd::contains(lockedHeroes, hero); }
void setActive(const CGHeroInstance * hero) { activeHero = hero; }
void lockHero(const CGHeroInstance * hero) { lockedHeroes.insert(hero); }
void unlockHero(const CGHeroInstance * hero) { lockedHeroes.erase(hero); }
private:
void resetAiState();

View File

@ -69,7 +69,7 @@ void ExecuteHeroChain::accept(VCAI * ai)
if(vstd::contains(blockedIndexes, i))
{
blockedIndexes.insert(node.parentIndex);
ai->nullkiller->lockHero(hero);
ai->nullkiller->lockHero(hero.get());
continue;
}
@ -80,21 +80,27 @@ void ExecuteHeroChain::accept(VCAI * ai)
{
if(hero->movement)
{
ai->nullkiller->setActive(hero);
ai->nullkiller->setActive(hero.get());
if(node.specialAction)
{
auto specialGoal = node.specialAction->whatToDo(hero);
if(node.specialAction->canAct(hero.get()))
{
auto specialGoal = node.specialAction->whatToDo(hero);
if(specialGoal->isElementar)
specialGoal->accept(ai);
}
else
{
//TODO: decompose
}
}
Goals::VisitTile(node.coord).sethero(hero).accept(ai);
}
// no exception means we were not able to rich the tile
ai->nullkiller->lockHero(hero);
ai->nullkiller->lockHero(hero.get());
blockedIndexes.insert(node.parentIndex);
}
catch(goalFulfilledException)

View File

@ -120,7 +120,11 @@ boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
{
if(heroChainPass)
{
calculateTownPortalTeleportations(heroChain);
return heroChain;
}
std::vector<CGPathNode *> initialNodes;
@ -147,6 +151,8 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
}
}
calculateTownPortalTeleportations(initialNodes);
return initialNodes;
}
@ -185,7 +191,7 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf
dstNode->specialAction->applyOnDestination(dstNode->actor->hero, destination, source, dstNode, srcNode);
}
#if VCMI_TRACE_PATHFINDER >= 2
#if AI_TRACE_LEVEL >= 2
logAi->trace(
"Commited %s -> %s, cost: %f, hero: %s, mask: %x, army: %i",
source.coord.toString(),
@ -310,7 +316,7 @@ void AINodeStorage::calculateHeroChain(
continue;
}
#if VCMI_TRACE_PATHFINDER >= 2
#if AI_TRACE_LEVEL >= 2
logAi->trace(
"Thy exchange %s[%i] -> %s[%i] at %s",
node->actor->toString(),
@ -335,7 +341,7 @@ void AINodeStorage::calculateHeroChain(
&& other->armyLoss < other->actor->armyValue
&& carrier->actor->canExchange(other->actor))
{
#if VCMI_TRACE_PATHFINDER >= 2
#if AI_TRACE_LEVEL >= 2
logAi->trace(
"Exchange allowed %s[%i] -> %s[%i] at %s",
other->actor->toString(),
@ -352,7 +358,7 @@ void AINodeStorage::calculateHeroChain(
if(hasLessMp && hasLessExperience)
{
#if VCMI_TRACE_PATHFINDER >= 2
#if AI_TRACE_LEVEL >= 2
logAi->trace("Exchange at %s is ineficient. Blocked.", carrier->coord.toString());
#endif
return;
@ -376,7 +382,7 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
if(!chainNodeOptional)
{
#if VCMI_TRACE_PATHFINDER >= 2
#if AI_TRACE_LEVEL >= 2
logAi->trace("Exchange at %s can not allocate node. Blocked.", carrier->coord.toString());
#endif
continue;
@ -386,7 +392,7 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
if(exchangeNode->action != CGPathNode::ENodeAction::UNKNOWN)
{
#if VCMI_TRACE_PATHFINDER >= 2
#if AI_TRACE_LEVEL >= 2
logAi->trace("Exchange at %s node is already in use. Blocked.", carrier->coord.toString());
#endif
continue;
@ -394,7 +400,7 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
if(exchangeNode->turns != 0xFF && exchangeNode->cost < chainInfo.cost)
{
#if VCMI_TRACE_PATHFINDER >= 2
#if AI_TRACE_LEVEL >= 2
logAi->trace(
"Exchange at %s is is not effective enough. %f < %f",
exchangeNode->coord.toString(),
@ -406,10 +412,16 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
commit(exchangeNode, carrier, carrier->action, chainInfo.turns, chainInfo.moveRemains, chainInfo.cost);
if(carrier->specialAction || carrier->chainOther)
{
// there is some action on source tile which should be performed before we can bypass it
exchangeNode->theNodeBefore = carrier;
}
exchangeNode->chainOther = other;
exchangeNode->armyLoss = chainInfo.armyLoss;
#if VCMI_TRACE_PATHFINDER >= 2
#if AI_TRACE_LEVEL >= 2
logAi->trace(
"Chain accepted at %s %s -> %s, mask %x, cost %f, army %i",
exchangeNode->coord.toString(),
@ -566,85 +578,120 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
}
}
if(source.isInitialPosition)
{
calculateTownPortalTeleportations(source, neighbours, pathfinderHelper);
}
return neighbours;
}
void AINodeStorage::calculateTownPortalTeleportations(
const PathNodeInfo & source,
std::vector<CGPathNode *> & neighbours,
const CPathfinderHelper * pathfinderHelper)
void AINodeStorage::getBestInitialNodeForTownPortal()
{
}
void AINodeStorage::calculateTownPortalTeleportations(std::vector<CGPathNode *> & initialNodes)
{
SpellID spellID = SpellID::TOWN_PORTAL;
const CSpell * townPortal = spellID.toSpell();
auto srcNode = getAINode(source.node);
auto hero = srcNode->actor->hero;
if(hero->canCastThisSpell(townPortal) && hero->mana >= hero->getSpellCost(townPortal))
std::set<const ChainActor *> actorsOfInitial;
for(const CGPathNode * node : initialNodes)
{
auto towns = cb->getTownsInfo(false);
auto aiNode = getAINode(node);
vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool
actorsOfInitial.insert(aiNode->actor->baseActor);
}
for(const ChainActor * actor : actorsOfInitial)
{
if(!actor->hero)
continue;
auto hero = actor->hero;
if(hero->canCastThisSpell(townPortal) && hero->mana >= hero->getSpellCost(townPortal))
{
return cb->getPlayerRelations(hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES;
});
auto towns = cb->getTownsInfo(false);
if(!towns.size())
{
return;
}
// TODO: Copy/Paste from TownPortalMechanics
auto skillLevel = hero->getSpellSchoolLevel(townPortal);
auto movementNeeded = GameConstants::BASE_MOVEMENT_COST * (skillLevel >= 3 ? 2 : 3);
float movementCost = (float)movementNeeded / (float)pathfinderHelper->getMaxMovePoints(EPathfindingLayer::LAND);
movementCost += source.node->cost;
if(source.node->moveRemains < movementNeeded)
{
return;
}
if(skillLevel < SecSkillLevel::ADVANCED)
{
const CGTownInstance * nearestTown = *vstd::minElementByFun(towns, [&](const CGTownInstance * t) -> int
vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool
{
return source.coord.dist2dSQ(t->visitablePos());
return cb->getPlayerRelations(hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES;
});
towns = std::vector<const CGTownInstance *>{ nearestTown };
}
for(const CGTownInstance * targetTown : towns)
{
// TODO: allow to hide visiting hero in garrison
if(targetTown->visitingHero)
continue;
auto nodeOptional = getOrCreateNode(targetTown->visitablePos(), EPathfindingLayer::LAND, srcNode->actor->castActor);
if(nodeOptional)
if(!towns.size())
{
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("Adding town portal node at %s", targetTown->name);
return; // no towns no need to run loop further
}
// TODO: Copy/Paste from TownPortalMechanics
auto skillLevel = hero->getSpellSchoolLevel(townPortal);
auto movementNeeded = GameConstants::BASE_MOVEMENT_COST * (skillLevel >= 3 ? 2 : 3);
for(const CGTownInstance * targetTown : towns)
{
CGPathNode * bestNode = nullptr;
for(CGPathNode * node : initialNodes)
{
auto aiNode = getAINode(node);
if(aiNode->actor->baseActor != actor
|| node->layer != EPathfindingLayer::LAND
|| node->moveRemains < movementNeeded)
{
continue;
}
if(skillLevel < SecSkillLevel::ADVANCED)
{
const CGTownInstance * nearestTown = *vstd::minElementByFun(towns, [&](const CGTownInstance * t) -> int
{
return node->coord.dist2dSQ(t->visitablePos());
});
if(targetTown != nearestTown)
continue;
}
if(!bestNode || bestNode->cost > node->cost)
bestNode = node;
}
if(!bestNode)
continue;
// TODO: allow to hide visiting hero in garrison
if(targetTown->visitingHero && targetTown->visitingHero != hero)
continue;
auto nodeOptional = getOrCreateNode(targetTown->visitablePos(), EPathfindingLayer::LAND, actor->castActor);
if(nodeOptional)
{
float movementCost = (float)movementNeeded / (float)hero->maxMovePoints(EPathfindingLayer::LAND);
movementCost += bestNode->cost;
#ifdef AI_TRACE_LEVEL >= 1
logAi->trace("Adding town portal node at %s", targetTown->name);
#endif
AIPathNode * node = nodeOptional.get();
AIPathNode * node = nodeOptional.get();
if(node->action == CGPathNode::UNKNOWN || node->cost > movementCost)
{
node->theNodeBefore = source.node;
node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown));
node->moveRemains = source.node->moveRemains + movementNeeded;
node->cost = movementCost;
if(node->action == CGPathNode::UNKNOWN || node->cost > movementCost)
{
commit(
node,
getAINode(bestNode),
CGPathNode::TELEPORT_NORMAL,
bestNode->turns,
bestNode->moveRemains - movementNeeded,
movementCost);
node->theNodeBefore = bestNode;
node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown));
}
initialNodes.push_back(node);
}
neighbours.push_back(node);
}
}
}
@ -679,7 +726,7 @@ bool AINodeStorage::hasBetterChain(
{
if(node.cost < candidateNode->cost)
{
#ifdef VCMI_TRACE_PATHFINDER
#ifdef AI_TRACE_LEVEL >= 1
logAi->trace(
"Block ineficient move %s:->%s, mask=%i, mp diff: %i",
source->coord.toString(),

View File

@ -10,8 +10,8 @@
#pragma once
#define VCMI_TRACE_PATHFINDER 1
#define AI_TRACE_LEVEL 1
#define VCMI_TRACE_PATHFINDER 2
#define AI_TRACE_LEVEL 2
#include "../../../lib/CPathfinder.h"
#include "../../../lib/mapObjects/CGHeroInstance.h"
@ -165,10 +165,7 @@ private:
void cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const;
void addHeroChain(const std::vector<ExchangeCandidate> & result);
void calculateTownPortalTeleportations(
const PathNodeInfo & source,
std::vector<CGPathNode *> & neighbours,
const CPathfinderHelper * pathfinderHelper);
void calculateTownPortalTeleportations(std::vector<CGPathNode *> & neighbours);
void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const;
void commit(
AIPathNode * destination,