mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Merge pull request #1657 from vcmi/nkai-fix-build
NKAI: improve build behavior
This commit is contained in:
		| @@ -1301,7 +1301,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h) | ||||
| 			if(path.nodes[i - 1].turns) | ||||
| 			{ | ||||
| 				//blockedHeroes.insert(h); //to avoid attempts of moving heroes with very little MPs | ||||
| 				break; | ||||
| 				return false; | ||||
| 			} | ||||
|  | ||||
| 			int3 endpos = path.nodes[i - 1].coord; | ||||
|   | ||||
| @@ -58,7 +58,7 @@ std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, c | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for(auto pair : creToPower) | ||||
| 	for(auto & pair : creToPower) | ||||
| 		resultingArmy.push_back(pair.second); | ||||
|  | ||||
| 	boost::sort(resultingArmy, [](const SlotInfo & left, const SlotInfo & right) -> bool | ||||
| @@ -112,7 +112,7 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, | ||||
| 	for(auto bonus : *bonusModifiers) | ||||
| 	{ | ||||
| 		// army bonuses will change and object bonuses are temporary | ||||
| 		if(bonus->source != Bonus::ARMY || bonus->source != Bonus::OBJECT) | ||||
| 		if(bonus->source != Bonus::ARMY && bonus->source != Bonus::OBJECT) | ||||
| 		{ | ||||
| 			newArmyInstance.addNewBonus(std::make_shared<Bonus>(*bonus)); | ||||
| 		} | ||||
| @@ -182,8 +182,10 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, | ||||
| 	{ | ||||
| 		auto weakest = getWeakestCreature(resultingArmy); | ||||
|  | ||||
| 		if(weakest != resultingArmy.end() && weakest->count == 1) //we check iterator validity for playing with settings that allow 0 stacks armies | ||||
| 		if(weakest->count == 1)  | ||||
| 		{ | ||||
| 			assert(resultingArmy.size() > 1); | ||||
|  | ||||
| 			resultingArmy.erase(weakest); | ||||
| 		} | ||||
| 		else | ||||
|   | ||||
| @@ -111,6 +111,12 @@ void HeroManager::update() | ||||
|  | ||||
| 	int globalMainCount = std::min(((int)myHeroes.size() + 2) / 3, cb->getMapSize().x / 50 + 1); | ||||
|  | ||||
| 	//vstd::amin(globalMainCount, 1 + (cb->getTownsInfo().size() / 3)); | ||||
| 	if(cb->getTownsInfo().size() < 4 && globalMainCount > 2) | ||||
| 	{ | ||||
| 		globalMainCount = 2; | ||||
| 	} | ||||
|  | ||||
| 	std::sort(myHeroes.begin(), myHeroes.end(), scoreSort); | ||||
|  | ||||
| 	for(auto hero : myHeroes) | ||||
|   | ||||
| @@ -250,10 +250,13 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader) | ||||
|  | ||||
| 		auto upgrade = ai->nullkiller->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources); | ||||
|  | ||||
| 		if(ai->nullkiller->heroManager->getHeroRole(path.targetHero) == HeroRole::MAIN) | ||||
| 		if(!upgrader->garrisonHero && ai->nullkiller->heroManager->getHeroRole(path.targetHero) == HeroRole::MAIN) | ||||
| 		{ | ||||
| 			upgrade.upgradeValue +=	 | ||||
| 				ai->nullkiller->armyManager->howManyReinforcementsCanGet(path.targetHero, path.heroArmy, upgrader);	 | ||||
| 				ai->nullkiller->armyManager->howManyReinforcementsCanGet( | ||||
| 					path.targetHero, | ||||
| 					path.heroArmy, | ||||
| 					upgrader->getUpperArmy());	 | ||||
| 		} | ||||
|  | ||||
| 		auto armyValue = (float)upgrade.upgradeValue / path.getHeroStrength(); | ||||
|   | ||||
| @@ -70,10 +70,7 @@ bool needToRecruitHero(const CGTownInstance * startupTown) | ||||
| 		return false; | ||||
|  | ||||
| 	if(!startupTown->garrisonHero && !startupTown->visitingHero) | ||||
| 		return false; | ||||
|  | ||||
| 	auto heroToCheck = startupTown->garrisonHero ? startupTown->garrisonHero.get() : startupTown->visitingHero.get(); | ||||
| 	auto paths = cb->getPathsInfo(heroToCheck); | ||||
| 		return true; | ||||
|  | ||||
| 	int treasureSourcesCount = 0; | ||||
|  | ||||
| @@ -84,18 +81,16 @@ bool needToRecruitHero(const CGTownInstance * startupTown) | ||||
| 			|| obj->ID == Obj::CAMPFIRE | ||||
| 			|| obj->ID == Obj::WATER_WHEEL) | ||||
| 		{ | ||||
| 			auto path = paths->getPathInfo(obj->visitablePos()); | ||||
| 			if((path->accessible == CGPathNode::BLOCKVIS || path->accessible == CGPathNode::VISITABLE)  | ||||
| 				&& path->reachable()) | ||||
| 			{ | ||||
| 				treasureSourcesCount++; | ||||
| 			} | ||||
| 			treasureSourcesCount++; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	auto basicCount = cb->getTownsInfo().size() + 2; | ||||
| 	auto boost = (int)std::floor(std::pow(treasureSourcesCount / 2.0, 2)); | ||||
| 	auto boost = std::min( | ||||
| 		(int)std::floor(std::pow(1 + (cb->getMapSize().x / 50), 2)), | ||||
| 		treasureSourcesCount / 2); | ||||
|  | ||||
| 	logAi->trace("Treasure sources found %d", treasureSourcesCount); | ||||
| 	logAi->trace("Startup allows %d+%d heroes", basicCount, boost); | ||||
|  | ||||
| 	return cb->getHeroCount(ai->playerID, true) < basicCount + boost; | ||||
|   | ||||
| @@ -130,6 +130,9 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj) | ||||
|  | ||||
| 		return danger; | ||||
| 	} | ||||
| 	case Obj::PANDORAS_BOX: | ||||
| 		return 10000; //Who knows what awaits us there | ||||
|  | ||||
| 	case Obj::ARTIFACT: | ||||
| 	case Obj::RESOURCE: | ||||
| 	{ | ||||
|   | ||||
| @@ -236,7 +236,6 @@ void Nullkiller::makeTurn() | ||||
| 		{ | ||||
| 			Goals::TTaskVec fastTasks = { | ||||
| 				choseBestTask(sptr(BuyArmyBehavior()), 1), | ||||
| 				choseBestTask(sptr(RecruitHeroBehavior()), 1), | ||||
| 				choseBestTask(sptr(BuildingBehavior()), 1) | ||||
| 			}; | ||||
|  | ||||
| @@ -251,6 +250,7 @@ void Nullkiller::makeTurn() | ||||
|  | ||||
| 		Goals::TTaskVec bestTasks = { | ||||
| 			bestTask, | ||||
| 			choseBestTask(sptr(RecruitHeroBehavior()), 1), | ||||
| 			choseBestTask(sptr(CaptureObjectsBehavior()), 1), | ||||
| 			choseBestTask(sptr(ClusterBehavior()), MAX_DEPTH), | ||||
| 			choseBestTask(sptr(DefenceBehavior()), MAX_DEPTH), | ||||
|   | ||||
| @@ -361,8 +361,8 @@ float RewardEvaluator::getTotalResourceRequirementStrength(int resType) const | ||||
| 		return 0; | ||||
|  | ||||
| 	float ratio = dailyIncome[resType] == 0 | ||||
| 		? requiredResources[resType] / 50 | ||||
| 		: (float)requiredResources[resType] / dailyIncome[resType] / 50; | ||||
| 		? (float)requiredResources[resType] / 50.0f | ||||
| 		: (float)requiredResources[resType] / dailyIncome[resType] / 50.0f; | ||||
|  | ||||
| 	return std::min(ratio, 1.0f); | ||||
| } | ||||
| @@ -377,10 +377,12 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons | ||||
| 	case Obj::MINE: | ||||
| 		return target->subID == Res::GOLD  | ||||
| 			? 0.5f  | ||||
| 			: 0.02f * getTotalResourceRequirementStrength(target->subID) + 0.02f * getResourceRequirementStrength(target->subID); | ||||
| 			: 0.4f * getTotalResourceRequirementStrength(target->subID) + 0.1f * getResourceRequirementStrength(target->subID); | ||||
|  | ||||
| 	case Obj::RESOURCE: | ||||
| 		return target->subID == Res::GOLD ? 0 : 0.1f * getResourceRequirementStrength(target->subID); | ||||
| 		return target->subID == Res::GOLD | ||||
| 			? 0 | ||||
| 			: 0.2f * getTotalResourceRequirementStrength(target->subID) + 0.4f * getResourceRequirementStrength(target->subID); | ||||
|  | ||||
| 	case Obj::CREATURE_BANK: | ||||
| 	{ | ||||
| @@ -547,7 +549,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG | ||||
| 	case Obj::SEA_CHEST: | ||||
| 		return 1500; | ||||
| 	case Obj::PANDORAS_BOX: | ||||
| 		return 5000; | ||||
| 		return 2500; | ||||
| 	case Obj::PRISON: | ||||
| 		//Objectively saves us 2500 to hire hero | ||||
| 		return GameConstants::HERO_GOLD_COST; | ||||
| @@ -800,21 +802,23 @@ public: | ||||
| 		evaluationContext.goldReward += 7 * bi.dailyIncome[Res::GOLD] / 2; // 7 day income but half we already have | ||||
| 		evaluationContext.heroRole = HeroRole::MAIN; | ||||
| 		evaluationContext.movementCostByRole[evaluationContext.heroRole] += bi.prerequisitesCount; | ||||
| 		evaluationContext.strategicalValue += buildThis.townInfo.armyStrength / 50000.0; | ||||
| 		evaluationContext.goldCost += bi.buildCostWithPrerequisits[Res::GOLD]; | ||||
|  | ||||
| 		if(bi.creatureID != CreatureID::NONE) | ||||
| 		{ | ||||
| 			evaluationContext.strategicalValue += buildThis.townInfo.armyStrength / 50000.0; | ||||
|  | ||||
| 			if(bi.baseCreatureID == bi.creatureID) | ||||
| 			{ | ||||
| 				evaluationContext.strategicalValue += 0.5f + 0.1f * bi.creatureLevel / (float)bi.prerequisitesCount; | ||||
| 				evaluationContext.strategicalValue += (0.5f + 0.1f * bi.creatureLevel) / (float)bi.prerequisitesCount; | ||||
| 				evaluationContext.armyReward += bi.armyStrength; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				auto potentialUpgradeValue = evaluationContext.evaluator.getUpgradeArmyReward(buildThis.town, bi); | ||||
| 				//evaluationContext.strategicalValue += 0.05f * bi.creatureLevel / (float)bi.prerequisitesCount; | ||||
| 				evaluationContext.armyReward += 0.3f * potentialUpgradeValue / (float)bi.prerequisitesCount; | ||||
| 				 | ||||
| 				evaluationContext.strategicalValue += potentialUpgradeValue / 10000.0f / (float)bi.prerequisitesCount; | ||||
| 				evaluationContext.armyReward += potentialUpgradeValue / (float)bi.prerequisitesCount; | ||||
| 			} | ||||
| 		} | ||||
| 		else if(bi.id == BuildingID::CITADEL || bi.id == BuildingID::CASTLE) | ||||
| @@ -824,7 +828,14 @@ public: | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			evaluationContext.strategicalValue += evaluationContext.evaluator.ai->buildAnalyzer->getGoldPreasure() * evaluationContext.goldReward / 2200.0f; | ||||
| 			auto goldPreasure = evaluationContext.evaluator.ai->buildAnalyzer->getGoldPreasure(); | ||||
|  | ||||
| 			evaluationContext.strategicalValue += evaluationContext.goldReward * goldPreasure / 3500.0f / bi.prerequisitesCount; | ||||
| 		} | ||||
|  | ||||
| 		if(bi.notEnoughRes && bi.prerequisitesCount == 1) | ||||
| 		{ | ||||
| 			evaluationContext.strategicalValue /= 2; | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
|   | ||||
| @@ -855,6 +855,10 @@ void AINodeStorage::setHeroes(std::map<const CGHeroInstance *, HeroRole> heroes) | ||||
|  | ||||
| 	for(auto & hero : heroes) | ||||
| 	{ | ||||
| 		// do not allow our own heroes in garrison to act on map | ||||
| 		if(hero.first->getOwner() == ai->playerID && hero.first->inTownGarrison) | ||||
| 			continue; | ||||
|  | ||||
| 		uint64_t mask = FirstActorMask << actors.size(); | ||||
| 		auto actor = std::make_shared<HeroActor>(hero.first, hero.second, mask, ai); | ||||
|  | ||||
|   | ||||
| @@ -274,12 +274,12 @@ ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other) | ||||
| 			return result; | ||||
| 		} | ||||
|  | ||||
| 	if(other->isMovable && other->armyValue <= actor->armyValue / 10 && other->armyValue < MIN_ARMY_STRENGTH_FOR_CHAIN) | ||||
| 		return result; | ||||
| 		if(other->isMovable && other->armyValue <= actor->armyValue / 10 && other->armyValue < MIN_ARMY_STRENGTH_FOR_CHAIN) | ||||
| 			return result; | ||||
|  | ||||
| 	TResources availableResources = resources - actor->armyCost - other->armyCost; | ||||
| 	HeroExchangeArmy * upgradedInitialArmy = tryUpgrade(actor->creatureSet, other->getActorObject(), availableResources); | ||||
| 	HeroExchangeArmy * newArmy; | ||||
| 		TResources availableResources = resources - actor->armyCost - other->armyCost; | ||||
| 		HeroExchangeArmy * upgradedInitialArmy = tryUpgrade(actor->creatureSet, other->getActorObject(), availableResources); | ||||
| 		HeroExchangeArmy * newArmy; | ||||
|  | ||||
| 		if(other->creatureSet->Slots().size()) | ||||
| 		{ | ||||
| @@ -303,20 +303,25 @@ ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other) | ||||
|  | ||||
| 		if(!newArmy) return result; | ||||
|  | ||||
| 		auto reinforcement = newArmy->getArmyStrength() - actor->creatureSet->getArmyStrength(); | ||||
| 		auto newArmyStrength = newArmy->getArmyStrength(); | ||||
| 		auto oldArmyStrength = actor->creatureSet->getArmyStrength(); | ||||
|  | ||||
| #if NKAI_PATHFINDER_TRACE_LEVEL >= 2 | ||||
| 		if(newArmyStrength <= oldArmyStrength) return result; | ||||
|  | ||||
| 		auto reinforcement = newArmyStrength - oldArmyStrength; | ||||
|  | ||||
| 	#if NKAI_PATHFINDER_TRACE_LEVEL >= 2 | ||||
| 		logAi->trace( | ||||
| 			"Exchange %s->%s reinforcement: %d, %f%%", | ||||
| 			actor->toString(), | ||||
| 			other->toString(), | ||||
| 			reinforcement, | ||||
| 			100.0f * reinforcement / actor->armyValue); | ||||
| #endif | ||||
| 	#endif | ||||
|  | ||||
| 	if(reinforcement <= actor->armyValue / 10 && reinforcement < MIN_ARMY_STRENGTH_FOR_CHAIN) | ||||
| 	{ | ||||
| 		delete newArmy; | ||||
| 		if(reinforcement <= actor->armyValue / 10 && reinforcement < MIN_ARMY_STRENGTH_FOR_CHAIN) | ||||
| 		{ | ||||
| 			delete newArmy; | ||||
|  | ||||
| 			return result; | ||||
| 		} | ||||
| @@ -365,7 +370,7 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade( | ||||
| 	{ | ||||
| 		auto buyArmy = ai->armyManager->getArmyAvailableToBuy(target, ai->cb->getTown(upgrader->id), resources); | ||||
|  | ||||
| 		for(auto creatureToBuy : buyArmy) | ||||
| 		for(auto & creatureToBuy : buyArmy) | ||||
| 		{ | ||||
| 			auto targetSlot = target->getSlotFor(creatureToBuy.cre); | ||||
|  | ||||
|   | ||||
| @@ -86,8 +86,8 @@ InputVariable: strategicalValue | ||||
|   lock-range: false | ||||
|   term: NONE Ramp 0.200 0.000 | ||||
|   term: LOWEST Triangle 0.000 0.010 0.250 | ||||
|   term: LOW Triangle 0.000 0.400 0.700 | ||||
|   term: MEDIUM Triangle 0.400 0.700 1.000 | ||||
|   term: LOW Triangle 0.000 0.250 0.700 | ||||
|   term: MEDIUM Triangle 0.250 0.700 1.000 | ||||
|   term: HIGH Ramp 0.700 1.000 | ||||
| InputVariable: goldPreasure | ||||
|   description: Ratio between weekly army cost and gold income | ||||
| @@ -189,10 +189,10 @@ RuleBlock: gold reward | ||||
|   rule: if armyReward is LOW and heroRole is MAIN and danger is not NONE and armyLoss is LOW then Value is HIGH | ||||
|   rule: if armyReward is LOW and heroRole is MAIN and danger is NONE then Value is BITLOW with 0.5 | ||||
|   rule: if armyReward is LOW and heroRole is MAIN and danger is NONE and mainTurnDistance is LOWEST then Value is HIGH | ||||
|   rule: if skillReward is LOW and heroRole is MAIN and armyLoss is LOW then Value is BITHIGH with 0.5 | ||||
|   rule: if skillReward is MEDIUM and heroRole is MAIN and armyLoss is LOW and fear is not HIGH then Value is HIGH with 0.5 | ||||
|   rule: if skillReward is LOW and heroRole is MAIN and armyLoss is LOW then Value is BITHIGH | ||||
|   rule: if skillReward is MEDIUM and heroRole is MAIN and armyLoss is LOW and fear is not HIGH then Value is BITHIGH | ||||
|   rule: if skillReward is HIGH and heroRole is MAIN and armyLoss is LOW and fear is not HIGH then Value is HIGH | ||||
|   rule: if strategicalValue is LOW and heroRole is MAIN and armyLoss is LOW then Value is BITLOW | ||||
|   rule: if strategicalValue is LOW and heroRole is MAIN and armyLoss is LOW then Value is BITHIGH | ||||
|   rule: if strategicalValue is LOWEST and heroRole is MAIN and armyLoss is LOW then Value is LOW | ||||
|   rule: if strategicalValue is LOW and heroRole is SCOUT and armyLoss is LOW and fear is not HIGH then Value is HIGH with 0.5 | ||||
|   rule: if strategicalValue is MEDIUM and heroRole is SCOUT and danger is NONE and fear is not HIGH then Value is HIGH | ||||
| @@ -211,9 +211,13 @@ RuleBlock: gold reward | ||||
|   rule: if heroRole is MAIN and danger is not NONE and armyLoss is LOW then Value is BITHIGH with 0.2 | ||||
|   rule: if heroRole is SCOUT then Value is BITLOW | ||||
|   rule: if goldCost is not NONE and goldReward is NONE and goldPreasure is HIGH then Value is LOWEST | ||||
|   rule: if turn is NOW then Value is BITHIGH with 0.2 | ||||
|   rule: if goldPreasure is HIGH and goldReward is HIGH and armyLoss is LOW and fear is not HIGH then Value is HIGHEST | ||||
|   rule: if goldPreasure is HIGH and goldReward is MEDIUM and armyLoss is LOW and fear is not HIGH then Value is HIGH | ||||
|   rule: if goldPreasure is HIGH and goldReward is LOW and armyLoss is LOW then Value is BITHIGH | ||||
|   rule: if turn is NOW then Value is LOW with 0.3 | ||||
|   rule: if turn is not NOW then Value is LOW with 0.4 | ||||
|   rule: if goldPreasure is HIGH and goldReward is HIGH and heroRole is MAIN and danger is not NONE and armyLoss is LOW and fear is not HIGH then Value is HIGHEST | ||||
|   rule: if goldPreasure is HIGH and goldReward is MEDIUM and heroRole is MAIN and danger is not NONE and armyLoss is LOW and fear is not HIGH then Value is HIGH | ||||
|   rule: if goldPreasure is HIGH and goldReward is HIGH and heroRole is SCOUT and danger is NONE and armyLoss is LOW and fear is not HIGH then Value is HIGHEST | ||||
|   rule: if goldPreasure is HIGH and goldReward is MEDIUM and heroRole is SCOUT and danger is NONE and armyLoss is LOW and fear is not HIGH then Value is HIGH | ||||
|   rule: if goldPreasure is HIGH and goldReward is LOW and heroRole is SCOUT and armyLoss is LOW then Value is BITHIGH | ||||
|   rule: if goldPreasure is HIGH and goldReward is LOW and heroRole is SCOUT and scoutTurnDistance is LOW and armyLoss is LOW then Value is HIGH with 0.5 | ||||
|   rule: if fear is MEDIUM then Value is LOW | ||||
|   rule: if fear is HIGH then Value is LOWEST | ||||
		Reference in New Issue
	
	Block a user