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:
parent
a2ac19e4ec
commit
1096f2e9e6
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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(),
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user