فهرست منبع

NKAI: playing around with defence

Andrii Danylchenko 2 سال پیش
والد
کامیت
6ba74f02bc

+ 41 - 2
AI/Nullkiller/Analyzers/ArmyManager.cpp

@@ -33,6 +33,45 @@ public:
 	}
 };
 
+void ArmyUpgradeInfo::addArmyToBuy(std::vector<SlotInfo> army)
+{
+	for(auto slot : army)
+	{
+		resultingArmy.push_back(slot);
+
+		upgradeValue += slot.power;
+		upgradeCost += slot.creature->getFullRecruitCost() * slot.count;
+	}
+}
+
+void ArmyUpgradeInfo::addArmyToGet(std::vector<SlotInfo> army)
+{
+	for(auto slot : army)
+	{
+		resultingArmy.push_back(slot);
+
+		upgradeValue += slot.power;
+	}
+}
+
+std::vector<SlotInfo> ArmyManager::toSlotInfo(std::vector<creInfo> army) const
+{
+	std::vector<SlotInfo> result;
+
+	for(auto i : army)
+	{
+		SlotInfo slot;
+
+		slot.creature = VLC->creh->objects[i.cre->getId()];
+		slot.count = i.count;
+		slot.power = evaluateStackPower(i.cre, i.count);
+
+		result.push_back(slot);
+	}
+
+	return result;
+}
+
 uint64_t ArmyManager::howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const
 {
 	return howManyReinforcementsCanGet(hero, hero, source);
@@ -136,7 +175,7 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
 		{
 			if(vstd::contains(allowedFactions, slot.creature->getFaction()))
 			{
-				auto slotID = newArmyInstance.getSlotFor(slot.creature);
+				auto slotID = newArmyInstance.getSlotFor(slot.creature->getId());
 
 				if(slotID.validSlot())
 				{
@@ -319,7 +358,7 @@ ui64 ArmyManager::howManyReinforcementsCanGet(const IBonusBearer * armyCarrier,
 	return newArmy > oldArmy ? newArmy - oldArmy : 0;
 }
 
-uint64_t ArmyManager::evaluateStackPower(const CCreature * creature, int count) const
+uint64_t ArmyManager::evaluateStackPower(const Creature * creature, int count) const
 {
 	return creature->getAIValue() * count;
 }

+ 7 - 2
AI/Nullkiller/Analyzers/ArmyManager.h

@@ -34,6 +34,9 @@ struct ArmyUpgradeInfo
 	std::vector<SlotInfo> resultingArmy;
 	uint64_t upgradeValue = 0;
 	TResources upgradeCost;
+
+	void addArmyToBuy(std::vector<SlotInfo> army);
+	void addArmyToGet(std::vector<SlotInfo> army);
 };
 
 class DLL_EXPORT IArmyManager //: public: IAbstractManager
@@ -57,6 +60,7 @@ public:
 	virtual std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const = 0;
 	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<SlotInfo> toSlotInfo(std::vector<creInfo> creatures) const = 0;
 
 	virtual std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const = 0;
 	virtual std::vector<creInfo> getArmyAvailableToBuy(
@@ -65,7 +69,7 @@ public:
 		TResources availableRes,
 		uint8_t turn = 0) const = 0;
 
-	virtual uint64_t evaluateStackPower(const CCreature * creature, int count) const = 0;
+	virtual uint64_t evaluateStackPower(const Creature * creature, int count) const = 0;
 	virtual SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const = 0;
 	virtual ArmyUpgradeInfo calculateCreaturesUpgrade(
 		const CCreatureSet * army,
@@ -99,6 +103,7 @@ public:
 	std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const override;
 	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<SlotInfo> toSlotInfo(std::vector<creInfo> creatures) const override;
 
 	std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const override;
 	std::vector<creInfo> getArmyAvailableToBuy(
@@ -108,7 +113,7 @@ public:
 		uint8_t turn = 0) const override;
 
 	std::shared_ptr<CCreatureSet> getArmyAvailableToBuyAsCCreatureSet(const CGDwelling * dwelling, TResources availableRes) const override;
-	uint64_t evaluateStackPower(const CCreature * creature, int count) const override;
+	uint64_t evaluateStackPower(const Creature * creature, int count) const override;
 	SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const override;
 	ArmyUpgradeInfo calculateCreaturesUpgrade(
 		const CCreatureSet * army, 

+ 73 - 20
AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp

@@ -17,6 +17,11 @@ namespace NKAI
 
 HitMapInfo HitMapInfo::NoTreat;
 
+double HitMapInfo::value() const
+{
+	return danger / std::sqrt(turn / 3.0f + 1);
+}
+
 void DangerHitMapAnalyzer::updateHitMap()
 {
 	if(hitMapUpToDate)
@@ -29,8 +34,12 @@ void DangerHitMapAnalyzer::updateHitMap()
 
 	auto cb = ai->cb.get();
 	auto mapSize = ai->cb->getMapSize();
-	hitMap.resize(boost::extents[mapSize.x][mapSize.y][mapSize.z]);
+	
+	if(hitMap.shape()[0] != mapSize.x || hitMap.shape()[1] != mapSize.y || hitMap.shape()[2] != mapSize.z)
+		hitMap.resize(boost::extents[mapSize.x][mapSize.y][mapSize.z]);
+
 	enemyHeroAccessibleObjects.clear();
+	townTreats.clear();
 
 	std::map<PlayerColor, std::map<const CGHeroInstance *, HeroRole>> heroes;
 
@@ -67,29 +76,26 @@ void DangerHitMapAnalyzer::updateHitMap()
 				if(path.getFirstBlockedAction())
 					continue;
 
-				auto tileDanger = path.getHeroStrength();
-				auto turn = path.turn();
 				auto & node = hitMap[pos.x][pos.y][pos.z];
 
-				auto newMaxDanger = tileDanger / std::sqrt(turn / 3.0f + 1);
-				auto currentMaxDanger = node.maximumDanger.danger / std::sqrt(node.maximumDanger.turn / 3.0f + 1);
+				HitMapInfo newTreat;
+
+				newTreat.hero = path.targetHero;
+				newTreat.turn = path.turn();
+				newTreat.danger = path.getHeroStrength();
 
-				if(newMaxDanger > currentMaxDanger)
+				if(newTreat.value() > node.maximumDanger.value())
 				{
-					node.maximumDanger.danger = tileDanger;
-					node.maximumDanger.turn = turn;
-					node.maximumDanger.hero = path.targetHero;
+					node.maximumDanger = newTreat;
 				}
 
-				if(turn < node.fastestDanger.turn
-					|| (turn == node.fastestDanger.turn && node.fastestDanger.danger < tileDanger))
+				if(newTreat.turn < node.fastestDanger.turn
+					|| (newTreat.turn == node.fastestDanger.turn && node.fastestDanger.danger < newTreat.danger))
 				{
-					node.fastestDanger.danger = tileDanger;
-					node.fastestDanger.turn = turn;
-					node.fastestDanger.hero = path.targetHero;
+					node.fastestDanger = newTreat;
 				}
 
-				if(turn == 0)
+				if(newTreat.turn == 0)
 				{
 					auto objects = cb->getVisitableObjs(pos, false);
 					
@@ -97,6 +103,26 @@ void DangerHitMapAnalyzer::updateHitMap()
 					{
 						if(cb->getPlayerRelations(obj->tempOwner, ai->playerID) != PlayerRelations::ENEMIES)
 							enemyHeroAccessibleObjects[path.targetHero].insert(obj);
+
+						if(obj->ID == Obj::TOWN && obj->getOwner() == ai->playerID)
+						{
+							auto & treats = townTreats[obj->id];
+							auto treat = std::find_if(treats.begin(), treats.end(), [&](const HitMapInfo & i) -> bool
+								{
+									return i.hero.hid == path.targetHero->id;
+								});
+
+							if(treat == treats.end())
+							{
+								treats.emplace_back();
+								treat = std::prev(treats.end(), 1);
+							}
+
+							if(newTreat.value() > treat->value())
+							{
+								*treat = newTreat;
+							}
+						}
 					}
 				}
 			}
@@ -115,7 +141,8 @@ void DangerHitMapAnalyzer::calculateTileOwners()
 	auto cb = ai->cb.get();
 	auto mapSize = ai->cb->getMapSize();
 
-	tileOwners.resize(boost::extents[mapSize.x][mapSize.y][mapSize.z]);
+	if(hitMap.shape()[0] != mapSize.x || hitMap.shape()[1] != mapSize.y || hitMap.shape()[2] != mapSize.z)
+		hitMap.resize(boost::extents[mapSize.x][mapSize.y][mapSize.z]);
 
 	std::map<const CGHeroInstance *, HeroRole> townHeroes;
 	std::map<const CGHeroInstance *, const CGTownInstance *> heroTownMap;
@@ -157,6 +184,7 @@ void DangerHitMapAnalyzer::calculateTileOwners()
 			float ourDistance = std::numeric_limits<float>::max();
 			float enemyDistance = std::numeric_limits<float>::max();
 			const CGTownInstance * enemyTown = nullptr;
+			const CGTownInstance * ourTown = nullptr;
 
 			for(AIPath & path : ai->pathfinder->getPathInfo(pos))
 			{
@@ -167,7 +195,11 @@ void DangerHitMapAnalyzer::calculateTileOwners()
 
 				if(town->getOwner() == ai->playerID)
 				{
-					vstd::amin(ourDistance, path.movementCost());
+					if(ourDistance > path.movementCost())
+					{
+						ourDistance = path.movementCost();
+						ourTown = town;
+					}
 				}
 				else
 				{
@@ -181,19 +213,40 @@ void DangerHitMapAnalyzer::calculateTileOwners()
 
 			if(ourDistance == enemyDistance)
 			{
-				tileOwners[pos.x][pos.y][pos.z] = PlayerColor::NEUTRAL;
+				hitMap[pos.x][pos.y][pos.z].closestTown = nullptr;
 			}
 			else if(!enemyTown || ourDistance < enemyDistance)
 			{
-				tileOwners[pos.x][pos.y][pos.z] = ai->playerID;
+				hitMap[pos.x][pos.y][pos.z].closestTown = ourTown;
 			}
 			else
 			{
-				tileOwners[pos.x][pos.y][pos.z] = enemyTown->getOwner();
+				hitMap[pos.x][pos.y][pos.z].closestTown = enemyTown;
 			}
 		});
 }
 
+const std::vector<HitMapInfo> & DangerHitMapAnalyzer::getTownTreats(const CGTownInstance * town) const
+{
+	static const std::vector<HitMapInfo> empty = {};
+
+	auto result = townTreats.find(town->id);
+
+	return result == townTreats.end() ? empty : result->second;
+}
+
+PlayerColor DangerHitMapAnalyzer::getTileOwner(const int3 & tile) const
+{
+	auto town = hitMap[tile.x][tile.y][tile.z].closestTown;
+
+	return town ? town->getOwner() : PlayerColor::NEUTRAL;
+}
+
+const CGTownInstance * DangerHitMapAnalyzer::getClosestTown(const int3 & tile) const
+{
+	return hitMap[tile.x][tile.y][tile.z].closestTown;
+}
+
 uint64_t DangerHitMapAnalyzer::enemyCanKillOurHeroesAlongThePath(const AIPath & path) const
 {
 	int3 tile = path.targetTile();

+ 12 - 1
AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h

@@ -35,6 +35,8 @@ struct HitMapInfo
 		turn = 255;
 		hero = HeroPtr();
 	}
+
+	double value() const;
 };
 
 struct HitMapNode
@@ -42,6 +44,8 @@ struct HitMapNode
 	HitMapInfo maximumDanger;
 	HitMapInfo fastestDanger;
 
+	const CGTownInstance * closestTown = nullptr;
+
 	HitMapNode() = default;
 
 	void reset()
@@ -51,15 +55,19 @@ struct HitMapNode
 	}
 };
 
+struct TileOwner
+{
+};
+
 class DangerHitMapAnalyzer
 {
 private:
 	boost::multi_array<HitMapNode, 3> hitMap;
-	boost::multi_array<PlayerColor, 3> tileOwners;
 	std::map<const CGHeroInstance *, std::set<const CGObjectInstance *>> enemyHeroAccessibleObjects;
 	bool hitMapUpToDate = false;
 	bool tileOwnersUpToDate = false;
 	const Nullkiller * ai;
+	std::map<ObjectInstanceID, std::vector<HitMapInfo>> townTreats;
 
 public:
 	DangerHitMapAnalyzer(const Nullkiller * ai) :ai(ai) {}
@@ -72,6 +80,9 @@ public:
 	const std::set<const CGObjectInstance *> & getOneTurnAccessibleObjects(const CGHeroInstance * enemy) const;
 	void reset();
 	void resetTileOwners() { tileOwnersUpToDate = false; }
+	PlayerColor getTileOwner(const int3 & tile) const;
+	const CGTownInstance * getClosestTown(const int3 & tile) const;
+	const std::vector<HitMapInfo> & getTownTreats(const CGTownInstance * town) const;
 };
 
 }

+ 18 - 6
AI/Nullkiller/Behaviors/DefenceBehavior.cpp

@@ -54,7 +54,9 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 	logAi->trace("Evaluating defence for %s", town->getNameTranslated());
 
 	auto treatNode = ai->nullkiller->dangerHitMap->getObjectTreat(town);
-	auto treats = { treatNode.maximumDanger, treatNode.fastestDanger };
+	std::vector<HitMapInfo> treats = ai->nullkiller->dangerHitMap->getTownTreats(town);
+	
+	treats.push_back(treatNode.fastestDanger); // no guarantee that fastest danger will be there
 
 	int dayOfWeek = cb->getDate(Date::DAY_OF_WEEK);
 
@@ -131,14 +133,24 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 
 			if(treat.hero.validAndSet()
 				&& treat.turn <= 1
-				&& (treat.danger == treatNode.maximumDanger.danger || treat.turn < treatNode.maximumDanger.turn)
-				&& isSafeToVisit(path.targetHero, path.heroArmy, treat.danger))
+				&& (treat.danger == treatNode.maximumDanger.danger || treat.turn < treatNode.maximumDanger.turn))
 			{
-				Composition composition;
+				auto heroCapturingPaths = ai->nullkiller->pathfinder->getPathInfo(treat.hero->visitablePos());
+				auto goals = CaptureObjectsBehavior::getVisitGoals(heroCapturingPaths, treat.hero.get());
+
+				for(int i = 0; i < heroCapturingPaths.size(); i++)
+				{
+					AIPath & path = heroCapturingPaths[i];
+					TSubgoal goal = goals[i];
 
-				composition.addNext(DefendTown(town, treat, path)).addNext(CaptureObject(treat.hero.get()));
+					if(!goal || goal->invalid() || !goal->isElementar()) continue;
 
-				tasks.push_back(Goals::sptr(composition));
+					Composition composition;
+
+					composition.addNext(DefendTown(town, treat, path, true)).addNext(goal);
+
+					tasks.push_back(Goals::sptr(composition));
+				}
 			}
 
 			bool treatIsWeak = path.getHeroStrength() / (float)treat.danger > TREAT_IGNORE_RATIO;

+ 56 - 12
AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp

@@ -12,6 +12,7 @@
 #include "../Engine/Nullkiller.h"
 #include "../Goals/ExecuteHeroChain.h"
 #include "../Goals/Composition.h"
+#include "../Goals/RecruitHero.h"
 #include "../Markers/HeroExchange.h"
 #include "../Markers/ArmyUpgrade.h"
 #include "GatherArmyBehavior.h"
@@ -240,12 +241,22 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
 	logAi->trace("Found %d paths", paths.size());
 #endif
 
+	bool hasMainAround = false;
+
+	for(const AIPath & path : paths)
+	{
+		auto heroRole = ai->nullkiller->heroManager->getHeroRole(path.targetHero);
+
+		if(heroRole == HeroRole::MAIN && path.turn() < SCOUT_TURN_DISTANCE_LIMIT)
+			hasMainAround = true;
+	}
+
 	for(const AIPath & path : paths)
 	{
 #if NKAI_TRACE_LEVEL >= 2
 		logAi->trace("Path found %s, %s, %lld", path.toString(), path.targetHero->getObjectName(), path.heroArmy->getArmyStrength());
 #endif
-		if(upgrader->visitingHero && upgrader->visitingHero.get() != path.targetHero)
+		if(upgrader->visitingHero && (upgrader->visitingHero.get() != path.targetHero || path.exchangeCount == 1))
 		{
 #if NKAI_TRACE_LEVEL >= 2
 			logAi->trace("Ignore path. Town has visiting hero.");
@@ -283,25 +294,58 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
 
 		auto upgrade = ai->nullkiller->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources);
 
-		if(!upgrader->garrisonHero && ai->nullkiller->heroManager->getHeroRole(path.targetHero) == HeroRole::MAIN)
+		if(!upgrader->garrisonHero
+			&& (
+				hasMainAround
+				|| ai->nullkiller->heroManager->getHeroRole(path.targetHero) == HeroRole::MAIN))
 		{
-			upgrade.upgradeValue +=	
-				ai->nullkiller->armyManager->howManyReinforcementsCanGet(
+			ArmyUpgradeInfo armyToGetOrBuy;
+
+			armyToGetOrBuy.addArmyToGet(
+				ai->nullkiller->armyManager->getBestArmy(
 					path.targetHero,
 					path.heroArmy,
-					upgrader->getUpperArmy());
+					upgrader->getUpperArmy()));
+
+			armyToGetOrBuy.addArmyToBuy(
+				ai->nullkiller->armyManager->toSlotInfo(
+					ai->nullkiller->armyManager->getArmyAvailableToBuy(
+						path.heroArmy,
+						upgrader,
+						ai->nullkiller->getFreeResources(),
+						path.turn())));
+
+			upgrade.upgradeValue += armyToGetOrBuy.upgradeValue;
+			upgrade.upgradeCost += armyToGetOrBuy.upgradeCost;
+			vstd::concatenate(upgrade.resultingArmy, armyToGetOrBuy.resultingArmy);
+
+			auto getOrBuyArmyValue = (float)armyToGetOrBuy.upgradeValue / path.getHeroStrength();
+
+			if(!upgrade.upgradeValue
+				&& armyToGetOrBuy.upgradeValue > 20000
+				&& ai->nullkiller->heroManager->canRecruitHero(town)
+				&& path.turn() < SCOUT_TURN_DISTANCE_LIMIT)
+			{
+				for(auto hero : cb->getAvailableHeroes(town))
+				{
+					auto scoutReinforcement =  ai->nullkiller->armyManager->howManyReinforcementsCanBuy(hero, town)
+						+ ai->nullkiller->armyManager->howManyReinforcementsCanGet(hero, town);
 
-			upgrade.upgradeValue +=
-				ai->nullkiller->armyManager->howManyReinforcementsCanBuy(
-					path.heroArmy,
-					upgrader,
-					ai->nullkiller->getFreeResources(),
-					path.turn());
+					if(scoutReinforcement >= armyToGetOrBuy.upgradeValue
+						&& ai->nullkiller->getFreeGold() >20000
+						&& ai->nullkiller->buildAnalyzer->getGoldPreasure() < MAX_GOLD_PEASURE)
+					{
+						Composition recruitHero;
+
+						recruitHero.addNext(ArmyUpgrade(path.targetHero, town, armyToGetOrBuy)).addNext(RecruitHero(town, hero));
+					}
+				}
+			}
 		}
 
 		auto armyValue = (float)upgrade.upgradeValue / path.getHeroStrength();
 
-		if((armyValue < 0.1f && armyValue < 20000) || upgrade.upgradeValue < 300) // avoid small upgrades
+		if((armyValue < 0.1f && upgrade.upgradeValue < 20000) || upgrade.upgradeValue < 300) // avoid small upgrades
 		{
 #if NKAI_TRACE_LEVEL >= 2
 			logAi->trace("Ignore path. Army value is too small (%f)", armyValue);

+ 21 - 0
AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp

@@ -66,6 +66,27 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const
 				}
 			}
 
+			int treasureSourcesCount = 0;
+
+			for(auto obj : ai->nullkiller->objectClusterizer->getNearbyObjects())
+			{
+				if((obj->ID == Obj::RESOURCE && obj->subID == GameResID(EGameResID::GOLD))
+					|| obj->ID == Obj::TREASURE_CHEST
+					|| obj->ID == Obj::CAMPFIRE
+					|| obj->ID == Obj::WATER_WHEEL
+					|| obj->ID ==Obj::ARTIFACT)
+				{
+					auto tile = obj->visitablePos();
+					auto closestTown = ai->nullkiller->dangerHitMap->getClosestTown(tile);
+
+					if(town == closestTown)
+						treasureSourcesCount++;
+				}
+			}
+
+			if(treasureSourcesCount < 10)
+				continue;
+
 			if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1
 				|| (ai->nullkiller->getFreeResources()[EGameResID::GOLD] > 10000
 					&& ai->nullkiller->buildAnalyzer->getGoldPreasure() < MAX_GOLD_PEASURE))

+ 42 - 38
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -282,18 +282,6 @@ uint64_t RewardEvaluator::getArmyReward(
 
 	switch(target->ID)
 	{
-	case Obj::TOWN:
-	{
-		auto town = dynamic_cast<const CGTownInstance *>(target);
-		auto fortLevel = town->fortLevel();
-		auto booster = isAnotherAi(town, *ai->cb) ? 1 : 2;
-
-		if(fortLevel < CGTownInstance::CITADEL)
-			return town->hasFort() ? booster * 500 : 0;
-		else
-			return booster * (fortLevel == CGTownInstance::CASTLE ? 5000 : 2000);
-	}
-
 	case Obj::HILL_FORT:
 		return ai->armyManager->calculateCreaturesUpgrade(army, target, ai->cb->getResourceAmount()).upgradeValue;
 	case Obj::CREATURE_BANK:
@@ -440,6 +428,22 @@ float RewardEvaluator::getTotalResourceRequirementStrength(int resType) const
 	return std::min(ratio, 1.0f);
 }
 
+uint64_t RewardEvaluator::townArmyGrowth(const CGTownInstance * town) const
+{
+	uint64_t result = 0;
+
+	for(auto creatureInfo : town->creatures)
+	{
+		if(creatureInfo.second.empty())
+			continue;
+
+		auto creature = creatureInfo.second.back().toCreature();
+		result += creature->getAIValue() * town->getGrowthInfo(creature->getLevel() - 1).totalGrowth();
+	}
+
+	return result;
+}
+
 float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) const
 {
 	if(!target)
@@ -475,13 +479,22 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
 	case Obj::TOWN:
 	{
 		if(ai->buildAnalyzer->getDevelopmentInfo().empty())
-			return 1;
+			return 10.0f;
 
 		auto town = dynamic_cast<const CGTownInstance *>(target);
+
+		if(town->getOwner() == ai->playerID)
+		{
+			auto armyIncome = townArmyGrowth(town);
+			auto dailyIncome = town->dailyIncome()[EGameResID::GOLD];
+
+			return std::min(1.0f, std::sqrt(armyIncome / 40000.0f)) + std::min(0.3f, dailyIncome / 10000.0f);
+		}
+
 		auto fortLevel = town->fortLevel();
-		auto booster = isAnotherAi(town, *ai->cb) ? 0.3 : 1;
+		auto booster = isAnotherAi(town, *ai->cb) ? 0.3f : 0.7f;
 
-		if(town->hasCapitol()) return 1;
+		if(town->hasCapitol()) return booster;
 
 		if(fortLevel < CGTownInstance::CITADEL)
 			return booster * (town->hasFort() ? 0.6 : 0.4);
@@ -690,23 +703,6 @@ void addTileDanger(EvaluationContext & evaluationContext, const int3 & tile, uin
 
 class DefendTownEvaluator : public IEvaluationContextBuilder
 {
-private:
-	uint64_t townArmyIncome(const CGTownInstance * town) const
-	{
-		uint64_t result = 0;
-
-		for(auto creatureInfo : town->creatures)
-		{
-			if(creatureInfo.second.empty())
-				continue;
-
-			auto creature = creatureInfo.second.back().toCreature();
-			result += creature->getAIValue() * town->getGrowthInfo(creature->getLevel() - 1).totalGrowth();
-		}
-
-		return result;
-	}
-
 public:
 	virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
 	{
@@ -717,10 +713,7 @@ public:
 		const CGTownInstance * town = defendTown.town;
 		auto & treat = defendTown.getTreat();
 
-		auto armyIncome = townArmyIncome(town);
-		auto dailyIncome = town->dailyIncome()[EGameResID::GOLD];
-
-		auto strategicalValue = std::sqrt(armyIncome / 60000.0f) + dailyIncome / 10000.0f;
+		auto strategicalValue = evaluationContext.evaluator.getStrategicalValue(town);
 
 		if(evaluationContext.evaluator.ai->buildAnalyzer->getDevelopmentInfo().size() == 1)
 			vstd::amax(evaluationContext.strategicalValue, 10.0);
@@ -732,9 +725,20 @@ public:
 
 		multiplier /= 1.0f + treat.turn / 5.0f;
 
-		evaluationContext.armyGrowth += armyIncome * multiplier;
+		if(defendTown.getTurn() > 0 && defendTown.isContrAttack())
+		{
+			auto ourSpeed = defendTown.hero->maxMovePoints(true);
+			auto enemySpeed = treat.hero->maxMovePoints(true);
+
+			if(enemySpeed > ourSpeed) multiplier *= 0.7f;
+		}
+
+		auto dailyIncome = town->dailyIncome()[EGameResID::GOLD];
+		auto armyGrowth = evaluationContext.evaluator.townArmyGrowth(town);
+
+		evaluationContext.armyGrowth += armyGrowth * multiplier;
 		evaluationContext.goldReward += dailyIncome * 5 * multiplier;
-		evaluationContext.addNonCriticalStrategicalValue(strategicalValue * multiplier);
+		evaluationContext.addNonCriticalStrategicalValue(1.7f * multiplier * strategicalValue);
 		vstd::amax(evaluationContext.danger, defendTown.getTreat().danger);
 		addTileDanger(evaluationContext, town->visitablePos(), defendTown.getTurn(), defendTown.getDefenceStrength());
 	}

+ 1 - 0
AI/Nullkiller/Engine/PriorityEvaluator.h

@@ -44,6 +44,7 @@ public:
 	int32_t getGoldReward(const CGObjectInstance * target, const CGHeroInstance * hero) const;
 	uint64_t getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const;
 	const HitMapInfo & getEnemyHeroDanger(const int3 & tile, uint8_t turn) const;
+	uint64_t townArmyGrowth(const CGTownInstance * town) const;
 };
 
 struct DLL_EXPORT EvaluationContext

+ 7 - 0
AI/Nullkiller/Markers/ArmyUpgrade.cpp

@@ -28,6 +28,13 @@ ArmyUpgrade::ArmyUpgrade(const AIPath & upgradePath, const CGObjectInstance * up
 	sethero(upgradePath.targetHero);
 }
 
+ArmyUpgrade::ArmyUpgrade(const CGHeroInstance * targetMain, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade)
+	: CGoal(Goals::ARMY_UPGRADE), upgrader(upgrader), upgradeValue(upgrade.upgradeValue),
+	initialValue(targetMain->getArmyStrength()), goldCost(upgrade.upgradeCost[EGameResID::GOLD])
+{
+	sethero(targetMain);
+}
+
 bool ArmyUpgrade::operator==(const ArmyUpgrade & other) const
 {
 	return false;

+ 1 - 0
AI/Nullkiller/Markers/ArmyUpgrade.h

@@ -27,6 +27,7 @@ namespace Goals
 
 	public:
 		ArmyUpgrade(const AIPath & upgradePath, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade);
+		ArmyUpgrade(const CGHeroInstance * targetMain, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade);
 
 		virtual bool operator==(const ArmyUpgrade & other) const override;
 		virtual std::string toString() const override;

+ 2 - 2
AI/Nullkiller/Markers/DefendTown.cpp

@@ -18,8 +18,8 @@ namespace NKAI
 
 using namespace Goals;
 
-DefendTown::DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath)
-	: CGoal(Goals::DEFEND_TOWN), treat(treat), defenceArmyStrength(defencePath.getHeroStrength()), turn(defencePath.turn())
+DefendTown::DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath, bool isContrattack)
+	: CGoal(Goals::DEFEND_TOWN), treat(treat), defenceArmyStrength(defencePath.getHeroStrength()), turn(defencePath.turn()), contrattack(isContrattack)
 {
 	settown(town);
 	sethero(defencePath.targetHero);

+ 4 - 1
AI/Nullkiller/Markers/DefendTown.h

@@ -24,9 +24,10 @@ namespace Goals
 		uint64_t defenceArmyStrength;
 		HitMapInfo treat;
 		uint8_t turn;
+		bool contrattack;
 
 	public:
-		DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath);
+		DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath, bool isContrattack = false);
 		DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const CGHeroInstance * defender);
 
 		virtual bool operator==(const DefendTown & other) const override;
@@ -37,6 +38,8 @@ namespace Goals
 		uint64_t getDefenceStrength() const { return defenceArmyStrength; }
 
 		uint8_t getTurn() const { return turn; }
+
+		bool isContrAttack() { return contrattack; }
 	};
 }