mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Nullkiller: parallel object clusterization, stabilization
This commit is contained in:
		
				
					committed by
					
						 Andrii Danylchenko
						Andrii Danylchenko
					
				
			
			
				
	
			
			
			
						parent
						
							66843b22d3
						
					
				
				
					commit
					9a203b8af9
				
			| @@ -115,6 +115,32 @@ const CGHeroInstance * HeroPtr::get(bool doWeExpectNull) const | ||||
| 	return h; | ||||
| } | ||||
|  | ||||
| const CGHeroInstance * HeroPtr::get(CCallback * cb, bool doWeExpectNull) const | ||||
| { | ||||
| 	//TODO? check if these all assertions every time we get info about hero affect efficiency | ||||
| 	// | ||||
| 	//behave terribly when attempting unauthorized access to hero that is not ours (or was lost) | ||||
| 	assert(doWeExpectNull || h); | ||||
|  | ||||
| 	if(h) | ||||
| 	{ | ||||
| 		auto obj = cb->getObj(hid); | ||||
| 		//const bool owned = obj && obj->tempOwner == ai->playerID; | ||||
|  | ||||
| 		if(doWeExpectNull && !obj) | ||||
| 		{ | ||||
| 			return nullptr; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			assert(obj); | ||||
| 			//assert(owned); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return h; | ||||
| } | ||||
|  | ||||
| const CGHeroInstance * HeroPtr::operator->() const | ||||
| { | ||||
| 	return get(); | ||||
| @@ -251,6 +277,11 @@ bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater) | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool isObjectPassable(const Nullkiller * ai, const CGObjectInstance * obj) | ||||
| { | ||||
| 	return isObjectPassable(obj, ai->playerID, ai->cb->getPlayerRelations(obj->tempOwner, ai->playerID)); | ||||
| } | ||||
|  | ||||
| bool isObjectPassable(const CGObjectInstance * obj) | ||||
| { | ||||
| 	return isObjectPassable(obj, ai->playerID, cb->getPlayerRelations(obj->tempOwner, ai->playerID)); | ||||
| @@ -355,7 +386,7 @@ uint64_t timeElapsed(boost::chrono::time_point<boost::chrono::steady_clock> star | ||||
| } | ||||
|  | ||||
| // todo: move to obj manager | ||||
| bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj) | ||||
| bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObjectInstance * obj) | ||||
| { | ||||
| 	switch(obj->ID) | ||||
| 	{ | ||||
| @@ -364,7 +395,7 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj) | ||||
| 		return obj->tempOwner != h->tempOwner; //do not visit our towns at random | ||||
| 	case Obj::BORDER_GATE: | ||||
| 	{ | ||||
| 		for(auto q : ai->myCb->getMyQuests()) | ||||
| 		for(auto q : ai->cb->getMyQuests()) | ||||
| 		{ | ||||
| 			if(q.obj == obj) | ||||
| 			{ | ||||
| @@ -378,7 +409,7 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj) | ||||
| 	case Obj::SEER_HUT: | ||||
| 	case Obj::QUEST_GUARD: | ||||
| 	{ | ||||
| 		for(auto q : ai->myCb->getMyQuests()) | ||||
| 		for(auto q : ai->cb->getMyQuests()) | ||||
| 		{ | ||||
| 			if(q.obj == obj) | ||||
| 			{ | ||||
| @@ -403,7 +434,7 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj) | ||||
| 			{ | ||||
| 				if(level.first | ||||
| 					&& h->getSlotFor(CreatureID(c)) != SlotID() | ||||
| 					&& cb->getResourceAmount().canAfford(c.toCreature()->cost)) | ||||
| 					&& ai->cb->getResourceAmount().canAfford(c.toCreature()->cost)) | ||||
| 				{ | ||||
| 					return true; | ||||
| 				} | ||||
| @@ -429,7 +460,7 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj) | ||||
| 	case Obj::SCHOOL_OF_MAGIC: | ||||
| 	case Obj::SCHOOL_OF_WAR: | ||||
| 	{ | ||||
| 		if(cb->getResourceAmount(Res::GOLD) < 1000) | ||||
| 		if(ai->cb->getResourceAmount(Res::GOLD) < 1000) | ||||
| 			return false; | ||||
| 		break; | ||||
| 	} | ||||
| @@ -439,10 +470,10 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj) | ||||
| 		break; | ||||
| 	case Obj::TREE_OF_KNOWLEDGE: | ||||
| 	{ | ||||
| 		if(ai->nullkiller->heroManager->getHeroRole(h) == HeroRole::SCOUT) | ||||
| 		if(ai->heroManager->getHeroRole(h) == HeroRole::SCOUT) | ||||
| 			return false; | ||||
|  | ||||
| 		TResources myRes = cb->getResourceAmount(); | ||||
| 		TResources myRes = ai->cb->getResourceAmount(); | ||||
| 		if(myRes[Res::GOLD] < 2000 || myRes[Res::GEMS] < 10) | ||||
| 			return false; | ||||
| 		break; | ||||
| @@ -450,14 +481,14 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj) | ||||
| 	case Obj::MAGIC_WELL: | ||||
| 		return h->mana < h->manaLimit(); | ||||
| 	case Obj::PRISON: | ||||
| 		return ai->myCb->getHeroesInfo().size() < VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER; | ||||
| 		return ai->cb->getHeroesInfo().size() < VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER; | ||||
| 	case Obj::TAVERN: | ||||
| 	{ | ||||
| 		//TODO: make AI actually recruit heroes | ||||
| 		//TODO: only on request | ||||
| 		if(ai->myCb->getHeroesInfo().size() >= VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER) | ||||
| 		if(ai->cb->getHeroesInfo().size() >= VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER) | ||||
| 			return false; | ||||
| 		else if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST) | ||||
| 		else if(ai->cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST) | ||||
| 			return false; | ||||
| 		break; | ||||
| 	} | ||||
|   | ||||
| @@ -18,8 +18,12 @@ | ||||
| #include "../../lib/mapObjects/CObjectHandler.h" | ||||
| #include "../../lib/mapObjects/CGHeroInstance.h" | ||||
| #include "../../lib/CPathfinder.h" | ||||
| #include <tbb/tbb.h> | ||||
|  | ||||
| using namespace tbb; | ||||
|  | ||||
| class CCallback; | ||||
| class Nullkiller; | ||||
| struct creInfo; | ||||
|  | ||||
| typedef const int3 & crint3; | ||||
| @@ -72,6 +76,7 @@ public: | ||||
| 	} | ||||
|  | ||||
| 	const CGHeroInstance * get(bool doWeExpectNull = false) const; | ||||
| 	const CGHeroInstance * get(CCallback * cb, bool doWeExpectNull = false) const; | ||||
| 	bool validAndSet() const; | ||||
|  | ||||
|  | ||||
| @@ -168,6 +173,7 @@ void foreach_neighbour(CCallback * cbp, const int3 & pos, std::function<void(CCa | ||||
|  | ||||
| bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater); | ||||
| bool isObjectPassable(const CGObjectInstance * obj); | ||||
| bool isObjectPassable(const Nullkiller * ai, const CGObjectInstance * obj); | ||||
| bool isObjectPassable(const CGObjectInstance * obj, PlayerColor playerColor, PlayerRelations::PlayerRelations objectRelations); | ||||
| bool isBlockVisitObj(const int3 & pos); | ||||
|  | ||||
| @@ -184,7 +190,27 @@ bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2 | ||||
| uint64_t timeElapsed(boost::chrono::time_point<boost::chrono::steady_clock> start); | ||||
|  | ||||
| // todo: move to obj manager | ||||
| bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj); | ||||
| bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObjectInstance * obj); | ||||
|  | ||||
| template<typename TFunc> | ||||
| void pforeachTilePos(crint3 mapSize, TFunc fn) | ||||
| { | ||||
| 	parallel_for(blocked_range<size_t>(0, mapSize.x), [&](const blocked_range<size_t>& r) | ||||
| 	{ | ||||
| 		int3 pos; | ||||
|  | ||||
| 		for(pos.x = r.begin(); pos.x != r.end(); ++pos.x) | ||||
| 		{ | ||||
| 			for(pos.y = 0; pos.y < mapSize.y; ++pos.y) | ||||
| 			{ | ||||
| 				for(pos.z = 0; pos.z < mapSize.z; ++pos.z) | ||||
| 				{ | ||||
| 					fn(pos); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| class CDistanceSorter | ||||
| { | ||||
| @@ -197,3 +223,75 @@ public: | ||||
| 	} | ||||
| 	bool operator()(const CGObjectInstance * lhs, const CGObjectInstance * rhs) const; | ||||
| }; | ||||
|  | ||||
| template <class T> | ||||
| class SharedPool | ||||
| { | ||||
| public: | ||||
| 	struct External_Deleter | ||||
| 	{ | ||||
| 		explicit External_Deleter(std::weak_ptr<SharedPool<T>* > pool) | ||||
| 			: pool(pool) | ||||
| 		{ | ||||
| 		} | ||||
|  | ||||
| 		void operator()(T * ptr) | ||||
| 		{ | ||||
| 			std::unique_ptr<T> uptr(ptr); | ||||
|  | ||||
| 			if(auto pool_ptr = pool.lock()) | ||||
| 			{ | ||||
| 				(*pool_ptr.get())->add(std::move(uptr)); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	private: | ||||
| 		std::weak_ptr<SharedPool<T>* > pool; | ||||
| 	}; | ||||
|  | ||||
| public: | ||||
| 	using ptr_type = std::unique_ptr<T, External_Deleter>; | ||||
|  | ||||
| 	SharedPool(std::function<std::unique_ptr<T>()> elementFactory) | ||||
| 		: elementFactory(elementFactory), pool(), sync(), instance_tracker(new SharedPool<T>*(this)) | ||||
| 	{} | ||||
| 	 | ||||
| 	void add(std::unique_ptr<T> t) | ||||
| 	{ | ||||
| 		std::lock_guard<std::mutex> lock(sync); | ||||
| 		pool.push_back(std::move(t)); | ||||
| 	} | ||||
|  | ||||
| 	ptr_type acquire() | ||||
| 	{ | ||||
| 		std::lock_guard<std::mutex> lock(sync); | ||||
| 		bool poolIsEmpty = pool.empty(); | ||||
| 		T * element = poolIsEmpty | ||||
| 			? elementFactory().release() | ||||
| 			: pool.back().release(); | ||||
|  | ||||
| 		ptr_type tmp( | ||||
| 			element, | ||||
| 			External_Deleter(std::weak_ptr<SharedPool<T>*>(instance_tracker))); | ||||
|  | ||||
| 		if(!poolIsEmpty) pool.pop_back(); | ||||
|  | ||||
| 		return std::move(tmp); | ||||
| 	} | ||||
|  | ||||
| 	bool empty() const | ||||
| 	{ | ||||
| 		return pool.empty(); | ||||
| 	} | ||||
|  | ||||
| 	size_t size() const | ||||
| 	{ | ||||
| 		return pool.size(); | ||||
| 	} | ||||
|  | ||||
| private: | ||||
| 	std::vector<std::unique_ptr<T>> pool; | ||||
| 	std::function<std::unique_ptr<T>()> elementFactory; | ||||
| 	std::shared_ptr<SharedPool<T> *> instance_tracker; | ||||
| 	std::mutex sync; | ||||
| }; | ||||
|   | ||||
| @@ -12,8 +12,6 @@ | ||||
| #include "../../../lib/mapping/CMap.h" //for victory conditions | ||||
| #include "../Engine/Nullkiller.h" | ||||
|  | ||||
| extern boost::thread_specific_ptr<CCallback> cb; | ||||
|  | ||||
| void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo) | ||||
| { | ||||
| 	auto townInfo = developmentInfo.town->town; | ||||
| @@ -71,7 +69,7 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo) | ||||
| 		{BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL} | ||||
| 	}; | ||||
|  | ||||
| 	if(developmentInfo.existingDwellings.size() >= 2 && cb->getDate(Date::DAY_OF_WEEK) > boost::date_time::Friday) | ||||
| 	if(developmentInfo.existingDwellings.size() >= 2 && ai->cb->getDate(Date::DAY_OF_WEEK) > boost::date_time::Friday) | ||||
| 	{ | ||||
| 		otherBuildings.push_back({BuildingID::CITADEL, BuildingID::CASTLE}); | ||||
| 	} | ||||
| @@ -99,7 +97,7 @@ int32_t convertToGold(const TResources & res) | ||||
|  | ||||
| TResources BuildAnalyzer::getResourcesRequiredNow() const | ||||
| { | ||||
| 	auto resourcesAvailable = cb->getResourceAmount(); | ||||
| 	auto resourcesAvailable = ai->cb->getResourceAmount(); | ||||
| 	auto result = requiredResources - resourcesAvailable; | ||||
|  | ||||
| 	result.positive(); | ||||
| @@ -109,7 +107,7 @@ TResources BuildAnalyzer::getResourcesRequiredNow() const | ||||
|  | ||||
| TResources BuildAnalyzer::getTotalResourcesRequired() const | ||||
| { | ||||
| 	auto resourcesAvailable = cb->getResourceAmount(); | ||||
| 	auto resourcesAvailable = ai->cb->getResourceAmount(); | ||||
| 	auto result = totalDevelopmentCost - resourcesAvailable; | ||||
|  | ||||
| 	result.positive(); | ||||
| @@ -125,7 +123,7 @@ void BuildAnalyzer::update() | ||||
|  | ||||
| 	reset(); | ||||
|  | ||||
| 	auto towns = cb->getTownsInfo(); | ||||
| 	auto towns = ai->cb->getTownsInfo(); | ||||
|  | ||||
| 	for(const CGTownInstance* town : towns) | ||||
| 	{ | ||||
| @@ -159,7 +157,7 @@ void BuildAnalyzer::update() | ||||
|  | ||||
| 	updateDailyIncome(); | ||||
|  | ||||
| 	goldPreasure = (float)armyCost[Res::GOLD] / (1 + cb->getResourceAmount(Res::GOLD) + (float)dailyIncome[Res::GOLD] * 7.0f); | ||||
| 	goldPreasure = (float)armyCost[Res::GOLD] / (1 + ai->cb->getResourceAmount(Res::GOLD) + (float)dailyIncome[Res::GOLD] * 7.0f); | ||||
|  | ||||
| 	logAi->trace("Gold preasure: %f", goldPreasure); | ||||
| } | ||||
| @@ -203,7 +201,7 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite( | ||||
|  | ||||
| 	if(!town->hasBuilt(building)) | ||||
| 	{ | ||||
| 		auto canBuild = cb->canBuildStructure(town, building); | ||||
| 		auto canBuild = ai->cb->canBuildStructure(town, building); | ||||
|  | ||||
| 		if(canBuild == EBuildingState::ALLOWED) | ||||
| 		{ | ||||
| @@ -262,8 +260,8 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite( | ||||
|  | ||||
| void BuildAnalyzer::updateDailyIncome() | ||||
| { | ||||
| 	auto objects = cb->getMyObjects(); | ||||
| 	auto towns = cb->getTownsInfo(); | ||||
| 	auto objects = ai->cb->getMyObjects(); | ||||
| 	auto towns = ai->cb->getTownsInfo(); | ||||
| 	 | ||||
| 	dailyIncome = TResources(); | ||||
|  | ||||
|   | ||||
| @@ -51,7 +51,7 @@ void DangerHitMapAnalyzer::updateHitMap() | ||||
|  | ||||
| 		boost::this_thread::interruption_point(); | ||||
|  | ||||
| 		foreach_tile_pos([&](const int3 & pos) | ||||
| 		pforeachTilePos(mapSize, [&](const int3 & pos) | ||||
| 		{ | ||||
| 			for(AIPath & path : ai->pathfinder->getPathInfo(pos)) | ||||
| 			{ | ||||
|   | ||||
| @@ -16,14 +16,16 @@ | ||||
|  | ||||
| void ObjectCluster::addObject(const CGObjectInstance * obj, const AIPath & path, float priority) | ||||
| { | ||||
| 	auto & info = objects[obj]; | ||||
| 	ClusterObjects::accessor info; | ||||
|  | ||||
| 	if(info.priority < priority) | ||||
| 	objects.insert(info, ClusterObjects::value_type(obj, ClusterObjectInfo())); | ||||
|  | ||||
| 	if(info->second.priority < priority) | ||||
| 	{ | ||||
| 		info.priority = priority; | ||||
| 		info.movementCost = path.movementCost() - path.firstNode().cost; | ||||
| 		info.danger = path.targetObjectDanger; | ||||
| 		info.turn = path.turn(); | ||||
| 		info->second.priority = priority; | ||||
| 		info->second.movementCost = path.movementCost() - path.firstNode().cost; | ||||
| 		info->second.danger = path.targetObjectDanger; | ||||
| 		info->second.turn = path.turn(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -125,7 +127,7 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons | ||||
| 			|| blocker->ID == Obj::BORDER_GATE | ||||
| 			|| blocker->ID == Obj::SHIPYARD) | ||||
| 		{ | ||||
| 			if(!isObjectPassable(blocker)) | ||||
| 			if(!isObjectPassable(ai, blocker)) | ||||
| 				return blocker; | ||||
| 		} | ||||
|  | ||||
| @@ -206,130 +208,134 @@ void ObjectClusterizer::clusterize() | ||||
|  | ||||
| 	logAi->debug("Begin object clusterization"); | ||||
|  | ||||
| 	for(const CGObjectInstance * obj : ai->memory->visitableObjs) | ||||
| 	std::vector<const CGObjectInstance *> objs( | ||||
| 		ai->memory->visitableObjs.begin(), | ||||
| 		ai->memory->visitableObjs.end()); | ||||
|  | ||||
| 	parallel_for(blocked_range<size_t>(0, objs.size()), [&](const blocked_range<size_t> & r) | ||||
| 	{ | ||||
| 		if(!shouldVisitObject(obj)) | ||||
| 			continue; | ||||
| 		auto priorityEvaluator = ai->priorityEvaluators->acquire(); | ||||
|  | ||||
| 		for(int i = r.begin(); i != r.end(); i++) | ||||
| 		{ | ||||
| 			auto obj = objs[i]; | ||||
|  | ||||
| 			if(!shouldVisitObject(obj)) | ||||
| 				return; | ||||
|  | ||||
| #if AI_TRACE_LEVEL >= 2 | ||||
| 		logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString()); | ||||
| 			logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString()); | ||||
| #endif | ||||
|  | ||||
| 		auto paths = ai->pathfinder->getPathInfo(obj->visitablePos()); | ||||
| 			auto paths = ai->pathfinder->getPathInfo(obj->visitablePos()); | ||||
|  | ||||
| 		if(paths.empty()) | ||||
| 		{ | ||||
| #if AI_TRACE_LEVEL >= 2 | ||||
| 			logAi->trace("No paths found."); | ||||
| #endif | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool | ||||
| 		{ | ||||
| 			return p1.movementCost() < p2.movementCost(); | ||||
| 		}); | ||||
|  | ||||
| 		if(vstd::contains(ignoreObjects, obj->ID)) | ||||
| 		{ | ||||
| 			farObjects.addObject(obj, paths.front(), 0); | ||||
|  | ||||
| #if AI_TRACE_LEVEL >= 2 | ||||
| 			logAi->trace("Object ignored. Moved to far objects with path %s", paths.front().toString()); | ||||
| #endif | ||||
|  | ||||
| 			continue; | ||||
| 		} | ||||
| 		 | ||||
| 		std::set<const CGHeroInstance *> heroesProcessed; | ||||
|  | ||||
| 		for(auto & path : paths) | ||||
| 		{ | ||||
| #if AI_TRACE_LEVEL >= 2 | ||||
| 			logAi->trace("Checking path %s", path.toString()); | ||||
| #endif | ||||
|  | ||||
| 			if(!shouldVisit(path.targetHero, obj)) | ||||
| 			if(paths.empty()) | ||||
| 			{ | ||||
| #if AI_TRACE_LEVEL >= 2 | ||||
| 				logAi->trace("Hero %s does not need to visit %s", path.targetHero->name, obj->getObjectName()); | ||||
| 				logAi->trace("No paths found."); | ||||
| #endif | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			if(path.nodes.size() > 1) | ||||
| 			std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool | ||||
| 			{ | ||||
| 				auto blocker = getBlocker(path); | ||||
| 				return p1.movementCost() < p2.movementCost(); | ||||
| 			}); | ||||
|  | ||||
| 				if(blocker) | ||||
| 				{ | ||||
| 					if(vstd::contains(heroesProcessed, path.targetHero)) | ||||
| 					{ | ||||
| 	#if AI_TRACE_LEVEL >= 2 | ||||
| 						logAi->trace("Hero %s is already processed.", path.targetHero->name); | ||||
| 	#endif | ||||
| 						continue; | ||||
| 					} | ||||
|  | ||||
| 					heroesProcessed.insert(path.targetHero); | ||||
|  | ||||
| 					auto cluster = blockedObjects[blocker]; | ||||
|  | ||||
| 					if(!cluster) | ||||
| 					{ | ||||
| 						cluster.reset(new ObjectCluster(blocker)); | ||||
| 						blockedObjects[blocker] = cluster; | ||||
| 					} | ||||
|  | ||||
| 					float priority = ai->priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj))); | ||||
|  | ||||
| 					if(priority < MIN_PRIORITY) | ||||
| 						continue; | ||||
|  | ||||
| 					cluster->addObject(obj, path, priority); | ||||
| 			if(vstd::contains(ignoreObjects, obj->ID)) | ||||
| 			{ | ||||
| 				farObjects.addObject(obj, paths.front(), 0); | ||||
|  | ||||
| #if AI_TRACE_LEVEL >= 2 | ||||
| 					logAi->trace("Path added to cluster %s%s", blocker->getObjectName(), blocker->visitablePos().toString()); | ||||
| 				logAi->trace("Object ignored. Moved to far objects with path %s", paths.front().toString()); | ||||
| #endif | ||||
|  | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			std::set<const CGHeroInstance *> heroesProcessed; | ||||
|  | ||||
| 			for(auto & path : paths) | ||||
| 			{ | ||||
| #if AI_TRACE_LEVEL >= 2 | ||||
| 				logAi->trace("Checking path %s", path.toString()); | ||||
| #endif | ||||
|  | ||||
| 				if(!shouldVisit(ai, path.targetHero, obj)) | ||||
| 				{ | ||||
| #if AI_TRACE_LEVEL >= 2 | ||||
| 					logAi->trace("Hero %s does not need to visit %s", path.targetHero->name, obj->getObjectName()); | ||||
| #endif | ||||
| 					continue; | ||||
| 				} | ||||
| 			} | ||||
| 			 | ||||
| 			heroesProcessed.insert(path.targetHero); | ||||
|  | ||||
| 			float priority = ai->priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj))); | ||||
| 				if(path.nodes.size() > 1) | ||||
| 				{ | ||||
| 					auto blocker = getBlocker(path); | ||||
|  | ||||
| 			if(priority < MIN_PRIORITY) | ||||
| 				continue; | ||||
| 			 | ||||
| 			bool interestingObject = path.turn() <= 2 || priority > 0.5f; | ||||
| 					if(blocker) | ||||
| 					{ | ||||
| 						if(vstd::contains(heroesProcessed, path.targetHero)) | ||||
| 						{ | ||||
| #if AI_TRACE_LEVEL >= 2 | ||||
| 							logAi->trace("Hero %s is already processed.", path.targetHero->name); | ||||
| #endif | ||||
| 							continue; | ||||
| 						} | ||||
|  | ||||
| 			if(interestingObject) | ||||
| 			{ | ||||
| 				nearObjects.addObject(obj, path, priority); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				farObjects.addObject(obj, path, priority); | ||||
| 			} | ||||
| 						heroesProcessed.insert(path.targetHero); | ||||
|  | ||||
| 						float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj))); | ||||
|  | ||||
| 						if(priority < MIN_PRIORITY) | ||||
| 							continue; | ||||
|  | ||||
| 						ClusterMap::accessor cluster; | ||||
| 						blockedObjects.insert( | ||||
| 							cluster, | ||||
| 							ClusterMap::value_type(blocker, std::make_shared<ObjectCluster>(blocker))); | ||||
|  | ||||
| 						cluster->second->addObject(obj, path, priority); | ||||
|  | ||||
| #if AI_TRACE_LEVEL >= 2 | ||||
| 			logAi->trace("Path %s added to %s objects. Turn: %d, priority: %f", | ||||
| 				path.toString(), | ||||
| 				interestingObject ? "near" : "far", | ||||
| 				path.turn(), | ||||
| 				priority); | ||||
| 						logAi->trace("Path added to cluster %s%s", blocker->getObjectName(), blocker->visitablePos().toString()); | ||||
| #endif | ||||
| 		} | ||||
| 	} | ||||
| 						continue; | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 	vstd::erase_if(blockedObjects, [](std::pair<const CGObjectInstance *, std::shared_ptr<ObjectCluster>> pair) -> bool | ||||
| 	{ | ||||
| 		return pair.second->objects.empty(); | ||||
| 				heroesProcessed.insert(path.targetHero); | ||||
|  | ||||
| 				float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj))); | ||||
|  | ||||
| 				if(priority < MIN_PRIORITY) | ||||
| 					continue; | ||||
|  | ||||
| 				bool interestingObject = path.turn() <= 2 || priority > 0.5f; | ||||
|  | ||||
| 				if(interestingObject) | ||||
| 				{ | ||||
| 					nearObjects.addObject(obj, path, priority); | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					farObjects.addObject(obj, path, priority); | ||||
| 				} | ||||
|  | ||||
| #if AI_TRACE_LEVEL >= 2 | ||||
| 				logAi->trace("Path %s added to %s objects. Turn: %d, priority: %f", | ||||
| 					path.toString(), | ||||
| 					interestingObject ? "near" : "far", | ||||
| 					path.turn(), | ||||
| 					priority); | ||||
| #endif | ||||
| 			} | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	logAi->trace("Near objects count: %i", nearObjects.objects.size()); | ||||
| 	logAi->trace("Far objects count: %i", farObjects.objects.size()); | ||||
|  | ||||
| 	for(auto pair : blockedObjects) | ||||
| 	{ | ||||
| 		logAi->trace("Cluster %s %s count: %i", pair.first->getObjectName(), pair.first->visitablePos().toString(), pair.second->objects.size()); | ||||
|   | ||||
| @@ -19,10 +19,12 @@ struct ClusterObjectInfo | ||||
| 	uint8_t turn; | ||||
| }; | ||||
|  | ||||
| typedef tbb::concurrent_hash_map<const CGObjectInstance *, ClusterObjectInfo> ClusterObjects; | ||||
|  | ||||
| struct ObjectCluster | ||||
| { | ||||
| public: | ||||
| 	std::map<const CGObjectInstance *, ClusterObjectInfo> objects; | ||||
| 	ClusterObjects objects; | ||||
| 	const CGObjectInstance * blocker; | ||||
|  | ||||
| 	void reset() | ||||
| @@ -45,12 +47,14 @@ public: | ||||
| 	const CGObjectInstance * calculateCenter() const; | ||||
| }; | ||||
|  | ||||
| typedef tbb::concurrent_hash_map<const CGObjectInstance *, std::shared_ptr<ObjectCluster>> ClusterMap; | ||||
|  | ||||
| class ObjectClusterizer | ||||
| { | ||||
| private: | ||||
| 	ObjectCluster nearObjects; | ||||
| 	ObjectCluster farObjects; | ||||
| 	std::map<const CGObjectInstance *, std::shared_ptr<ObjectCluster>> blockedObjects; | ||||
| 	ClusterMap blockedObjects; | ||||
| 	const Nullkiller * ai; | ||||
|  | ||||
| public: | ||||
|   | ||||
| @@ -71,7 +71,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath> | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		if(objToVisit && !shouldVisit(path.targetHero, objToVisit)) | ||||
| 		if(objToVisit && !shouldVisit(ai->nullkiller.get(), path.targetHero, objToVisit)) | ||||
| 			continue; | ||||
|  | ||||
| 		auto hero = path.targetHero; | ||||
|   | ||||
| @@ -43,6 +43,13 @@ void Nullkiller::init(std::shared_ptr<CCallback> cb, PlayerColor playerID) | ||||
| 	this->playerID = playerID; | ||||
|  | ||||
| 	priorityEvaluator.reset(new PriorityEvaluator(this)); | ||||
| 	priorityEvaluators.reset( | ||||
| 		new SharedPool<PriorityEvaluator>( | ||||
| 			[&]()->std::unique_ptr<PriorityEvaluator> | ||||
| 			{ | ||||
| 				return std::make_unique<PriorityEvaluator>(this); | ||||
| 			})); | ||||
|  | ||||
| 	dangerHitMap.reset(new DangerHitMapAnalyzer(this)); | ||||
| 	buildAnalyzer.reset(new BuildAnalyzer(this)); | ||||
| 	objectClusterizer.reset(new ObjectClusterizer(this)); | ||||
| @@ -224,16 +231,23 @@ void Nullkiller::makeTurn() | ||||
| 		HeroPtr hero = bestTask->getHero(); | ||||
|  | ||||
| 		if(bestTask->priority < NEXT_SCAN_MIN_PRIORITY | ||||
| 			&& hero.validAndSet() | ||||
| 			&& heroManager->getHeroRole(hero) == HeroRole::MAIN | ||||
| 			&& scanDepth != ScanDepth::FULL) | ||||
| 		{ | ||||
| 			logAi->trace( | ||||
| 				"Goal %s has too low priority %f so increasing scan depth", | ||||
| 				bestTask->toString(), | ||||
| 				bestTask->priority); | ||||
| 			scanDepth = (ScanDepth)((int)scanDepth + 1); | ||||
| 			continue; | ||||
| 			HeroRole heroRole = HeroRole::MAIN; | ||||
|  | ||||
| 			if(hero.validAndSet()) | ||||
| 				heroRole = heroManager->getHeroRole(hero); | ||||
|  | ||||
| 			if(heroRole == HeroRole::MAIN) | ||||
| 			{ | ||||
| 				logAi->trace( | ||||
| 					"Goal %s has too low priority %f so increasing scan depth", | ||||
| 					bestTask->toString(), | ||||
| 					bestTask->priority); | ||||
| 				scanDepth = (ScanDepth)((int)scanDepth + 1); | ||||
|  | ||||
| 				continue; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if(bestTask->priority < MIN_PRIORITY) | ||||
|   | ||||
| @@ -9,8 +9,6 @@ | ||||
| */ | ||||
| #pragma once | ||||
|  | ||||
| #include <boost/asio.hpp> | ||||
|  | ||||
| #include "PriorityEvaluator.h" | ||||
| #include "FuzzyHelper.h" | ||||
| #include "AIMemory.h" | ||||
| @@ -58,6 +56,7 @@ public: | ||||
| 	std::unique_ptr<BuildAnalyzer> buildAnalyzer; | ||||
| 	std::unique_ptr<ObjectClusterizer> objectClusterizer; | ||||
| 	std::unique_ptr<PriorityEvaluator> priorityEvaluator; | ||||
| 	std::unique_ptr<SharedPool<PriorityEvaluator>> priorityEvaluators; | ||||
| 	std::unique_ptr<AIPathfinder> pathfinder; | ||||
| 	std::unique_ptr<HeroManager> heroManager; | ||||
| 	std::unique_ptr<ArmyManager> armyManager; | ||||
|   | ||||
| @@ -29,8 +29,6 @@ | ||||
| #define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter | ||||
| #define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us | ||||
|  | ||||
| extern boost::thread_specific_ptr<CCallback> cb; | ||||
|  | ||||
| EvaluationContext::EvaluationContext(const Nullkiller * ai) | ||||
| 	: movementCost(0.0), | ||||
| 	manaCost(0), | ||||
| @@ -78,7 +76,7 @@ void PriorityEvaluator::initVisitTile() | ||||
| 	value = engine->getOutputVariable("Value"); | ||||
| } | ||||
|  | ||||
| int32_t estimateTownIncome(const CGObjectInstance * target, const CGHeroInstance * hero) | ||||
| int32_t estimateTownIncome(CCallback * cb, const CGObjectInstance * target, const CGHeroInstance * hero) | ||||
| { | ||||
| 	auto relations = cb->getPlayerRelations(hero->tempOwner, target->tempOwner); | ||||
|  | ||||
| @@ -116,7 +114,7 @@ uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHero | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| uint64_t getDwellingScore(const CGObjectInstance * target, bool checkGold) | ||||
| uint64_t getDwellingScore(CCallback * cb, const CGObjectInstance * target, bool checkGold) | ||||
| { | ||||
| 	auto dwelling = dynamic_cast<const CGDwelling *>(target); | ||||
| 	uint64_t score = 0; | ||||
| @@ -207,14 +205,14 @@ uint64_t RewardEvaluator::getArmyReward( | ||||
| 	case Obj::TOWN: | ||||
| 		return target->tempOwner == PlayerColor::NEUTRAL ? 1000 : 10000; | ||||
| 	case Obj::HILL_FORT: | ||||
| 		return ai->armyManager->calculateCreateresUpgrade(army, target, cb->getResourceAmount()).upgradeValue; | ||||
| 		return ai->armyManager->calculateCreateresUpgrade(army, target, ai->cb->getResourceAmount()).upgradeValue; | ||||
| 	case Obj::CREATURE_BANK: | ||||
| 		return getCreatureBankArmyReward(target, hero); | ||||
| 	case Obj::CREATURE_GENERATOR1: | ||||
| 	case Obj::CREATURE_GENERATOR2: | ||||
| 	case Obj::CREATURE_GENERATOR3: | ||||
| 	case Obj::CREATURE_GENERATOR4: | ||||
| 		return getDwellingScore(target, checkGold); | ||||
| 		return getDwellingScore(ai->cb.get(), target, checkGold); | ||||
| 	case Obj::CRYPT: | ||||
| 	case Obj::SHIPWRECK: | ||||
| 	case Obj::SHIPWRECK_SURVIVOR: | ||||
| @@ -225,7 +223,7 @@ uint64_t RewardEvaluator::getArmyReward( | ||||
| 	case Obj::DRAGON_UTOPIA: | ||||
| 		return 10000; | ||||
| 	case Obj::HERO: | ||||
| 		return cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES | ||||
| 		return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES | ||||
| 			? enemyArmyEliminationRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->getArmyStrength() | ||||
| 			: 0; | ||||
| 	default: | ||||
| @@ -241,7 +239,7 @@ int RewardEvaluator::getGoldCost(const CGObjectInstance * target, const CGHeroIn | ||||
| 	switch(target->ID) | ||||
| 	{ | ||||
| 	case Obj::HILL_FORT: | ||||
| 		return ai->armyManager->calculateCreateresUpgrade(army, target, cb->getResourceAmount()).upgradeCost[Res::GOLD]; | ||||
| 		return ai->armyManager->calculateCreateresUpgrade(army, target, ai->cb->getResourceAmount()).upgradeCost[Res::GOLD]; | ||||
| 	case Obj::SCHOOL_OF_MAGIC: | ||||
| 	case Obj::SCHOOL_OF_WAR: | ||||
| 		return 1000; | ||||
| @@ -325,7 +323,7 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons | ||||
| 			: 0.5f; | ||||
|  | ||||
| 	case Obj::HERO: | ||||
| 		return cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES | ||||
| 		return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES | ||||
| 			? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance *>(target)) | ||||
| 			: 0; | ||||
|  | ||||
| @@ -380,7 +378,7 @@ float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGH | ||||
| 	case Obj::WITCH_HUT: | ||||
| 		return evaluateWitchHutSkillScore(dynamic_cast<const CGWitchHut *>(target), hero, role); | ||||
| 	case Obj::HERO: | ||||
| 		return cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES | ||||
| 		return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES | ||||
| 			? enemyHeroEliminationSkillRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->level | ||||
| 			: 0; | ||||
| 	default: | ||||
| @@ -438,7 +436,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG | ||||
| 	case Obj::WATER_WHEEL: | ||||
| 		return 1000; | ||||
| 	case Obj::TOWN: | ||||
| 		return dailyIncomeMultiplier * estimateTownIncome(target, hero); | ||||
| 		return dailyIncomeMultiplier * estimateTownIncome(ai->cb.get(), target, hero); | ||||
| 	case Obj::MINE: | ||||
| 	case Obj::ABANDONED_MINE: | ||||
| 		return dailyIncomeMultiplier * (isGold ? 1000 : 75); | ||||
| @@ -459,7 +457,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG | ||||
| 	case Obj::SEA_CHEST: | ||||
| 		return 1500; | ||||
| 	case Obj::HERO: | ||||
| 		return cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES | ||||
| 		return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES | ||||
| 			? heroEliminationBonus + enemyArmyEliminationGoldRewardRatio * getArmyCost(dynamic_cast<const CGHeroInstance *>(target)) | ||||
| 			: 0; | ||||
| 	default: | ||||
| @@ -550,7 +548,12 @@ public: | ||||
|  | ||||
| class ExecuteHeroChainEvaluationContextBuilder : public IEvaluationContextBuilder | ||||
| { | ||||
| private: | ||||
| 	const Nullkiller * ai; | ||||
|  | ||||
| public: | ||||
| 	ExecuteHeroChainEvaluationContextBuilder(const Nullkiller * ai) : ai(ai) {} | ||||
|  | ||||
| 	virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override | ||||
| 	{ | ||||
| 		if(task->goalType != Goals::EXECUTE_HERO_CHAIN) | ||||
| @@ -578,14 +581,14 @@ public: | ||||
| 		} | ||||
|  | ||||
| 		auto heroPtr = task->hero; | ||||
| 		auto day = cb->getDate(Date::DAY); | ||||
| 		auto hero = heroPtr.get(); | ||||
| 		auto day = ai->cb->getDate(Date::DAY); | ||||
| 		auto hero = heroPtr.get(ai->cb.get()); | ||||
| 		bool checkGold = evaluationContext.danger == 0; | ||||
| 		auto army = path.heroArmy; | ||||
|  | ||||
| 		const CGObjectInstance * target = cb->getObj((ObjectInstanceID)task->objid, false); | ||||
| 		const CGObjectInstance * target = ai->cb->getObj((ObjectInstanceID)task->objid, false); | ||||
|  | ||||
| 		if (target && cb->getPlayerRelations(target->tempOwner, hero->tempOwner) == PlayerRelations::ENEMIES) | ||||
| 		if (target && ai->cb->getPlayerRelations(target->tempOwner, hero->tempOwner) == PlayerRelations::ENEMIES) | ||||
| 		{ | ||||
| 			evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero); | ||||
| 			evaluationContext.armyReward += evaluationContext.evaluator.getArmyReward(target, hero, army, checkGold); | ||||
| @@ -603,7 +606,12 @@ public: | ||||
|  | ||||
| class ClusterEvaluationContextBuilder : public IEvaluationContextBuilder | ||||
| { | ||||
| private: | ||||
| 	const Nullkiller * ai; | ||||
|  | ||||
| public: | ||||
| 	ClusterEvaluationContextBuilder(const Nullkiller * ai) : ai(ai) {} | ||||
|  | ||||
| 	virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override | ||||
| 	{ | ||||
| 		if(task->goalType != Goals::UNLOCK_CLUSTER) | ||||
| @@ -627,7 +635,7 @@ public: | ||||
| 		for(auto objInfo : objects) | ||||
| 		{ | ||||
| 			auto target = objInfo.first; | ||||
| 			auto day = cb->getDate(Date::DAY); | ||||
| 			auto day = ai->cb->getDate(Date::DAY); | ||||
| 			bool checkGold = objInfo.second.danger == 0; | ||||
| 			auto army = hero; | ||||
|  | ||||
| @@ -709,9 +717,9 @@ PriorityEvaluator::PriorityEvaluator(const Nullkiller * ai) | ||||
| 	:ai(ai) | ||||
| { | ||||
| 	initVisitTile(); | ||||
| 	evaluationContextBuilders.push_back(std::make_shared<ExecuteHeroChainEvaluationContextBuilder>()); | ||||
| 	evaluationContextBuilders.push_back(std::make_shared<ExecuteHeroChainEvaluationContextBuilder>(ai)); | ||||
| 	evaluationContextBuilders.push_back(std::make_shared<BuildThisEvaluationContextBuilder>()); | ||||
| 	evaluationContextBuilders.push_back(std::make_shared<ClusterEvaluationContextBuilder>()); | ||||
| 	evaluationContextBuilders.push_back(std::make_shared<ClusterEvaluationContextBuilder>(ai)); | ||||
| 	evaluationContextBuilders.push_back(std::make_shared<HeroExchangeEvaluator>()); | ||||
| 	evaluationContextBuilders.push_back(std::make_shared<ArmyUpgradeEvaluator>()); | ||||
| 	evaluationContextBuilders.push_back(std::make_shared<DefendTownEvaluator>()); | ||||
| @@ -769,7 +777,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task) | ||||
| 		closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio); | ||||
| 		strategicalValueVariable->setValue(evaluationContext.strategicalValue); | ||||
| 		goldPreasureVariable->setValue(ai->buildAnalyzer->getGoldPreasure()); | ||||
| 		goldCostVariable->setValue(evaluationContext.goldCost / ((float)cb->getResourceAmount(Res::GOLD) + (float)ai->buildAnalyzer->getDailyIncome()[Res::GOLD] + 1.0f)); | ||||
| 		goldCostVariable->setValue(evaluationContext.goldCost / ((float)ai->cb->getResourceAmount(Res::GOLD) + (float)ai->buildAnalyzer->getDailyIncome()[Res::GOLD] + 1.0f)); | ||||
| 		turnVariable->setValue(evaluationContext.turn); | ||||
| 		fearVariable->setValue(evaluationContext.enemyHeroDangerRatio); | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,6 @@ | ||||
| * | ||||
| */ | ||||
| #include "StdInc.h" | ||||
| #include <tbb/tbb.h> | ||||
| #include "AINodeStorage.h" | ||||
| #include "Actions/TownPortalAction.h" | ||||
| #include "../Goals/Goals.h" | ||||
| @@ -20,8 +19,6 @@ | ||||
| #include "../../../lib/PathfinderUtil.h" | ||||
| #include "../../../lib/CPlayerState.h" | ||||
|  | ||||
| using namespace tbb; | ||||
|  | ||||
| std::shared_ptr<boost::multi_array<AIPathNode, 5>> AISharedStorage::shared; | ||||
| std::set<int3> commitedTiles; | ||||
| std::set<int3> commitedTilesInitial; | ||||
| @@ -504,7 +501,7 @@ bool AINodeStorage::calculateHeroChain() | ||||
| 	{ | ||||
| 		std::mutex resultMutex; | ||||
|  | ||||
| 	std::random_shuffle(data.begin(), data.end()); | ||||
| 		std::random_shuffle(data.begin(), data.end()); | ||||
|  | ||||
| 		parallel_for(blocked_range<size_t>(0, data.size()), [&](const blocked_range<size_t>& r) | ||||
| 		{ | ||||
| @@ -526,7 +523,7 @@ bool AINodeStorage::calculateHeroChain() | ||||
| 		HeroChainCalculationTask task(*this, nodes, data, chainMask, heroChainTurn); | ||||
|  | ||||
| 		task.execute(r); | ||||
| 		task.flushResult(heroChain);\ | ||||
| 		task.flushResult(heroChain); | ||||
| 	} | ||||
|  | ||||
| 	CCreature::DisableChildLinkage = false; | ||||
|   | ||||
| @@ -51,10 +51,11 @@ namespace AIPathfinding | ||||
| 	{ | ||||
| 	private: | ||||
| 		const IShipyard * shipyard; | ||||
| 		const CPlayerSpecificInfoCallback * cb; | ||||
|  | ||||
| 	public: | ||||
| 		BuildBoatAction(const IShipyard * shipyard) | ||||
| 			: shipyard(shipyard) | ||||
| 		BuildBoatAction(const CPlayerSpecificInfoCallback * cb, const IShipyard * shipyard) | ||||
| 			: cb(cb), shipyard(shipyard) | ||||
| 		{ | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -196,15 +196,26 @@ HeroExchangeMap::HeroExchangeMap(const HeroActor * actor, const Nullkiller * ai) | ||||
|  | ||||
| HeroExchangeMap::~HeroExchangeMap() | ||||
| { | ||||
| 	CCreature::DisableChildLinkage = true; | ||||
|  | ||||
| 	for(auto & exchange : exchangeMap) | ||||
| 	{ | ||||
| 		if(!exchange.second) continue; | ||||
|  | ||||
| 		delete exchange.second->creatureSet; | ||||
| 	} | ||||
|  | ||||
| 	CCreature::DisableChildLinkage = false; | ||||
|  | ||||
| 	for(auto & exchange : exchangeMap) | ||||
| 	{ | ||||
| 		if(!exchange.second) continue; | ||||
|  | ||||
| 		delete exchange.second; | ||||
| 	} | ||||
|  | ||||
| 	exchangeMap.clear(); | ||||
|  | ||||
| } | ||||
|  | ||||
| ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other) | ||||
| @@ -270,7 +281,7 @@ ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other) | ||||
| 		} | ||||
|  | ||||
| 	if(other->isMovable && other->armyValue <= actor->armyValue / 10 && other->armyValue < MIN_ARMY_STRENGTH_FOR_CHAIN) | ||||
| 		return nullptr; | ||||
| 		return result; | ||||
|  | ||||
| 	TResources availableResources = resources - actor->armyCost - other->armyCost; | ||||
| 	HeroExchangeArmy * upgradedInitialArmy = tryUpgrade(actor->creatureSet, other->getActorObject(), availableResources); | ||||
|   | ||||
| @@ -69,7 +69,7 @@ namespace AIPathfinding | ||||
| 			if(shipyard->shipyardStatus() == IShipyard::GOOD) | ||||
| 			{ | ||||
| 				int3 boatLocation = shipyard->bestLocation(); | ||||
| 				virtualBoats[boatLocation] = std::make_shared<BuildBoatAction>(shipyard); | ||||
| 				virtualBoats[boatLocation] = std::make_shared<BuildBoatAction>(cb, shipyard); | ||||
| 				logAi->debug("Virtual boat added at %s", boatLocation.toString()); | ||||
| 			} | ||||
| 		} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user