1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-28 08:48:48 +02:00

NKAI: water and air walking

This commit is contained in:
Andrii Danylchenko 2023-09-16 12:33:02 +03:00
parent f54a3c4e6c
commit 2dd0d76412
15 changed files with 282 additions and 39 deletions

View File

@ -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();

View File

@ -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

View File

@ -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<const SpecialAction> specialAction;
const ChainActor * actor;

View File

@ -44,6 +44,7 @@ namespace AIPathfinding
std::shared_ptr<AINodeStorage> nodeStorage)
:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage)), aiNodeStorage(nodeStorage)
{
options.canUseCast = true;
}
AIPathfinderConfig::~AIPathfinderConfig() = default;

View File

@ -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();
}
}
}

View File

@ -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);
};
}
}

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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<const VirtualBoatAction> 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<WaterWalkingAction>(hero);
}
if(hero->canCastThisSpell(airWalk.toSpell()))
{
airWalkingActions[hero] = std::make_shared<AirWalkingAction>(hero);
}
}
collectVirtualBoats();
}
void AILayerTransitionRule::collectVirtualBoats()
{
std::vector<const IShipyard *> shipyards;
@ -113,50 +171,56 @@ namespace AIPathfinding
return virtualBoat;
}
bool AILayerTransitionRule::tryEmbarkVirtualBoat(
bool AILayerTransitionRule::tryUseSpecialAction(
CDestinationNodeInfo & destination,
const PathNodeInfo & source,
std::shared_ptr<const VirtualBoatAction> virtualBoat) const
std::shared_ptr<const SpecialAction> 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;
}

View File

@ -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<int3, std::shared_ptr<const BuildBoatAction>> virtualBoats;
std::shared_ptr<AINodeStorage> nodeStorage;
std::map<const CGHeroInstance *, std::shared_ptr<const SummonBoatAction>> summonableVirtualBoats;
std::map<const CGHeroInstance *, std::shared_ptr<const WaterWalkingAction>> waterWalkingActions;
std::map<const CGHeroInstance *, std::shared_ptr<const AirWalkingAction>> airWalkingActions;
public:
AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, Nullkiller * ai, std::shared_ptr<AINodeStorage> nodeStorage);
@ -41,15 +44,17 @@ namespace AIPathfinding
private:
void setup();
void collectVirtualBoats();
std::shared_ptr<const VirtualBoatAction> findVirtualBoat(
CDestinationNodeInfo & destination,
const PathNodeInfo & source) const;
bool tryEmbarkVirtualBoat(
bool tryUseSpecialAction(
CDestinationNodeInfo & destination,
const PathNodeInfo & source,
std::shared_ptr<const VirtualBoatAction> virtualBoat) const;
std::shared_ptr<const SpecialAction> specialAction,
EPathNodeAction targetAction) const;
};
}

View File

@ -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;
}

View File

@ -72,6 +72,8 @@ public:
const CGHeroInstance * hero;
std::vector<TurnInfo *> turnsInfo;
const PathfinderOptions & options;
bool canCastFly;
bool canCastWaterWalk;
CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options);
virtual ~CPathfinderHelper();

View File

@ -31,6 +31,7 @@ PathfinderOptions::PathfinderOptions()
, oneTurnSpecialLayersLimit(true)
, originalMovementRules(false)
, turnLimit(std::numeric_limits<uint8_t>::max())
, canUseCast(false)
{
}

View File

@ -71,6 +71,11 @@ struct DLL_LINKAGE PathfinderOptions
/// Max number of turns to compute. Default = infinite
uint8_t turnLimit;
/// <summary>
/// For AI. Allows water walk and fly layers if hero can cast appropriate spells
/// </summary>
bool canUseCast;
PathfinderOptions();
};