diff --git a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp index b1f21308c..625739953 100644 --- a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp @@ -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; diff --git a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h index e96fa3c33..64cb6238f 100644 --- a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h +++ b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h @@ -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 & getOneTurnAccessibleObjects(const CGHeroInstance * enemy) const; void reset(); }; diff --git a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp index 54017d8bf..d699aee20 100644 --- a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp +++ b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp @@ -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 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); } } diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index 88973de0b..3ee12bab5 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -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 pathsToDefend; + std::map> 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))); - } - }*/ } \ No newline at end of file diff --git a/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp b/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp index 2c2b1f5c5..3f959fb2d 100644 --- a/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp +++ b/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp @@ -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; diff --git a/AI/Nullkiller/Behaviors/StartupBehavior.cpp b/AI/Nullkiller/Behaviors/StartupBehavior.cpp index 4f1cc608c..de2237d34 100644 --- a/AI/Nullkiller/Behaviors/StartupBehavior.cpp +++ b/AI/Nullkiller/Behaviors/StartupBehavior.cpp @@ -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); diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index b0b8eeee1..40c742709 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -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 diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index a6968999c..09b46a537 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -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> evaluationContextBuilders; diff --git a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp index cac1bf018..e8ca5b50c 100644 --- a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp +++ b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp @@ -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); diff --git a/AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp b/AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp index cff2eaf6c..ac50c48fc 100644 --- a/AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp +++ b/AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp @@ -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(); diff --git a/AI/Nullkiller/Pathfinding/Actions/BoatActions.h b/AI/Nullkiller/Pathfinding/Actions/BoatActions.h index 4a764af41..cba5a0c0b 100644 --- a/AI/Nullkiller/Pathfinding/Actions/BoatActions.h +++ b/AI/Nullkiller/Pathfinding/Actions/BoatActions.h @@ -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; }; } \ No newline at end of file diff --git a/AI/Nullkiller/Pathfinding/Actions/SpecialAction.h b/AI/Nullkiller/Pathfinding/Actions/SpecialAction.h index 0cd4027d6..6d7cbbbac 100644 --- a/AI/Nullkiller/Pathfinding/Actions/SpecialAction.h +++ b/AI/Nullkiller/Pathfinding/Actions/SpecialAction.h @@ -37,4 +37,6 @@ public: } virtual std::string toString() const = 0; + + virtual const CGObjectInstance * targetObject() const { return nullptr; } }; \ No newline at end of file