mirror of
https://github.com/vcmi/vcmi.git
synced 2025-08-13 19:54:17 +02:00
Nullkiller: rework defence a bit
This commit is contained in:
committed by
Andrii Danylchenko
parent
1096f2e9e6
commit
84e5e6ac17
@@ -31,7 +31,7 @@ const int ACTUAL_RESOURCE_COUNT = 7;
|
||||
const int ALLOWED_ROAMING_HEROES = 8;
|
||||
|
||||
//implementation-dependent
|
||||
extern const double SAFE_ATTACK_CONSTANT;
|
||||
extern const float SAFE_ATTACK_CONSTANT;
|
||||
extern const int GOLD_RESERVE;
|
||||
|
||||
//provisional class for AI to store a reference to an owned hero object
|
||||
|
@@ -39,11 +39,6 @@ Goals::TGoalVec DefenceBehavior::getTasks()
|
||||
|
||||
if(heroes.size())
|
||||
{
|
||||
auto mainArmy = *vstd::maxElementByFun(heroes, [](const CGHeroInstance * hero) -> uint64_t
|
||||
{
|
||||
return hero->getTotalStrength();
|
||||
});
|
||||
|
||||
for(auto town : cb->getTownsInfo())
|
||||
{
|
||||
evaluateDefence(tasks, town);
|
||||
@@ -53,13 +48,41 @@ Goals::TGoalVec DefenceBehavior::getTasks()
|
||||
return tasks;
|
||||
}
|
||||
|
||||
uint64_t townArmyIncome(const CGTownInstance * town)
|
||||
{
|
||||
uint64_t result = 0;
|
||||
|
||||
for(auto creatureInfo : town->creatures)
|
||||
{
|
||||
if(creatureInfo.second.empty())
|
||||
continue;
|
||||
|
||||
auto creature = creatureInfo.second.back().toCreature();
|
||||
result += creature->AIValue * town->getGrowthInfo(creature->level).totalGrowth();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town)
|
||||
{
|
||||
logAi->debug("Evaluating defence for %s", town->name);
|
||||
auto basicPriority = 0.3f + std::sqrt(townArmyIncome(town) / 20000.0f)
|
||||
+ town->dailyIncome()[Res::GOLD] / 10000.0f;
|
||||
|
||||
logAi->debug("Evaluating defence for %s, basic priority %f", town->name, basicPriority);
|
||||
|
||||
auto treatNode = ai->nullkiller->dangerHitMap->getObjectTreat(town);
|
||||
auto treats = { treatNode.fastestDanger, treatNode.maximumDanger };
|
||||
|
||||
if(!treatNode.fastestDanger.hero)
|
||||
{
|
||||
logAi->debug("No treat found for town %s", town->name);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int dayOfWeek = cb->getDate(Date::DAY_OF_WEEK);
|
||||
|
||||
if(town->garrisonHero)
|
||||
{
|
||||
if(!ai->nullkiller->isHeroLocked(town->garrisonHero.get()))
|
||||
@@ -75,17 +98,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(town->visitingHero && isSafeToVisit(town->visitingHero.get(), treatNode.maximumDanger.danger))
|
||||
{
|
||||
logAi->debug(
|
||||
"Town %s has visiting hero %s who is strong enough to defend the town",
|
||||
town->name,
|
||||
town->visitingHero->name);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
uint64_t reinforcement = ai->ah->howManyReinforcementsCanBuy(town->getUpperArmy(), town);
|
||||
|
||||
if(reinforcement)
|
||||
@@ -103,44 +116,68 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
return;
|
||||
}
|
||||
|
||||
for(AIPath & path : paths)
|
||||
{
|
||||
for(auto & treat : treats)
|
||||
{
|
||||
if(isSafeToVisit(path.targetHero, path.heroArmy, treat.danger))
|
||||
{
|
||||
logAi->debug(
|
||||
"Hero %s can eliminate danger for town %s using path %s.",
|
||||
path.targetHero->name,
|
||||
town->name,
|
||||
path.toString());
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(auto & treat : treats)
|
||||
{
|
||||
logAi->debug(
|
||||
"Town %s has treat %lld in %s turns, hero: %s",
|
||||
"Town %s has treat %lld in %s turns, hero: %s",
|
||||
town->name,
|
||||
treat.danger,
|
||||
std::to_string(treat.turn),
|
||||
treat.hero->name);
|
||||
|
||||
bool treatIsUnderControl = false;
|
||||
|
||||
for(AIPath & path : paths)
|
||||
{
|
||||
if(path.getHeroStrength() > treat.danger)
|
||||
{
|
||||
if(dayOfWeek + treat.turn < 6 && isSafeToVisit(path.targetHero, path.heroArmy, treat.danger)
|
||||
|| path.exchangeCount == 1 && path.turn() < treat.turn
|
||||
|| path.turn() < treat.turn - 1)
|
||||
{
|
||||
logAi->debug(
|
||||
"Hero %s can eliminate danger for town %s using path %s.",
|
||||
path.targetHero->name,
|
||||
town->name,
|
||||
path.toString());
|
||||
|
||||
treatIsUnderControl = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(treatIsUnderControl)
|
||||
continue;
|
||||
|
||||
if(ai->canRecruitAnyHero(town))
|
||||
{
|
||||
auto heroesInTavern = cb->getAvailableHeroes(town);
|
||||
|
||||
for(auto hero : heroesInTavern)
|
||||
{
|
||||
if(hero->getTotalStrength() > treat.danger)
|
||||
{
|
||||
tasks.push_back(Goals::sptr(Goals::RecruitHero().settown(town).setobjid(hero->id.getNum()).setpriority(1)));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(AIPath & path : paths)
|
||||
{
|
||||
#if AI_TRACE_LEVEL >= 1
|
||||
logAi->trace(
|
||||
"Hero %s can defend town with force %lld in %s turns, path: %s",
|
||||
"Hero %s can defend town with force %lld in %s turns, cost: %f, path: %s",
|
||||
path.targetHero->name,
|
||||
path.getHeroStrength(),
|
||||
std::to_string(path.turn()),
|
||||
path.movementCost(),
|
||||
path.toString());
|
||||
#endif
|
||||
|
||||
float priority = 0.6f + (float)path.getHeroStrength() / treat.danger / (treat.turn + 1);
|
||||
float priority = basicPriority
|
||||
+ std::min(SAFE_ATTACK_CONSTANT, (float)path.getHeroStrength() / treat.danger) / (treat.turn + 1);
|
||||
|
||||
if(path.targetHero == town->visitingHero && path.exchangeCount == 1)
|
||||
{
|
||||
@@ -156,7 +193,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
continue;
|
||||
}
|
||||
|
||||
if(path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= treat.danger)
|
||||
if(path.turn() <= treat.turn && path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= treat.danger)
|
||||
{
|
||||
#if AI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Move %s to defend town %s with priority %f",
|
||||
|
@@ -31,13 +31,17 @@ std::string RecruitHeroBehavior::toString() const
|
||||
Goals::TGoalVec RecruitHeroBehavior::getTasks()
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
auto towns = cb->getTownsInfo();
|
||||
|
||||
if(ai->canRecruitAnyHero())
|
||||
for(auto town : towns)
|
||||
{
|
||||
if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1
|
||||
|| cb->getResourceAmount(Res::GOLD) > 10000)
|
||||
if(!town->garrisonHero && ai->canRecruitAnyHero(town))
|
||||
{
|
||||
tasks.push_back(Goals::sptr(Goals::RecruitHero()));
|
||||
if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1
|
||||
|| cb->getResourceAmount(Res::GOLD) > 10000)
|
||||
{
|
||||
tasks.push_back(Goals::sptr(Goals::RecruitHero().settown(town)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -66,16 +66,37 @@ void ExchangeSwapTownHeroes::accept(VCAI * ai)
|
||||
if(town->visitingHero && town->visitingHero.get() != garrisonHero)
|
||||
cb->swapGarrisonHero(town);
|
||||
|
||||
makePossibleUpgrades(town);
|
||||
ai->moveHeroToTile(town->visitablePos(), garrisonHero);
|
||||
|
||||
cb->swapGarrisonHero(town); // selected hero left in garrison with strongest army
|
||||
ai->nullkiller->lockHero(town->garrisonHero.get());
|
||||
auto upperArmy = town->getUpperArmy();
|
||||
|
||||
if(!town->garrisonHero && upperArmy->stacksCount() != 0)
|
||||
{
|
||||
// dismiss creatures we are not able to pick to be able to hide in garrison
|
||||
if(upperArmy->getArmyStrength() < 500
|
||||
&& town->fortLevel() >= CGTownInstance::CITADEL)
|
||||
{
|
||||
for(auto slot : upperArmy->Slots())
|
||||
{
|
||||
cb->dismissCreature(upperArmy, slot.first);
|
||||
}
|
||||
|
||||
if(town->visitingHero)
|
||||
cb->swapGarrisonHero(town);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cb->swapGarrisonHero(town); // selected hero left in garrison with strongest army
|
||||
}
|
||||
|
||||
ai->nullkiller->lockHero(garrisonHero);
|
||||
|
||||
if(town->visitingHero && town->visitingHero != garrisonHero)
|
||||
{
|
||||
ai->nullkiller->unlockHero(town->visitingHero.get());
|
||||
makePossibleUpgrades(town->visitingHero);
|
||||
}
|
||||
|
||||
logAi->debug("Put hero %s to garrison of %s", town->garrisonHero->name, town->name);
|
||||
logAi->debug("Put hero %s to garrison of %s", garrisonHero->name, town->name);
|
||||
}
|
@@ -96,9 +96,36 @@ void ExecuteHeroChain::accept(VCAI * ai)
|
||||
}
|
||||
}
|
||||
|
||||
if(node.turns == 0)
|
||||
{
|
||||
auto targetNode = cb->getPathsInfo(hero.get())->getPathInfo(node.coord);
|
||||
|
||||
if(!targetNode->accessible || targetNode->turns != 0)
|
||||
{
|
||||
logAi->error(
|
||||
"Enable to complete chain. Expected hero %s to arive to %s but he in 0 turns but he can not do this",
|
||||
hero.name,
|
||||
node.coord.toString(),
|
||||
hero->visitablePos().toString());
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Goals::VisitTile(node.coord).sethero(hero).accept(ai);
|
||||
}
|
||||
|
||||
if(node.turns == 0)
|
||||
{
|
||||
logAi->error(
|
||||
"Enable to complete chain. Expected hero %s to arive to %s but he is at %s",
|
||||
hero.name,
|
||||
node.coord.toString(),
|
||||
hero->visitablePos().toString());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// no exception means we were not able to rich the tile
|
||||
ai->nullkiller->lockHero(hero.get());
|
||||
blockedIndexes.insert(node.parentIndex);
|
||||
|
@@ -190,17 +190,6 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf
|
||||
{
|
||||
dstNode->specialAction->applyOnDestination(dstNode->actor->hero, destination, source, dstNode, srcNode);
|
||||
}
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"Commited %s -> %s, cost: %f, hero: %s, mask: %x, army: %i",
|
||||
source.coord.toString(),
|
||||
destination.coord.toString(),
|
||||
destination.cost,
|
||||
dstNode->actor->toString(),
|
||||
dstNode->actor->chainMask,
|
||||
dstNode->actor->armyValue);
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
||||
@@ -212,6 +201,11 @@ void AINodeStorage::commit(
|
||||
int movementLeft,
|
||||
float cost) const
|
||||
{
|
||||
if(destination->actor->chainMask == 195 && turn == 0)
|
||||
{
|
||||
throw std::exception();
|
||||
}
|
||||
|
||||
destination->action = action;
|
||||
destination->cost = cost;
|
||||
destination->moveRemains = movementLeft;
|
||||
@@ -221,6 +215,19 @@ void AINodeStorage::commit(
|
||||
destination->danger = source->danger;
|
||||
destination->theNodeBefore = source->theNodeBefore;
|
||||
destination->chainOther = nullptr;
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"Commited %s -> %s, cost: %f, turn: %s, mp: %d, hero: %s, mask: %x, army: %lld",
|
||||
source->coord.toString(),
|
||||
destination->coord.toString(),
|
||||
destination->cost,
|
||||
std::to_string(destination->turns),
|
||||
destination->moveRemains,
|
||||
destination->actor->toString(),
|
||||
destination->actor->chainMask,
|
||||
destination->actor->armyValue);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
|
||||
@@ -318,7 +325,7 @@ void AINodeStorage::calculateHeroChain(
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"Thy exchange %s[%i] -> %s[%i] at %s",
|
||||
"Thy exchange %s[%x] -> %s[%x] at %s",
|
||||
node->actor->toString(),
|
||||
node->actor->chainMask,
|
||||
srcNode->actor->toString(),
|
||||
@@ -343,7 +350,7 @@ void AINodeStorage::calculateHeroChain(
|
||||
{
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"Exchange allowed %s[%i] -> %s[%i] at %s",
|
||||
"Exchange allowed %s[%x] -> %s[%x] at %s",
|
||||
other->actor->toString(),
|
||||
other->actor->chainMask,
|
||||
carrier->actor->toString(),
|
||||
@@ -423,12 +430,14 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"Chain accepted at %s %s -> %s, mask %x, cost %f, army %i",
|
||||
"Chain accepted at %s %s -> %s, mask %x, cost %f, turn: %s, mp: %d, army %i",
|
||||
exchangeNode->coord.toString(),
|
||||
other->actor->toString(),
|
||||
exchangeNode->actor->toString(),
|
||||
exchangeNode->actor->chainMask,
|
||||
exchangeNode->cost,
|
||||
std::to_string(exchangeNode->turns),
|
||||
exchangeNode->moveRemains,
|
||||
exchangeNode->actor->armyValue);
|
||||
#endif
|
||||
heroChain.push_back(exchangeNode);
|
||||
@@ -581,16 +590,110 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
|
||||
return neighbours;
|
||||
}
|
||||
|
||||
void AINodeStorage::getBestInitialNodeForTownPortal()
|
||||
struct TowmPortalFinder
|
||||
{
|
||||
const std::vector<CGPathNode *> & initialNodes;
|
||||
SecSkillLevel::SecSkillLevel townPortalSkillLevel;
|
||||
uint64_t movementNeeded;
|
||||
const ChainActor * actor;
|
||||
const CGHeroInstance * hero;
|
||||
std::vector<const CGTownInstance *> targetTowns;
|
||||
AINodeStorage * nodeStorage;
|
||||
|
||||
}
|
||||
SpellID spellID;
|
||||
const CSpell * townPortal;
|
||||
|
||||
TowmPortalFinder(
|
||||
const ChainActor * actor,
|
||||
const std::vector<CGPathNode *> & initialNodes,
|
||||
std::vector<const CGTownInstance *> targetTowns,
|
||||
AINodeStorage * nodeStorage)
|
||||
:actor(actor), initialNodes(initialNodes), hero(actor->hero),
|
||||
targetTowns(targetTowns), nodeStorage(nodeStorage)
|
||||
{
|
||||
spellID = SpellID::TOWN_PORTAL;
|
||||
townPortal = spellID.toSpell();
|
||||
|
||||
// TODO: Copy/Paste from TownPortalMechanics
|
||||
townPortalSkillLevel = SecSkillLevel::SecSkillLevel(hero->getSpellSchoolLevel(townPortal));
|
||||
movementNeeded = GameConstants::BASE_MOVEMENT_COST * (townPortalSkillLevel >= SecSkillLevel::EXPERT ? 2 : 3);
|
||||
}
|
||||
|
||||
bool actorCanCastTownPortal()
|
||||
{
|
||||
return hero->canCastThisSpell(townPortal) && hero->mana >= hero->getSpellCost(townPortal);
|
||||
}
|
||||
|
||||
CGPathNode * getBestInitialNodeForTownPortal(const CGTownInstance * targetTown)
|
||||
{
|
||||
CGPathNode * bestNode = nullptr;
|
||||
|
||||
for(CGPathNode * node : initialNodes)
|
||||
{
|
||||
auto aiNode = nodeStorage->getAINode(node);
|
||||
|
||||
if(aiNode->actor->baseActor != actor
|
||||
|| node->layer != EPathfindingLayer::LAND
|
||||
|| node->moveRemains < movementNeeded)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(townPortalSkillLevel < SecSkillLevel::ADVANCED)
|
||||
{
|
||||
const CGTownInstance * nearestTown = *vstd::minElementByFun(targetTowns, [&](const CGTownInstance * t) -> int
|
||||
{
|
||||
return node->coord.dist2dSQ(t->visitablePos());
|
||||
});
|
||||
|
||||
if(targetTown != nearestTown)
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!bestNode || bestNode->cost > node->cost)
|
||||
bestNode = node;
|
||||
}
|
||||
|
||||
return bestNode;
|
||||
}
|
||||
|
||||
boost::optional<AIPathNode *> createTownPortalNode(const CGTownInstance * targetTown)
|
||||
{
|
||||
auto bestNode = getBestInitialNodeForTownPortal(targetTown);
|
||||
|
||||
if(!bestNode)
|
||||
return boost::none;
|
||||
|
||||
auto nodeOptional = nodeStorage->getOrCreateNode(targetTown->visitablePos(), EPathfindingLayer::LAND, actor->castActor);
|
||||
|
||||
if(!nodeOptional)
|
||||
return boost::none;
|
||||
|
||||
AIPathNode * node = nodeOptional.get();
|
||||
float movementCost = (float)movementNeeded / (float)hero->maxMovePoints(EPathfindingLayer::LAND);
|
||||
|
||||
movementCost += bestNode->cost;
|
||||
|
||||
if(node->action == CGPathNode::UNKNOWN || node->cost > movementCost)
|
||||
{
|
||||
nodeStorage->commit(
|
||||
node,
|
||||
nodeStorage->getAINode(bestNode),
|
||||
CGPathNode::TELEPORT_NORMAL,
|
||||
bestNode->turns,
|
||||
bestNode->moveRemains - movementNeeded,
|
||||
movementCost);
|
||||
|
||||
node->theNodeBefore = bestNode;
|
||||
node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown));
|
||||
}
|
||||
|
||||
return nodeOptional;
|
||||
}
|
||||
};
|
||||
|
||||
void AINodeStorage::calculateTownPortalTeleportations(std::vector<CGPathNode *> & initialNodes)
|
||||
{
|
||||
SpellID spellID = SpellID::TOWN_PORTAL;
|
||||
const CSpell * townPortal = spellID.toSpell();
|
||||
|
||||
std::set<const ChainActor *> actorsOfInitial;
|
||||
|
||||
for(const CGPathNode * node : initialNodes)
|
||||
@@ -605,92 +708,36 @@ void AINodeStorage::calculateTownPortalTeleportations(std::vector<CGPathNode *>
|
||||
if(!actor->hero)
|
||||
continue;
|
||||
|
||||
auto hero = actor->hero;
|
||||
auto towns = cb->getTownsInfo(false);
|
||||
|
||||
if(hero->canCastThisSpell(townPortal) && hero->mana >= hero->getSpellCost(townPortal))
|
||||
vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool
|
||||
{
|
||||
auto towns = cb->getTownsInfo(false);
|
||||
return cb->getPlayerRelations(actor->hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES;
|
||||
});
|
||||
|
||||
vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool
|
||||
{
|
||||
return cb->getPlayerRelations(hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES;
|
||||
});
|
||||
if(!towns.size())
|
||||
{
|
||||
return; // no towns no need to run loop further
|
||||
}
|
||||
|
||||
if(!towns.size())
|
||||
{
|
||||
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);
|
||||
TowmPortalFinder townPortalFinder(actor, initialNodes, towns, this);
|
||||
|
||||
if(townPortalFinder.actorCanCastTownPortal())
|
||||
{
|
||||
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)
|
||||
if(targetTown->visitingHero && targetTown->visitingHero != actor->hero)
|
||||
continue;
|
||||
|
||||
auto nodeOptional = getOrCreateNode(targetTown->visitablePos(), EPathfindingLayer::LAND, actor->castActor);
|
||||
auto nodeOptional = townPortalFinder.createTownPortalNode(targetTown);
|
||||
|
||||
if(nodeOptional)
|
||||
{
|
||||
float movementCost = (float)movementNeeded / (float)hero->maxMovePoints(EPathfindingLayer::LAND);
|
||||
|
||||
movementCost += bestNode->cost;
|
||||
|
||||
#ifdef AI_TRACE_LEVEL >= 1
|
||||
#if AI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Adding town portal node at %s", targetTown->name);
|
||||
#endif
|
||||
|
||||
AIPathNode * node = nodeOptional.get();
|
||||
|
||||
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);
|
||||
initialNodes.push_back(nodeOptional.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -726,20 +773,21 @@ bool AINodeStorage::hasBetterChain(
|
||||
{
|
||||
if(node.cost < candidateNode->cost)
|
||||
{
|
||||
#ifdef AI_TRACE_LEVEL >= 1
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"Block ineficient move %s:->%s, mask=%i, mp diff: %i",
|
||||
"Block ineficient battle move %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
||||
source->coord.toString(),
|
||||
candidateNode->coord.toString(),
|
||||
candidateNode->actor->hero->name,
|
||||
candidateNode->actor->chainMask,
|
||||
candidateNode->actor->armyValue,
|
||||
node.moveRemains - candidateNode->moveRemains);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(candidateActor->actorExchangeCount == 1
|
||||
&& (candidateActor->chainMask & node.actor->chainMask) == 0)
|
||||
if((candidateActor->chainMask & node.actor->chainMask) == 0)
|
||||
continue;
|
||||
|
||||
auto nodeActor = node.actor;
|
||||
@@ -749,15 +797,42 @@ bool AINodeStorage::hasBetterChain(
|
||||
if(nodeArmyValue > candidateArmyValue
|
||||
&& node.cost <= candidateNode->cost)
|
||||
{
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"Block ineficient move because of stronger army %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
||||
source->coord.toString(),
|
||||
candidateNode->coord.toString(),
|
||||
candidateNode->actor->hero->name,
|
||||
candidateNode->actor->chainMask,
|
||||
candidateNode->actor->armyValue,
|
||||
node.moveRemains - candidateNode->moveRemains);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
if(nodeArmyValue == candidateArmyValue
|
||||
/*if(nodeArmyValue == candidateArmyValue
|
||||
&& nodeActor->heroFightingStrength >= candidateActor->heroFightingStrength
|
||||
&& node.cost <= candidateNode->cost)
|
||||
{
|
||||
if(nodeActor->heroFightingStrength == candidateActor->heroFightingStrength
|
||||
&& node.cost == candidateNode->cost
|
||||
&& &node < candidateNode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"Block ineficient move because of stronger hero %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
||||
source->coord.toString(),
|
||||
candidateNode->coord.toString(),
|
||||
candidateNode->actor->hero->name,
|
||||
candidateNode->actor->chainMask,
|
||||
candidateNode->actor->armyValue,
|
||||
node.moveRemains - candidateNode->moveRemains);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -821,11 +896,12 @@ void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path, int pa
|
||||
if(node->chainOther)
|
||||
fillChainInfo(node->chainOther, path, parentIndex);
|
||||
|
||||
if(node->actor->hero->visitablePos() != node->coord)
|
||||
//if(node->actor->hero->visitablePos() != node->coord)
|
||||
{
|
||||
AIPathNodeInfo pathNode;
|
||||
pathNode.cost = node->cost;
|
||||
pathNode.targetHero = node->actor->hero;
|
||||
pathNode.chainMask = node->actor->chainMask;
|
||||
pathNode.specialAction = node->specialAction;
|
||||
pathNode.turns = node->turns;
|
||||
pathNode.danger = node->danger;
|
||||
@@ -862,7 +938,7 @@ int3 AIPath::targetTile() const
|
||||
{
|
||||
if(nodes.size())
|
||||
{
|
||||
return nodes.front().coord;
|
||||
return targetNode().coord;
|
||||
}
|
||||
|
||||
return int3(-1, -1, -1);
|
||||
@@ -873,36 +949,35 @@ const AIPathNodeInfo & AIPath::firstNode() const
|
||||
return nodes.back();
|
||||
}
|
||||
|
||||
const AIPathNodeInfo & AIPath::targetNode() const
|
||||
{
|
||||
auto & node = nodes.front();
|
||||
|
||||
return targetHero == node.targetHero ? node : nodes.at(1);
|
||||
}
|
||||
|
||||
uint64_t AIPath::getPathDanger() const
|
||||
{
|
||||
if(nodes.size())
|
||||
{
|
||||
return nodes.front().danger;
|
||||
}
|
||||
if(nodes.empty())
|
||||
return 0;
|
||||
|
||||
return 0;
|
||||
return targetNode().danger;
|
||||
}
|
||||
|
||||
float AIPath::movementCost() const
|
||||
{
|
||||
if(nodes.size())
|
||||
{
|
||||
return nodes.front().cost;
|
||||
}
|
||||
if(nodes.empty())
|
||||
return 0.0f;
|
||||
|
||||
// TODO: boost:optional?
|
||||
return 0.0;
|
||||
return targetNode().cost;
|
||||
}
|
||||
|
||||
uint8_t AIPath::turn() const
|
||||
{
|
||||
if(nodes.size())
|
||||
{
|
||||
return nodes.front().turns;
|
||||
}
|
||||
if(nodes.empty())
|
||||
return 0;
|
||||
|
||||
// TODO: boost:optional?
|
||||
return 0;
|
||||
return targetNode().turns;
|
||||
}
|
||||
|
||||
uint64_t AIPath::getHeroStrength() const
|
||||
@@ -921,9 +996,11 @@ uint64_t AIPath::getTotalDanger(HeroPtr hero) const
|
||||
std::string AIPath::toString()
|
||||
{
|
||||
std::stringstream str;
|
||||
|
||||
|
||||
str << targetHero->name << "[" << std::hex << chainMask << std::dec << "]" << ": ";
|
||||
|
||||
for(auto node : nodes)
|
||||
str << node.targetHero->name << "->" << node.coord.toString() << "; ";
|
||||
str << node.targetHero->name << "[" << std::hex << node.chainMask << std::dec << "]" << "->" << node.coord.toString() << "; ";
|
||||
|
||||
return str.str();
|
||||
}
|
@@ -10,8 +10,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define VCMI_TRACE_PATHFINDER 2
|
||||
#define AI_TRACE_LEVEL 2
|
||||
#define VCMI_TRACE_PATHFINDER 1
|
||||
#define AI_TRACE_LEVEL 1
|
||||
|
||||
#include "../../../lib/CPathfinder.h"
|
||||
#include "../../../lib/mapObjects/CGHeroInstance.h"
|
||||
@@ -39,6 +39,7 @@ struct AIPathNodeInfo
|
||||
uint64_t danger;
|
||||
const CGHeroInstance * targetHero;
|
||||
int parentIndex;
|
||||
uint64_t chainMask;
|
||||
std::shared_ptr<const ISpecialAction> specialAction;
|
||||
};
|
||||
|
||||
@@ -66,6 +67,8 @@ struct AIPath
|
||||
|
||||
const AIPathNodeInfo & firstNode() const;
|
||||
|
||||
const AIPathNodeInfo & targetNode() const;
|
||||
|
||||
float movementCost() const;
|
||||
|
||||
uint8_t turn() const;
|
||||
@@ -99,7 +102,7 @@ private:
|
||||
|
||||
public:
|
||||
/// more than 1 chain layer for each hero allows us to have more than 1 path to each tile so we can chose more optimal one.
|
||||
static const int NUM_CHAINS = 5 * GameConstants::MAX_HEROES_PER_PLAYER;
|
||||
static const int NUM_CHAINS = 10 * GameConstants::MAX_HEROES_PER_PLAYER;
|
||||
|
||||
AINodeStorage(const int3 & sizes);
|
||||
~AINodeStorage();
|
||||
@@ -120,11 +123,28 @@ public:
|
||||
|
||||
virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override;
|
||||
|
||||
void commit(
|
||||
AIPathNode * destination,
|
||||
const AIPathNode * source,
|
||||
CGPathNode::ENodeAction action,
|
||||
int turn,
|
||||
int movementLeft,
|
||||
float cost) const;
|
||||
|
||||
const AIPathNode * getAINode(const CGPathNode * node) const;
|
||||
void updateAINode(CGPathNode * node, std::function<void (AIPathNode *)> updater);
|
||||
|
||||
bool hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const;
|
||||
|
||||
bool isMovementIneficient(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
|
||||
{
|
||||
// further chain distribution is calculated as the last stage
|
||||
if(heroChainPass && destination.node->turns > heroChainTurn)
|
||||
return true;
|
||||
|
||||
return hasBetterChain(source, destination);
|
||||
}
|
||||
|
||||
template<class NodeRange>
|
||||
bool hasBetterChain(
|
||||
const CGPathNode * source,
|
||||
@@ -167,13 +187,6 @@ private:
|
||||
|
||||
void calculateTownPortalTeleportations(std::vector<CGPathNode *> & neighbours);
|
||||
void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const;
|
||||
void commit(
|
||||
AIPathNode * destination,
|
||||
const AIPathNode * source,
|
||||
CGPathNode::ENodeAction action,
|
||||
int turn,
|
||||
int movementLeft,
|
||||
float cost) const;
|
||||
|
||||
ExchangeCandidate calculateExchange(
|
||||
ChainActor * exchangeActor,
|
||||
|
@@ -45,7 +45,7 @@ namespace AIPathfinding
|
||||
const PathfinderConfig * pathfinderConfig,
|
||||
CPathfinderHelper * pathfinderHelper) const
|
||||
{
|
||||
if(nodeStorage->hasBetterChain(source, destination))
|
||||
if(nodeStorage->isMovementIneficient(source, destination))
|
||||
{
|
||||
destination.blocked = true;
|
||||
|
||||
|
@@ -32,7 +32,7 @@ extern FuzzyHelper * fh;
|
||||
|
||||
class CGVisitableOPW;
|
||||
|
||||
const double SAFE_ATTACK_CONSTANT = 1.5;
|
||||
const float SAFE_ATTACK_CONSTANT = 1.5;
|
||||
|
||||
//one thread may be turn of AI and another will be handling a side effect for AI2
|
||||
boost::thread_specific_ptr<CCallback> cb;
|
||||
@@ -1064,9 +1064,13 @@ void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
|
||||
checkHeroArmy(h);
|
||||
break;
|
||||
case Obj::TOWN:
|
||||
moveCreaturesToHero(dynamic_cast<const CGTownInstance *>(obj));
|
||||
if(h->visitedTown) //we are inside, not just attacking
|
||||
{
|
||||
makePossibleUpgrades(h.get());
|
||||
|
||||
if(!h->visitedTown->garrisonHero)
|
||||
moveCreaturesToHero(h->visitedTown);
|
||||
|
||||
townVisitsThisWeek[h].insert(h->visitedTown);
|
||||
ah->updateHeroRoles();
|
||||
if(ah->getHeroRole(h) == HeroRole::MAIN && !h->hasSpellbook() && ah->freeGold() >= GameConstants::SPELLBOOK_GOLD_COST)
|
||||
|
Reference in New Issue
Block a user