Преглед изворни кода

Nullkiller: town portal fixes

Andrii Danylchenko пре 4 година
родитељ
комит
1096f2e9e6

+ 16 - 7
AI/Nullkiller/Behaviors/DefenceBehavior.cpp

@@ -62,7 +62,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 
 	if(town->garrisonHero)
 	{
-		if(ai->nullkiller->isActive(town->garrisonHero.get()))
+		if(!ai->nullkiller->isHeroLocked(town->garrisonHero.get()))
 		{
 			tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, nullptr).setpriority(5)));
 			return;
@@ -70,8 +70,8 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 
 		logAi->debug(
 			"Hero %s in garrison of town %s is suposed to defend the town",
-			town->name,
-			town->garrisonHero->name);
+			town->garrisonHero->name,
+			town->name);
 
 		return;
 	}
@@ -107,9 +107,13 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 	{
 		for(auto & treat : treats)
 		{
-			if(path.turn() < treat.turn && isSafeToVisit(path.targetHero, path.heroArmy, treat.danger))
+			if(isSafeToVisit(path.targetHero, path.heroArmy, treat.danger))
 			{
-				logAi->debug("Town %s is not in danger.", town->name);
+				logAi->debug(
+					"Hero %s can eliminate danger for town %s using path %s.", 
+					path.targetHero->name,
+					town->name,
+					path.toString());
 
 				return;
 			}
@@ -141,15 +145,18 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 			if(path.targetHero == town->visitingHero && path.exchangeCount == 1)
 			{
 #if AI_TRACE_LEVEL >= 1
-				logAi->trace("Put %s to garrison of town %s with priority %f", 
+				logAi->trace("Put %s to garrison of town %s with priority %f",
 					path.targetHero->name,
 					town->name,
 					priority);
 #endif
 
 				tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, town->visitingHero.get()).setpriority(priority)));
+
+				continue;
 			}
-			else if(path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= treat.danger)
+				
+			if(path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= treat.danger)
 			{
 #if AI_TRACE_LEVEL >= 1
 				logAi->trace("Move %s to defend town %s with priority %f",
@@ -159,6 +166,8 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 #endif
 
 				tasks.push_back(Goals::sptr(Goals::ExecuteHeroChain(path, town).setpriority(priority)));
+
+				continue;
 			}
 		}
 	}

+ 8 - 5
AI/Nullkiller/Engine/Nullkiller.cpp

@@ -77,8 +77,8 @@ void Nullkiller::updateAiState()
 	// TODO: move to hero manager
 	auto activeHeroes = ai->getMyHeroes();
 
-	vstd::erase_if(activeHeroes, [&](const HeroPtr & hero) -> bool{
-		return vstd::contains(lockedHeroes, hero);
+	vstd::erase_if(activeHeroes, [this](const HeroPtr & hero) -> bool{
+		return isHeroLocked(hero.h);
 	});
 
 	ai->ah->updatePaths(activeHeroes, true);
@@ -96,14 +96,17 @@ void Nullkiller::makeTurn()
 		Goals::TGoalVec bestTasks = {
 			choseBestTask(std::make_shared<BuyArmyBehavior>()),
 			choseBestTask(std::make_shared<CaptureObjectsBehavior>()),
-			choseBestTask(std::make_shared<RecruitHeroBehavior>()),
-			choseBestTask(std::make_shared<DefenceBehavior>())
+			choseBestTask(std::make_shared<RecruitHeroBehavior>())
 		};
 
 		if(cb->getDate(Date::DAY) == 1)
 		{
 			bestTasks.push_back(choseBestTask(std::make_shared<StartupBehavior>()));
 		}
+		else
+		{
+			bestTasks.push_back(choseBestTask(std::make_shared<DefenceBehavior>()));
+		}
 
 		Goals::TSubgoal bestTask = choseBestTask(bestTasks);
 
@@ -118,7 +121,7 @@ void Nullkiller::makeTurn()
 
 		try
 		{
-			activeHero = bestTask->hero;
+			activeHero = bestTask->hero.get();
 
 			bestTask->accept(ai.get());
 		}

+ 7 - 6
AI/Nullkiller/Engine/Nullkiller.h

@@ -18,18 +18,19 @@ class Nullkiller
 {
 private:
 	std::unique_ptr<PriorityEvaluator> priorityEvaluator;
-	HeroPtr activeHero;
-	std::set<HeroPtr> lockedHeroes;
+	const CGHeroInstance * activeHero;
+	std::set<const CGHeroInstance *> lockedHeroes;
 
 public:
 	std::unique_ptr<DangerHitMapAnalyzer> dangerHitMap;
 
 	Nullkiller();
 	void makeTurn();
-	bool isActive(const CGHeroInstance * hero) const { return activeHero.h == hero; }
-	void setActive(const HeroPtr & hero) { activeHero = hero; }
-	void lockHero(const HeroPtr & hero) { lockedHeroes.insert(hero); }
-	void unlockHero(const HeroPtr & hero) { lockedHeroes.erase(hero); }
+	bool isActive(const CGHeroInstance * hero) const { return activeHero == hero; }
+	bool isHeroLocked(const CGHeroInstance * hero) const { return vstd::contains(lockedHeroes, hero); }
+	void setActive(const CGHeroInstance * hero) { activeHero = hero; }
+	void lockHero(const CGHeroInstance * hero) { lockedHeroes.insert(hero); }
+	void unlockHero(const CGHeroInstance * hero) { lockedHeroes.erase(hero); }
 
 private:
 	void resetAiState();

+ 11 - 5
AI/Nullkiller/Goals/ExecuteHeroChain.cpp

@@ -69,7 +69,7 @@ void ExecuteHeroChain::accept(VCAI * ai)
 		if(vstd::contains(blockedIndexes, i))
 		{
 			blockedIndexes.insert(node.parentIndex);
-			ai->nullkiller->lockHero(hero);
+			ai->nullkiller->lockHero(hero.get());
 
 			continue;
 		}
@@ -80,21 +80,27 @@ void ExecuteHeroChain::accept(VCAI * ai)
 		{
 			if(hero->movement)
 			{
-				ai->nullkiller->setActive(hero);
+				ai->nullkiller->setActive(hero.get());
 
 				if(node.specialAction)
 				{
-					auto specialGoal = node.specialAction->whatToDo(hero);
+					if(node.specialAction->canAct(hero.get()))
+					{
+						auto specialGoal = node.specialAction->whatToDo(hero);
 
-					if(specialGoal->isElementar)
 						specialGoal->accept(ai);
+					}
+					else
+					{
+						//TODO: decompose
+					}
 				}
 
 				Goals::VisitTile(node.coord).sethero(hero).accept(ai);
 			}
 
 			// no exception means we were not able to rich the tile
-			ai->nullkiller->lockHero(hero);
+			ai->nullkiller->lockHero(hero.get());
 			blockedIndexes.insert(node.parentIndex);
 		}
 		catch(goalFulfilledException)

+ 111 - 64
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -120,7 +120,11 @@ boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
 std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
 {
 	if(heroChainPass)
+	{
+		calculateTownPortalTeleportations(heroChain);
+
 		return heroChain;
+	}
 
 	std::vector<CGPathNode *> initialNodes;
 
@@ -147,6 +151,8 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
 		}
 	}
 
+	calculateTownPortalTeleportations(initialNodes);
+
 	return initialNodes;
 }
 
@@ -185,7 +191,7 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf
 			dstNode->specialAction->applyOnDestination(dstNode->actor->hero, destination, source, dstNode, srcNode);
 		}
 
-#if VCMI_TRACE_PATHFINDER >= 2
+#if AI_TRACE_LEVEL >= 2
 		logAi->trace(
 			"Commited %s -> %s, cost: %f, hero: %s, mask: %x, army: %i", 
 			source.coord.toString(),
@@ -310,7 +316,7 @@ void AINodeStorage::calculateHeroChain(
 			continue;
 		}
 
-#if VCMI_TRACE_PATHFINDER >= 2
+#if AI_TRACE_LEVEL >= 2
 		logAi->trace(
 			"Thy exchange %s[%i] -> %s[%i] at %s",
 			node->actor->toString(),
@@ -335,7 +341,7 @@ void AINodeStorage::calculateHeroChain(
 		&& other->armyLoss < other->actor->armyValue
 		&& carrier->actor->canExchange(other->actor))
 	{
-#if VCMI_TRACE_PATHFINDER >= 2
+#if AI_TRACE_LEVEL >= 2
 		logAi->trace(
 			"Exchange allowed %s[%i] -> %s[%i] at %s",
 			other->actor->toString(),
@@ -352,7 +358,7 @@ void AINodeStorage::calculateHeroChain(
 
 			if(hasLessMp && hasLessExperience)
 			{
-#if VCMI_TRACE_PATHFINDER >= 2
+#if AI_TRACE_LEVEL >= 2
 				logAi->trace("Exchange at %s is ineficient. Blocked.", carrier->coord.toString());
 #endif
 				return;
@@ -376,7 +382,7 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
 
 		if(!chainNodeOptional)
 		{
-#if VCMI_TRACE_PATHFINDER >= 2
+#if AI_TRACE_LEVEL >= 2
 			logAi->trace("Exchange at %s can not allocate node. Blocked.", carrier->coord.toString());
 #endif
 			continue;
@@ -386,7 +392,7 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
 
 		if(exchangeNode->action != CGPathNode::ENodeAction::UNKNOWN)
 		{
-#if VCMI_TRACE_PATHFINDER >= 2
+#if AI_TRACE_LEVEL >= 2
 			logAi->trace("Exchange at %s node is already in use. Blocked.", carrier->coord.toString());
 #endif
 			continue;
@@ -394,7 +400,7 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
 		
 		if(exchangeNode->turns != 0xFF && exchangeNode->cost < chainInfo.cost)
 		{
-#if VCMI_TRACE_PATHFINDER >= 2
+#if AI_TRACE_LEVEL >= 2
 			logAi->trace(
 				"Exchange at %s is is not effective enough. %f < %f", 
 				exchangeNode->coord.toString(), 
@@ -406,10 +412,16 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
 
 		commit(exchangeNode, carrier, carrier->action, chainInfo.turns, chainInfo.moveRemains, chainInfo.cost);
 
+		if(carrier->specialAction || carrier->chainOther)
+		{
+			// there is some action on source tile which should be performed before we can bypass it
+			exchangeNode->theNodeBefore = carrier;
+		}
+
 		exchangeNode->chainOther = other;
 		exchangeNode->armyLoss = chainInfo.armyLoss;
 
-#if VCMI_TRACE_PATHFINDER >= 2
+#if AI_TRACE_LEVEL >= 2
 		logAi->trace(
 			"Chain accepted at %s %s -> %s, mask %x, cost %f, army %i", 
 			exchangeNode->coord.toString(), 
@@ -566,85 +578,120 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
 		}
 	}
 
-	if(source.isInitialPosition)
-	{
-		calculateTownPortalTeleportations(source, neighbours, pathfinderHelper);
-	}
-
 	return neighbours;
 }
 
-void AINodeStorage::calculateTownPortalTeleportations(
-	const PathNodeInfo & source,
-	std::vector<CGPathNode *> & neighbours,
-	const CPathfinderHelper * pathfinderHelper)
+void AINodeStorage::getBestInitialNodeForTownPortal()
+{
+
+}
+
+void AINodeStorage::calculateTownPortalTeleportations(std::vector<CGPathNode *> & initialNodes)
 {
 	SpellID spellID = SpellID::TOWN_PORTAL;
 	const CSpell * townPortal = spellID.toSpell();
-	auto srcNode = getAINode(source.node);
-	auto hero = srcNode->actor->hero;
 
-	if(hero->canCastThisSpell(townPortal) && hero->mana >= hero->getSpellCost(townPortal))
-	{
-		auto towns = cb->getTownsInfo(false);
+	std::set<const ChainActor *> actorsOfInitial;
 
-		vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool
-		{
-			return cb->getPlayerRelations(hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES;
-		});
+	for(const CGPathNode * node : initialNodes)
+	{
+		auto aiNode = getAINode(node);
 
-		if(!towns.size())
-		{
-			return;
-		}
+		actorsOfInitial.insert(aiNode->actor->baseActor);
+	}
 
-		// TODO: Copy/Paste from TownPortalMechanics
-		auto skillLevel = hero->getSpellSchoolLevel(townPortal);
-		auto movementNeeded = GameConstants::BASE_MOVEMENT_COST * (skillLevel >= 3 ? 2 : 3);
-		float movementCost = (float)movementNeeded / (float)pathfinderHelper->getMaxMovePoints(EPathfindingLayer::LAND);
+	for(const ChainActor * actor : actorsOfInitial)
+	{
+		if(!actor->hero)
+			continue;
 
-		movementCost += source.node->cost;
+		auto hero = actor->hero;
 
-		if(source.node->moveRemains < movementNeeded)
+		if(hero->canCastThisSpell(townPortal) && hero->mana >= hero->getSpellCost(townPortal))
 		{
-			return;
-		}
+			auto towns = cb->getTownsInfo(false);
 
-		if(skillLevel < SecSkillLevel::ADVANCED)
-		{
-			const CGTownInstance * nearestTown = *vstd::minElementByFun(towns, [&](const CGTownInstance * t) -> int
+			vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool
 			{
-				return source.coord.dist2dSQ(t->visitablePos());
+				return cb->getPlayerRelations(hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES;
 			});
 
-			towns = std::vector<const CGTownInstance *>{ nearestTown };
-		}
-
-		for(const CGTownInstance * targetTown : towns)
-		{
-			// TODO: allow to hide visiting hero in garrison
-			if(targetTown->visitingHero)
-				continue;
+			if(!towns.size())
+			{
+				return; // no towns no need to run loop further
+			}
 
-			auto nodeOptional = getOrCreateNode(targetTown->visitablePos(), EPathfindingLayer::LAND, srcNode->actor->castActor);
+			// TODO: Copy/Paste from TownPortalMechanics
+			auto skillLevel = hero->getSpellSchoolLevel(townPortal);
+			auto movementNeeded = GameConstants::BASE_MOVEMENT_COST * (skillLevel >= 3 ? 2 : 3);
 
-			if(nodeOptional)
+			for(const CGTownInstance * targetTown : towns)
 			{
-#ifdef VCMI_TRACE_PATHFINDER
-				logAi->trace("Adding town portal node at %s", targetTown->name);
-#endif
+				CGPathNode * bestNode = nullptr;
 
-				AIPathNode * node = nodeOptional.get();
+				for(CGPathNode * node : initialNodes)
+				{
+					auto aiNode = getAINode(node);
+
+					if(aiNode->actor->baseActor != actor
+						|| node->layer != EPathfindingLayer::LAND
+						|| node->moveRemains < movementNeeded)
+					{
+						continue;
+					}
+
+					if(skillLevel < SecSkillLevel::ADVANCED)
+					{
+						const CGTownInstance * nearestTown = *vstd::minElementByFun(towns, [&](const CGTownInstance * t) -> int
+						{
+							return node->coord.dist2dSQ(t->visitablePos());
+						});
+
+						if(targetTown != nearestTown)
+							continue;
+					}
+
+					if(!bestNode || bestNode->cost > node->cost)
+						bestNode = node;
+				}
+
+				if(!bestNode)
+					continue;
 
-				if(node->action == CGPathNode::UNKNOWN || node->cost > movementCost)
+				// TODO: allow to hide visiting hero in garrison
+				if(targetTown->visitingHero && targetTown->visitingHero != hero)
+					continue;
+
+				auto nodeOptional = getOrCreateNode(targetTown->visitablePos(), EPathfindingLayer::LAND, actor->castActor);
+
+				if(nodeOptional)
 				{
-					node->theNodeBefore = source.node;
-					node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown));
-					node->moveRemains = source.node->moveRemains + movementNeeded;
-					node->cost = movementCost;
+					float movementCost = (float)movementNeeded / (float)hero->maxMovePoints(EPathfindingLayer::LAND);
+
+					movementCost += bestNode->cost;
+
+#ifdef AI_TRACE_LEVEL >= 1
+					logAi->trace("Adding town portal node at %s", targetTown->name);
+#endif
+
+					AIPathNode * node = nodeOptional.get();
+
+					if(node->action == CGPathNode::UNKNOWN || node->cost > movementCost)
+					{
+						commit(
+							node,
+							getAINode(bestNode),
+							CGPathNode::TELEPORT_NORMAL,
+							bestNode->turns,
+							bestNode->moveRemains - movementNeeded,
+							movementCost);
+
+						node->theNodeBefore = bestNode;
+						node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown));
+					}
+
+					initialNodes.push_back(node);
 				}
-				
-				neighbours.push_back(node);
 			}
 		}
 	}
@@ -679,7 +726,7 @@ bool AINodeStorage::hasBetterChain(
 		{
 			if(node.cost < candidateNode->cost)
 			{
-#ifdef VCMI_TRACE_PATHFINDER
+#ifdef AI_TRACE_LEVEL >= 1
 				logAi->trace(
 					"Block ineficient move %s:->%s, mask=%i, mp diff: %i",
 					source->coord.toString(),

+ 3 - 6
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -10,8 +10,8 @@
 
 #pragma once
 
-#define VCMI_TRACE_PATHFINDER 1
-#define AI_TRACE_LEVEL 1
+#define VCMI_TRACE_PATHFINDER 2
+#define AI_TRACE_LEVEL 2
 
 #include "../../../lib/CPathfinder.h"
 #include "../../../lib/mapObjects/CGHeroInstance.h"
@@ -165,10 +165,7 @@ private:
 	void cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const;
 	void addHeroChain(const std::vector<ExchangeCandidate> & result);
 
-	void calculateTownPortalTeleportations(
-		const PathNodeInfo & source,
-		std::vector<CGPathNode *> & neighbours,
-		const CPathfinderHelper * pathfinderHelper);
+	void calculateTownPortalTeleportations(std::vector<CGPathNode *> & neighbours);
 	void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const;
 	void commit(
 		AIPathNode * destination,