1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-14 02:33:51 +02:00

AI: town portal support

This commit is contained in:
Andrii Danylchenko 2019-01-07 23:33:31 +02:00 committed by ArseniyShestakov
parent 2e20dce71f
commit 8fee46de7c
12 changed files with 185 additions and 26 deletions

View File

@ -98,6 +98,19 @@ float FuzzyHelper::evaluate(Goals::BuildBoat & g)
return g.parent->accept(this) - buildBoatPenalty; return g.parent->accept(this) - buildBoatPenalty;
} }
float FuzzyHelper::evaluate(Goals::AdventureSpellCast & g)
{
if(!g.parent)
{
return 0;
}
const CSpell * spell = g.getSpell();
const float spellCastPenalty = (float)g.hero->getSpellCost(spell) / g.hero->mana;
return g.parent->accept(this) - spellCastPenalty;
}
float FuzzyHelper::evaluate(Goals::CompleteQuest & g) float FuzzyHelper::evaluate(Goals::CompleteQuest & g)
{ {
// TODO: How to evaluate quest complexity? // TODO: How to evaluate quest complexity?
@ -120,6 +133,7 @@ float FuzzyHelper::evaluate(Goals::VisitObj & g)
return visitObjEngine.evaluate(g); return visitObjEngine.evaluate(g);
} }
float FuzzyHelper::evaluate(Goals::VisitHero & g) float FuzzyHelper::evaluate(Goals::VisitHero & g)
{ {
auto obj = ai->myCb->getObj(ObjectInstanceID(g.objid)); //we assume for now that these goals are similar auto obj = ai->myCb->getObj(ObjectInstanceID(g.objid)); //we assume for now that these goals are similar

View File

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

View File

@ -31,7 +31,7 @@ TSubgoal AdventureSpellCast::whatToDoToAchieve()
if(!hero.validAndSet()) if(!hero.validAndSet())
throw cannotFulfillGoalException("Invalid hero!"); throw cannotFulfillGoalException("Invalid hero!");
auto spell = spellID.toSpell(); auto spell = getSpell();
logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->name, hero->name); logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->name, hero->name);
@ -49,7 +49,26 @@ TSubgoal AdventureSpellCast::whatToDoToAchieve()
void AdventureSpellCast::accept(VCAI * ai) void AdventureSpellCast::accept(VCAI * ai)
{ {
if(town && spellID == SpellID::TOWN_PORTAL)
{
ai->selectedObject = town->id;
}
auto wait = cb->waitTillRealize;
cb->waitTillRealize = true;
cb->castSpell(hero.h, spellID, tile); cb->castSpell(hero.h, spellID, tile);
ai->ah->resetPaths();
if(town && spellID == SpellID::TOWN_PORTAL)
{
// visit town
ai->moveHeroToTile(town->visitablePos(), hero);
}
cb->waitTillRealize = wait;
throw goalFulfilledException(sptr(*this));
} }
std::string AdventureSpellCast::name() const std::string AdventureSpellCast::name() const

View File

@ -30,6 +30,11 @@ namespace Goals
return TGoalVec(); return TGoalVec();
} }
const CSpell * getSpell() const
{
return spellID.toSpell();
}
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
void accept(VCAI * ai) override; void accept(VCAI * ai) override;
std::string name() const override; std::string name() const override;

View File

@ -71,6 +71,8 @@ void BuildBoat::accept(VCAI * ai)
shipyard->bestLocation().toString()); shipyard->bestLocation().toString());
cb->buildBoat(shipyard); cb->buildBoat(shipyard);
throw goalFulfilledException(sptr(*this));
} }
std::string BuildBoat::name() const std::string BuildBoat::name() const

View File

@ -57,15 +57,17 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
//TODO: include evaluation of monsters gather in calculation //TODO: include evaluation of monsters gather in calculation
for(auto t : cb->getTownsInfo()) for(auto t : cb->getTownsInfo())
{ {
auto pos = t->visitablePos(); auto waysToVisit = ai->ah->howToVisitObj(hero, t);
if(ai->isAccessibleForHero(pos, hero))
if(waysToVisit.size())
{ {
//grab army from town //grab army from town
if(!t->visitingHero && howManyReinforcementsCanGet(hero.get(), t)) if(!t->visitingHero && howManyReinforcementsCanGet(hero.get(), t))
{ {
if(!vstd::contains(ai->townVisitsThisWeek[hero], t)) if(!vstd::contains(ai->townVisitsThisWeek[hero], t))
ret.push_back(sptr(VisitTile(pos).sethero(hero))); vstd::concatenate(ret, waysToVisit);
} }
//buy army in town //buy army in town
if (!t->visitingHero || t->visitingHero == hero.get(true)) if (!t->visitingHero || t->visitingHero == hero.get(true))
{ {
@ -118,15 +120,20 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
else else
return false; return false;
}); });
for(auto h : otherHeroes) for(auto h : otherHeroes)
{ {
// Go to the other hero if we are faster // Go to the other hero if we are faster
if (!vstd::contains(ai->visitedHeroes[hero], h) if(!vstd::contains(ai->visitedHeroes[hero], h))
&& ai->isAccessibleForHero(h->visitablePos(), hero, true)) //visit only once each turn //FIXME: this is only bug workaround {
ret.push_back(sptr(VisitHero(h->id.getNum()).setisAbstract(true).sethero(hero))); vstd::concatenate(ret, ai->ah->howToVisitObj(hero, h));
// Let the other hero come to us }
if (!vstd::contains(ai->visitedHeroes[h], hero))
ret.push_back(sptr(VisitHero(hero->id.getNum()).setisAbstract(true).sethero(h))); // Go to the other hero if we are faster
if(!vstd::contains(ai->visitedHeroes[h], hero))
{
vstd::concatenate(ret, ai->ah->howToVisitObj(h, hero.get()));
}
} }
std::vector<const CGObjectInstance *> objs; std::vector<const CGObjectInstance *> objs;
@ -161,6 +168,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
} }
} }
} }
for(auto h : cb->getHeroesInfo()) for(auto h : cb->getHeroesInfo())
{ {
for(auto obj : objs) for(auto obj : objs)

View File

@ -9,7 +9,12 @@
*/ */
#include "StdInc.h" #include "StdInc.h"
#include "AINodeStorage.h" #include "AINodeStorage.h"
#include "../Goals/Goals.h"
#include "../../../CCallback.h"
#include "../../../lib/mapping/CMap.h"
#include "../../../lib/mapObjects/MapObjects.h"
extern boost::thread_specific_ptr<CCallback> cb;
AINodeStorage::AINodeStorage(const int3 & Sizes) AINodeStorage::AINodeStorage(const int3 & Sizes)
: sizes(Sizes) : sizes(Sizes)
@ -133,28 +138,128 @@ std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
return neighbours; return neighbours;
} }
void AINodeStorage::setHero(HeroPtr heroPtr)
{
hero = heroPtr.get();
}
class TownPortalAction : public ISpecialAction
{
private:
const CGTownInstance * target;
const HeroPtr hero;
public:
TownPortalAction(const CGTownInstance * target)
:target(target)
{
}
virtual Goals::TSubgoal whatToDo(HeroPtr hero) const override
{
const CGTownInstance * targetTown = target; // const pointer is not allowed in settown
return sptr(Goals::AdventureSpellCast(hero, SpellID::TOWN_PORTAL).settown(targetTown).settile(targetTown->visitablePos()));
}
};
std::vector<CGPathNode *> AINodeStorage::calculateTeleportations( std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
const PathNodeInfo & source, const PathNodeInfo & source,
const PathfinderConfig * pathfinderConfig, const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) const CPathfinderHelper * pathfinderHelper)
{ {
std::vector<CGPathNode *> neighbours; std::vector<CGPathNode *> neighbours;
auto accessibleExits = pathfinderHelper->getTeleportExits(source);
auto srcNode = getAINode(source.node);
for(auto & neighbour : accessibleExits) if(source.isNodeObjectVisitable())
{ {
auto node = getOrCreateNode(neighbour, source.node->layer, srcNode->chainMask); auto accessibleExits = pathfinderHelper->getTeleportExits(source);
auto srcNode = getAINode(source.node);
if(!node) for(auto & neighbour : accessibleExits)
continue; {
auto node = getOrCreateNode(neighbour, source.node->layer, srcNode->chainMask);
neighbours.push_back(node.get()); if(!node)
continue;
neighbours.push_back(node.get());
}
}
if(hero->getPosition(false) == source.coord)
{
calculateTownPortalTeleportations(source, neighbours);
} }
return neighbours; return neighbours;
} }
void AINodeStorage::calculateTownPortalTeleportations(
const PathNodeInfo & source,
std::vector<CGPathNode *> & neighbours)
{
SpellID spellID = SpellID::TOWN_PORTAL;
const CSpell * townPortal = spellID.toSpell();
auto srcNode = getAINode(source.node);
if(hero->canCastThisSpell(townPortal) && hero->mana >= hero->getSpellCost(townPortal))
{
auto towns = cb->getTownsInfo(false);
vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool
{
return cb->getPlayerRelations(hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES;
});
if(!towns.size())
{
return;
}
// TODO: Copy/Paste from TownPortalMechanics
auto skillLevel = hero->getSpellSchoolLevel(townPortal);
auto movementCost = GameConstants::BASE_MOVEMENT_COST * (skillLevel >= 3 ? 2 : 3);
if(hero->movement < movementCost)
{
return;
}
if(skillLevel < SecSkillLevel::ADVANCED)
{
const CGTownInstance * nearestTown = *vstd::minElementByFun(towns, [&](const CGTownInstance * t) -> int
{
return hero->visitablePos().dist2dSQ(t->visitablePos());
});
towns = std::vector<const CGTownInstance *>{ nearestTown };
}
for(const CGTownInstance * targetTown : towns)
{
if(targetTown->visitingHero)
continue;
auto nodeOptional = getOrCreateNode(targetTown->visitablePos(), EPathfindingLayer::LAND, srcNode->chainMask | CAST_CHAIN);
if(nodeOptional)
{
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("Adding town portal node at %s", targetTown->name);
#endif
AIPathNode * node = nodeOptional.get();
node->theNodeBefore = source.node;
node->specialAction.reset(new TownPortalAction(targetTown));
node->moveRemains = source.node->moveRemains;
neighbours.push_back(node);
}
}
}
}
bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
{ {
auto pos = destination.coord; auto pos = destination.coord;
@ -174,12 +279,14 @@ bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNode
if(node.turns < destinationNode->turns if(node.turns < destinationNode->turns
|| (node.turns == destinationNode->turns && node.moveRemains >= destinationNode->moveRemains)) || (node.turns == destinationNode->turns && node.moveRemains >= destinationNode->moveRemains))
{ {
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace( logAi->trace(
"Block ineficient move %s:->%s, mask=%i, mp diff: %i", "Block ineficient move %s:->%s, mask=%i, mp diff: %i",
source.coord.toString(), source.coord.toString(),
destination.coord.toString(), destination.coord.toString(),
destinationNode->chainMask, destinationNode->chainMask,
node.moveRemains - destinationNode->moveRemains); node.moveRemains - destinationNode->moveRemains);
#endif
return true; return true;
} }

View File

@ -113,13 +113,13 @@ public:
std::vector<AIPath> getChainInfo(int3 pos, bool isOnLand) const; std::vector<AIPath> getChainInfo(int3 pos, bool isOnLand) const;
bool isTileAccessible(int3 pos, const EPathfindingLayer layer) const; bool isTileAccessible(int3 pos, const EPathfindingLayer layer) const;
void setHero(HeroPtr heroPtr) void setHero(HeroPtr heroPtr);
{
hero = heroPtr.get();
}
const CGHeroInstance * getHero() const const CGHeroInstance * getHero() const
{ {
return hero; return hero;
} }
private:
void calculateTownPortalTeleportations(const PathNodeInfo & source, std::vector<CGPathNode *> & neighbours);
}; };

View File

@ -741,9 +741,7 @@ void VCAI::showMapObjectSelectDialog(QueryID askID, const Component & icon, cons
{ {
NET_EVENT_HANDLER; NET_EVENT_HANDLER;
status.addQuery(askID, "Map object select query"); status.addQuery(askID, "Map object select query");
requestActionASAP([=](){ answerQuery(askID, 0); }); requestActionASAP([=](){ answerQuery(askID, selectedObject.getNum()); });
//TODO: Town portal destination selection goes here
} }
void VCAI::saveGame(BinarySerializer & h, const int version) void VCAI::saveGame(BinarySerializer & h, const int version)

View File

@ -112,6 +112,7 @@ public:
std::shared_ptr<CCallback> myCb; std::shared_ptr<CCallback> myCb;
std::unique_ptr<boost::thread> makingTurn; std::unique_ptr<boost::thread> makingTurn;
ObjectInstanceID selectedObject;
AIhelper * ah; AIhelper * ah;

View File

@ -710,7 +710,7 @@ std::vector < const CGTownInstance *> CPlayerSpecificInfoCallback::getTownsInfo(
{ {
for(const auto & town : i.second.towns) for(const auto & town : i.second.towns)
{ {
if (i.first==player || (isVisible(town, player) && !onlyOur)) if(i.first == player || (!onlyOur && isVisible(town, player)))
{ {
ret.push_back(town); ret.push_back(town);
} }

View File

@ -56,6 +56,10 @@ std::vector<CGPathNode *> NodeStorage::calculateTeleportations(
const CPathfinderHelper * pathfinderHelper) const CPathfinderHelper * pathfinderHelper)
{ {
std::vector<CGPathNode *> neighbours; std::vector<CGPathNode *> neighbours;
if(!source.isNodeObjectVisitable())
return neighbours;
auto accessibleExits = pathfinderHelper->getTeleportExits(source); auto accessibleExits = pathfinderHelper->getTeleportExits(source);
for(auto & neighbour : accessibleExits) for(auto & neighbour : accessibleExits)
@ -315,7 +319,7 @@ void CPathfinder::calculatePaths()
/// For now we disable teleports usage for patrol movement /// For now we disable teleports usage for patrol movement
/// VCAI not aware about patrol and may stuck while attempt to use teleport /// VCAI not aware about patrol and may stuck while attempt to use teleport
if(!source.isNodeObjectVisitable() || patrolState == PATROL_RADIUS) if(patrolState == PATROL_RADIUS)
continue; continue;
auto teleportationNodes = config->nodeStorage->calculateTeleportations(source, config.get(), hlp.get()); auto teleportationNodes = config->nodeStorage->calculateTeleportations(source, config.get(), hlp.get());