1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +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

@ -29,6 +29,7 @@ set(VCAI_SRCS
Goals/GatherArmy.cpp
Goals/GatherTroops.cpp
Goals/BuyArmy.cpp
Goals/AdventureSpellCast.cpp
Goals/Win.cpp
Goals/VisitTile.cpp
Goals/VisitObj.cpp
@ -41,6 +42,7 @@ set(VCAI_SRCS
Goals/DigAtTile.cpp
Goals/GetArtOfType.cpp
Goals/FindObj.cpp
Goals/CompleteQuest.cpp
main.cpp
VCAI.cpp
)
@ -71,6 +73,7 @@ set(VCAI_HEADERS
Goals/GatherArmy.h
Goals/GatherTroops.h
Goals/BuyArmy.h
Goals/AdventureSpellCast.h
Goals/Win.h
Goals/VisitTile.h
Goals/VisitObj.h
@ -83,6 +86,7 @@ set(VCAI_HEADERS
Goals/DigAtTile.h
Goals/GetArtOfType.h
Goals/FindObj.h
Goals/CompleteQuest.h
Goals/Goals.h
VCAI.h
)

View File

@ -93,6 +93,19 @@ float FuzzyHelper::evaluate(Goals::BuildBoat & g)
return g.parent->accept(this) - buildBoatPenalty;
}
float FuzzyHelper::evaluate(Goals::CompleteQuest & g)
{
// TODO: How to evaluate quest complexity?
const float questPenalty = 0.2;
if(!g.parent)
{
return 0;
}
return g.parent->accept(this) * questPenalty;
}
float FuzzyHelper::evaluate(Goals::VisitObj & g)
{
return visitObjEngine.evaluate(g);

View File

@ -32,6 +32,7 @@ public:
float evaluate(Goals::BuildBoat & g);
float evaluate(Goals::GatherArmy & g);
float evaluate(Goals::ClearWayTo & g);
float evaluate(Goals::CompleteQuest & g);
float evaluate(Goals::Invalid & g);
float evaluate(Goals::AbstractGoal & g);
void setPriority(Goals::TSubgoal & g);

View File

@ -40,8 +40,6 @@ std::string AbstractGoal::name() const //TODO: virtualize
return "INVALID";
case WIN:
return "WIN";
case DO_NOT_LOSE:
return "DO NOT LOOSE";
case CONQUER:
return "CONQUER";
case BUILD:
@ -95,8 +93,6 @@ std::string AbstractGoal::name() const //TODO: virtualize
case GET_ART_TYPE:
desc = "GET ARTIFACT OF TYPE " + VLC->arth->artifacts[aid]->Name();
break;
case ISSUE_COMMAND:
return "ISSUE COMMAND (unsupported)";
case VISIT_TILE:
desc = "VISIT TILE " + tile.toString();
break;

View File

@ -37,6 +37,35 @@ namespace Goals
class ClearWayTo;
class Invalid;
class Trade;
class CompleteQuest;
class AdventureSpellCast;
enum EGoals
{
INVALID = -1,
WIN, CONQUER, BUILD, //build needs to get a real reasoning
EXPLORE, GATHER_ARMY,
BOOST_HERO,
RECRUIT_HERO,
BUILD_STRUCTURE, //if hero set, then in visited town
COLLECT_RES,
GATHER_TROOPS, // val of creatures with objid
VISIT_OBJ, //visit or defeat or collect the object
FIND_OBJ, //find and visit any obj with objid + resid //TODO: consider universal subid for various types (aid, bid)
VISIT_HERO, //heroes can move around - set goal abstract and track hero every turn
GET_ART_TYPE,
VISIT_TILE, //tile, in conjunction with hero elementar; assumes tile is reachable
CLEAR_WAY_TO,
DIG_AT_TILE,//elementar with hero on tile
BUY_ARMY, //at specific town
TRADE, //val resID at object objid
BUILD_BOAT,
COMPLETE_QUEST,
ADVENTURE_SPELL_CAST
};
class DLL_EXPORT TSubgoal : public std::shared_ptr<AbstractGoal>
{
@ -48,35 +77,6 @@ namespace Goals
typedef std::vector<TSubgoal> TGoalVec;
enum EGoals
{
INVALID = -1,
WIN, DO_NOT_LOSE, CONQUER, BUILD, //build needs to get a real reasoning
EXPLORE, GATHER_ARMY,
BOOST_HERO,
RECRUIT_HERO,
BUILD_STRUCTURE, //if hero set, then in visited town
COLLECT_RES,
GATHER_TROOPS, // val of creatures with objid
OBJECT_GOALS_BEGIN,
VISIT_OBJ, //visit or defeat or collect the object
FIND_OBJ, //find and visit any obj with objid + resid //TODO: consider universal subid for various types (aid, bid)
VISIT_HERO, //heroes can move around - set goal abstract and track hero every turn
GET_ART_TYPE,
//BUILD_STRUCTURE,
ISSUE_COMMAND,
VISIT_TILE, //tile, in conjunction with hero elementar; assumes tile is reachable
CLEAR_WAY_TO,
DIG_AT_TILE,//elementar with hero on tile
BUY_ARMY, //at specific town
TRADE, //val resID at object objid
BUILD_BOAT
};
//method chaining + clone pattern
#define VSETTER(type, field) virtual AbstractGoal & set ## field(const type &rhs) {field = rhs; return *this;};
#define OSETTER(type, field) CGoal<T> & set ## field(const type &rhs) override { field = rhs; return *this; };

View File

@ -0,0 +1,63 @@
/*
* AdventureSpellCast.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 "AdventureSpellCast.h"
#include "../VCAI.h"
#include "../FuzzyHelper.h"
#include "../AIhelper.h"
#include "../../lib/mapping/CMap.h" //for victory conditions
#include "../../lib/CPathfinder.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
bool AdventureSpellCast::operator==(const AdventureSpellCast & other) const
{
return hero.h == other.hero.h;
}
TSubgoal AdventureSpellCast::whatToDoToAchieve()
{
if(!hero.validAndSet())
throw cannotFulfillGoalException("Invalid hero!");
auto spell = spellID.toSpell();
logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->name, hero->name);
if(!spell->isAdventureSpell())
throw cannotFulfillGoalException(spell->name + " is not an adventure spell.");
if(!vstd::contains(hero->spells, spellID))
throw cannotFulfillGoalException("Hero has no " + spell->name);
if(hero->mana < hero->getSpellCost(spellID.toSpell()))
throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->name);
return iAmElementar();
}
void AdventureSpellCast::accept(VCAI * ai)
{
cb->castSpell(hero.h, spellID, tile);
}
std::string AdventureSpellCast::name() const
{
return "AdventureSpellCast " + spellID.toSpell()->name;
}
std::string AdventureSpellCast::completeMessage() const
{
return "Spell casted successfully " + spellID.toSpell()->name;
}

View File

@ -0,0 +1,39 @@
/*
* AdventureSpellCast.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 "CGoal.h"
namespace Goals
{
class DLL_EXPORT AdventureSpellCast : public CGoal<AdventureSpellCast>
{
private:
SpellID spellID;
public:
AdventureSpellCast(HeroPtr hero, SpellID spellID)
: CGoal(Goals::ADVENTURE_SPELL_CAST), spellID(spellID)
{
sethero(hero);
}
TGoalVec getAllPossibleSubgoals() override
{
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override;
void accept(VCAI * ai) override;
std::string name() const override;
std::string completeMessage() const override;
virtual bool operator==(const AdventureSpellCast & other) const override;
};
}

View File

@ -60,11 +60,13 @@ namespace Goals
{
return new T(static_cast<T const &>(*this)); //casting enforces template instantiation
}
TSubgoal iAmElementar()
TSubgoal iAmElementar() const
{
setisElementar(true); //FIXME: it's not const-correct, maybe we shoudl only set returned clone?
TSubgoal ptr;
ptr.reset(clone());
ptr->setisElementar(true);
return ptr;
}
template<typename Handler> void serialize(Handler & h, const int version)
@ -79,7 +81,7 @@ namespace Goals
if(goalType != g.goalType)
return false;
return (*this) == (dynamic_cast<const T &>(g));
return (*this) == (static_cast<const T &>(g));
}
virtual bool operator==(const T & other) const = 0;

View File

@ -0,0 +1,276 @@
/*
* CompleteQuest.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 "Goals.h"
#include "../VCAI.h"
#include "../FuzzyHelper.h"
#include "../AIhelper.h"
#include "../../lib/mapping/CMap.h" //for victory conditions
#include "../../lib/CPathfinder.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
bool CompleteQuest::operator==(const CompleteQuest & other) const
{
return q.quest->qid == other.q.quest->qid;
}
TGoalVec CompleteQuest::getAllPossibleSubgoals()
{
TGoalVec solutions;
if(q.quest->missionType && q.quest->progress != CQuest::COMPLETE)
{
logAi->debug("Trying to realize quest: %s", questToString());
switch(q.quest->missionType)
{
case CQuest::MISSION_ART:
return missionArt();
case CQuest::MISSION_HERO:
return missionHero();
case CQuest::MISSION_ARMY:
return missionArmy();
case CQuest::MISSION_RESOURCES:
return missionResources();
case CQuest::MISSION_KILL_HERO:
case CQuest::MISSION_KILL_CREATURE:
return missionDestroyObj();
case CQuest::MISSION_PRIMARY_STAT:
return missionIncreasePrimaryStat();
case CQuest::MISSION_LEVEL:
return missionLevel();
case CQuest::MISSION_PLAYER:
if(ai->playerID.getNum() != q.quest->m13489val)
logAi->debug("Can't be player of color %d", q.quest->m13489val);
break;
case CQuest::MISSION_KEYMASTER:
return missionKeymaster();
} //end of switch
}
return TGoalVec();
}
TSubgoal CompleteQuest::whatToDoToAchieve()
{
if(q.quest->missionType == CQuest::MISSION_NONE)
{
throw cannotFulfillGoalException("Can not complete inactive quest");
}
TGoalVec solutions = getAllPossibleSubgoals();
if(solutions.empty())
throw cannotFulfillGoalException("Can not complete quest " + questToString());
TSubgoal result = fh->chooseSolution(solutions);
logAi->trace(
"Returning %s, tile: %s, objid: %d, hero: %s",
result->name(),
result->tile.toString(),
result->objid,
result->hero.validAndSet() ? result->hero->name : "not specified");
return result;
}
std::string CompleteQuest::name() const
{
return "CompleteQuest";
}
std::string CompleteQuest::completeMessage() const
{
return "Completed quest " + questToString();
}
std::string CompleteQuest::questToString() const
{
if(q.quest->missionType == CQuest::MISSION_NONE)
return "inactive quest";
MetaString ms;
q.quest->getRolloverText(ms, false);
return ms.toString();
}
TGoalVec CompleteQuest::tryCompleteQuest() const
{
TGoalVec solutions;
auto heroes = cb->getHeroesInfo(); //TODO: choose best / free hero from among many possibilities?
for(auto hero : heroes)
{
if(q.quest->checkQuest(hero))
{
vstd::concatenate(solutions, ai->ah->howToVisitObj(hero, ObjectIdRef(q.obj->id)));
}
}
return solutions;
}
TGoalVec CompleteQuest::missionArt() const
{
TGoalVec solutions = tryCompleteQuest();
if(!solutions.empty())
return solutions;
for(auto art : q.quest->m5arts)
{
solutions.push_back(sptr(GetArtOfType(art))); //TODO: transport?
}
return solutions;
}
TGoalVec CompleteQuest::missionHero() const
{
TGoalVec solutions = tryCompleteQuest();
if(solutions.empty())
{
//rule of a thumb - quest heroes usually are locked in prisons
solutions.push_back(sptr(FindObj(Obj::PRISON)));
}
return solutions;
}
TGoalVec CompleteQuest::missionArmy() const
{
TGoalVec solutions = tryCompleteQuest();
if(!solutions.empty())
return solutions;
for(auto creature : q.quest->m6creatures)
{
solutions.push_back(sptr(GatherTroops(creature.type->idNumber, creature.count)));
}
return solutions;
}
TGoalVec CompleteQuest::missionIncreasePrimaryStat() const
{
TGoalVec solutions = tryCompleteQuest();
if(solutions.empty())
{
for(int i = 0; i < q.quest->m2stats.size(); ++i)
{
// TODO: library, school and other boost objects
logAi->debug("Don't know how to increase primary stat %d", i);
}
}
return solutions;
}
TGoalVec CompleteQuest::missionLevel() const
{
TGoalVec solutions = tryCompleteQuest();
if(solutions.empty())
{
logAi->debug("Don't know how to reach hero level %d", q.quest->m13489val);
}
return solutions;
}
TGoalVec CompleteQuest::missionKeymaster() const
{
TGoalVec solutions = tryCompleteQuest();
if(solutions.empty())
{
solutions.push_back(sptr(Goals::FindObj(Obj::KEYMASTER, q.obj->subID)));
}
return solutions;
}
TGoalVec CompleteQuest::missionResources() const
{
TGoalVec solutions;
auto heroes = cb->getHeroesInfo(); //TODO: choose best / free hero from among many possibilities?
if(heroes.size())
{
if(q.quest->checkQuest(heroes.front())) //it doesn't matter which hero it is
{
return ai->ah->howToVisitObj(q.obj);
}
else
{
for(int i = 0; i < q.quest->m7resources.size(); ++i)
{
if(q.quest->m7resources[i])
solutions.push_back(sptr(CollectRes(i, q.quest->m7resources[i])));
}
}
}
else
{
solutions.push_back(sptr(Goals::RecruitHero())); //FIXME: checkQuest requires any hero belonging to player :(
}
return solutions;
}
TGoalVec CompleteQuest::missionDestroyObj() const
{
TGoalVec solutions;
auto obj = cb->getObjByQuestIdentifier(q.quest->m13489val);
if(!obj)
return ai->ah->howToVisitObj(q.obj);
if(obj->ID == Obj::HERO)
{
auto relations = cb->getPlayerRelations(ai->playerID, obj->tempOwner);
if(relations == PlayerRelations::SAME_PLAYER)
{
auto heroToProtect = cb->getHero(obj->id);
solutions.push_back(sptr(GatherArmy().sethero(heroToProtect)));
}
else if(relations == PlayerRelations::ENEMIES)
{
solutions = ai->ah->howToVisitObj(obj);
}
}
return solutions;
}

View File

@ -0,0 +1,46 @@
/*
* CompleteQuest.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 "CGoal.h"
#include "../../../lib/VCMI_Lib.h"
namespace Goals
{
class DLL_EXPORT CompleteQuest : public CGoal<CompleteQuest>
{
private:
const QuestInfo q;
public:
CompleteQuest(const QuestInfo quest)
: CGoal(Goals::COMPLETE_QUEST), q(quest)
{
}
TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override;
std::string name() const override;
std::string completeMessage() const override;
virtual bool operator==(const CompleteQuest & other) const override;
private:
TGoalVec tryCompleteQuest() const;
TGoalVec missionArt() const;
TGoalVec missionHero() const;
TGoalVec missionArmy() const;
TGoalVec missionResources() const;
TGoalVec missionDestroyObj() const;
TGoalVec missionIncreasePrimaryStat() const;
TGoalVec missionLevel() const;
TGoalVec missionKeymaster() const;
std::string questToString() const;
};
}

View File

@ -33,6 +33,8 @@ bool Conquer::operator==(const Conquer & other) const
TSubgoal Conquer::whatToDoToAchieve()
{
logAi->trace("Entering goal CONQUER");
return fh->chooseSolution(getAllPossibleSubgoals());
}

View File

@ -48,6 +48,8 @@ int GatherTroops::getCreaturesCount(const CArmedInstance * army)
TSubgoal GatherTroops::whatToDoToAchieve()
{
logAi->trace("Entering GatherTroops::whatToDoToAchieve");
auto heroes = cb->getHeroesInfo(true);
for(auto hero : heroes)

View File

@ -29,4 +29,6 @@
#include "GetArtOfType.h"
#include "ClearWayTo.h"
#include "DigAtTile.h"
#include "FindObj.h"
#include "FindObj.h"
#include "CompleteQuest.h"
#include "AdventureSpellCast.h"

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

View File

@ -873,7 +873,7 @@ void VCAI::mainLoop()
basicGoals.push_back(ah->whatToDo());
for (auto quest : myCb->getMyQuests())
{
basicGoals.push_back(questToGoal(quest));
basicGoals.push_back(sptr(Goals::CompleteQuest(quest)));
}
basicGoals.push_back(sptr(Goals::Build()));
@ -1013,11 +1013,13 @@ void VCAI::mainLoop()
//remove goals we couldn't decompose
for (auto goal : goalsToRemove)
vstd::erase_if_present(basicGoals, goal);
//add abstract goals
boost::sort(goalsToAdd, [](const Goals::TSubgoal & lhs, const Goals::TSubgoal & rhs) -> bool
{
return lhs->priority > rhs->priority; //highest priority at the beginning
});
//max number of goals = 10
int i = 0;
while (basicGoals.size() < 10 && goalsToAdd.size() > i)
@ -2434,170 +2436,6 @@ Goals::TSubgoal VCAI::decomposeGoal(Goals::TSubgoal ultimateGoal)
return abstractGoal;
}
Goals::TSubgoal VCAI::questToGoal(const QuestInfo & q)
{
Goals::TSubgoal result = sptr(Goals::Invalid());
if (q.quest->missionType && q.quest->progress != CQuest::COMPLETE)
{
MetaString ms;
q.quest->getRolloverText(ms, false);
logAi->debug("Trying to realize quest: %s", ms.toString());
auto heroes = cb->getHeroesInfo(); //TODO: choose best / free hero from among many possibilities?
switch (q.quest->missionType)
{
case CQuest::MISSION_ART:
{
for (auto hero : heroes)
{
if (q.quest->checkQuest(hero))
{
return sptr(Goals::VisitObj(q.obj->id.getNum()).sethero(hero));
}
}
for (auto art : q.quest->m5arts)
{
return sptr(Goals::GetArtOfType(art)); //TODO: transport?
}
break;
}
case CQuest::MISSION_HERO:
{
//striveToGoal (CGoal(RECRUIT_HERO));
for (auto hero : heroes)
{
if (q.quest->checkQuest(hero))
{
return sptr(Goals::VisitObj(q.obj->id.getNum()).sethero(hero));
}
}
return sptr(Goals::FindObj(Obj::PRISON)); //rule of a thumb - quest heroes usually are locked in prisons
//BNLOG ("Don't know how to recruit hero with id %d\n", q.quest->m13489val);
}
case CQuest::MISSION_ARMY:
{
for (auto hero : heroes)
{
if (q.quest->checkQuest(hero)) //very bad info - stacks can be split between multiple heroes :(
{
result = sptr(Goals::VisitObj(q.obj->id.getNum()).sethero(hero));
break;
}
}
if(result->invalid())
{
for(auto creature : q.quest->m6creatures)
{
result = sptr(Goals::GatherTroops(creature.type->idNumber, creature.count));
break;
}
}
//TODO: exchange armies... oh my
//BNLOG ("Don't know how to recruit %d of %s\n", (int)(creature.count) % creature.type->namePl);
break;
}
case CQuest::MISSION_RESOURCES:
{
if (heroes.size())
{
if (q.quest->checkQuest(heroes.front())) //it doesn't matter which hero it is
{
return sptr(Goals::VisitObj(q.obj->id.getNum()));
}
else
{
for (int i = 0; i < q.quest->m7resources.size(); ++i)
{
if (q.quest->m7resources[i])
return sptr(Goals::CollectRes(i, q.quest->m7resources[i]));
}
}
}
else
return sptr(Goals::RecruitHero()); //FIXME: checkQuest requires any hero belonging to player :(
break;
}
case CQuest::MISSION_KILL_HERO:
case CQuest::MISSION_KILL_CREATURE:
{
auto obj = cb->getObjByQuestIdentifier(q.quest->m13489val);
if(!obj)
return sptr(Goals::VisitObj(q.obj->id.getNum())); //visit seer hut
if(obj->ID == Obj::HERO)
{
auto relations = myCb->getPlayerRelations(playerID, obj->tempOwner);
if(relations == PlayerRelations::SAME_PLAYER)
{
auto heroToProtect = cb->getHero(obj->id);
return sptr(Goals::GatherArmy().sethero(heroToProtect));
}
else if(relations == PlayerRelations::ALLIES)
{
break;
}
}
return sptr(Goals::VisitObj(obj->id.getNum()));
}
case CQuest::MISSION_PRIMARY_STAT:
{
auto heroes = cb->getHeroesInfo();
for (auto hero : heroes)
{
if (q.quest->checkQuest(hero))
{
return sptr(Goals::VisitObj(q.obj->id.getNum()).sethero(hero));
}
}
for (int i = 0; i < q.quest->m2stats.size(); ++i)
{
logAi->debug("Don't know how to increase primary stat %d", i);
}
break;
}
case CQuest::MISSION_LEVEL:
{
auto heroes = cb->getHeroesInfo();
for (auto hero : heroes)
{
if (q.quest->checkQuest(hero))
{
return sptr(Goals::VisitObj(q.obj->id.getNum()).sethero(hero)); //TODO: causes infinite loop :/
}
}
logAi->debug("Don't know how to reach hero level %d", q.quest->m13489val);
break;
}
case CQuest::MISSION_PLAYER:
{
if (playerID.getNum() != q.quest->m13489val)
logAi->debug("Can't be player of color %d", q.quest->m13489val);
break;
}
case CQuest::MISSION_KEYMASTER:
{
return sptr(Goals::FindObj(Obj::KEYMASTER, q.obj->subID));
break;
}
} //end of switch
}
logAi->trace(
"Returning %s, tile: %s, objid: %d, hero: %s",
result->name(),
result->tile.toString(),
result->objid,
result->hero.validAndSet() ? result->hero->name : "not specified");
return result;
}
void VCAI::performTypicalActions()
{
for(auto h : getUnblockedHeroes())

View File

@ -210,7 +210,6 @@ public:
void setGoal(HeroPtr h, Goals::TSubgoal goal);
void evaluateGoal(HeroPtr h); //evaluates goal assigned to hero, if any
void completeGoal(Goals::TSubgoal goal); //safely removes goal from reserved hero
Goals::TSubgoal questToGoal(const QuestInfo & q);
void recruitHero(const CGTownInstance * t, bool throwing = false);
bool isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, boost::optional<uint32_t> movementCostLimit = boost::none);