Pārlūkot izejas kodu

AI: town portal support

Andrii Danylchenko 7 gadi atpakaļ
vecāks
revīzija
8fee46de7c

+ 14 - 0
AI/VCAI/FuzzyHelper.cpp

@@ -98,6 +98,19 @@ float FuzzyHelper::evaluate(Goals::BuildBoat & g)
 	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)
 {
 	// TODO: How to evaluate quest complexity?
@@ -120,6 +133,7 @@ float FuzzyHelper::evaluate(Goals::VisitObj & g)
 
 	return visitObjEngine.evaluate(g);
 }
+
 float FuzzyHelper::evaluate(Goals::VisitHero & g)
 {
 	auto obj = ai->myCb->getObj(ObjectInstanceID(g.objid)); //we assume for now that these goals are similar

+ 1 - 0
AI/VCAI/FuzzyHelper.h

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

+ 20 - 1
AI/VCAI/Goals/AdventureSpellCast.cpp

@@ -31,7 +31,7 @@ TSubgoal AdventureSpellCast::whatToDoToAchieve()
 	if(!hero.validAndSet())
 		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);
 
@@ -49,7 +49,26 @@ TSubgoal AdventureSpellCast::whatToDoToAchieve()
 
 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);
+	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

+ 5 - 0
AI/VCAI/Goals/AdventureSpellCast.h

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

+ 2 - 0
AI/VCAI/Goals/BuildBoat.cpp

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

+ 17 - 9
AI/VCAI/Goals/GatherArmy.cpp

@@ -57,15 +57,17 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
 	//TODO: include evaluation of monsters gather in calculation
 	for(auto t : cb->getTownsInfo())
 	{
-		auto pos = t->visitablePos();
-		if(ai->isAccessibleForHero(pos, hero))
+		auto waysToVisit = ai->ah->howToVisitObj(hero, t);
+
+		if(waysToVisit.size())
 		{
 			//grab army from town
 			if(!t->visitingHero && howManyReinforcementsCanGet(hero.get(), t))
 			{
 				if(!vstd::contains(ai->townVisitsThisWeek[hero], t))
-					ret.push_back(sptr(VisitTile(pos).sethero(hero)));
+					vstd::concatenate(ret, waysToVisit);
 			}
+
 			//buy army in town
 			if (!t->visitingHero || t->visitingHero == hero.get(true))
 			{
@@ -118,15 +120,20 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
 		else
 		   return false;
 	});
+
 	for(auto h : otherHeroes)
 	{
 		// Go to the other hero if we are faster
-		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)));
-		// 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)));
+		if(!vstd::contains(ai->visitedHeroes[hero], h))
+		{
+			vstd::concatenate(ret, ai->ah->howToVisitObj(hero, 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;
@@ -161,6 +168,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
 			}
 		}
 	}
+
 	for(auto h : cb->getHeroesInfo())
 	{
 		for(auto obj : objs)

+ 114 - 7
AI/VCAI/Pathfinding/AINodeStorage.cpp

@@ -9,7 +9,12 @@
 */
 #include "StdInc.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)
 	: sizes(Sizes)
@@ -133,28 +138,128 @@ std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
 	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(
 	const PathNodeInfo & source,
 	const PathfinderConfig * pathfinderConfig,
 	const CPathfinderHelper * pathfinderHelper)
 {
 	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)
-			continue;
+		for(auto & neighbour : accessibleExits)
+		{
+			auto node = getOrCreateNode(neighbour, source.node->layer, srcNode->chainMask);
+
+			if(!node)
+				continue;
 
-		neighbours.push_back(node.get());
+			neighbours.push_back(node.get());
+		}
+	}
+
+	if(hero->getPosition(false) == source.coord)
+	{
+		calculateTownPortalTeleportations(source, 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
 {
 	auto pos = destination.coord;
@@ -174,12 +279,14 @@ bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNode
 			if(node.turns < destinationNode->turns
 				|| (node.turns == destinationNode->turns && node.moveRemains >= destinationNode->moveRemains))
 			{
+#ifdef VCMI_TRACE_PATHFINDER
 				logAi->trace(
 					"Block ineficient move %s:->%s, mask=%i, mp diff: %i",
 					source.coord.toString(),
 					destination.coord.toString(),
 					destinationNode->chainMask,
 					node.moveRemains - destinationNode->moveRemains);
+#endif
 
 				return true;
 			}

+ 4 - 4
AI/VCAI/Pathfinding/AINodeStorage.h

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

+ 1 - 3
AI/VCAI/VCAI.cpp

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

+ 1 - 0
AI/VCAI/VCAI.h

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

+ 1 - 1
lib/CGameInfoCallback.cpp

@@ -710,7 +710,7 @@ std::vector < const CGTownInstance *> CPlayerSpecificInfoCallback::getTownsInfo(
 	{
 		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);
 			}

+ 5 - 1
lib/CPathfinder.cpp

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