2
0
Andrii Danylchenko 4 жил өмнө
parent
commit
774f531c4e

+ 112 - 45
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -132,7 +132,8 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
 		initialNode->turns = actor->initialTurn;
 		initialNode->moveRemains = actor->initialMovement;
 		initialNode->danger = 0;
-		initialNode->cost = 0.0;
+		initialNode->cost = actor->initialTurn;
+		initialNode->action = CGPathNode::ENodeAction::NORMAL;
 
 		if(actor->isMovable)
 		{
@@ -175,6 +176,16 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf
 		{
 			dstNode->specialAction->applyOnDestination(dstNode->actor->hero, destination, source, dstNode, srcNode);
 		}
+
+#ifdef VCMI_TRACE_PATHFINDER_EX
+		logAi->trace(
+			"Commited %s -> %s, cost: %f, hero: %s, mask: %i", 
+			source.coord.toString(),
+			destination.coord.toString(),
+			destination.cost,
+			dstNode->actor->hero->name,
+			dstNode->actor->chainMask);
+#endif
 	});
 }
 
@@ -194,6 +205,7 @@ void AINodeStorage::commit(
 	destination->manaCost = source->manaCost;
 	destination->danger = source->danger;
 	destination->theNodeBefore = source->theNodeBefore;
+	destination->chainOther = nullptr;
 }
 
 std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
@@ -227,33 +239,53 @@ bool AINodeStorage::calculateHeroChain()
 	heroChainPass = true;
 	heroChain.resize(0);
 
+	std::vector<AIPathNode *> buffer;
+
+	buffer.reserve(NUM_CHAINS);
+
 	foreach_tile_pos([&](const int3 & pos) {
 		auto layer = EPathfindingLayer::LAND;
 		auto chains = nodes[pos.x][pos.y][pos.z][layer];
 
+		buffer.resize(0);
+
 		for(AIPathNode & node : chains)
 		{
-			if(node.locked && node.turns < 1)
-				addHeroChain(&node);
+			if(node.turns <= heroChainMaxTurns && node.action != CGPathNode::ENodeAction::UNKNOWN)
+				buffer.push_back(&node);
+		}
+
+		for(AIPathNode * node : buffer)
+		{
+			addHeroChain(node, buffer);
 		}
 	});
 
 	return heroChain.size();
 }
 
-void AINodeStorage::addHeroChain(AIPathNode * srcNode)
+void AINodeStorage::addHeroChain(AIPathNode * srcNode, std::vector<AIPathNode *> variants)
 {
-	auto chains = nodes[srcNode->coord.x][srcNode->coord.y][srcNode->coord.z][srcNode->layer];
-
-	for(AIPathNode & node : chains)
+	for(AIPathNode * node : variants)
 	{
-		if(!node.locked || !node.actor || node.action == CGPathNode::ENodeAction::UNKNOWN && node.actor->hero)
+		if(node == srcNode || !node->actor || node->turns > heroChainMaxTurns 
+			|| node->action == CGPathNode::ENodeAction::UNKNOWN && node->actor->hero)
 		{
 			continue;
 		}
 
-		addHeroChain(srcNode, &node);
-		addHeroChain(&node, srcNode);
+#ifdef VCMI_TRACE_PATHFINDER_EX
+		logAi->trace(
+			"Thy exchange %s[%i] -> %s[%i] at %s",
+			node->actor->hero->name,
+			node->actor->chainMask,
+			srcNode->actor->hero->name,
+			srcNode->actor->chainMask,
+			srcNode->coord.toString());
+#endif
+
+		addHeroChain(srcNode, node);
+		//addHeroChain(&node, srcNode);
 	}
 }
 
@@ -261,37 +293,65 @@ void AINodeStorage::addHeroChain(AIPathNode * carrier, AIPathNode * other)
 {
 	if(carrier->actor->canExchange(other->actor))
 	{
+#ifdef VCMI_TRACE_PATHFINDER_EX
+		logAi->trace(
+			"Exchange allowed %s[%i] -> %s[%i] at %s",
+			other->actor->hero->name,
+			other->actor->chainMask,
+			carrier->actor->hero->name,
+			carrier->actor->chainMask,
+			carrier->coord.toString());
+#endif
+
 		bool hasLessMp = carrier->turns > other->turns || carrier->moveRemains < other->moveRemains;
 		bool hasLessExperience = carrier->actor->hero->exp < other->actor->hero->exp;
 
-#ifdef VCMI_TRACE_PATHFINDER
-		logAi->trace("Check hero exhange at %s, %s -> %s", carrier->coord.toString(), other->actor->hero->name, carrier->actor->hero->name);
-#endif
-
 		if(hasLessMp && hasLessExperience)
+		{
+#ifdef VCMI_TRACE_PATHFINDER_EX
+			logAi->trace("Exchange at %s is ineficient. Blocked.", carrier->coord.toString());
+#endif
 			return;
+		}
 
 		auto newActor = carrier->actor->exchange(other->actor);
 		auto chainNodeOptional = getOrCreateNode(carrier->coord, carrier->layer, newActor);
 
 		if(!chainNodeOptional)
+		{
+#ifdef VCMI_TRACE_PATHFINDER_EX
+			logAi->trace("Exchange at %s can not allocate node. Blocked.", carrier->coord.toString());
+#endif
 			return;
+		}
 
 		auto chainNode = chainNodeOptional.get();
 
-		if(chainNode->locked)
-			return;
-
-#ifdef VCMI_TRACE_PATHFINDER
-		logAi->trace("Hero exhange at %s, %s -> %s", carrier->coord.toString(), other->actor->hero->name, carrier->actor->hero->name);
+		if(chainNode->action != CGPathNode::ENodeAction::UNKNOWN)
+		{
+#ifdef VCMI_TRACE_PATHFINDER_EX
+			logAi->trace("Exchange at %s node is already in use. Blocked.", carrier->coord.toString());
 #endif
+			return;
+		}
 		
-		commitExchange(chainNode, carrier, other);
-		heroChain.push_back(chainNode);
+		if(commitExchange(chainNode, carrier, other))
+		{
+#ifdef VCMI_TRACE_PATHFINDER_EX
+			logAi->trace(
+				"Chain accepted at %s %s -> %s, mask %i, cost %f", 
+				chainNode->coord.toString(), 
+				other->actor->hero->name, 
+				chainNode->actor->hero->name,
+				chainNode->actor->chainMask,
+				chainNode->cost);
+#endif
+			heroChain.push_back(chainNode);
+		}
 	}
 }
 
-void AINodeStorage::commitExchange(
+bool AINodeStorage::commitExchange(
 	AIPathNode * exchangeNode, 
 	AIPathNode * carrierParentNode, 
 	AIPathNode * otherParentNode) const
@@ -302,7 +362,7 @@ void AINodeStorage::commitExchange(
 
 	auto armyLoss = carrierParentNode->armyLoss + otherParentNode->armyLoss;
 	auto turns = carrierParentNode->turns;
-	auto cost = carrierParentNode->cost;
+	auto cost = carrierParentNode->cost + otherParentNode->cost / 1000.0;
 	auto movementLeft = carrierParentNode->moveRemains;
 
 	if(carrierParentNode->turns < otherParentNode->turns)
@@ -312,27 +372,24 @@ void AINodeStorage::commitExchange(
 			+ carrierParentNode->moveRemains / (float)moveRemains;
 
 		turns = otherParentNode->turns;
-		cost = waitingCost;
+		cost += waitingCost;
 		movementLeft = moveRemains;
 	}
 		
 	if(exchangeNode->turns != 0xFF && exchangeNode->cost < cost)
-		return;
-
-#ifdef VCMI_TRACE_PATHFINDER
-	logAi->trace(
-		"Accepted hero exhange at %s, carrier %s, mp cost %f", 
-		exchangeNode->coord.toString(),
-		carrierActor->hero->name,
-		cost);
+	{
+#ifdef VCMI_TRACE_PATHFINDER_EX
+		logAi->trace("Exchange at %s is is not effective enough. %f < %f", exchangeNode->coord.toString(), exchangeNode->cost, cost);
 #endif
+		return false;
+	}
 	
 	commit(exchangeNode, carrierParentNode, carrierParentNode->action, turns, movementLeft, cost);
 
-	exchangeNode->theNodeBefore = carrierParentNode;
 	exchangeNode->chainOther = otherParentNode;
 	exchangeNode->armyLoss = armyLoss;
-	exchangeNode->manaCost = carrierParentNode->manaCost;
+
+	return true;
 }
 
 const CGHeroInstance * AINodeStorage::getHero(const CGPathNode * node) const
@@ -538,6 +595,7 @@ std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand)
 		path.heroArmy = node.actor->creatureSet;
 		path.armyLoss = node.armyLoss;
 		path.targetObjectDanger = evaluateDanger(pos, path.targetHero);
+		path.chainMask = node.actor->chainMask;
 		
 		fillChainInfo(&node, path);
 
@@ -551,22 +609,26 @@ void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path) const
 {
 	while(node != nullptr)
 	{
-		if(!node->actor->hero || node->coord == node->actor->hero->visitablePos())
+		if(!node->actor->hero)
 			return;
 
-		AIPathNodeInfo pathNode;
-		pathNode.cost = node->cost;
-		pathNode.targetHero = node->actor->hero;
-		pathNode.turns = node->turns;
-		pathNode.danger = node->danger;
-		pathNode.coord = node->coord;
-
-		path.nodes.push_back(pathNode);
-		path.specialAction = node->specialAction;
-
 		if(node->chainOther)
 			fillChainInfo(node->chainOther, path);
 
+		if(node->actor->hero->visitablePos() != node->coord)
+		{
+			AIPathNodeInfo pathNode;
+			pathNode.cost = node->cost;
+			pathNode.targetHero = node->actor->hero;
+			pathNode.turns = node->turns;
+			pathNode.danger = node->danger;
+			pathNode.coord = node->coord;
+
+			path.nodes.push_back(pathNode);
+		}
+
+		path.specialAction = node->specialAction;
+
 		node = getAINode(node->theNodeBefore);
 	}
 }
@@ -586,6 +648,11 @@ int3 AIPath::firstTileToGet() const
 	return int3(-1, -1, -1);
 }
 
+const AIPathNodeInfo & AIPath::firstNode() const
+{
+	return nodes.back();
+}
+
 uint64_t AIPath::getPathDanger() const
 {
 	if(nodes.size())

+ 8 - 2
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -11,6 +11,7 @@
 #pragma once
 
 #define VCMI_TRACE_PATHFINDER
+#define VCMI_TRACE_PATHFINDER_EX
 
 #include "../../../lib/CPathfinder.h"
 #include "../../../lib/mapObjects/CGHeroInstance.h"
@@ -19,6 +20,7 @@
 #include "../Goals/AbstractGoal.h"
 #include "Actions/ISpecialAction.h"
 #include "Actors.h"
+#include <inttypes.h>
 
 #define VCMI_TRACE_PATHFINDER
 
@@ -49,6 +51,7 @@ struct AIPath
 	uint64_t armyLoss;
 	const CGHeroInstance * targetHero;
 	const CCreatureSet * heroArmy;
+	uint64_t chainMask;
 
 	AIPath();
 
@@ -60,6 +63,8 @@ struct AIPath
 
 	int3 firstTileToGet() const;
 
+	const AIPathNodeInfo & firstNode() const;
+
 	float movementCost() const;
 
 	uint64_t getHeroStrength() const;
@@ -78,6 +83,7 @@ private:
 	std::vector<std::shared_ptr<ChainActor>> actors;
 	std::vector<CGPathNode *> heroChain;
 	bool heroChainPass; // true if we need to calculate hero chain
+	int heroChainMaxTurns = 0;
 
 public:
 	/// more than 1 chain layer for each hero allows us to have more than 1 path to each tile so we can chose more optimal one.
@@ -123,7 +129,7 @@ public:
 private:
 	STRONG_INLINE
 	void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility);
-	void addHeroChain(AIPathNode * srcNode);
+	void addHeroChain(AIPathNode * srcNode, std::vector<AIPathNode *> variants);
 	void addHeroChain(AIPathNode * carrier, AIPathNode * other);
 	void calculateTownPortalTeleportations(const PathNodeInfo & source, std::vector<CGPathNode *> & neighbours);
 	void fillChainInfo(const AIPathNode * node, AIPath & path) const;
@@ -135,7 +141,7 @@ private:
 		int movementLeft, 
 		float cost) const;
 
-	void AINodeStorage::commitExchange(
+	bool AINodeStorage::commitExchange(
 		AIPathNode * exchangeNode, 
 		AIPathNode * carrierParentNode, 
 		AIPathNode * otherParentNode) const;

+ 7 - 5
AI/Nullkiller/Pathfinding/AIPathfinder.cpp

@@ -51,18 +51,20 @@ void AIPathfinder::updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain)
 	}
 
 	logAi->debug("Recalculate all paths");
+	int pass = 0;
 
 	storage->clear();
 	storage->setHeroes(heroes, ai);
 
 	auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, storage);
-	cb->calculatePaths(config);
 
-	while(useHeroChain && storage->calculateHeroChain())
-	{
-		config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, storage);
+	do {
+		logAi->trace("Recalculate paths pass %" PRIi32, pass++);
 		cb->calculatePaths(config);
-	}
+
+		logAi->trace("Recalculate chain pass %" PRIi32, pass);
+		useHeroChain = useHeroChain && storage->calculateHeroChain();
+	} while(useHeroChain);
 }
 
 void AIPathfinder::updatePaths(const HeroPtr & hero)

+ 0 - 1
AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp

@@ -13,7 +13,6 @@
 #include "Rules/AIMovementAfterDestinationRule.h"
 #include "Rules/AIMovementToDestinationRule.h"
 #include "Rules/AIPreviousNodeRule.h"
-#include "Rules/AIMovementCostRule.h"
 
 namespace AIPathfinding
 {

+ 7 - 9
AI/Nullkiller/Pathfinding/Actors.cpp

@@ -82,21 +82,16 @@ void ChainActor::setBaseActor(HeroActor * base)
 	armyValue = base->armyValue;
 	chainMask = base->chainMask;
 	creatureSet = base->creatureSet;
+	isMovable = base->isMovable;
 }
 
 void HeroActor::setupSpecialActors()
 {
 	auto allActors = std::vector<ChainActor *>{ this };
 
-	for(int i = 1; i <= SPECIAL_ACTORS_COUNT; i++)
+	for(ChainActor & specialActor : specialActors)
 	{
-		ChainActor & specialActor = specialActors[i - 1];
-
 		specialActor.setBaseActor(this);
-		specialActor.allowBattle = (i & 1) > 0;
-		specialActor.allowSpellCast = (i & 2) > 0;
-		specialActor.allowUseResources = (i & 4) > 0;
-
 		allActors.push_back(&specialActor);
 	}
 
@@ -104,6 +99,9 @@ void HeroActor::setupSpecialActors()
 	{
 		ChainActor * actor = allActors[i];
 
+		actor->allowBattle = (i & 1) > 0;
+		actor->allowSpellCast = (i & 2) > 0;
+		actor->allowUseResources = (i & 4) > 0;
 		actor->battleActor = allActors[i | 1];
 		actor->castActor = allActors[i | 2];
 		actor->resourceActor = allActors[i | 4];
@@ -180,7 +178,7 @@ CCreatureSet * HeroActor::pickBestCreatures(const CCreatureSet * army1, const CC
 	{
 		for(auto & i : armyPtr->Slots())
 		{
-			creToPower[i.second->type] += i.second->getPower();
+			creToPower[i.second->type] += i.second->count;
 		}
 	}
 	//TODO - consider more than just power (ie morale penalty, hero specialty in certain stacks, etc)
@@ -193,7 +191,7 @@ CCreatureSet * HeroActor::pickBestCreatures(const CCreatureSet * army1, const CC
 		typedef const std::pair<const CCreature *, int> & CrePowerPair;
 		auto creIt = boost::max_element(creToPower, [](CrePowerPair lhs, CrePowerPair rhs)
 		{
-			return lhs.second < rhs.second;
+			return lhs.first->AIValue * lhs.second < rhs.first->AIValue * rhs.second;
 		});
 
 		target->addToSlot(SlotID(i), creIt->first->idNumber, TQuantity(creIt->second));

+ 24 - 11
AI/Nullkiller/Pathfinding/PathfindingManager.cpp

@@ -125,14 +125,23 @@ Goals::TGoalVec PathfindingManager::findPaths(
 
 	for(auto path : chainInfo)
 	{
-		if(hero && hero.get() != path.targetHero)
+		if(hero && hero.get() != path.targetHero || path.nodes.empty())
 			continue;
 
-		int3 firstTileToGet = path.firstTileToGet();
+		const AIPathNodeInfo & firstNode = path.firstNode();
+		int3 firstTileToGet = firstNode.coord;
+
 #ifdef VCMI_TRACE_PATHFINDER
-		logAi->trace("Path found size=%i, first tile=%s", path.nodes.size(), firstTileToGet.toString());
+		std::stringstream str;
+
+		str << "Path found ";
+
+		for(auto node : path.nodes)
+			str << node.targetHero->name << "->" << node.coord.toString() << "; ";
+
+		logAi->trace(str.str());
 #endif
-		if(firstTileToGet.valid() && ai->isTileNotReserved(hero.get(), firstTileToGet))
+		if(ai->isTileNotReserved(hero.get(), firstTileToGet))
 		{
 			danger = path.getTotalDanger(hero);
 
@@ -148,7 +157,7 @@ Goals::TGoalVec PathfindingManager::findPaths(
 				{
 					solution = dest == firstTileToGet
 						? doVisitTile(firstTileToGet)
-						: clearWayTo(hero, firstTileToGet);
+						: clearWayTo(firstNode.targetHero, firstTileToGet);
 				}
 
 				if(solution->invalid())
@@ -161,7 +170,13 @@ Goals::TGoalVec PathfindingManager::findPaths(
 				solution->evaluationContext.armyLoss += path.armyLoss;
 				solution->evaluationContext.heroStrength = path.getHeroStrength();
 #ifdef VCMI_TRACE_PATHFINDER
-				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());
+				logAi->trace("It's safe for %s to visit tile %s with danger %s, loss %s, army strength %s, goal %s", 
+					hero->name, 
+					dest.toString(), 
+					std::to_string(danger),
+					std::to_string(path.armyLoss),
+					std::to_string(path.heroArmy->getArmyStrength()),
+					solution->name());
 #endif
 				result.push_back(solution);
 
@@ -210,9 +225,7 @@ Goals::TSubgoal PathfindingManager::clearWayTo(HeroPtr hero, int3 firstTileToGet
 		{
 			if(topObj != hero.get(true)) //the hero we want to free
 			{
-				logAi->error("%s stands in the way of %s", topObj->getObjectName(), hero->getObjectName());
-
-				return sptr(Goals::Invalid());
+				logAi->warn("%s stands in the way of %s", topObj->getObjectName(), hero->getObjectName());
 			}
 		}
 
@@ -240,10 +253,10 @@ Goals::TSubgoal PathfindingManager::clearWayTo(HeroPtr hero, int3 firstTileToGet
 	return sptr(Goals::VisitTile(firstTileToGet).sethero(hero).setisAbstract(true));
 }
 
-void PathfindingManager::updatePaths(std::vector<HeroPtr> heroes)
+void PathfindingManager::updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain)
 {
 	logAi->debug("AIPathfinder has been reseted.");
-	pathfinder->updatePaths(heroes);
+	pathfinder->updatePaths(heroes, useHeroChain);
 }
 
 void PathfindingManager::updatePaths(const HeroPtr & hero)

+ 2 - 2
AI/Nullkiller/Pathfinding/PathfindingManager.h

@@ -20,7 +20,7 @@ public:
 	virtual void init(CPlayerSpecificInfoCallback * CB) = 0;
 	virtual void setAI(VCAI * AI) = 0;
 
-	virtual void updatePaths(std::vector<HeroPtr> heroes) = 0;
+	virtual void updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain = false) = 0;
 	virtual void updatePaths(const HeroPtr & hero) = 0;
 	virtual Goals::TGoalVec howToVisitTile(const HeroPtr & hero, const int3 & tile, bool allowGatherArmy = true) const = 0;
 	virtual Goals::TGoalVec howToVisitObj(const HeroPtr & hero, ObjectIdRef obj, bool allowGatherArmy = true) const = 0;
@@ -47,7 +47,7 @@ public:
 	Goals::TGoalVec howToVisitTile(const int3 & tile, bool allowGatherArmy = true) const override;
 	Goals::TGoalVec howToVisitObj(ObjectIdRef obj, bool allowGatherArmy = true) const override;
 	std::vector<AIPath> getPathsToTile(const HeroPtr & hero, const int3 & tile) const override;
-	void updatePaths(std::vector<HeroPtr> heroes) override;
+	void updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain = false) override;
 	void updatePaths(const HeroPtr & hero) override;
 
 	STRONG_INLINE

+ 1 - 30
AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.cpp

@@ -38,39 +38,10 @@ namespace AIPathfinding
 
 		auto srcNode = nodeStorage->getAINode(source.node);
 
-		if(srcNode->specialAction)
+		if(srcNode->specialAction || srcNode->chainOther)
 		{
 			// there is some action on source tile which should be performed before we can bypass it
 			destination.node->theNodeBefore = source.node;
 		}
-
-		auto dstNode = nodeStorage->getAINode(destination.node);
-		auto srcActor = srcNode->actor;
-		auto dstActor = dstNode->actor;
-
-		if(srcActor == dstActor)
-		{
-			return;
-		}
-
-		auto carrierActor = dstActor->carrierParent;
-		auto otherActor = dstActor->otherParent;
-
-		nodeStorage->updateAINode(destination.node, [&](AIPathNode * dstNode) {
-			if(source.coord == destination.coord)
-			{
-				auto carrierNode = nodeStorage->getOrCreateNode(source.coord, source.node->layer, carrierActor).get();
-				auto otherNode = nodeStorage->getOrCreateNode(source.coord, source.node->layer, otherActor).get();
-
-				if(destination.coord != carrierNode->coord)
-					dstNode->theNodeBefore = carrierNode;
-
-				dstNode->chainOther = otherNode;
-
-#ifdef VCMI_TRACE_PATHFINDER
-				logAi->trace("Link Hero exhange at %s, %s -> %s", dstNode->coord.toString(), otherNode->actor->hero->name, carrierNode->actor->hero->name);
-#endif
-			}
-		});
 	}
 }