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:
parent
e996879733
commit
5d022ba77c
@ -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
|
||||
)
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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; };
|
||||
|
63
AI/VCAI/Goals/AdventureSpellCast.cpp
Normal file
63
AI/VCAI/Goals/AdventureSpellCast.cpp
Normal 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;
|
||||
}
|
39
AI/VCAI/Goals/AdventureSpellCast.h
Normal file
39
AI/VCAI/Goals/AdventureSpellCast.h
Normal 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;
|
||||
};
|
||||
}
|
@ -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;
|
||||
|
276
AI/VCAI/Goals/CompleteQuest.cpp
Normal file
276
AI/VCAI/Goals/CompleteQuest.cpp
Normal 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;
|
||||
}
|
46
AI/VCAI/Goals/CompleteQuest.h
Normal file
46
AI/VCAI/Goals/CompleteQuest.h
Normal 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;
|
||||
};
|
||||
}
|
@ -33,6 +33,8 @@ bool Conquer::operator==(const Conquer & other) const
|
||||
|
||||
TSubgoal Conquer::whatToDoToAchieve()
|
||||
{
|
||||
logAi->trace("Entering goal CONQUER");
|
||||
|
||||
return fh->chooseSolution(getAllPossibleSubgoals());
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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"
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
168
AI/VCAI/VCAI.cpp
168
AI/VCAI/VCAI.cpp
@ -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())
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user