mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Nullkiller AI: stabilization of build and prioritization fixes
This commit is contained in:
		
				
					committed by
					
						 Andrii Danylchenko
						Andrii Danylchenko
					
				
			
			
				
	
			
			
			
						parent
						
							de2361650b
						
					
				
				
					commit
					400967904b
				
			| @@ -47,6 +47,12 @@ void AIhelper::setAI(VCAI * AI) | ||||
| 	heroManager->setAI(AI); | ||||
| } | ||||
|  | ||||
| void AIhelper::update() | ||||
| { | ||||
| 	armyManager->update(); | ||||
| 	heroManager->update(); | ||||
| } | ||||
|  | ||||
| bool AIhelper::getBuildingOptions(const CGTownInstance * t) | ||||
| { | ||||
| 	return buildingManager->getBuildingOptions(t); | ||||
| @@ -162,6 +168,16 @@ void AIhelper::updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain) | ||||
| 	pathfindingManager->updatePaths(heroes, useHeroChain); | ||||
| } | ||||
|  | ||||
| uint64_t AIhelper::evaluateStackPower(const CCreature * creature, int count) const | ||||
| { | ||||
| 	return armyManager->evaluateStackPower(creature, count); | ||||
| } | ||||
|  | ||||
| SlotInfo AIhelper::getTotalCreaturesAvailable(CreatureID creatureID) const | ||||
| { | ||||
| 	return armyManager->getTotalCreaturesAvailable(creatureID); | ||||
| } | ||||
|  | ||||
| bool AIhelper::canGetArmy(const CArmedInstance * army, const CArmedInstance * source) const | ||||
| { | ||||
| 	return armyManager->canGetArmy(army, source); | ||||
| @@ -212,11 +228,6 @@ HeroRole AIhelper::getHeroRole(const HeroPtr & hero) const | ||||
| 	return heroManager->getHeroRole(hero); | ||||
| } | ||||
|  | ||||
| void AIhelper::updateHeroRoles() | ||||
| { | ||||
| 	heroManager->updateHeroRoles(); | ||||
| } | ||||
|  | ||||
| float AIhelper::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const | ||||
| { | ||||
| 	return heroManager->evaluateSecSkill(skill, hero); | ||||
|   | ||||
| @@ -80,14 +80,17 @@ public: | ||||
| 	std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const override; | ||||
| 	std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const override; | ||||
| 	std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const override; | ||||
| 	uint64_t evaluateStackPower(const CCreature * creature, int count) const override; | ||||
| 	SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const override; | ||||
|  | ||||
| 	const std::map<HeroPtr, HeroRole> & getHeroRoles() const override; | ||||
| 	HeroRole getHeroRole(const HeroPtr & hero) const override; | ||||
| 	int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const override; | ||||
| 	void updateHeroRoles() override; | ||||
| 	float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const override; | ||||
| 	float evaluateHero(const CGHeroInstance * hero) const override; | ||||
|  | ||||
| 	void update() override; | ||||
|  | ||||
| private: | ||||
| 	bool notifyGoalCompleted(Goals::TSubgoal goal) override; | ||||
|  | ||||
|   | ||||
| @@ -14,6 +14,336 @@ | ||||
| extern boost::thread_specific_ptr<CCallback> cb; | ||||
| extern boost::thread_specific_ptr<VCAI> ai; | ||||
|  | ||||
| void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo) | ||||
| { | ||||
| 	auto townInfo = developmentInfo.town->town; | ||||
| 	auto creatures = townInfo->creatures; | ||||
| 	auto buildings = townInfo->getAllBuildings(); | ||||
|  | ||||
| 	std::map<BuildingID, BuildingID> parentMap; | ||||
|  | ||||
| 	for(auto &pair : townInfo->buildings) | ||||
| 	{ | ||||
| 		if(pair.second->upgrade != -1) | ||||
| 		{ | ||||
| 			parentMap[pair.second->upgrade] = pair.first; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	BuildingID prefixes[] = {BuildingID::DWELL_UP_FIRST, BuildingID::DWELL_FIRST}; | ||||
|  | ||||
| 	for(int level = 0; level < GameConstants::CREATURES_PER_TOWN; level++) | ||||
| 	{ | ||||
| 		logAi->trace("Checking dwelling level %d", level); | ||||
| 		BuildingInfo nextToBuild = BuildingInfo(); | ||||
|  | ||||
| 		for(BuildingID prefix : prefixes) | ||||
| 		{ | ||||
| 			BuildingID building = BuildingID(prefix + level); | ||||
|  | ||||
| 			if(!vstd::contains(buildings, building)) | ||||
| 				continue; // no such building in town | ||||
|  | ||||
| 			auto info = getBuildingOrPrerequisite(developmentInfo.town, building); | ||||
|  | ||||
| 			if(info.exists) | ||||
| 			{ | ||||
| 				developmentInfo.addExistingDwelling(info); | ||||
|  | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			nextToBuild = info; | ||||
| 		} | ||||
|  | ||||
| 		if(nextToBuild.id != BuildingID::NONE) | ||||
| 		{ | ||||
| 			developmentInfo.addBuildingToBuild(nextToBuild); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo) | ||||
| { | ||||
| 	logAi->trace("Checking other buildings"); | ||||
|  | ||||
| 	std::vector<std::vector<BuildingID>> otherBuildings = { | ||||
| 		{BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL} | ||||
| 	}; | ||||
|  | ||||
| 	if(developmentInfo.existingDwellings.size() >= 2 && cb->getDate(Date::DAY_OF_WEEK) > boost::date_time::Friday) | ||||
| 	{ | ||||
| 		otherBuildings.push_back({BuildingID::CITADEL, BuildingID::CASTLE}); | ||||
| 	} | ||||
|  | ||||
| 	for(auto & buildingSet : otherBuildings) | ||||
| 	{ | ||||
| 		for(auto & buildingID : buildingSet) | ||||
| 		{ | ||||
| 			if(!developmentInfo.town->hasBuilt(buildingID)) | ||||
| 			{ | ||||
| 				developmentInfo.addBuildingToBuild(getBuildingOrPrerequisite(developmentInfo.town, buildingID)); | ||||
|  | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| int32_t convertToGold(const TResources & res) | ||||
| { | ||||
| 	return res[Res::GOLD]  | ||||
| 		+ 75 * (res[Res::WOOD] + res[Res::ORE])  | ||||
| 		+ 125 * (res[Res::GEMS] + res[Res::CRYSTAL] + res[Res::MERCURY] + res[Res::SULFUR]); | ||||
| } | ||||
|  | ||||
| TResources BuildAnalyzer::getResourcesRequiredNow() const | ||||
| { | ||||
| 	auto resourcesAvailable = cb->getResourceAmount(); | ||||
| 	auto result = requiredResources - resourcesAvailable; | ||||
|  | ||||
| 	result.positive(); | ||||
|  | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| TResources BuildAnalyzer::getTotalResourcesRequired() const | ||||
| { | ||||
| 	auto resourcesAvailable = cb->getResourceAmount(); | ||||
| 	auto result = totalDevelopmentCost - resourcesAvailable; | ||||
|  | ||||
| 	result.positive(); | ||||
|  | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| void BuildAnalyzer::update() | ||||
| { | ||||
| 	logAi->trace("Start analysing build"); | ||||
|  | ||||
| 	BuildingInfo bi; | ||||
|  | ||||
| 	reset(); | ||||
|  | ||||
| 	auto towns = cb->getTownsInfo(); | ||||
|  | ||||
| 	for(const CGTownInstance* town : towns) | ||||
| 	{ | ||||
| 		logAi->trace("Checking town %s", town->name); | ||||
|  | ||||
| 		auto townInfo = town->town; | ||||
|  | ||||
| 		developmentInfos.push_back(TownDevelopmentInfo(town)); | ||||
| 		TownDevelopmentInfo & developmentInfo = developmentInfos.back(); | ||||
|  | ||||
| 		updateTownDwellings(developmentInfo); | ||||
| 		updateOtherBuildings(developmentInfo); | ||||
|  | ||||
| 		requiredResources += developmentInfo.requiredResources; | ||||
| 		totalDevelopmentCost += developmentInfo.townDevelopmentCost; | ||||
| 		armyCost += developmentInfo.armyCost; | ||||
|  | ||||
| 		for(auto bi : developmentInfo.toBuild) | ||||
| 		{ | ||||
| 			logAi->trace("Building preferences %s", bi.toString()); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	std::sort(developmentInfos.begin(), developmentInfos.end(), [](const TownDevelopmentInfo & t1, const TownDevelopmentInfo & t2) -> bool | ||||
| 	{ | ||||
| 		auto val1 = convertToGold(t1.armyCost) - convertToGold(t1.townDevelopmentCost); | ||||
| 		auto val2 = convertToGold(t2.armyCost) - convertToGold(t2.townDevelopmentCost); | ||||
|  | ||||
| 		return val1 > val2; | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void BuildAnalyzer::reset() | ||||
| { | ||||
| 	requiredResources = TResources(); | ||||
| 	totalDevelopmentCost = TResources(); | ||||
| 	armyCost = TResources(); | ||||
| 	developmentInfos.clear(); | ||||
| } | ||||
|  | ||||
| BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite( | ||||
| 	const CGTownInstance* town, | ||||
| 	BuildingID toBuild, | ||||
| 	bool excludeDwellingDependencies) const | ||||
| { | ||||
| 	BuildingID building = toBuild; | ||||
| 	auto townInfo = town->town; | ||||
|  | ||||
| 	const CBuilding * buildPtr = townInfo->buildings.at(building); | ||||
| 	const CCreature * creature = nullptr; | ||||
| 	CreatureID baseCreatureID; | ||||
|  | ||||
| 	if(BuildingID::DWELL_FIRST <= toBuild && toBuild <= BuildingID::DWELL_UP_LAST) | ||||
| 	{ | ||||
| 		int level = toBuild - BuildingID::DWELL_FIRST; | ||||
| 		auto creatures = townInfo->creatures.at(level % GameConstants::CREATURES_PER_TOWN); | ||||
| 		auto creatureID = creatures.at(level / GameConstants::CREATURES_PER_TOWN); | ||||
|  | ||||
| 		baseCreatureID = creatures.front(); | ||||
| 		creature = creatureID.toCreature(); | ||||
| 	} | ||||
|  | ||||
| 	auto info = BuildingInfo(buildPtr, creature, baseCreatureID); | ||||
|  | ||||
| 	logAi->trace("checking %s", buildPtr->Name()); | ||||
| 	logAi->trace("buildInfo %s", info.toString()); | ||||
|  | ||||
| 	buildPtr = nullptr; | ||||
|  | ||||
| 	if(!town->hasBuilt(building)) | ||||
| 	{ | ||||
| 		auto canBuild = cb->canBuildStructure(town, building); | ||||
|  | ||||
| 		if(canBuild == EBuildingState::ALLOWED) | ||||
| 		{ | ||||
| 			info.canBuild = true; | ||||
| 		} | ||||
| 		else if(canBuild == EBuildingState::NO_RESOURCES) | ||||
| 		{ | ||||
| 			logAi->trace("cant build. Not enough resources. Need %s", info.buildCost.toString()); | ||||
| 			info.notEnoughRes = true; | ||||
| 		} | ||||
| 		else if(canBuild == EBuildingState::PREREQUIRES) | ||||
| 		{ | ||||
| 			auto buildExpression = town->genBuildingRequirements(building, false); | ||||
| 			auto missingBuildings = buildExpression.getFulfillmentCandidates([&](const BuildingID & id) -> bool | ||||
| 			{ | ||||
| 				return town->hasBuilt(id); | ||||
| 			}); | ||||
|  | ||||
| 			auto otherDwelling = [](const BuildingID & id) -> bool | ||||
| 			{ | ||||
| 				return BuildingID::DWELL_FIRST <= id && id <= BuildingID::DWELL_UP_LAST; | ||||
| 			}; | ||||
|  | ||||
| 			if(vstd::contains_if(missingBuildings, otherDwelling)) | ||||
| 			{ | ||||
| 				logAi->trace("cant build. Need other dwelling"); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				buildPtr = townInfo->buildings.at(building); | ||||
|  | ||||
| 				logAi->trace("cant build. Need %d", missingBuildings[0].num); | ||||
|  | ||||
| 				BuildingInfo prerequisite = getBuildingOrPrerequisite(town, missingBuildings[0], excludeDwellingDependencies); | ||||
|  | ||||
| 				prerequisite.buildCostWithPrerequisits += info.buildCost; | ||||
| 				prerequisite.creatureCost = info.creatureCost; | ||||
| 				prerequisite.creatureGrows = info.creatureGrows; | ||||
| 				prerequisite.creatureLevel = info.creatureLevel; | ||||
| 				prerequisite.creatureID = info.creatureID; | ||||
| 				prerequisite.baseCreatureID = info.baseCreatureID; | ||||
| 				prerequisite.prerequisitesCount++; | ||||
|  | ||||
| 				return prerequisite; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		logAi->trace("exists"); | ||||
| 		info.exists = true; | ||||
| 	} | ||||
|  | ||||
| 	return info; | ||||
| } | ||||
|  | ||||
| TResources BuildAnalyzer::getDailyIncome() const | ||||
| { | ||||
| 	auto objects = cb->getMyObjects(); | ||||
| 	auto towns = cb->getTownsInfo(); | ||||
| 	TResources dailyIncome = TResources(); | ||||
|  | ||||
| 	for(const CGObjectInstance* obj : objects) | ||||
| 	{ | ||||
| 		const CGMine* mine = dynamic_cast<const CGMine*>(obj); | ||||
|  | ||||
| 		if(mine) | ||||
| 		{ | ||||
| 			dailyIncome[mine->producedResource] += mine->producedQuantity; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for(const CGTownInstance* town : towns) | ||||
| 	{ | ||||
| 		dailyIncome += town->dailyIncome(); | ||||
| 	} | ||||
|  | ||||
| 	return dailyIncome; | ||||
| } | ||||
|  | ||||
| void TownDevelopmentInfo::addExistingDwelling(const BuildingInfo & existingDwelling) | ||||
| { | ||||
| 	existingDwellings.push_back(existingDwelling); | ||||
|  | ||||
| 	armyCost += existingDwelling.creatureCost * existingDwelling.creatureGrows; | ||||
| } | ||||
|  | ||||
| void TownDevelopmentInfo::addBuildingToBuild(const BuildingInfo & nextToBuild) | ||||
| { | ||||
| 	townDevelopmentCost += nextToBuild.buildCostWithPrerequisits; | ||||
|  | ||||
| 	if(nextToBuild.canBuild) | ||||
| 	{ | ||||
| 		toBuild.push_back(nextToBuild); | ||||
| 		hasSomethingToBuild = true; | ||||
| 	} | ||||
| 	else if(nextToBuild.notEnoughRes) | ||||
| 	{ | ||||
| 		requiredResources += nextToBuild.buildCost; | ||||
| 		hasSomethingToBuild = true; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| BuildingInfo::BuildingInfo() | ||||
| { | ||||
| 	id = BuildingID::NONE; | ||||
| 	creatureGrows = 0; | ||||
| 	creatureID = CreatureID::NONE; | ||||
| 	buildCost = 0; | ||||
| 	buildCostWithPrerequisits = 0; | ||||
| 	prerequisitesCount = 0; | ||||
| 	name = ""; | ||||
| } | ||||
|  | ||||
| BuildingInfo::BuildingInfo(const CBuilding * building, const CCreature * creature, CreatureID baseCreature) | ||||
| { | ||||
| 	id = building->bid; | ||||
| 	buildCost = building->resources; | ||||
| 	buildCostWithPrerequisits = building->resources; | ||||
| 	dailyIncome = building->produce; | ||||
| 	exists = false;; | ||||
| 	prerequisitesCount = 1; | ||||
| 	name = building->Name(); | ||||
|  | ||||
| 	if(creature) | ||||
| 	{ | ||||
| 		creatureGrows = creature->growth; | ||||
| 		creatureID = creature->idNumber; | ||||
| 		creatureCost = creature->cost; | ||||
| 		creatureLevel = creature->level; | ||||
| 		baseCreatureID = baseCreature; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		creatureGrows = 0; | ||||
| 		creatureID = CreatureID::NONE; | ||||
| 		baseCreatureID = CreatureID::NONE; | ||||
| 		creatureCost = TResources(); | ||||
| 		creatureLevel = 0; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| std::string BuildingInfo::toString() const | ||||
| { | ||||
| 	return name + ", cost: " + buildCost.toString() | ||||
| 		+ ", creature: " + std::to_string(creatureGrows) + " x " + std::to_string(creatureLevel) | ||||
| 		+ " x " + creatureCost.toString() | ||||
| 		+ ", daily: " + dailyIncome.toString(); | ||||
| } | ||||
| @@ -12,11 +12,82 @@ | ||||
| #include "../VCAI.h" | ||||
| #include "../../../lib/ResourceSet.h" | ||||
|  | ||||
|  | ||||
| class BuildingInfo | ||||
| { | ||||
| public: | ||||
| 	BuildingID id; | ||||
| 	TResources buildCost; | ||||
| 	TResources buildCostWithPrerequisits; | ||||
| 	int creatureGrows; | ||||
| 	uint8_t creatureLevel; | ||||
| 	TResources creatureCost; | ||||
| 	CreatureID creatureID; | ||||
| 	CreatureID baseCreatureID; | ||||
| 	TResources dailyIncome; | ||||
| 	uint8_t prerequisitesCount; | ||||
| 	std::string name; | ||||
| 	bool exists = false; | ||||
| 	bool canBuild = false; | ||||
| 	bool notEnoughRes = false; | ||||
|  | ||||
| 	BuildingInfo(); | ||||
|  | ||||
| 	BuildingInfo(const CBuilding* building, const CCreature* creature, CreatureID baseCreatureID); | ||||
|  | ||||
| 	std::string toString() const; | ||||
| }; | ||||
|  | ||||
| class TownDevelopmentInfo | ||||
| { | ||||
| public: | ||||
| 	const CGTownInstance* town; | ||||
| 	std::vector<BuildingInfo> toBuild; | ||||
| 	std::vector<BuildingInfo> existingDwellings; | ||||
| 	TResources townDevelopmentCost; | ||||
| 	TResources requiredResources; | ||||
| 	TResources armyCost; | ||||
| 	int armyScore; | ||||
| 	int economicsScore; | ||||
| 	HeroRole townRole; | ||||
| 	bool hasSomethingToBuild; | ||||
|  | ||||
| 	TownDevelopmentInfo(const CGTownInstance* town) | ||||
| 		:town(town), armyScore(0), economicsScore(0), toBuild(), townDevelopmentCost(), requiredResources(), townRole(HeroRole::SCOUT), hasSomethingToBuild(false) | ||||
| 	{ | ||||
| 	} | ||||
|  | ||||
| 	TownDevelopmentInfo() : TownDevelopmentInfo(nullptr) {} | ||||
|  | ||||
| 	void addBuildingToBuild(const BuildingInfo & building); | ||||
| 	void addExistingDwelling(const BuildingInfo & existingDwelling); | ||||
| }; | ||||
|  | ||||
| class BuildAnalyzer | ||||
| { | ||||
| private: | ||||
| 	TResources requiredResources; | ||||
| 	TResources totalDevelopmentCost; | ||||
| 	std::vector<TownDevelopmentInfo> developmentInfos; | ||||
| 	TResources armyCost; | ||||
|  | ||||
| public: | ||||
| 	void update(); | ||||
|  | ||||
| 	TResources getResourcesRequiredNow() const; | ||||
| 	TResources getTotalResourcesRequired() const; | ||||
| 	const std::vector<TownDevelopmentInfo> & getDevelopmentInfo() const { return developmentInfos; } | ||||
|  | ||||
| 	TResources getDailyIncome() const; | ||||
|  | ||||
| private: | ||||
| 	BuildingInfo getBuildingOrPrerequisite( | ||||
| 		const CGTownInstance* town, | ||||
| 		BuildingID toBuild, | ||||
| 		bool excludeDwellingDependencies = true) const; | ||||
|  | ||||
|  | ||||
| 	void updateTownDwellings(TownDevelopmentInfo & developmentInfo); | ||||
| 	void updateOtherBuildings(TownDevelopmentInfo & developmentInfo); | ||||
| 	void reset(); | ||||
| }; | ||||
|   | ||||
| @@ -49,6 +49,9 @@ void DangerHitMapAnalyzer::updateHitMap() | ||||
| 		{ | ||||
| 			for(AIPath & path : ai->ah->getPathsToTile(pos)) | ||||
| 			{ | ||||
| 				if(path.getFirstBlockedAction()) | ||||
| 					continue; | ||||
|  | ||||
| 				auto tileDanger = path.getHeroStrength(); | ||||
| 				auto turn = path.turn(); | ||||
| 				auto & node = hitMap[pos.x][pos.y][pos.z]; | ||||
|   | ||||
| @@ -168,3 +168,43 @@ ui64 ArmyManager::howManyReinforcementsCanGet(const CCreatureSet * target, const | ||||
|  | ||||
| 	return newArmy > oldArmy ? newArmy - oldArmy : 0; | ||||
| } | ||||
|  | ||||
| uint64_t ArmyManager::evaluateStackPower(const CCreature * creature, int count) const | ||||
| { | ||||
| 	return creature->AIValue * count; | ||||
| } | ||||
|  | ||||
| SlotInfo ArmyManager::getTotalCreaturesAvailable(CreatureID creatureID) const | ||||
| { | ||||
| 	auto creatureInfo = totalArmy.find(creatureID); | ||||
|  | ||||
| 	return creatureInfo == totalArmy.end() ? SlotInfo() : creatureInfo->second; | ||||
| } | ||||
|  | ||||
| void ArmyManager::update() | ||||
| { | ||||
| 	logAi->trace("Start analysing army"); | ||||
|  | ||||
| 	std::vector<const CCreatureSet *> total; | ||||
| 	auto heroes = cb->getHeroesInfo(); | ||||
| 	auto towns = cb->getTownsInfo(); | ||||
|  | ||||
| 	std::copy(heroes.begin(), heroes.end(), std::back_inserter(total)); | ||||
| 	std::copy(towns.begin(), towns.end(), std::back_inserter(total)); | ||||
|  | ||||
| 	totalArmy.clear(); | ||||
|  | ||||
| 	for(auto army : total) | ||||
| 	{ | ||||
| 		for(auto slot : army->Slots()) | ||||
| 		{ | ||||
| 			totalArmy[slot.second->getCreatureID()].count += slot.second->count; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for(auto army : totalArmy) | ||||
| 	{ | ||||
| 		army.second.creature = army.first.toCreature(); | ||||
| 		army.second.power = evaluateStackPower(army.second.creature, army.second.count); | ||||
| 	} | ||||
| } | ||||
| @@ -30,6 +30,7 @@ class DLL_EXPORT IArmyManager //: public: IAbstractManager | ||||
| public: | ||||
| 	virtual void init(CPlayerSpecificInfoCallback * CB) = 0; | ||||
| 	virtual void setAI(VCAI * AI) = 0; | ||||
| 	virtual void update() = 0; | ||||
| 	virtual bool canGetArmy(const CArmedInstance * target, const CArmedInstance * source) const = 0; | ||||
| 	virtual ui64 howManyReinforcementsCanBuy(const CCreatureSet * target, const CGDwelling * source) const = 0; | ||||
| 	virtual ui64 howManyReinforcementsCanGet(const CCreatureSet * target, const CCreatureSet * source) const = 0; | ||||
| @@ -37,6 +38,8 @@ public: | ||||
| 	virtual std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const = 0; | ||||
| 	virtual std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const = 0; | ||||
| 	virtual std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const = 0; | ||||
| 	virtual uint64_t evaluateStackPower(const CCreature * creature, int count) const = 0; | ||||
| 	virtual SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const = 0; | ||||
| }; | ||||
|  | ||||
| class DLL_EXPORT ArmyManager : public IArmyManager | ||||
| @@ -44,10 +47,12 @@ class DLL_EXPORT ArmyManager : public IArmyManager | ||||
| private: | ||||
| 	CPlayerSpecificInfoCallback * cb; //this is enough, but we downcast from CCallback | ||||
| 	VCAI * ai; | ||||
| 	std::map<CreatureID, SlotInfo> totalArmy; | ||||
|  | ||||
| public: | ||||
| 	void init(CPlayerSpecificInfoCallback * CB) override; | ||||
| 	void setAI(VCAI * AI) override; | ||||
| 	void update() override; | ||||
|  | ||||
| 	bool canGetArmy(const CArmedInstance * target, const CArmedInstance * source) const override; | ||||
| 	ui64 howManyReinforcementsCanBuy(const CCreatureSet * target, const CGDwelling * source) const override; | ||||
| @@ -56,4 +61,6 @@ public: | ||||
| 	std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const override; | ||||
| 	std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const override; | ||||
| 	std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const override; | ||||
| 	uint64_t evaluateStackPower(const CCreature * creature, int count) const override; | ||||
| 	SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const override; | ||||
| }; | ||||
|   | ||||
| @@ -33,6 +33,32 @@ Goals::TGoalVec BuildingBehavior::getTasks() | ||||
| { | ||||
| 	Goals::TGoalVec tasks; | ||||
|  | ||||
| 	TResources resourcesRequired = ai->nullkiller->buildAnalyzer->getResourcesRequiredNow(); | ||||
| 	TResources totalDevelopmentCost = ai->nullkiller->buildAnalyzer->getTotalResourcesRequired(); | ||||
| 	TResources availableResources = cb->getResourceAmount(); | ||||
| 	TResources dailyIncome = ai->nullkiller->buildAnalyzer->getDailyIncome(); | ||||
|  | ||||
| 	logAi->trace("Resources amount: %s", availableResources.toString()); | ||||
|  | ||||
| 	resourcesRequired -= availableResources; | ||||
| 	resourcesRequired.positive(); | ||||
|  | ||||
| 	logAi->trace("daily income: %s", dailyIncome.toString()); | ||||
| 	logAi->trace("resources required to develop towns now: %s, total: %s", | ||||
| 		resourcesRequired.toString(), | ||||
| 		totalDevelopmentCost.toString()); | ||||
|  | ||||
| 	auto & developmentInfos = ai->nullkiller->buildAnalyzer->getDevelopmentInfo(); | ||||
|  | ||||
| 	for(auto & developmentInfo : developmentInfos) | ||||
| 	{ | ||||
| 		auto town = developmentInfo.town; | ||||
|  | ||||
| 		for(auto & buildingInfo : developmentInfo.toBuild) | ||||
| 		{ | ||||
| 			tasks.push_back(sptr(BuildThis(buildingInfo, developmentInfo))); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return tasks; | ||||
| } | ||||
|   | ||||
| @@ -28,17 +28,6 @@ std::string CaptureObjectsBehavior::toString() const | ||||
| 	return "Capture objects"; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<const ISpecialAction> getFirstBlockedAction(const AIPath & path) | ||||
| { | ||||
| 	for(auto node : path.nodes) | ||||
| 	{ | ||||
| 		if(node.specialAction && !node.specialAction->canAct(node.targetHero)) | ||||
| 			return node.specialAction; | ||||
| 	} | ||||
|  | ||||
| 	return std::shared_ptr<const ISpecialAction>(); | ||||
| } | ||||
|  | ||||
| Goals::TGoalVec CaptureObjectsBehavior::getTasks() | ||||
| { | ||||
| 	Goals::TGoalVec tasks; | ||||
| @@ -76,7 +65,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks() | ||||
| 				logAi->trace("Path found %s", path.toString()); | ||||
| #endif | ||||
|  | ||||
| 				if(getFirstBlockedAction(path)) | ||||
| 				if(path.getFirstBlockedAction()) | ||||
| 				{ | ||||
| #ifdef VCMI_TRACE_PATHFINDER | ||||
| 					// TODO: decomposition? | ||||
| @@ -88,7 +77,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks() | ||||
| 				if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path)) | ||||
| 				{ | ||||
| #ifdef VCMI_TRACE_PATHFINDER | ||||
| 					logAi->trace("Ignore path. Target hero can be killed by enemy"); | ||||
| 					logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %d", path.heroArmy->getArmyStrength()); | ||||
| #endif | ||||
| 					continue; | ||||
| 				} | ||||
| @@ -112,7 +101,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks() | ||||
| 					hero->name, | ||||
| 					path.getHeroStrength(), | ||||
| 					danger, | ||||
| 					path.armyLoss); | ||||
| 					path.getTotalArmyLoss()); | ||||
| #endif | ||||
|  | ||||
| 				if(isSafe) | ||||
|   | ||||
| @@ -40,7 +40,7 @@ Goals::TGoalVec RecruitHeroBehavior::getTasks() | ||||
| 			if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1 | ||||
| 				|| cb->getResourceAmount(Res::GOLD) > 10000) | ||||
| 			{ | ||||
| 				tasks.push_back(Goals::sptr(Goals::RecruitHero().settown(town))); | ||||
| 				tasks.push_back(Goals::sptr(Goals::RecruitHero().settown(town).setpriority(3))); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -16,6 +16,7 @@ | ||||
| #include "../Behaviors/BuyArmyBehavior.h" | ||||
| #include "../Behaviors/StartupBehavior.h" | ||||
| #include "../Behaviors/DefenceBehavior.h" | ||||
| #include "../Behaviors/BuildingBehavior.h" | ||||
| #include "../Goals/Invalid.h" | ||||
|  | ||||
| extern boost::thread_specific_ptr<CCallback> cb; | ||||
| @@ -25,6 +26,7 @@ Nullkiller::Nullkiller() | ||||
| { | ||||
| 	priorityEvaluator.reset(new PriorityEvaluator()); | ||||
| 	dangerHitMap.reset(new DangerHitMapAnalyzer()); | ||||
| 	buildAnalyzer.reset(new BuildAnalyzer()); | ||||
| } | ||||
|  | ||||
| Goals::TSubgoal Nullkiller::choseBestTask(Goals::TGoalVec & tasks) const | ||||
| @@ -78,14 +80,17 @@ void Nullkiller::updateAiState() | ||||
| 	// TODO: move to hero manager | ||||
| 	auto activeHeroes = ai->getMyHeroes(); | ||||
|  | ||||
| 	vstd::erase_if(activeHeroes, [this](const HeroPtr & hero) -> bool{ | ||||
| 	vstd::erase_if(activeHeroes, [this](const HeroPtr & hero) -> bool | ||||
| 	{ | ||||
| 		auto lockedReason = getHeroLockedReason(hero.h); | ||||
|  | ||||
| 		return lockedReason == HeroLockedReason::DEFENCE || lockedReason == HeroLockedReason::STARTUP; | ||||
| 	}); | ||||
|  | ||||
| 	ai->ah->updatePaths(activeHeroes, true); | ||||
| 	ai->ah->updateHeroRoles(); | ||||
| 	ai->ah->update(); | ||||
|  | ||||
| 	buildAnalyzer->update(); | ||||
| } | ||||
|  | ||||
| bool Nullkiller::arePathHeroesLocked(const AIPath & path) const | ||||
| @@ -111,7 +116,8 @@ void Nullkiller::makeTurn() | ||||
| 			choseBestTask(std::make_shared<BuyArmyBehavior>()), | ||||
| 			choseBestTask(std::make_shared<CaptureObjectsBehavior>()), | ||||
| 			choseBestTask(std::make_shared<RecruitHeroBehavior>()), | ||||
| 			choseBestTask(std::make_shared<DefenceBehavior>()) | ||||
| 			choseBestTask(std::make_shared<DefenceBehavior>()), | ||||
| 			choseBestTask(std::make_shared<BuildingBehavior>()) | ||||
| 		}; | ||||
|  | ||||
| 		if(cb->getDate(Date::DAY) == 1) | ||||
| @@ -140,12 +146,12 @@ void Nullkiller::makeTurn() | ||||
| 		{ | ||||
| 			logAi->trace(bestTask->completeMessage()); | ||||
| 		} | ||||
| 		catch(std::exception & e) | ||||
| 		/*catch(std::exception & e) | ||||
| 		{ | ||||
| 			logAi->debug("Failed to realize subgoal of type %s, I will stop.", bestTask->name()); | ||||
| 			logAi->debug("The error message was: %s", e.what()); | ||||
|  | ||||
| 			return; | ||||
| 		} | ||||
| 		}*/ | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -35,6 +35,7 @@ private: | ||||
|  | ||||
| public: | ||||
| 	std::unique_ptr<DangerHitMapAnalyzer> dangerHitMap; | ||||
| 	std::unique_ptr<BuildAnalyzer> buildAnalyzer; | ||||
|  | ||||
| 	Nullkiller(); | ||||
| 	void makeTurn(); | ||||
|   | ||||
| @@ -35,11 +35,6 @@ class CGTownInstance; | ||||
| extern boost::thread_specific_ptr<CCallback> cb; | ||||
| extern boost::thread_specific_ptr<VCAI> ai; | ||||
|  | ||||
| PriorityEvaluator::PriorityEvaluator() | ||||
| { | ||||
| 	initVisitTile(); | ||||
| } | ||||
|  | ||||
| PriorityEvaluator::~PriorityEvaluator() | ||||
| { | ||||
| 	delete engine; | ||||
| @@ -112,7 +107,8 @@ uint64_t getDwellingScore(const CGObjectInstance * target, bool checkGold) | ||||
| 		if(creLevel.first && creLevel.second.size()) | ||||
| 		{ | ||||
| 			auto creature = creLevel.second.back().toCreature(); | ||||
| 			if(checkGold &&	!cb->getResourceAmount().canAfford(creature->cost * creLevel.first)) | ||||
| 			auto creaturesAreFree = creature->level == 1; | ||||
| 			if(!creaturesAreFree && checkGold && !cb->getResourceAmount().canAfford(creature->cost * creLevel.first)) | ||||
| 				continue; | ||||
|  | ||||
| 			score += creature->AIValue * creLevel.first; | ||||
| @@ -179,7 +175,7 @@ uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * h | ||||
| 	case Obj::SHIPWRECK: | ||||
| 	case Obj::SHIPWRECK_SURVIVOR: | ||||
| 	case Obj::WARRIORS_TOMB: | ||||
| 		return 1500; | ||||
| 		return 1000; | ||||
| 	case Obj::ARTIFACT: | ||||
| 		return evaluateArtifactArmyValue(dynamic_cast<const CGArtifact *>(target)->storedArtifact); | ||||
| 	case Obj::DRAGON_UTOPIA: | ||||
| @@ -208,6 +204,34 @@ float getEnemyHeroStrategicalValue(const CGHeroInstance * enemy) | ||||
| 	return objectValue / 2.0f + enemy->level / 15.0f; | ||||
| } | ||||
|  | ||||
| float getResourceRequirementStrength(int resType) | ||||
| { | ||||
| 	TResources requiredResources = ai->nullkiller->buildAnalyzer->getResourcesRequiredNow(); | ||||
| 	TResources dailyIncome = ai->nullkiller->buildAnalyzer->getDailyIncome(); | ||||
|  | ||||
| 	if(requiredResources[resType] == 0) | ||||
| 		return 0; | ||||
|  | ||||
| 	if(dailyIncome[resType] == 0) | ||||
| 		return 1; | ||||
|  | ||||
| 	return (float)requiredResources[resType] / dailyIncome[resType] / 3; | ||||
| } | ||||
|  | ||||
| float getTotalResourceRequirementStrength(int resType) | ||||
| { | ||||
| 	TResources requiredResources = ai->nullkiller->buildAnalyzer->getTotalResourcesRequired(); | ||||
| 	TResources dailyIncome = ai->nullkiller->buildAnalyzer->getDailyIncome(); | ||||
|  | ||||
| 	if(requiredResources[resType] == 0) | ||||
| 		return 0; | ||||
|  | ||||
| 	if(dailyIncome[resType] == 0) | ||||
| 		return requiredResources[resType] / 30; | ||||
|  | ||||
| 	return (float)requiredResources[resType] / dailyIncome[resType] / 30; | ||||
| } | ||||
|  | ||||
| float getStrategicalValue(const CGObjectInstance * target) | ||||
| { | ||||
| 	if(!target) | ||||
| @@ -215,6 +239,12 @@ float getStrategicalValue(const CGObjectInstance * target) | ||||
|  | ||||
| 	switch(target->ID) | ||||
| 	{ | ||||
| 	case Obj::MINE: | ||||
| 		return target->subID == Res::GOLD ? 0.8f : 0.05f + 0.3f * getTotalResourceRequirementStrength(target->subID) + 0.5f * getResourceRequirementStrength(target->subID); | ||||
|  | ||||
| 	case Obj::RESOURCE: | ||||
| 		return target->subID == Res::GOLD ? 0 : 0.5f * getResourceRequirementStrength(target->subID); | ||||
|  | ||||
| 	case Obj::TOWN: | ||||
| 		return target->tempOwner == PlayerColor::NEUTRAL ? 0.5 : 1; | ||||
|  | ||||
| @@ -261,7 +291,10 @@ float getSkillReward(const CGObjectInstance * target, const CGHeroInstance * her | ||||
| 	case Obj::MERCENARY_CAMP: | ||||
| 	case Obj::SHRINE_OF_MAGIC_GESTURE: | ||||
| 	case Obj::SHRINE_OF_MAGIC_INCANTATION: | ||||
| 	case Obj::TREE_OF_KNOWLEDGE: | ||||
| 		return 1; | ||||
| 	case Obj::LEARNING_STONE: | ||||
| 		return 1.0f / std::sqrtf(hero->level); | ||||
| 	case Obj::ARENA: | ||||
| 	case Obj::SHRINE_OF_MAGIC_THOUGHT: | ||||
| 		return 2; | ||||
| @@ -339,6 +372,86 @@ int32_t getGoldReward(const CGObjectInstance * target, const CGHeroInstance * he | ||||
| 	} | ||||
| } | ||||
|  | ||||
| class ExecuteHeroChainEvaluationContextBuilder : public IEvaluationContextBuilder | ||||
| { | ||||
| public: | ||||
| 	virtual Goals::EvaluationContext buildEvaluationContext(Goals::TSubgoal task) const override | ||||
| 	{ | ||||
| 		auto evaluationContext = task->evaluationContext; | ||||
|  | ||||
| 		int objId = task->objid; | ||||
|  | ||||
| 		if(task->parent) | ||||
| 			objId = task->parent->objid; | ||||
|  | ||||
| 		auto heroPtr = task->hero; | ||||
| 		const CGObjectInstance * target = cb->getObj((ObjectInstanceID)objId, false); | ||||
| 		auto day = cb->getDate(Date::DAY); | ||||
| 		auto hero = heroPtr.get(); | ||||
| 		bool checkGold = evaluationContext.danger == 0; | ||||
|  | ||||
| 		evaluationContext.armyLossPersentage = task->evaluationContext.armyLoss / (double)task->evaluationContext.heroStrength; | ||||
| 		evaluationContext.heroRole = ai->ah->getHeroRole(heroPtr); | ||||
| 		evaluationContext.goldReward = getGoldReward(target, hero); | ||||
| 		evaluationContext.armyReward = getArmyReward(target, hero, checkGold); | ||||
| 		evaluationContext.skillReward = getSkillReward(target, hero, evaluationContext.heroRole); | ||||
| 		evaluationContext.strategicalValue = getStrategicalValue(target); | ||||
|  | ||||
| 		return evaluationContext; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| class BuildThisEvaluationContextBuilder : public IEvaluationContextBuilder | ||||
| { | ||||
| public: | ||||
| 	virtual Goals::EvaluationContext buildEvaluationContext(Goals::TSubgoal task) const override | ||||
| 	{ | ||||
| 		Goals::EvaluationContext evaluationContext; | ||||
| 		Goals::BuildThis & buildThis = dynamic_cast<Goals::BuildThis &>(*task); | ||||
| 		auto & bi = buildThis.buildingInfo; | ||||
| 		 | ||||
| 		evaluationContext.goldReward = bi.dailyIncome[Res::GOLD] / 2; | ||||
| 		evaluationContext.heroRole = HeroRole::MAIN; | ||||
| 		evaluationContext.movementCostByRole[evaluationContext.heroRole] = bi.prerequisitesCount; | ||||
| 		evaluationContext.armyReward = 0; | ||||
| 		evaluationContext.strategicalValue = buildThis.townInfo.armyScore / 50000.0; | ||||
|  | ||||
| 		if(bi.creatureID != CreatureID::NONE) | ||||
| 		{ | ||||
| 			evaluationContext.strategicalValue += 0.5f + 0.1f * bi.creatureLevel; | ||||
|  | ||||
| 			if(bi.baseCreatureID == bi.creatureID) | ||||
| 			{ | ||||
| 				evaluationContext.armyReward = ai->ah->evaluateStackPower(bi.creatureID.toCreature(), bi.creatureGrows); | ||||
| 			} | ||||
| 			 | ||||
| 			auto creaturesToUpgrade = ai->ah->getTotalCreaturesAvailable(bi.baseCreatureID); | ||||
| 			auto upgradedPower = ai->ah->evaluateStackPower(bi.creatureID.toCreature(), creaturesToUpgrade.count); | ||||
|  | ||||
| 			evaluationContext.armyReward = upgradedPower - creaturesToUpgrade.power; | ||||
| 		} | ||||
| 		 | ||||
| 		return evaluationContext; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| PriorityEvaluator::PriorityEvaluator() | ||||
| { | ||||
| 	initVisitTile(); | ||||
| 	evaluationContextBuilders[Goals::EXECUTE_HERO_CHAIN] = std::make_shared<ExecuteHeroChainEvaluationContextBuilder>(); | ||||
| 	evaluationContextBuilders[Goals::BUILD_STRUCTURE] = std::make_shared<BuildThisEvaluationContextBuilder>(); | ||||
| } | ||||
|  | ||||
| Goals::EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal) const | ||||
| { | ||||
| 	auto builder = evaluationContextBuilders.find(goal->goalType); | ||||
|  | ||||
| 	if(builder == evaluationContextBuilders.end()) | ||||
| 		return goal->evaluationContext; | ||||
|  | ||||
| 	return builder->second->buildEvaluationContext(goal); | ||||
| } | ||||
|  | ||||
| /// distance | ||||
| /// nearest hero? | ||||
| /// gold income | ||||
| @@ -351,45 +464,28 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task) | ||||
| 	if(task->priority > 0) | ||||
| 		return task->priority; | ||||
|  | ||||
| 	auto heroPtr = task->hero; | ||||
| 	auto evaluationContext = buildEvaluationContext(task); | ||||
|  | ||||
| 	if(!heroPtr.validAndSet()) | ||||
| 		return 2;  | ||||
|  | ||||
| 	int objId = task->objid; | ||||
|  | ||||
| 	if(task->parent) | ||||
| 		objId = task->parent->objid; | ||||
|  | ||||
| 	const CGObjectInstance * target = cb->getObj((ObjectInstanceID)objId, false); | ||||
| 	int rewardType = (evaluationContext.goldReward > 0 ? 1 : 0)  | ||||
| 		+ (evaluationContext.armyReward > 0 ? 1 : 0) | ||||
| 		+ (evaluationContext.skillReward > 0 ? 1 : 0) | ||||
| 		+ (evaluationContext.strategicalValue > 0 ? 1 : 0); | ||||
| 	 | ||||
| 	auto day = cb->getDate(Date::DAY); | ||||
| 	auto hero = heroPtr.get(); | ||||
| 	auto armyTotal = task->evaluationContext.heroStrength; | ||||
| 	double armyLossPersentage = task->evaluationContext.armyLoss / (double)armyTotal; | ||||
| 	uint64_t danger = task->evaluationContext.danger; | ||||
| 	HeroRole heroRole = ai->ah->getHeroRole(heroPtr); | ||||
| 	int32_t goldReward = getGoldReward(target, hero); | ||||
| 	bool checkGold = danger == 0; | ||||
| 	uint64_t armyReward = getArmyReward(target, hero, checkGold); | ||||
| 	float skillReward = getSkillReward(target, hero, heroRole); | ||||
| 	float strategicalValue = getStrategicalValue(target); | ||||
| 	double result = 0; | ||||
| 	int rewardType = (goldReward > 0 ? 1 : 0) + (armyReward > 0 ? 1 : 0) + (skillReward > 0 ? 1 : 0) + (strategicalValue > 0 ? 1 : 0); | ||||
| 	 | ||||
|  | ||||
| 	try | ||||
| 	{ | ||||
| 		armyLossPersentageVariable->setValue(armyLossPersentage); | ||||
| 		heroRoleVariable->setValue(heroRole); | ||||
| 		mainTurnDistanceVariable->setValue(task->evaluationContext.movementCostByRole[HeroRole::MAIN]); | ||||
| 		scoutTurnDistanceVariable->setValue(task->evaluationContext.movementCostByRole[HeroRole::SCOUT]); | ||||
| 		goldRewardVariable->setValue(goldReward); | ||||
| 		armyRewardVariable->setValue(armyReward); | ||||
| 		skillRewardVariable->setValue(skillReward); | ||||
| 		dangerVariable->setValue(danger); | ||||
| 		armyLossPersentageVariable->setValue(evaluationContext.armyLossPersentage); | ||||
| 		heroRoleVariable->setValue(evaluationContext.heroRole); | ||||
| 		mainTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::MAIN]); | ||||
| 		scoutTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::SCOUT]); | ||||
| 		goldRewardVariable->setValue(evaluationContext.goldReward); | ||||
| 		armyRewardVariable->setValue(evaluationContext.armyReward); | ||||
| 		skillRewardVariable->setValue(evaluationContext.skillReward); | ||||
| 		dangerVariable->setValue(evaluationContext.danger); | ||||
| 		rewardTypeVariable->setValue(rewardType); | ||||
| 		closestHeroRatioVariable->setValue(task->evaluationContext.closestWayRatio); | ||||
| 		strategicalValueVariable->setValue(strategicalValue); | ||||
| 		closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio); | ||||
| 		strategicalValueVariable->setValue(evaluationContext.strategicalValue); | ||||
|  | ||||
| 		engine->process(); | ||||
| 		//engine.process(VISIT_TILE); //TODO: Process only Visit_Tile | ||||
| @@ -402,16 +498,16 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task) | ||||
| 	assert(result >= 0); | ||||
|  | ||||
| #ifdef VCMI_TRACE_PATHFINDER | ||||
| 	logAi->trace("Evaluated %s, hero %s, loss: %f, turns: %f, gold: %d, army gain: %d, danger: %d, role: %s, strategical value: %f, result %f", | ||||
| 	logAi->trace("Evaluated %s, loss: %f, turns main: %f, scout: %f, gold: %d, army gain: %d, danger: %d, role: %s, strategical value: %f, result %f", | ||||
| 		task->name(), | ||||
| 		hero->name, | ||||
| 		armyLossPersentage, | ||||
| 		task->evaluationContext.movementCost, | ||||
| 		goldReward, | ||||
| 		armyReward, | ||||
| 		danger, | ||||
| 		heroRole ? "scout" : "main", | ||||
| 		strategicalValue, | ||||
| 		evaluationContext.armyLossPersentage, | ||||
| 		evaluationContext.movementCostByRole[HeroRole::MAIN], | ||||
| 		evaluationContext.movementCostByRole[HeroRole::SCOUT], | ||||
| 		evaluationContext.goldReward, | ||||
| 		evaluationContext.armyReward, | ||||
| 		evaluationContext.danger, | ||||
| 		evaluationContext.heroRole ? "scout" : "main", | ||||
| 		evaluationContext.strategicalValue, | ||||
| 		result); | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -10,12 +10,12 @@ | ||||
| #pragma once | ||||
| #include "fl/Headers.h" | ||||
| #include "../Goals/Goals.h" | ||||
| #include "../FuzzyEngines.h" | ||||
|  | ||||
| class VCAI; | ||||
| class CArmedInstance; | ||||
| class CBank; | ||||
| struct SectorMap; | ||||
| class IEvaluationContextBuilder | ||||
| { | ||||
| public: | ||||
| 	virtual Goals::EvaluationContext buildEvaluationContext(Goals::TSubgoal goal) const = 0; | ||||
| }; | ||||
|  | ||||
| class PriorityEvaluator | ||||
| { | ||||
| @@ -40,4 +40,7 @@ private: | ||||
| 	fl::InputVariable * rewardTypeVariable; | ||||
| 	fl::InputVariable * closestHeroRatioVariable; | ||||
| 	fl::OutputVariable * value; | ||||
| 	std::map<Goals::EGoals, std::shared_ptr<IEvaluationContextBuilder>> evaluationContextBuilders; | ||||
|  | ||||
| 	Goals::EvaluationContext buildEvaluationContext(Goals::TSubgoal goal) const; | ||||
| }; | ||||
|   | ||||
| @@ -96,12 +96,17 @@ namespace Goals | ||||
| 	{ | ||||
| 		float movementCost; | ||||
| 		std::map<HeroRole, float> movementCostByRole; | ||||
| 		float scoutMovementCost; | ||||
| 		int manaCost; | ||||
| 		uint64_t danger; | ||||
| 		float closestWayRatio; | ||||
| 		uint64_t armyLoss; | ||||
| 		uint64_t heroStrength; | ||||
| 		float armyLossPersentage; | ||||
| 		float armyReward; | ||||
| 		int32_t goldReward; | ||||
| 		float skillReward; | ||||
| 		float strategicalValue; | ||||
| 		HeroRole heroRole; | ||||
|  | ||||
| 		EvaluationContext() | ||||
| 			: movementCost(0.0), | ||||
| @@ -110,7 +115,12 @@ namespace Goals | ||||
| 			closestWayRatio(1), | ||||
| 			armyLoss(0), | ||||
| 			heroStrength(0), | ||||
| 			movementCostByRole() | ||||
| 			movementCostByRole(), | ||||
| 			skillReward(0), | ||||
| 			goldReward(0), | ||||
| 			armyReward(0), | ||||
| 			armyLossPersentage(0), | ||||
| 			heroRole(HeroRole::SCOUT) | ||||
| 		{ | ||||
| 		} | ||||
| 	}; | ||||
|   | ||||
| @@ -31,6 +31,11 @@ bool BuildThis::operator==(const BuildThis & other) const | ||||
| 	return town == other.town && bid == other.bid; | ||||
| } | ||||
|  | ||||
| std::string BuildThis::name() const | ||||
| { | ||||
| 	return "Build " + buildingInfo.name + "(" + std::to_string(bid) + ") in " + town->name; | ||||
| } | ||||
|  | ||||
| TSubgoal BuildThis::whatToDoToAchieve() | ||||
| { | ||||
| 	auto b = BuildingID(bid); | ||||
|   | ||||
| @@ -10,6 +10,7 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "CGoal.h" | ||||
| #include "../Analyzers/BuildAnalyzer.h" | ||||
|  | ||||
| struct HeroPtr; | ||||
| class VCAI; | ||||
| @@ -20,10 +21,19 @@ namespace Goals | ||||
| 	class DLL_EXPORT BuildThis : public CGoal<BuildThis> | ||||
| 	{ | ||||
| 	public: | ||||
| 		BuildingInfo buildingInfo; | ||||
| 		TownDevelopmentInfo townInfo; | ||||
|  | ||||
| 		BuildThis() //should be private, but unit test uses it | ||||
| 			: CGoal(Goals::BUILD_STRUCTURE) | ||||
| 		{ | ||||
| 		} | ||||
| 		BuildThis(const BuildingInfo & buildingInfo, const TownDevelopmentInfo & townInfo) //should be private, but unit test uses it | ||||
| 			: CGoal(Goals::BUILD_STRUCTURE), buildingInfo(buildingInfo), townInfo(townInfo) | ||||
| 		{ | ||||
| 			bid = buildingInfo.id; | ||||
| 			town = townInfo.town; | ||||
| 		} | ||||
| 		BuildThis(BuildingID Bid, const CGTownInstance * tid) | ||||
| 			: CGoal(Goals::BUILD_STRUCTURE) | ||||
| 		{ | ||||
| @@ -44,5 +54,6 @@ namespace Goals | ||||
| 		TSubgoal whatToDoToAchieve() override; | ||||
| 		//bool fulfillsMe(TSubgoal goal) override; | ||||
| 		virtual bool operator==(const BuildThis & other) const override; | ||||
| 		virtual std::string name() const override; | ||||
| 	}; | ||||
| } | ||||
|   | ||||
| @@ -175,7 +175,7 @@ void ExecuteHeroChain::accept(VCAI * ai) | ||||
|  | ||||
| std::string ExecuteHeroChain::name() const | ||||
| { | ||||
| 	return "ExecuteHeroChain " + targetName; | ||||
| 	return "ExecuteHeroChain " + targetName + " by " + chainPath.targetHero->name; | ||||
| } | ||||
|  | ||||
| std::string ExecuteHeroChain::completeMessage() const | ||||
|   | ||||
| @@ -101,8 +101,10 @@ float HeroManager::evaluateFightingStrength(const CGHeroInstance * hero) const | ||||
| 	return evaluateSpeciality(hero) + wariorSkillsScores.evaluateSecSkills(hero) + hero->level * 1.5f; | ||||
| } | ||||
|  | ||||
| void HeroManager::updateHeroRoles() | ||||
| void HeroManager::update() | ||||
| { | ||||
| 	logAi->trace("Start analysing our heroes"); | ||||
|  | ||||
| 	std::map<HeroPtr, float> scores; | ||||
| 	auto myHeroes = ai->getMyHeroes(); | ||||
|  | ||||
|   | ||||
| @@ -26,7 +26,7 @@ public: | ||||
| 	virtual const std::map<HeroPtr, HeroRole> & getHeroRoles() const = 0; | ||||
| 	virtual int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const = 0; | ||||
| 	virtual HeroRole getHeroRole(const HeroPtr & hero) const = 0; | ||||
| 	virtual void updateHeroRoles() = 0; | ||||
| 	virtual void update() = 0; | ||||
| 	virtual float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const = 0; | ||||
| 	virtual float evaluateHero(const CGHeroInstance * hero) const = 0; | ||||
| }; | ||||
| @@ -64,7 +64,7 @@ public: | ||||
| 	const std::map<HeroPtr, HeroRole> & getHeroRoles() const override; | ||||
| 	HeroRole getHeroRole(const HeroPtr & hero) const override; | ||||
| 	int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const override; | ||||
| 	void updateHeroRoles() override; | ||||
| 	void update() override; | ||||
| 	float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const override; | ||||
| 	float evaluateHero(const CGHeroInstance * hero) const override; | ||||
|  | ||||
|   | ||||
| @@ -76,7 +76,7 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta | ||||
| void AINodeStorage::clear() | ||||
| { | ||||
| 	actors.clear(); | ||||
| 	heroChainPass = false; | ||||
| 	heroChainPass = EHeroChainPass::INITIAL; | ||||
| 	heroChainTurn = 0; | ||||
| 	heroChainMaxTurns = 1; | ||||
| } | ||||
| @@ -262,9 +262,37 @@ bool AINodeStorage::increaseHeroChainTurnLimit() | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| EPathfindingLayer phisycalLayers[2] = {EPathfindingLayer::LAND, EPathfindingLayer::SAIL}; | ||||
|  | ||||
| bool AINodeStorage::calculateHeroChainFinal() | ||||
| { | ||||
| 	heroChainPass = EHeroChainPass::FINAL; | ||||
| 	heroChain.resize(0); | ||||
|  | ||||
| 	for(auto layer : phisycalLayers) | ||||
| 	{ | ||||
| 		foreach_tile_pos([&](const int3 & pos) | ||||
| 		{ | ||||
| 			auto chains = nodes[pos.x][pos.y][pos.z][layer]; | ||||
|  | ||||
| 			for(AIPathNode & node : chains) | ||||
| 			{ | ||||
| 				if(node.turns > heroChainTurn | ||||
| 					&& node.action != CGPathNode::ENodeAction::UNKNOWN | ||||
| 					&& node.actor->actorExchangeCount > 1) | ||||
| 				{ | ||||
| 					heroChain.push_back(&node); | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| 	 | ||||
| 	return heroChain.size(); | ||||
| } | ||||
|  | ||||
| bool AINodeStorage::calculateHeroChain() | ||||
| { | ||||
| 	heroChainPass = true; | ||||
| 	heroChainPass = EHeroChainPass::CHAIN; | ||||
| 	heroChain.resize(0); | ||||
|  | ||||
| 	std::vector<AIPathNode *> existingChains; | ||||
| @@ -273,37 +301,40 @@ bool AINodeStorage::calculateHeroChain() | ||||
| 	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]; | ||||
|  | ||||
| 		existingChains.resize(0); | ||||
| 		newChains.resize(0); | ||||
|  | ||||
| 		for(AIPathNode & node : chains) | ||||
| 	for(auto layer : phisycalLayers) | ||||
| 	{ | ||||
| 		foreach_tile_pos([&](const int3 & pos) | ||||
| 		{ | ||||
| 			if(node.turns <= heroChainTurn && node.action != CGPathNode::ENodeAction::UNKNOWN) | ||||
| 				existingChains.push_back(&node); | ||||
| 		} | ||||
| 			auto chains = nodes[pos.x][pos.y][pos.z][layer]; | ||||
|  | ||||
| 		for(AIPathNode * node : existingChains) | ||||
| 		{ | ||||
| 			if(node->actor->isMovable) | ||||
| 			existingChains.resize(0); | ||||
| 			newChains.resize(0); | ||||
|  | ||||
| 			for(AIPathNode & node : chains) | ||||
| 			{ | ||||
| 				calculateHeroChain(node, existingChains, newChains); | ||||
| 				if(node.turns <= heroChainTurn && node.action != CGPathNode::ENodeAction::UNKNOWN) | ||||
| 					existingChains.push_back(&node); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		cleanupInefectiveChains(newChains); | ||||
| 		addHeroChain(newChains); | ||||
| 	}); | ||||
| 			for(AIPathNode * node : existingChains) | ||||
| 			{ | ||||
| 				if(node->actor->isMovable) | ||||
| 				{ | ||||
| 					calculateHeroChain(node, existingChains, newChains); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			cleanupInefectiveChains(newChains); | ||||
| 			addHeroChain(newChains); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	return heroChain.size(); | ||||
| } | ||||
|  | ||||
| void AINodeStorage::cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const | ||||
| { | ||||
| 	vstd::erase_if(result, [&](ExchangeCandidate & chainInfo) -> bool | ||||
| 	vstd::erase_if(result, [&](const ExchangeCandidate & chainInfo) -> bool | ||||
| 	{ | ||||
| 		auto pos = chainInfo.coord; | ||||
| 		auto chains = nodes[pos.x][pos.y][pos.z][EPathfindingLayer::LAND]; | ||||
| @@ -320,12 +351,27 @@ void AINodeStorage::calculateHeroChain( | ||||
| { | ||||
| 	for(AIPathNode * node : variants) | ||||
| 	{ | ||||
| 		if(node == srcNode  | ||||
| 			|| !node->actor  | ||||
| 			|| node->turns > heroChainTurn  | ||||
| 		if(node == srcNode || !node->actor) | ||||
| 			continue; | ||||
|  | ||||
| 		if(node->turns > heroChainTurn  | ||||
| 			|| (node->action == CGPathNode::ENodeAction::UNKNOWN && node->actor->hero) | ||||
| 			|| (node->actor->chainMask & srcNode->actor->chainMask) != 0) | ||||
| 		{ | ||||
| #if AI_TRACE_LEVEL >= 2 | ||||
| 			logAi->trace( | ||||
| 				"Skip exchange %s[%x] -> %s[%x] at %s because of %s", | ||||
| 				node->actor->toString(), | ||||
| 				node->actor->chainMask, | ||||
| 				srcNode->actor->toString(), | ||||
| 				srcNode->actor->chainMask, | ||||
| 				srcNode->coord.toString(), | ||||
| 				(node->turns > heroChainTurn  | ||||
| 					? "turn limit"  | ||||
| 					: (node->action == CGPathNode::ENodeAction::UNKNOWN && node->actor->hero) | ||||
| 						? "action unknown" | ||||
| 						: "chain mask")); | ||||
| #endif | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| @@ -793,7 +839,7 @@ bool AINodeStorage::hasBetterChain( | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if((candidateActor->chainMask & node.actor->chainMask) == 0) | ||||
| 		if(candidateActor->chainMask != node.actor->chainMask) | ||||
| 			continue; | ||||
|  | ||||
| 		auto nodeActor = node.actor; | ||||
| @@ -931,6 +977,17 @@ AIPath::AIPath() | ||||
| { | ||||
| } | ||||
|  | ||||
| std::shared_ptr<const ISpecialAction> AIPath::getFirstBlockedAction() const | ||||
| { | ||||
| 	for(auto node : nodes) | ||||
| 	{ | ||||
| 		if(node.specialAction && !node.specialAction->canAct(node.targetHero)) | ||||
| 			return node.specialAction; | ||||
| 	} | ||||
|  | ||||
| 	return std::shared_ptr<const ISpecialAction>(); | ||||
| } | ||||
|  | ||||
| int3 AIPath::firstTileToGet() const | ||||
| { | ||||
| 	if(nodes.size()) | ||||
| @@ -1005,7 +1062,7 @@ uint64_t AIPath::getTotalArmyLoss() const | ||||
| 	return armyLoss + targetObjectArmyLoss; | ||||
| } | ||||
|  | ||||
| std::string AIPath::toString() | ||||
| std::string AIPath::toString() const | ||||
| { | ||||
| 	std::stringstream str; | ||||
|  | ||||
|   | ||||
| @@ -79,7 +79,9 @@ struct AIPath | ||||
|  | ||||
| 	uint64_t getHeroStrength() const; | ||||
|  | ||||
| 	std::string toString(); | ||||
| 	std::string toString() const; | ||||
|  | ||||
| 	std::shared_ptr<const ISpecialAction> AIPath::getFirstBlockedAction() const; | ||||
| }; | ||||
|  | ||||
| struct ExchangeCandidate : public AIPathNode | ||||
| @@ -88,6 +90,15 @@ struct ExchangeCandidate : public AIPathNode | ||||
| 	AIPathNode * otherParent; | ||||
| }; | ||||
|  | ||||
| enum EHeroChainPass | ||||
| { | ||||
| 	INITIAL, // single heroes unlimited distance | ||||
|  | ||||
| 	CHAIN, // chains with limited distance | ||||
|  | ||||
| 	FINAL // same as SINGLE but for heroes from CHAIN pass | ||||
| }; | ||||
|  | ||||
| class AINodeStorage : public INodeStorage | ||||
| { | ||||
| private: | ||||
| @@ -100,7 +111,7 @@ private: | ||||
| 	std::unique_ptr<FuzzyHelper> dangerEvaluator; | ||||
| 	std::vector<std::shared_ptr<ChainActor>> actors; | ||||
| 	std::vector<CGPathNode *> heroChain; | ||||
| 	bool heroChainPass; // true if we need to calculate hero chain | ||||
| 	EHeroChainPass heroChainPass; // true if we need to calculate hero chain | ||||
| 	int heroChainTurn; | ||||
| 	int heroChainMaxTurns; | ||||
| 	PlayerColor playerID; | ||||
| @@ -146,7 +157,7 @@ public: | ||||
| 	bool isMovementIneficient(const PathNodeInfo & source, CDestinationNodeInfo & destination) const | ||||
| 	{ | ||||
| 		// further chain distribution is calculated as the last stage | ||||
| 		if(heroChainPass && destination.node->turns > heroChainTurn) | ||||
| 		if(heroChainPass == EHeroChainPass::CHAIN && destination.node->turns > heroChainTurn) | ||||
| 			return true; | ||||
|  | ||||
| 		return hasBetterChain(source, destination); | ||||
| @@ -169,6 +180,7 @@ public: | ||||
| 	const std::set<const CGHeroInstance *> getAllHeroes() const; | ||||
| 	void clear(); | ||||
| 	bool calculateHeroChain(); | ||||
| 	bool calculateHeroChainFinal(); | ||||
|  | ||||
| 	uint64_t evaluateDanger(const int3 &  tile, const CGHeroInstance * hero) const | ||||
| 	{ | ||||
|   | ||||
| @@ -66,21 +66,21 @@ void AIPathfinder::updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain) | ||||
|  | ||||
| 	do | ||||
| 	{ | ||||
| 		logAi->trace("Recalculate paths pass %d", pass++); | ||||
| 		cb->calculatePaths(config); | ||||
|  | ||||
| 		if(useHeroChain) | ||||
| 		do | ||||
| 		{ | ||||
| 			logAi->trace("Recalculate chain pass %d", pass); | ||||
| 			logAi->trace("Recalculate paths pass %d", pass++); | ||||
| 			cb->calculatePaths(config); | ||||
| 		} while(useHeroChain && storage->calculateHeroChain()); | ||||
|  | ||||
| 			continueCalculation = storage->calculateHeroChain(); | ||||
| 		if(!useHeroChain) | ||||
| 			break; | ||||
|  | ||||
| 			if(!continueCalculation) | ||||
| 			{ | ||||
| 				logAi->trace("Increase chain turn limit"); | ||||
|  | ||||
| 				continueCalculation = storage->increaseHeroChainTurnLimit() && storage->calculateHeroChain(); | ||||
| 			} | ||||
| 		if(storage->calculateHeroChainFinal()) | ||||
| 		{ | ||||
| 			logAi->trace("Recalculate paths pass final"); | ||||
| 			cb->calculatePaths(config); | ||||
| 		} | ||||
|  | ||||
| 		continueCalculation = storage->increaseHeroChainTurnLimit() && storage->calculateHeroChain(); | ||||
| 	} while(continueCalculation); | ||||
| } | ||||
|   | ||||
| @@ -853,10 +853,10 @@ void VCAI::makeTurn() | ||||
| 		logAi->debug("Making turn thread has been interrupted. We'll end without calling endTurn."); | ||||
| 		return; | ||||
| 	} | ||||
| 	catch (std::exception & e) | ||||
| 	/*catch (std::exception & e) | ||||
| 	{ | ||||
| 		logAi->debug("Making turn thread has caught an exception: %s", e.what()); | ||||
| 	} | ||||
| 	}*/ | ||||
|  | ||||
| 	endTurn(); | ||||
| } | ||||
| @@ -1079,7 +1079,12 @@ void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h) | ||||
| 				moveCreaturesToHero(h->visitedTown); | ||||
|  | ||||
| 			townVisitsThisWeek[h].insert(h->visitedTown); | ||||
| 			ah->updateHeroRoles(); | ||||
|  | ||||
| 			if(!ai->nullkiller) | ||||
| 			{ | ||||
| 				ah->update(); | ||||
| 			} | ||||
|  | ||||
| 			if(ah->getHeroRole(h) == HeroRole::MAIN && !h->hasSpellbook() && ah->freeGold() >= GameConstants::SPELLBOOK_GOLD_COST) | ||||
| 			{ | ||||
| 				if(h->visitedTown->hasBuilt(BuildingID::MAGES_GUILD_1)) | ||||
| @@ -1994,9 +1999,11 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) | ||||
| 				doChannelProbing(); | ||||
| 		} | ||||
|  | ||||
| 		if(path.nodes[0].action == CGPathNode::BLOCKING_VISIT) | ||||
| 		if(path.nodes[0].action == CGPathNode::BLOCKING_VISIT || path.nodes[0].action == CGPathNode::BATTLE) | ||||
| 		{ | ||||
| 			ret = h && i == 0; // when we take resource we do not reach its position. We even might not move | ||||
| 			// when we take resource we do not reach its position. We even might not move | ||||
| 			// also guarded town is not get visited automatically after capturing | ||||
| 			ret = h && i == 0; | ||||
| 		} | ||||
| 	} | ||||
| 	if(h) | ||||
| @@ -2485,6 +2492,8 @@ void VCAI::recruitHero(const CGTownInstance * t, bool throwing) | ||||
| 		} | ||||
| 		cb->recruitHero(t, hero); | ||||
|  | ||||
| 		ai->ah->update(); | ||||
|  | ||||
| 		if(t->visitingHero) | ||||
| 			moveHeroToTile(t->visitablePos(), t->visitingHero.get()); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user