diff --git a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp index 3651e567f..aa2f91e59 100644 --- a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp @@ -72,7 +72,13 @@ void DangerHitMapAnalyzer::updateHitMap() if(ai->cb->getPlayerRelations(ai->playerID, pair.first) != PlayerRelations::ENEMIES) continue; - ai->pathfinder->updatePaths(pair.second, PathfinderSettings()); + PathfinderSettings ps; + + ps.mainTurnDistanceLimit = 10; + ps.scoutTurnDistanceLimit = 10; + ps.useHeroChain = false; + + ai->pathfinder->updatePaths(pair.second, ps); boost::this_thread::interruption_point(); diff --git a/AI/Nullkiller/CMakeLists.txt b/AI/Nullkiller/CMakeLists.txt index a6560989f..a22641684 100644 --- a/AI/Nullkiller/CMakeLists.txt +++ b/AI/Nullkiller/CMakeLists.txt @@ -9,6 +9,7 @@ set(Nullkiller_SRCS Pathfinding/Actions/BuyArmyAction.cpp Pathfinding/Actions/BoatActions.cpp Pathfinding/Actions/TownPortalAction.cpp + Pathfinding/Actions/AdventureSpellCastMovementActions.cpp Pathfinding/Rules/AILayerTransitionRule.cpp Pathfinding/Rules/AIMovementAfterDestinationRule.cpp Pathfinding/Rules/AIMovementToDestinationRule.cpp @@ -69,6 +70,7 @@ set(Nullkiller_HEADERS Pathfinding/Actions/BuyArmyAction.h Pathfinding/Actions/BoatActions.h Pathfinding/Actions/TownPortalAction.h + Pathfinding/Actions/AdventureSpellCastMovementActions.h Pathfinding/Rules/AILayerTransitionRule.h Pathfinding/Rules/AIMovementAfterDestinationRule.h Pathfinding/Rules/AIMovementToDestinationRule.h diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index c127f294b..71464509d 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -45,7 +45,7 @@ struct AIPathNode : public CGPathNode { uint64_t danger; uint64_t armyLoss; - uint32_t manaCost; + int32_t manaCost; const AIPathNode * chainOther; std::shared_ptr specialAction; const ChainActor * actor; diff --git a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp index b7314b3d1..2259ef029 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp +++ b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp @@ -44,6 +44,7 @@ namespace AIPathfinding std::shared_ptr nodeStorage) :PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage)), aiNodeStorage(nodeStorage) { + options.canUseCast = true; } AIPathfinderConfig::~AIPathfinderConfig() = default; diff --git a/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.cpp b/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.cpp new file mode 100644 index 000000000..ff4934b36 --- /dev/null +++ b/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.cpp @@ -0,0 +1,82 @@ +/* +* AdventureSpellCastMovementActions.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ + +#include "StdInc.h" +#include "../../AIGateway.h" +#include "../../Goals/AdventureSpellCast.h" +#include "../../Goals/CaptureObject.h" +#include "../../Goals/Invalid.h" +#include "../../Goals/BuildBoat.h" +#include "../../../../lib/mapObjects/MapObjects.h" +#include "AdventureSpellCastMovementActions.h" + +namespace NKAI +{ + +namespace AIPathfinding +{ + AdventureCastAction::AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero) + :spellToCast(spellToCast), hero(hero) + { + manaCost = hero->getSpellCost(spellToCast.toSpell()); + } + + WaterWalkingAction::WaterWalkingAction(const CGHeroInstance * hero) + :AdventureCastAction(SpellID::WATER_WALK, hero) + { } + + AirWalkingAction::AirWalkingAction(const CGHeroInstance * hero) + : AdventureCastAction(SpellID::FLY, hero) + { + } + + void AdventureCastAction::applyOnDestination( + const CGHeroInstance * hero, + CDestinationNodeInfo & destination, + const PathNodeInfo & source, + AIPathNode * dstMode, + const AIPathNode * srcNode) const + { + dstMode->manaCost = srcNode->manaCost + manaCost; + dstMode->theNodeBefore = source.node; + } + + void AdventureCastAction::execute(const CGHeroInstance * hero) const + { + assert(hero == this->hero); + + Goals::AdventureSpellCast(hero, spellToCast).accept(ai); + } + + bool AdventureCastAction::canAct(const AIPathNode * source) const + { + assert(hero == this->hero); + + auto hero = source->actor->hero; + +#ifdef VCMI_TRACE_PATHFINDER + logAi->trace( + "Hero %s has %d mana and needed %d and already spent %d", + hero->name, + hero->mana, + getManaCost(hero), + source->manaCost); +#endif + + return hero->mana >= source->manaCost + manaCost; + } + + std::string AdventureCastAction::toString() const + { + return "Cast " + spellToCast.toSpell()->getNameTranslated() + " by " + hero->getNameTranslated(); + } +} + +} diff --git a/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.h b/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.h new file mode 100644 index 000000000..0667e400a --- /dev/null +++ b/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.h @@ -0,0 +1,58 @@ +/* +* AdventureSpellCastMovementActions.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ + +#pragma once + +#include "SpecialAction.h" +#include "../../../../lib/mapObjects/MapObjects.h" + +namespace NKAI +{ + +namespace AIPathfinding +{ + class AdventureCastAction : public SpecialAction + { + private: + SpellID spellToCast; + const CGHeroInstance * hero; + int manaCost; + + public: + AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero); + + virtual void execute(const CGHeroInstance * hero) const override; + + virtual void applyOnDestination( + const CGHeroInstance * hero, + CDestinationNodeInfo & destination, + const PathNodeInfo & source, + AIPathNode * dstMode, + const AIPathNode * srcNode) const override; + + virtual bool canAct(const AIPathNode * source) const override; + + virtual std::string toString() const override; + }; + + class WaterWalkingAction : public AdventureCastAction + { + public: + WaterWalkingAction(const CGHeroInstance * hero); + }; + + class AirWalkingAction : public AdventureCastAction + { + public: + AirWalkingAction(const CGHeroInstance * hero); + }; +} + +} diff --git a/AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp b/AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp index bbd1e5297..7a6ab3f10 100644 --- a/AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp +++ b/AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp @@ -114,7 +114,7 @@ namespace AIPathfinding source->manaCost); #endif - return hero->mana >= (si32)(source->manaCost + getManaCost(hero)); + return hero->mana >= source->manaCost + getManaCost(hero); } std::string SummonBoatAction::toString() const @@ -122,7 +122,7 @@ namespace AIPathfinding return "Summon Boat"; } - uint32_t SummonBoatAction::getManaCost(const CGHeroInstance * hero) const + int32_t SummonBoatAction::getManaCost(const CGHeroInstance * hero) const { SpellID summonBoat = SpellID::SUMMON_BOAT; diff --git a/AI/Nullkiller/Pathfinding/Actions/BoatActions.h b/AI/Nullkiller/Pathfinding/Actions/BoatActions.h index 92249eb78..5e6ca50d4 100644 --- a/AI/Nullkiller/Pathfinding/Actions/BoatActions.h +++ b/AI/Nullkiller/Pathfinding/Actions/BoatActions.h @@ -20,8 +20,6 @@ namespace AIPathfinding { class VirtualBoatAction : public SpecialAction { - public: - virtual const ChainActor * getActor(const ChainActor * sourceActor) const = 0; }; class SummonBoatAction : public VirtualBoatAction @@ -43,7 +41,7 @@ namespace AIPathfinding virtual std::string toString() const override; private: - uint32_t getManaCost(const CGHeroInstance * hero) const; + int32_t getManaCost(const CGHeroInstance * hero) const; }; class BuildBoatAction : public VirtualBoatAction diff --git a/AI/Nullkiller/Pathfinding/Actions/SpecialAction.h b/AI/Nullkiller/Pathfinding/Actions/SpecialAction.h index c14589e75..77270ce1a 100644 --- a/AI/Nullkiller/Pathfinding/Actions/SpecialAction.h +++ b/AI/Nullkiller/Pathfinding/Actions/SpecialAction.h @@ -22,6 +22,7 @@ namespace NKAI { struct AIPathNode; +class ChainActor; class SpecialAction { @@ -54,6 +55,11 @@ public: { return {}; } + + virtual const ChainActor * getActor(const ChainActor * sourceActor) const + { + return sourceActor; + } }; class CompositeAction : public SpecialAction diff --git a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp index f555b18c3..7dbac9010 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp @@ -10,6 +10,8 @@ #include "StdInc.h" #include "AILayerTransitionRule.h" #include "../../Engine/Nullkiller.h" +#include "../../../../lib/pathfinder/CPathfinder.h" +#include "../../../../lib/pathfinder/TurnInfo.h" namespace NKAI { @@ -31,23 +33,79 @@ namespace AIPathfinding if(!destination.blocked) { - return; + if(source.node->layer == EPathfindingLayer::LAND + && (destination.node->layer == EPathfindingLayer::AIR || destination.node->layer == EPathfindingLayer::WATER)) + { + if(pathfinderHelper->getTurnInfo()->isLayerAvailable(destination.node->layer)) + return; + else + destination.blocked = true; + } + else + { + return; + } } if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::SAIL) { std::shared_ptr virtualBoat = findVirtualBoat(destination, source); - if(virtualBoat && tryEmbarkVirtualBoat(destination, source, virtualBoat)) + if(virtualBoat && tryUseSpecialAction(destination, source, virtualBoat, EPathNodeAction::EMBARK)) { #if NKAI_PATHFINDER_TRACE_LEVEL >= 1 logAi->trace("Embarking to virtual boat while moving %s -> %s!", source.coord.toString(), destination.coord.toString()); +#endif + } + } + + if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::WATER) + { + auto action = waterWalkingActions.find(nodeStorage->getHero(source.node)); + + if(action != waterWalkingActions.end() && tryUseSpecialAction(destination, source, action->second, EPathNodeAction::NORMAL)) + { +#if NKAI_PATHFINDER_TRACE_LEVEL >= 2 + logAi->trace("Casting water walk while moving %s -> %s!", source.coord.toString(), destination.coord.toString()); +#endif + } + } + + if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::AIR) + { + auto action = airWalkingActions.find(nodeStorage->getHero(source.node)); + + if(action != airWalkingActions.end() && tryUseSpecialAction(destination, source, action->second, EPathNodeAction::NORMAL)) + { +#if NKAI_PATHFINDER_TRACE_LEVEL >= 2 + logAi->trace("Casting fly while moving %s -> %s!", source.coord.toString(), destination.coord.toString()); #endif } } } void AILayerTransitionRule::setup() + { + SpellID waterWalk = SpellID::WATER_WALK; + SpellID airWalk = SpellID::FLY; + + for(const CGHeroInstance * hero : nodeStorage->getAllHeroes()) + { + if(hero->canCastThisSpell(waterWalk.toSpell())) + { + waterWalkingActions[hero] = std::make_shared(hero); + } + + if(hero->canCastThisSpell(airWalk.toSpell())) + { + airWalkingActions[hero] = std::make_shared(hero); + } + } + + collectVirtualBoats(); + } + + void AILayerTransitionRule::collectVirtualBoats() { std::vector shipyards; @@ -113,50 +171,56 @@ namespace AIPathfinding return virtualBoat; } - bool AILayerTransitionRule::tryEmbarkVirtualBoat( + bool AILayerTransitionRule::tryUseSpecialAction( CDestinationNodeInfo & destination, const PathNodeInfo & source, - std::shared_ptr virtualBoat) const + std::shared_ptr specialAction, + EPathNodeAction targetAction) const { bool result = false; - nodeStorage->updateAINode(destination.node, [&](AIPathNode * node) + if(!specialAction->canAct(nodeStorage->getAINode(source.node))) { - auto boatNodeOptional = nodeStorage->getOrCreateNode( - node->coord, - node->layer, - virtualBoat->getActor(node->actor)); + return false; + } - if(boatNodeOptional) + nodeStorage->updateAINode(destination.node, [&](AIPathNode * node) { - AIPathNode * boatNode = boatNodeOptional.value(); + auto castNodeOptional = nodeStorage->getOrCreateNode( + node->coord, + node->layer, + specialAction->getActor(node->actor)); - if(boatNode->action == EPathNodeAction::UNKNOWN) + if(castNodeOptional) { - boatNode->addSpecialAction(virtualBoat); - destination.blocked = false; - destination.action = EPathNodeAction::EMBARK; - destination.node = boatNode; - result = true; + AIPathNode * castNode = castNodeOptional.value(); + + if(castNode->action == EPathNodeAction::UNKNOWN) + { + castNode->addSpecialAction(specialAction); + destination.blocked = false; + destination.action = targetAction; + destination.node = castNode; + result = true; + } + else + { +#if NKAI_PATHFINDER_TRACE_LEVEL >= 1 + logAi->trace( + "Special transition node already allocated. Blocked moving %s -> %s", + source.coord.toString(), + destination.coord.toString()); +#endif + } } else { -#if NKAI_PATHFINDER_TRACE_LEVEL >= 1 - logAi->trace( - "Special transition node already allocated. Blocked moving %s -> %s", + logAi->debug( + "Can not allocate special transition node while moving %s -> %s", source.coord.toString(), destination.coord.toString()); -#endif } - } - else - { - logAi->debug( - "Can not allocate special transition node while moving %s -> %s", - source.coord.toString(), - destination.coord.toString()); - } - }); + }); return result; } diff --git a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.h b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.h index f7d5e27b8..243cb96a9 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.h +++ b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.h @@ -13,6 +13,7 @@ #include "../AINodeStorage.h" #include "../../AIGateway.h" #include "../Actions/BoatActions.h" +#include "../Actions/AdventureSpellCastMovementActions.h" #include "../../../../CCallback.h" #include "../../../../lib/mapObjects/MapObjects.h" #include "../../../../lib/pathfinder/PathfindingRules.h" @@ -29,6 +30,8 @@ namespace AIPathfinding std::map> virtualBoats; std::shared_ptr nodeStorage; std::map> summonableVirtualBoats; + std::map> waterWalkingActions; + std::map> airWalkingActions; public: AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, Nullkiller * ai, std::shared_ptr nodeStorage); @@ -41,15 +44,17 @@ namespace AIPathfinding private: void setup(); + void collectVirtualBoats(); std::shared_ptr findVirtualBoat( CDestinationNodeInfo & destination, const PathNodeInfo & source) const; - bool tryEmbarkVirtualBoat( + bool tryUseSpecialAction( CDestinationNodeInfo & destination, const PathNodeInfo & source, - std::shared_ptr virtualBoat) const; + std::shared_ptr specialAction, + EPathNodeAction targetAction) const; }; } diff --git a/lib/pathfinder/CPathfinder.cpp b/lib/pathfinder/CPathfinder.cpp index 1cb3b7163..8044535fb 100644 --- a/lib/pathfinder/CPathfinder.cpp +++ b/lib/pathfinder/CPathfinder.cpp @@ -20,6 +20,7 @@ #include "../TerrainHandler.h" #include "../mapObjects/CGHeroInstance.h" #include "../mapping/CMap.h" +#include "spells/CSpellHandler.h" VCMI_LIB_NAMESPACE_BEGIN @@ -472,6 +473,12 @@ CPathfinderHelper::CPathfinderHelper(CGameState * gs, const CGHeroInstance * Her turnsInfo.reserve(16); updateTurnInfo(); initializePatrol(); + + SpellID flySpell = SpellID::FLY; + canCastFly = Hero->canCastThisSpell(flySpell.toSpell()); + + SpellID waterWalk = SpellID::WATER_WALK; + canCastWaterWalk = Hero->canCastThisSpell(waterWalk.toSpell()); } CPathfinderHelper::~CPathfinderHelper() @@ -501,12 +508,18 @@ bool CPathfinderHelper::isLayerAvailable(const EPathfindingLayer & layer) const if(!options.useFlying) return false; + if(canCastFly && options.canUseCast) + return true; + break; case EPathfindingLayer::WATER: if(!options.useWaterWalking) return false; + if(canCastWaterWalk && options.canUseCast) + return true; + break; } diff --git a/lib/pathfinder/CPathfinder.h b/lib/pathfinder/CPathfinder.h index 0071f41eb..a52de7b65 100644 --- a/lib/pathfinder/CPathfinder.h +++ b/lib/pathfinder/CPathfinder.h @@ -72,6 +72,8 @@ public: const CGHeroInstance * hero; std::vector turnsInfo; const PathfinderOptions & options; + bool canCastFly; + bool canCastWaterWalk; CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options); virtual ~CPathfinderHelper(); diff --git a/lib/pathfinder/PathfinderOptions.cpp b/lib/pathfinder/PathfinderOptions.cpp index 42af2f53b..4c83acafc 100644 --- a/lib/pathfinder/PathfinderOptions.cpp +++ b/lib/pathfinder/PathfinderOptions.cpp @@ -31,6 +31,7 @@ PathfinderOptions::PathfinderOptions() , oneTurnSpecialLayersLimit(true) , originalMovementRules(false) , turnLimit(std::numeric_limits::max()) + , canUseCast(false) { } diff --git a/lib/pathfinder/PathfinderOptions.h b/lib/pathfinder/PathfinderOptions.h index f26cf6273..96d75cb2a 100644 --- a/lib/pathfinder/PathfinderOptions.h +++ b/lib/pathfinder/PathfinderOptions.h @@ -71,6 +71,11 @@ struct DLL_LINKAGE PathfinderOptions /// Max number of turns to compute. Default = infinite uint8_t turnLimit; + /// + /// For AI. Allows water walk and fly layers if hero can cast appropriate spells + /// + bool canUseCast; + PathfinderOptions(); };