فهرست منبع

AI: inefective chain cancellation

Andrii Danylchenko 4 سال پیش
والد
کامیت
f44eaf8132

+ 115 - 51
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -245,15 +245,18 @@ bool AINodeStorage::calculateHeroChain()
 	heroChainPass = true;
 	heroChain.resize(0);
 
-	std::vector<AIPathNode *> buffer;
+	std::vector<AIPathNode *> existingChains;
+	std::vector<ExchangeCandidate> newChains;
 
-	buffer.reserve(NUM_CHAINS);
+	existingChains.reserve(NUM_CHAINS);
+	newChains.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);
+		existingChains.resize(0);
+		newChains.resize(0);
 
 		for(AIPathNode & node : chains)
 		{
@@ -261,22 +264,40 @@ bool AINodeStorage::calculateHeroChain()
 				logAi->trace(node.actor->toString());
 
 			if(node.turns <= heroChainMaxTurns && node.action != CGPathNode::ENodeAction::UNKNOWN)
-				buffer.push_back(&node);
+				existingChains.push_back(&node);
 		}
 
-		for(AIPathNode * node : buffer)
+		for(AIPathNode * node : existingChains)
 		{
 			if(node->actor->hero)
 			{
-				addHeroChain(node, buffer);
+				calculateHeroChain(node, existingChains, newChains);
 			}
 		}
+
+		cleanupInefectiveChains(newChains);
+		addHeroChain(newChains);
 	});
 
 	return heroChain.size();
 }
 
-void AINodeStorage::addHeroChain(AIPathNode * srcNode, std::vector<AIPathNode *> variants)
+void AINodeStorage::cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const
+{
+	vstd::erase_if(result, [&](ExchangeCandidate & chainInfo) -> bool
+	{
+		auto pos = chainInfo.coord;
+		auto chains = nodes[pos.x][pos.y][pos.z][EPathfindingLayer::LAND];
+
+		return hasBetterChain(chainInfo.carrierParent, &chainInfo, chains)
+			|| hasBetterChain(chainInfo.carrierParent, &chainInfo, result);
+	});
+}
+
+void AINodeStorage::calculateHeroChain(
+	AIPathNode * srcNode, 
+	const std::vector<AIPathNode *> & variants, 
+	std::vector<ExchangeCandidate> & result) const
 {
 	for(AIPathNode * node : variants)
 	{
@@ -296,12 +317,14 @@ void AINodeStorage::addHeroChain(AIPathNode * srcNode, std::vector<AIPathNode *>
 			srcNode->coord.toString());
 #endif
 
-		addHeroChain(srcNode, node);
-		//addHeroChain(&node, srcNode);
+		calculateHeroChain(srcNode, node, result);
 	}
 }
 
-void AINodeStorage::addHeroChain(AIPathNode * carrier, AIPathNode * other)
+void AINodeStorage::calculateHeroChain(
+	AIPathNode * carrier, 
+	AIPathNode * other, 
+	std::vector<ExchangeCandidate> & result) const
 {
 	if(!carrier->actor->isMovable)
 		return;
@@ -330,6 +353,18 @@ void AINodeStorage::addHeroChain(AIPathNode * carrier, AIPathNode * other)
 		}
 
 		auto newActor = carrier->actor->exchange(other->actor);
+		
+		result.push_back(calculateExchange(newActor, carrier, other));
+	}
+}
+
+void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
+{
+	for(const ExchangeCandidate & chainInfo : result)
+	{
+		auto carrier = chainInfo.carrierParent;
+		auto newActor = chainInfo.actor;
+		auto other = chainInfo.otherParent;
 		auto chainNodeOptional = getOrCreateNode(carrier->coord, carrier->layer, newActor);
 
 		if(!chainNodeOptional)
@@ -337,74 +372,81 @@ void AINodeStorage::addHeroChain(AIPathNode * carrier, AIPathNode * other)
 #ifdef VCMI_TRACE_PATHFINDER_EX
 			logAi->trace("Exchange at %s can not allocate node. Blocked.", carrier->coord.toString());
 #endif
-			return;
+			continue;
 		}
 
-		auto chainNode = chainNodeOptional.get();
+		auto exchangeNode = chainNodeOptional.get();
 
-		if(chainNode->action != CGPathNode::ENodeAction::UNKNOWN)
+		if(exchangeNode->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;
+			continue;
 		}
 		
-		if(commitExchange(chainNode, carrier, other))
+		if(exchangeNode->turns != 0xFF && exchangeNode->cost < chainInfo.cost)
 		{
 #ifdef VCMI_TRACE_PATHFINDER_EX
 			logAi->trace(
-				"Chain accepted at %s %s -> %s, mask %i, cost %f", 
-				chainNode->coord.toString(), 
-				other->actor->toString(), 
-				chainNode->actor->toString(),
-				chainNode->actor->chainMask,
-				chainNode->cost);
+				"Exchange at %s is is not effective enough. %f < %f", 
+				exchangeNode->coord.toString(), 
+				exchangeNode->cost, 
+				chainInfo.cost);
 #endif
-			heroChain.push_back(chainNode);
+			continue;
 		}
+
+		commit(exchangeNode, carrier, carrier->action, chainInfo.turns, chainInfo.moveRemains, chainInfo.cost);
+
+		exchangeNode->chainOther = other;
+		exchangeNode->armyLoss = chainInfo.armyLoss;
+
+#ifdef VCMI_TRACE_PATHFINDER_EX
+		logAi->trace(
+			"Chain accepted at %s %s -> %s, mask %i, cost %f", 
+			exchangeNode->coord.toString(), 
+			other->actor->toString(), 
+			exchangeNode->actor->toString(),
+			exchangeNode->actor->chainMask,
+			exchangeNode->cost);
+#endif
+		heroChain.push_back(exchangeNode);
 	}
 }
 
-bool AINodeStorage::commitExchange(
-	AIPathNode * exchangeNode, 
+ExchangeCandidate AINodeStorage::calculateExchange(
+	ChainActor * exchangeActor, 
 	AIPathNode * carrierParentNode, 
 	AIPathNode * otherParentNode) const
 {
+	ExchangeCandidate candidate;
+
 	auto carrierActor = carrierParentNode->actor;
-	auto exchangeActor = exchangeNode->actor;
 	auto otherActor = otherParentNode->actor;
 
-	auto armyLoss = carrierParentNode->armyLoss + otherParentNode->armyLoss;
-	auto turns = carrierParentNode->turns;
-	auto cost = carrierParentNode->cost + otherParentNode->cost / 1000.0;
-	auto movementLeft = carrierParentNode->moveRemains;
+	candidate.layer = carrierParentNode->layer;
+	candidate.coord = carrierParentNode->coord;
+	candidate.carrierParent = carrierParentNode;
+	candidate.otherParent = otherParentNode;
+	candidate.actor = exchangeActor;
+	candidate.armyLoss = carrierParentNode->armyLoss + otherParentNode->armyLoss;
+	candidate.turns = carrierParentNode->turns;
+	candidate.cost = carrierParentNode->cost + otherParentNode->cost / 1000.0;
+	candidate.moveRemains = carrierParentNode->moveRemains;
 
 	if(carrierParentNode->turns < otherParentNode->turns)
 	{
-		int moveRemains = exchangeActor->hero->maxMovePoints(exchangeNode->layer);
+		int moveRemains = exchangeActor->hero->maxMovePoints(carrierParentNode->layer);
 		float waitingCost = otherParentNode->turns - carrierParentNode->turns - 1
 			+ carrierParentNode->moveRemains / (float)moveRemains;
 
-		turns = otherParentNode->turns;
-		cost += waitingCost;
-		movementLeft = moveRemains;
+		candidate.turns = otherParentNode->turns;
+		candidate.cost += waitingCost;
+		candidate.moveRemains = moveRemains;
 	}
-		
-	if(exchangeNode->turns != 0xFF && exchangeNode->cost < 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->chainOther = otherParentNode;
-	exchangeNode->armyLoss = armyLoss;
-
-	return true;
+	return candidate;
 }
 
 const CGHeroInstance * AINodeStorage::getHero(const CGPathNode * node) const
@@ -587,13 +629,23 @@ bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNode
 {
 	auto pos = destination.coord;
 	auto chains = nodes[pos.x][pos.y][pos.z][EPathfindingLayer::LAND];
-	auto destinationNode = getAINode(destination.node);
+
+	return hasBetterChain(source.node, getAINode(destination.node), chains);
+}
+
+template<class NodeRange>
+bool AINodeStorage::hasBetterChain(
+	const CGPathNode * source, 
+	const AIPathNode * destinationNode,
+	const NodeRange & chains) const
+{
+	auto dstActor = destinationNode->actor;
 
 	for(const AIPathNode & node : chains)
 	{
 		auto sameNode = node.actor == destinationNode->actor;
 
-		if(sameNode	|| node.action == CGPathNode::ENodeAction::UNKNOWN)
+		if(sameNode	|| node.action == CGPathNode::ENodeAction::UNKNOWN || !node.actor->hero)
 		{
 			continue;
 		}
@@ -605,14 +657,26 @@ bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNode
 #ifdef VCMI_TRACE_PATHFINDER
 				logAi->trace(
 					"Block ineficient move %s:->%s, mask=%i, mp diff: %i",
-					source.coord.toString(),
-					destination.coord.toString(),
+					source->coord.toString(),
+					destinationNode->coord.toString(),
 					destinationNode->actor->chainMask,
 					node.moveRemains - destinationNode->moveRemains);
 #endif
 				return true;
 			}
 		}
+
+		if(dstActor->actorExchangeCount == 1)
+			continue;
+
+		auto nodeActor = node.actor;
+
+		if(nodeActor->armyValue - node.armyLoss >= dstActor->armyValue - destinationNode->armyLoss
+			&& nodeActor->heroFightingStrength >= dstActor->heroFightingStrength
+			&& node.cost >= destinationNode->cost)
+		{
+			return true;
+		}
 	}
 
 	return false;

+ 29 - 4
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -68,6 +68,12 @@ struct AIPath
 	uint64_t getHeroStrength() const;
 };
 
+struct ExchangeCandidate : public AIPathNode
+{
+	AIPathNode * carrierParent;
+	AIPathNode * otherParent;
+};
+
 class AINodeStorage : public INodeStorage
 {
 private:
@@ -110,6 +116,13 @@ public:
 	void updateAINode(CGPathNode * node, std::function<void (AIPathNode *)> updater);
 
 	bool hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const;
+
+	template<class NodeRange>
+	bool hasBetterChain(
+		const CGPathNode * source, 
+		const AIPathNode * destinationNode,
+		const NodeRange & chains) const;
+
 	boost::optional<AIPathNode *> getOrCreateNode(const int3 & coord, const EPathfindingLayer layer, const ChainActor * actor);
 	std::vector<AIPath> getChainInfo(const int3 & pos, bool isOnLand) const;
 	bool isTileAccessible(const HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const;
@@ -130,8 +143,20 @@ public:
 private:
 	STRONG_INLINE
 	void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility);
-	void addHeroChain(AIPathNode * srcNode, std::vector<AIPathNode *> variants);
-	void addHeroChain(AIPathNode * carrier, AIPathNode * other);
+
+	void calculateHeroChain(
+		AIPathNode * srcNode, 
+		const std::vector<AIPathNode *> & variants, 
+		std::vector<ExchangeCandidate> & result) const;
+
+	void calculateHeroChain(
+		AIPathNode * carrier, 
+		AIPathNode * other, 
+		std::vector<ExchangeCandidate> & result) const;
+	
+	void cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const;
+	void addHeroChain(const std::vector<ExchangeCandidate> & result);
+
 	void calculateTownPortalTeleportations(const PathNodeInfo & source, std::vector<CGPathNode *> & neighbours);
 	void fillChainInfo(const AIPathNode * node, AIPath & path) const;
 	void commit(
@@ -142,8 +167,8 @@ private:
 		int movementLeft, 
 		float cost) const;
 
-	bool AINodeStorage::commitExchange(
-		AIPathNode * exchangeNode, 
+	ExchangeCandidate calculateExchange(
+		ChainActor * exchangeActor, 
 		AIPathNode * carrierParentNode, 
 		AIPathNode * otherParentNode) const;
 };

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

@@ -18,25 +18,28 @@
 
 ChainActor::ChainActor(const CGHeroInstance * hero, uint64_t chainMask)
 	:hero(hero), isMovable(true), chainMask(chainMask), creatureSet(hero),
-	baseActor(this), carrierParent(nullptr), otherParent(nullptr)
+	baseActor(this), carrierParent(nullptr), otherParent(nullptr), actorExchangeCount(1)
 {
 	initialPosition = hero->visitablePos();
 	layer = hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND;
 	initialMovement = hero->movement;
 	initialTurn = 0;
 	armyValue = hero->getArmyStrength();
+	heroFightingStrength = hero->getFightingStrength();
 }
 
 ChainActor::ChainActor(const ChainActor * carrier, const ChainActor * other, const CCreatureSet * heroArmy)
 	:hero(carrier->hero), isMovable(true), creatureSet(heroArmy), chainMask(carrier->chainMask | other->chainMask),
-	baseActor(this), carrierParent(carrier), otherParent(other)
+	baseActor(this), carrierParent(carrier), otherParent(other), heroFightingStrength(carrier->heroFightingStrength),
+	actorExchangeCount(carrier->actorExchangeCount + other->actorExchangeCount)
 {
 	armyValue = heroArmy->getArmyStrength();
 }
 
 ChainActor::ChainActor(const CGObjectInstance * obj, const CCreatureSet * creatureSet, uint64_t chainMask, int initialTurn)
 	:hero(nullptr), isMovable(false), creatureSet(creatureSet), chainMask(chainMask),
-	baseActor(this), carrierParent(nullptr), otherParent(nullptr), initialTurn(initialTurn), initialMovement(0)
+	baseActor(this), carrierParent(nullptr), otherParent(nullptr), initialTurn(initialTurn), initialMovement(0),
+	heroFightingStrength(0), actorExchangeCount(1)
 {
 	initialPosition = obj->visitablePos();
 	layer = EPathfindingLayer::LAND;
@@ -77,6 +80,7 @@ void ChainActor::setBaseActor(HeroActor * base)
 	chainMask = base->chainMask;
 	creatureSet = base->creatureSet;
 	isMovable = base->isMovable;
+	heroFightingStrength = base->heroFightingStrength;
 }
 
 void HeroActor::setupSpecialActors()

+ 2 - 0
AI/Nullkiller/Pathfinding/Actors.h

@@ -44,6 +44,8 @@ public:
 	uint32_t initialMovement;
 	uint32_t initialTurn;
 	uint64_t armyValue;
+	float heroFightingStrength;
+	uint8_t actorExchangeCount;
 
 	ChainActor(){}