From 84e5e6ac172e6b41f8f86db4215a4afe61e7770c Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sun, 16 May 2021 14:09:49 +0300 Subject: [PATCH] Nullkiller: rework defence a bit --- AI/Nullkiller/AIUtility.h | 2 +- AI/Nullkiller/Behaviors/DefenceBehavior.cpp | 113 ++++--- .../Behaviors/RecruitHeroBehavior.cpp | 12 +- .../Goals/ExchangeSwapTownHeroes.cpp | 29 +- AI/Nullkiller/Goals/ExecuteHeroChain.cpp | 27 ++ AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 311 +++++++++++------- AI/Nullkiller/Pathfinding/AINodeStorage.h | 33 +- .../Rules/AIMovementAfterDestinationRule.cpp | 2 +- AI/Nullkiller/VCAI.cpp | 8 +- 9 files changed, 360 insertions(+), 177 deletions(-) diff --git a/AI/Nullkiller/AIUtility.h b/AI/Nullkiller/AIUtility.h index 4d6adb061..2cebbd171 100644 --- a/AI/Nullkiller/AIUtility.h +++ b/AI/Nullkiller/AIUtility.h @@ -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 diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index e2ceb5f44..74e640369 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -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", diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index b5005ab76..b3845a400 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -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))); + } } } diff --git a/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp b/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp index c9a9c3b45..ba4d9b9c1 100644 --- a/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp +++ b/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp @@ -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); } \ No newline at end of file diff --git a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp index 2c2d9990c..e7758a9e6 100644 --- a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp +++ b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp @@ -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); diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 5d676a364..bef15dfc7 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -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 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 & 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 AINodeStorage::calculateTeleportations( return neighbours; } -void AINodeStorage::getBestInitialNodeForTownPortal() +struct TowmPortalFinder { + const std::vector & initialNodes; + SecSkillLevel::SecSkillLevel townPortalSkillLevel; + uint64_t movementNeeded; + const ChainActor * actor; + const CGHeroInstance * hero; + std::vector targetTowns; + AINodeStorage * nodeStorage; -} + SpellID spellID; + const CSpell * townPortal; + + TowmPortalFinder( + const ChainActor * actor, + const std::vector & initialNodes, + std::vector 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 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 & initialNodes) { - SpellID spellID = SpellID::TOWN_PORTAL; - const CSpell * townPortal = spellID.toSpell(); - std::set actorsOfInitial; for(const CGPathNode * node : initialNodes) @@ -605,92 +708,36 @@ void AINodeStorage::calculateTownPortalTeleportations(std::vector 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(); } \ No newline at end of file diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index 26d1bbed7..e666c55ac 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -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 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 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 bool hasBetterChain( const CGPathNode * source, @@ -167,13 +187,6 @@ private: void calculateTownPortalTeleportations(std::vector & 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, diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp index 6505bc7a4..6c45b7497 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp @@ -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; diff --git a/AI/Nullkiller/VCAI.cpp b/AI/Nullkiller/VCAI.cpp index 5077dff57..c57099d05 100644 --- a/AI/Nullkiller/VCAI.cpp +++ b/AI/Nullkiller/VCAI.cpp @@ -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 cb; @@ -1064,9 +1064,13 @@ void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h) checkHeroArmy(h); break; case Obj::TOWN: - moveCreaturesToHero(dynamic_cast(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)