mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	AI: danger hitmap using visible enemy heroes
This commit is contained in:
		
				
					committed by
					
						 Andrii Danylchenko
						Andrii Danylchenko
					
				
			
			
				
	
			
			
			
						parent
						
							04bf6f536d
						
					
				
				
					commit
					4bf653f596
				
			| @@ -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); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
| @@ -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
									
								
								AI/Nullkiller/Engine/DangerHitMapAnalyzer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								AI/Nullkiller/Engine/DangerHitMapAnalyzer.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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
									
								
								AI/Nullkiller/Engine/DangerHitMapAnalyzer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								AI/Nullkiller/Engine/DangerHitMapAnalyzer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
| }; | ||||
| @@ -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() | ||||
|   | ||||
| @@ -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; } | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user