Răsfoiți Sursa

AI: danger hitmap using visible enemy heroes

Andrii Danylchenko 4 ani în urmă
părinte
comite
4bf653f596

+ 3 - 3
AI/Nullkiller/AIUtility.cpp

@@ -101,16 +101,16 @@ const CGHeroInstance * HeroPtr::get(bool doWeExpectNull) const
 	if(h)
 	{
 		auto obj = cb->getObj(hid);
-		const bool owned = obj && obj->tempOwner == ai->playerID;
+		//const bool owned = obj && obj->tempOwner == ai->playerID;
 
-		if(doWeExpectNull && !owned)
+		if(doWeExpectNull && !obj)
 		{
 			return nullptr;
 		}
 		else
 		{
 			assert(obj);
-			assert(owned);
+			//assert(owned);
 		}
 	}
 

+ 10 - 0
AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp

@@ -9,6 +9,7 @@
  */
 #include "StdInc.h"
 #include "../VCAI.h"
+#include "../Engine/Nullkiller.h"
 #include "../AIhelper.h"
 #include "../Goals/ExecuteHeroChain.h"
 #include "CaptureObjectsBehavior.h"
@@ -47,6 +48,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks()
 				continue;
 
 			const int3 pos = objToVisit->visitablePos();
+
 			auto paths = ai->ah->getPathsToTile(pos);
 			std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj;
 			std::shared_ptr<ExecuteHeroChain> closestWay;
@@ -61,6 +63,14 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks()
 				logAi->trace("Path found %s", path.toString());
 #endif
 
+				if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
+				{
+#ifdef VCMI_TRACE_PATHFINDER
+					logAi->trace("Ignore path. Target hero can be killed by enemy");
+#endif
+					continue;
+				}
+
 				if(!shouldVisit(path.targetHero, objToVisit))
 					continue;
 

+ 2 - 0
AI/Nullkiller/CMakeLists.txt

@@ -55,6 +55,7 @@ set(VCAI_SRCS
 		Goals/ExecuteHeroChain.cpp
 		Engine/Nullkiller.cpp
 		Engine/PriorityEvaluator.cpp
+		Engine/DangerHitMapAnalyzer.cpp
 		Behaviors/Behavior.cpp
 		Behaviors/CaptureObjectsBehavior.cpp
 		Behaviors/RecruitHeroBehavior.cpp
@@ -117,6 +118,7 @@ set(VCAI_HEADERS
 		Goals/Goals.h
 		Engine/Nullkiller.h
 		Engine/PriorityEvaluator.h
+		Engine/DangerHitMapAnalyzer.h
 		Behaviors/Behavior.h
 		Behaviors/CaptureObjectsBehavior.h
 		Behaviors/RecruitHeroBehavior.h

+ 65 - 0
AI/Nullkiller/Engine/DangerHitMapAnalyzer.cpp

@@ -0,0 +1,65 @@
+#include "StdInc.h"
+#include "DangerHitMapAnalyzer.h"
+
+extern boost::thread_specific_ptr<CCallback> cb;
+extern boost::thread_specific_ptr<VCAI> ai;
+
+void DangerHitMapAnalyzer::updateHitMap()
+{
+	auto mapSize = cb->getMapSize();
+	hitMap.resize(boost::extents[mapSize.x][mapSize.y][mapSize.z]);
+
+	std::map<PlayerColor, std::vector<HeroPtr>> heroes;
+
+	for(const CGObjectInstance * obj : ai->visitableObjs)
+	{
+		if(obj->ID == Obj::HERO)
+		{
+			HeroPtr hero = dynamic_cast<const CGHeroInstance *>(obj);
+
+			heroes[hero->tempOwner].push_back(hero);
+		}
+	}
+
+	foreach_tile_pos([&](const int3 & pos){
+		hitMap[pos.x][pos.y][pos.z].reset();
+	});
+
+	for(auto pair : heroes)
+	{
+		ai->ah->updatePaths(pair.second, false);
+
+		foreach_tile_pos([&](const int3 & pos){
+			for(AIPath & path : ai->ah->getPathsToTile(pos))
+			{
+				auto tileDanger = path.getHeroStrength();
+				auto turn = path.turn();
+				auto & node = hitMap[pos.x][pos.y][pos.z];
+
+				if(tileDanger > node.maximumDanger.danger
+					|| tileDanger == node.maximumDanger.danger && node.maximumDanger.turn > turn)
+				{
+					node.maximumDanger.danger = tileDanger;
+					node.maximumDanger.turn = turn;
+				}
+
+				if(turn < node.fastestDanger.turn
+					|| turn == node.fastestDanger.turn && node.fastestDanger.danger < tileDanger)
+				{
+					node.fastestDanger.danger = tileDanger;
+					node.fastestDanger.turn = turn;
+				}
+			}
+		});
+	}
+}
+
+uint64_t DangerHitMapAnalyzer::enemyCanKillOurHeroesAlongThePath(const AIPath & path) const
+{
+	int3 tile = path.targetTile();
+	int turn = path.turn();
+	const HitMapNode & info = hitMap[tile.x][tile.y][tile.z];
+
+	return info.fastestDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.fastestDanger.danger)
+		|| info.maximumDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.maximumDanger.danger);
+}

+ 38 - 0
AI/Nullkiller/Engine/DangerHitMapAnalyzer.h

@@ -0,0 +1,38 @@
+#pragma once
+
+#include "../VCAI.h"
+#include "../AIHelper.h"
+
+struct HitMapInfo
+{
+	uint64_t danger;
+	uint8_t turn;
+
+	void reset()
+	{
+		danger = 0;
+		turn = 255;
+	}
+};
+
+struct HitMapNode
+{
+	HitMapInfo maximumDanger;
+	HitMapInfo fastestDanger;
+
+	void reset()
+	{
+		maximumDanger.reset();
+		fastestDanger.reset();
+	}
+};
+
+class DangerHitMapAnalyzer
+{
+private:
+	boost::multi_array<HitMapNode, 3> hitMap;
+
+public:
+	void updateHitMap();
+	uint64_t enemyCanKillOurHeroesAlongThePath(const AIPath & path) const;
+};

+ 4 - 2
AI/Nullkiller/Engine/Nullkiller.cpp

@@ -13,12 +13,12 @@ extern boost::thread_specific_ptr<VCAI> ai;
 Nullkiller::Nullkiller()
 {
 	priorityEvaluator.reset(new PriorityEvaluator());
+	dangerHitMap.reset(new DangerHitMapAnalyzer());
 }
 
 Goals::TSubgoal Nullkiller::choseBestTask(Goals::TGoalVec tasks)
 {
-	Goals::TSubgoal bestTask = *vstd::maxElementByFun(tasks, [](Goals::TSubgoal goal) -> float
-	{
+	Goals::TSubgoal bestTask = *vstd::maxElementByFun(tasks, [](Goals::TSubgoal goal) -> float{
 		return goal->priority;
 	});
 
@@ -53,6 +53,8 @@ Goals::TSubgoal Nullkiller::choseBestTask(Behavior & behavior)
 void Nullkiller::resetAiState()
 {
 	lockedHeroes.clear();
+
+	dangerHitMap->updateHitMap();
 }
 
 void Nullkiller::updateAiState()

+ 3 - 0
AI/Nullkiller/Engine/Nullkiller.h

@@ -1,6 +1,7 @@
 #pragma once
 
 #include "PriorityEvaluator.h"
+#include "DangerHitMapAnalyzer.h"
 #include "../Goals/AbstractGoal.h"
 #include "../Behaviors/Behavior.h"
 
@@ -12,6 +13,8 @@ private:
 	std::set<HeroPtr> lockedHeroes;
 
 public:
+	std::unique_ptr<DangerHitMapAnalyzer> dangerHitMap;
+
 	Nullkiller();
 	void makeTurn();
 	bool isActive(const CGHeroInstance * hero) const { return activeHero.h == hero; }

+ 17 - 2
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -33,9 +33,10 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
 
 	//TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
 	int3 pos;
-	const PlayerColor player = ai->playerID;
+	const PlayerColor player = playerID;
+	const PlayerColor fowPlayer = ai->playerID;
 	const int3 sizes = gs->getMapSize();
-	const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(player)->fogOfWarMap;
+	const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(fowPlayer)->fogOfWarMap;
 
 	//make 200% sure that these are loop invariants (also a bit shorter code), let compiler do the rest(loop unswitching)
 	const bool useFlying = options.useFlying;
@@ -478,10 +479,13 @@ void AINodeStorage::setHeroes(std::vector<HeroPtr> heroes, const VCAI * _ai)
 	cb = _ai->myCb.get();
 	ai = _ai;
 
+	playerID = ai->playerID;
+
 	for(auto & hero : heroes)
 	{
 		uint64_t mask = 1 << actors.size();
 
+		playerID = hero->tempOwner;
 		actors.push_back(std::make_shared<HeroActor>(hero.get(), mask, ai));
 	}
 }
@@ -824,6 +828,17 @@ float AIPath::movementCost() const
 	return 0.0;
 }
 
+uint8_t AIPath::turn() const
+{
+	if(nodes.size())
+	{
+		return nodes.front().turns;
+	}
+
+	// TODO: boost:optional?
+	return 0;
+}
+
 uint64_t AIPath::getHeroStrength() const
 {
 	return targetHero->getFightingStrength() * heroArmy->getArmyStrength();

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

@@ -34,7 +34,7 @@ struct AIPathNode : public CGPathNode
 struct AIPathNodeInfo
 {
 	float cost;
-	int turns;
+	uint8_t turns;
 	int3 coord;
 	uint64_t danger;
 	const CGHeroInstance * targetHero;
@@ -66,6 +66,8 @@ struct AIPath
 
 	float movementCost() const;
 
+	uint8_t turn() const;
+
 	uint64_t getHeroStrength() const;
 
 	std::string toString();
@@ -91,6 +93,7 @@ private:
 	std::vector<CGPathNode *> heroChain;
 	bool heroChainPass; // true if we need to calculate hero chain
 	int heroChainTurn;
+	PlayerColor playerID;
 
 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.