1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-06 09:09:40 +02:00

AI: CompleteQuest goal and summon boat spell support

This commit is contained in:
Andrii Danylchenko
2018-12-09 15:20:46 +02:00
committed by ArseniyShestakov
parent e996879733
commit 5d022ba77c
20 changed files with 668 additions and 243 deletions

View File

@@ -82,6 +82,7 @@ void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPat
heroNode.chainMask = 0;
heroNode.danger = 0;
heroNode.manaCost = 0;
heroNode.specialAction.reset();
heroNode.update(coord, layer, accessibility);
}
@@ -97,6 +98,12 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf
dstNode->danger = srcNode->danger;
dstNode->action = destination.action;
dstNode->theNodeBefore = srcNode->theNodeBefore;
dstNode->manaCost = srcNode->manaCost;
if(dstNode->specialAction)
{
dstNode->specialAction->applyOnDestination(getHero(), destination, source, dstNode, srcNode);
}
});
}
@@ -186,6 +193,7 @@ std::vector<AIPath> AINodeStorage::getChainInfo(int3 pos, bool isOnLand) const
{
std::vector<AIPath> paths;
auto chains = nodes[pos.x][pos.y][pos.z][isOnLand ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL];
auto initialPos = hero->visitablePos();
for(const AIPathNode & node : chains)
{
@@ -197,7 +205,7 @@ std::vector<AIPath> AINodeStorage::getChainInfo(int3 pos, bool isOnLand) const
AIPath path;
const AIPathNode * current = &node;
while(current != nullptr)
while(current != nullptr && current->coord != initialPos)
{
AIPathNodeInfo pathNode;

View File

@@ -15,16 +15,28 @@
#include "../AIUtility.h"
#include "../Goals/AbstractGoal.h"
class AIPathNode;
class ISpecialAction
{
public:
virtual Goals::TSubgoal whatToDo(HeroPtr hero) const = 0;
virtual void applyOnDestination(
HeroPtr hero,
CDestinationNodeInfo & destination,
const PathNodeInfo & source,
AIPathNode * dstMode,
const AIPathNode * srcNode) const
{
}
};
struct AIPathNode : public CGPathNode
{
uint32_t chainMask;
uint64_t danger;
uint32_t manaCost;
std::shared_ptr<const ISpecialAction> specialAction;
};

View File

@@ -48,10 +48,10 @@ std::vector<AIPath> AIPathfinder::getPathInfo(HeroPtr hero, int3 tile)
}
storageMap[hero] = nodeStorage;
nodeStorage->setHero(hero.get());
auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, nodeStorage);
nodeStorage->setHero(hero.get());
cb->calculatePaths(config, hero.get());
}
else

View File

@@ -16,14 +16,31 @@
namespace AIPathfinding
{
class BuildBoatAction : public ISpecialAction
class VirtualBoatAction : public ISpecialAction
{
private:
uint64_t specialChain;
public:
VirtualBoatAction(uint64_t specialChain)
:specialChain(specialChain)
{
}
uint64_t getSpecialChain() const
{
return specialChain;
}
};
class BuildBoatAction : public VirtualBoatAction
{
private:
const IShipyard * shipyard;
public:
BuildBoatAction(const IShipyard * shipyard)
:shipyard(shipyard)
:VirtualBoatAction(AINodeStorage::RESOURCE_CHAIN), shipyard(shipyard)
{
}
@@ -33,6 +50,51 @@ namespace AIPathfinding
}
};
class SummonBoatAction : public VirtualBoatAction
{
public:
SummonBoatAction()
:VirtualBoatAction(AINodeStorage::CAST_CHAIN)
{
}
virtual Goals::TSubgoal whatToDo(HeroPtr hero) const override
{
return sptr(Goals::AdventureSpellCast(hero, SpellID::SUMMON_BOAT));
}
virtual void applyOnDestination(
HeroPtr hero,
CDestinationNodeInfo & destination,
const PathNodeInfo & source,
AIPathNode * dstMode,
const AIPathNode * srcNode) const override
{
dstMode->manaCost = srcNode->manaCost + getManaCost(hero);
dstMode->theNodeBefore = source.node;
}
bool isAffordableBy(HeroPtr hero, const AIPathNode * source) const
{
logAi->trace(
"Hero %s has %d mana and needed %d and already spent %d",
hero->name,
hero->mana,
getManaCost(hero),
source->manaCost);
return hero->mana >= source->manaCost + getManaCost(hero);
}
private:
uint32_t getManaCost(HeroPtr hero) const
{
SpellID summonBoat = SpellID::SUMMON_BOAT;
return hero->getSpellCost(summonBoat.toSpell());
}
};
class BattleAction : public ISpecialAction
{
private:
@@ -58,6 +120,7 @@ namespace AIPathfinding
VCAI * ai;
std::map<int3, std::shared_ptr<const BuildBoatAction>> virtualBoats;
std::shared_ptr<AINodeStorage> nodeStorage;
std::shared_ptr<const SummonBoatAction> summonableVirtualBoat;
public:
AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, VCAI * ai, std::shared_ptr<AINodeStorage> nodeStorage)
@@ -79,37 +142,14 @@ namespace AIPathfinding
return;
}
if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::SAIL
&& vstd::contains(virtualBoats, destination.coord))
if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::SAIL)
{
logAi->trace("Bypassing virtual boat at %s!", destination.coord.toString());
std::shared_ptr<const VirtualBoatAction> virtualBoat = findVirtualBoat(destination, source);
nodeStorage->updateAINode(destination.node, [&](AIPathNode * node)
if(virtualBoat && tryEmbarkVirtualBoat(destination, source, virtualBoat))
{
std::shared_ptr<const BuildBoatAction> virtualBoat = virtualBoats.at(destination.coord);
auto boatNodeOptional = nodeStorage->getOrCreateNode(
node->coord,
node->layer,
node->chainMask | AINodeStorage::RESOURCE_CHAIN);
if(boatNodeOptional)
{
AIPathNode * boatNode = boatNodeOptional.get();
boatNode->specialAction = virtualBoat;
destination.blocked = false;
destination.action = CGPathNode::ENodeAction::EMBARK;
destination.node = boatNode;
}
else
{
logAi->trace(
"Can not allocate boat node while moving %s -> %s",
source.coord.toString(),
destination.coord.toString());
}
});
logAi->trace("Embarking to virtual boat while moving %s -> %s!", source.coord.toString(), destination.coord.toString());
}
}
}
@@ -142,6 +182,84 @@ namespace AIPathfinding
logAi->debug("Virtual boat added at %s", boatLocation.toString());
}
}
auto hero = nodeStorage->getHero();
if(vstd::contains(hero->spells, SpellID::SUMMON_BOAT))
{
auto summonBoatSpell = SpellID(SpellID::SUMMON_BOAT).toSpell();
if(hero->getSpellSchoolLevel(summonBoatSpell) == SecSkillLevel::EXPERT)
{
summonableVirtualBoat.reset(new SummonBoatAction());
}
}
}
std::shared_ptr<const VirtualBoatAction> findVirtualBoat(
CDestinationNodeInfo &destination,
const PathNodeInfo &source) const
{
std::shared_ptr<const VirtualBoatAction> virtualBoat;
if(vstd::contains(virtualBoats, destination.coord))
{
virtualBoat = virtualBoats.at(destination.coord);
}
else if(
summonableVirtualBoat
&& summonableVirtualBoat->isAffordableBy(nodeStorage->getHero(), nodeStorage->getAINode(source.node)))
{
virtualBoat = summonableVirtualBoat;
}
return virtualBoat;
}
bool tryEmbarkVirtualBoat(
CDestinationNodeInfo &destination,
const PathNodeInfo &source,
std::shared_ptr<const VirtualBoatAction> virtualBoat) const
{
bool result = false;
nodeStorage->updateAINode(destination.node, [&](AIPathNode * node)
{
auto boatNodeOptional = nodeStorage->getOrCreateNode(
node->coord,
node->layer,
node->chainMask | virtualBoat->getSpecialChain());
if(boatNodeOptional)
{
AIPathNode * boatNode = boatNodeOptional.get();
if(boatNode->action == CGPathNode::NOT_SET)
{
boatNode->specialAction = virtualBoat;
destination.blocked = false;
destination.action = CGPathNode::ENodeAction::EMBARK;
destination.node = boatNode;
result = true;
}
else
{
logAi->trace(
"Special transition node already allocated. Blocked moving %s -> %s",
source.coord.toString(),
destination.coord.toString());
}
}
else
{
logAi->trace(
"Can not allocate special transition node while moving %s -> %s",
source.coord.toString(),
destination.coord.toString());
}
});
return result;
}
};

View File

@@ -11,7 +11,7 @@
#include "PathfindingManager.h"
#include "AIPathfinder.h"
#include "AIPathfinderConfig.h"
#include "Goals/Goals.h"
#include "../Goals/Goals.h"
#include "../../../lib/CGameInfoCallback.h"
#include "../../../lib/mapping/CMap.h"
@@ -130,8 +130,6 @@ Goals::TGoalVec PathfindingManager::findPath(
if(isSafeToVisit(hero, danger))
{
logAi->trace("It's safe for %s to visit tile %s with danger %s", hero->name, dest.toString(), std::to_string(danger));
Goals::TSubgoal solution;
if(path.specialAction)
@@ -153,6 +151,8 @@ Goals::TGoalVec PathfindingManager::findPath(
solution->evaluationContext.movementCost += path.movementCost();
logAi->trace("It's safe for %s to visit tile %s with danger %s, goal %s", hero->name, dest.toString(), std::to_string(danger), solution->name());
result.push_back(solution);
continue;
@@ -212,11 +212,15 @@ Goals::TSubgoal PathfindingManager::clearWayTo(HeroPtr hero, int3 firstTileToGet
return sptr(Goals::VisitObj(topObj->id.getNum()).sethero(hero));
}
//TODO: we should be able to return apriopriate quest here
//ret.push_back(ai->questToGoal());
//however, visiting obj for firts time will give us quest
//do not access quets guard if we can't complete the quest
logAi->trace("Can not visit this quest guard! Not ready!");
auto questObj = dynamic_cast<const IQuestObject*>(topObj);
if(questObj)
{
auto questInfo = QuestInfo(questObj->quest, topObj, topObj->visitablePos());
return sptr(Goals::CompleteQuest(questInfo));
}
return sptr(Goals::Invalid());
}
}