Browse Source

Nullkiller: stabilisation and fixes

Andrii Danylchenko 4 years ago
parent
commit
e3c87fb58d

+ 6 - 3
AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp

@@ -39,14 +39,14 @@ Goals::TGoalVec BuyArmyBehavior::getTasks()
 
 	if(heroes.size())
 	{
-		auto mainHero = vstd::maxElementByFun(heroes, [](const CGHeroInstance * hero) -> uint64_t
+		auto mainArmy = vstd::maxElementByFun(heroes, [](const CGHeroInstance * hero) -> uint64_t
 		{
-			return hero->getFightingStrength();
+			return hero->getTotalStrength();
 		});
 
 		for(auto town : cb->getTownsInfo())
 		{
-			const CGHeroInstance * targetHero = *mainHero;
+			const CGHeroInstance * targetHero = *mainArmy;
 
 			/*if(town->visitingHero)
 			{
@@ -62,6 +62,9 @@ Goals::TGoalVec BuyArmyBehavior::getTasks()
 
 			auto reinforcement = ai->ah->howManyReinforcementsCanBuy(targetHero, town);
 
+			if(reinforcement)
+				reinforcement = ai->ah->howManyReinforcementsCanBuy(town->getUpperArmy(), town);
+
 			if(reinforcement)
 			{
 				tasks.push_back(Goals::sptr(Goals::BuyArmy(town, reinforcement).setpriority(5)));

+ 40 - 8
AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp

@@ -79,6 +79,35 @@ const CGHeroInstance * getNearestHero(const CGTownInstance * town)
 	return shortestPath.targetHero;
 }
 
+bool needToRecruitHero(const CGTownInstance * startupTown)
+{
+	if(!ai->canRecruitAnyHero(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);
+
+	for(auto obj : ai->visitableObjs)
+	{
+		if(obj->ID == Obj::RESOURCE && obj->subID == Res::GOLD
+			|| obj->ID == Obj::TREASURE_CHEST
+			|| obj->ID == Obj::CAMPFIRE
+			|| obj->ID == Obj::WATER_WHEEL)
+		{
+			auto path = paths->getPathInfo(obj->visitablePos());
+			if(path->accessible == CGPathNode::BLOCKVIS && path->turns != 255)
+			{
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
 Goals::TGoalVec StartupBehavior::getTasks()
 {
 	Goals::TGoalVec tasks;
@@ -88,8 +117,8 @@ Goals::TGoalVec StartupBehavior::getTasks()
 		return tasks;
 
 	const CGTownInstance * startupTown = towns.front();
-	bool canRecruitHero = ai->canRecruitAnyHero(startupTown);
-		
+	bool canRecruitHero = needToRecruitHero(startupTown);
+
 	if(towns.size() > 1)
 	{
 		startupTown = *vstd::maxElementByFun(towns, [](const CGTownInstance * town) -> float
@@ -131,14 +160,17 @@ Goals::TGoalVec StartupBehavior::getTasks()
 				auto garrisonHero = startupTown->garrisonHero.get();
 				auto garrisonHeroScore = ai->ah->evaluateHero(garrisonHero);
 
-				if(garrisonHeroScore > visitingHeroScore)
+				if(visitingHeroScore > garrisonHeroScore
+					|| ai->ah->getHeroRole(garrisonHero) == HeroRole::SCOUT && ai->ah->getHeroRole(visitingHero) == HeroRole::MAIN)
 				{
-					if(ai->ah->howManyReinforcementsCanGet(garrisonHero, visitingHero) > 200)
-						tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, garrisonHero).setpriority(100)));
+					if(canRecruitHero || ai->ah->howManyReinforcementsCanGet(visitingHero, garrisonHero) > 200)
+					{
+						tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero).setpriority(100)));
+					}
 				}
-				else
+				else if(ai->ah->howManyReinforcementsCanGet(garrisonHero, visitingHero) > 200)
 				{
-					tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero).setpriority(100)));
+					tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, garrisonHero).setpriority(100)));
 				}
 			}
 			else if(canRecruitHero)
@@ -157,7 +189,7 @@ Goals::TGoalVec StartupBehavior::getTasks()
 	{
 		for(const CGTownInstance * town : towns)
 		{
-			if(town->garrisonHero)
+			if(town->garrisonHero && town->garrisonHero->movement)
 				tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(town, nullptr).setpriority(0.0001f)));
 		}
 	}

+ 16 - 12
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -99,7 +99,7 @@ uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHero
 	return result;
 }
 
-uint64_t getDwellingScore(const CGObjectInstance * target)
+uint64_t getDwellingScore(const CGObjectInstance * target, bool checkGold)
 {
 	auto dwelling = dynamic_cast<const CGDwelling *>(target);
 	uint64_t score = 0;
@@ -109,17 +109,17 @@ uint64_t getDwellingScore(const CGObjectInstance * target)
 		if(creLevel.first && creLevel.second.size())
 		{
 			auto creature = creLevel.second.back().toCreature();
-			if(cb->getResourceAmount().canAfford(creature->cost * creLevel.first))
-			{
-				score += creature->AIValue * creLevel.first;
-			}
+			if(checkGold &&	!cb->getResourceAmount().canAfford(creature->cost * creLevel.first))
+				continue;
+
+			score += creature->AIValue * creLevel.first;
 		}
 	}
 
 	return score;
 }
 
-uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero)
+uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero, bool checkGold)
 {
 	if(!target)
 		return 0;
@@ -127,17 +127,20 @@ uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * h
 	switch(target->ID)
 	{
 	case Obj::TOWN:
-		return target->tempOwner == PlayerColor::NEUTRAL ? 5000 : 1000;
+		return target->tempOwner == PlayerColor::NEUTRAL ? 1000 : 10000;
 	case Obj::CREATURE_BANK:
 		return getCreatureBankArmyReward(target, hero);
 	case Obj::CREATURE_GENERATOR1:
-		return getDwellingScore(target);
+	case Obj::CREATURE_GENERATOR2:
+	case Obj::CREATURE_GENERATOR3:
+	case Obj::CREATURE_GENERATOR4:
+		return getDwellingScore(target, checkGold);
 	case Obj::CRYPT:
 	case Obj::SHIPWRECK:
 	case Obj::SHIPWRECK_SURVIVOR:
 		return 1500;
 	case Obj::ARTIFACT:
-		return dynamic_cast<const CGArtifact *>(target)->storedArtifact-> artType->getArtClassSerial() == CArtifact::ART_MAJOR ? 3000 : 1500;
+		return dynamic_cast<const CGArtifact *>(target)->storedArtifact-> artType->getArtClassSerial() * 300;
 	case Obj::DRAGON_UTOPIA:
 		return 10000;
 	default:
@@ -259,11 +262,12 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
 	auto hero = heroPtr.get();
 	auto armyTotal = task->evaluationContext.heroStrength;
 	double armyLossPersentage = task->evaluationContext.armyLoss / (double)armyTotal;
-	int32_t goldReward = getGoldReward(target, hero);
-	uint64_t armyReward = getArmyReward(target, hero);
+	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);
-	uint64_t danger = task->evaluationContext.danger;
 	double result = 0;
 	int rewardType = (goldReward > 0 ? 1 : 0) + (armyReward > 0 ? 1 : 0) + (skillReward > 0 ? 1 : 0);
 	

+ 12 - 3
AI/Nullkiller/Goals/ExecuteHeroChain.cpp

@@ -82,6 +82,14 @@ void ExecuteHeroChain::accept(VCAI * ai)
 			{
 				ai->nullkiller->setActive(hero);
 
+				if(node.specialAction)
+				{
+					auto specialGoal = node.specialAction->whatToDo(hero);
+
+					if(specialGoal->isElementar)
+						specialGoal->accept(ai);
+				}
+
 				Goals::VisitTile(node.coord).sethero(hero).accept(ai);
 			}
 
@@ -138,15 +146,13 @@ TSubgoal ExchangeSwapTownHeroes::whatToDoToAchieve()
 
 void ExchangeSwapTownHeroes::accept(VCAI * ai)
 {
-	if(!garrisonHero && town->garrisonHero && town->visitingHero)
-		throw cannotFulfillGoalException("Invalid configuration. Garrison hero is null.");
-
 	if(!garrisonHero)
 	{
 		if(!town->garrisonHero)
 			throw cannotFulfillGoalException("Invalid configuration. There is no hero in town garrison.");
 		
 		cb->swapGarrisonHero(town);
+		ai->buildArmyIn(town);
 		ai->nullkiller->unlockHero(town->visitingHero.get());
 		logAi->debug("Extracted hero %s from garrison of %s", town->visitingHero->name, town->name);
 
@@ -162,7 +168,10 @@ void ExchangeSwapTownHeroes::accept(VCAI * ai)
 	ai->nullkiller->lockHero(town->garrisonHero.get());
 
 	if(town->visitingHero)
+	{
 		ai->nullkiller->unlockHero(town->visitingHero.get());
+		makePossibleUpgrades(town->visitingHero);
+	}
 
 	logAi->debug("Put hero %s to garrison of %s", town->garrisonHero->name, town->name);
 }

+ 19 - 9
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -501,7 +501,7 @@ void AINodeStorage::setTownsAndDwellings(
 	{
 		uint64_t mask = 1 << actors.size();
 
-		if(town->getUpperArmy()->getArmyStrength())
+		if(!town->garrisonHero && town->getUpperArmy()->getArmyStrength())
 		{
 			actors.push_back(std::make_shared<TownGarrisonActor>(town, mask));
 		}
@@ -563,7 +563,7 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
 
 	if(source.isInitialPosition)
 	{
-		calculateTownPortalTeleportations(source, neighbours);
+		calculateTownPortalTeleportations(source, neighbours, pathfinderHelper);
 	}
 
 	return neighbours;
@@ -571,7 +571,8 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
 
 void AINodeStorage::calculateTownPortalTeleportations(
 	const PathNodeInfo & source,
-	std::vector<CGPathNode *> & neighbours)
+	std::vector<CGPathNode *> & neighbours,
+	const CPathfinderHelper * pathfinderHelper)
 {
 	SpellID spellID = SpellID::TOWN_PORTAL;
 	const CSpell * townPortal = spellID.toSpell();
@@ -594,9 +595,12 @@ void AINodeStorage::calculateTownPortalTeleportations(
 
 		// TODO: Copy/Paste from TownPortalMechanics
 		auto skillLevel = hero->getSpellSchoolLevel(townPortal);
-		auto movementCost = GameConstants::BASE_MOVEMENT_COST * (skillLevel >= 3 ? 2 : 3);
+		auto movementNeeded = GameConstants::BASE_MOVEMENT_COST * (skillLevel >= 3 ? 2 : 3);
+		float movementCost = (float)movementNeeded / (float)pathfinderHelper->getMaxMovePoints(EPathfindingLayer::LAND);
+
+		movementCost += source.node->cost;
 
-		if(hero->movement < movementCost)
+		if(source.node->moveRemains < movementNeeded)
 		{
 			return;
 		}
@@ -605,7 +609,7 @@ void AINodeStorage::calculateTownPortalTeleportations(
 		{
 			const CGTownInstance * nearestTown = *vstd::minElementByFun(towns, [&](const CGTownInstance * t) -> int
 			{
-				return hero->visitablePos().dist2dSQ(t->visitablePos());
+				return source.coord.dist2dSQ(t->visitablePos());
 			});
 
 			towns = std::vector<const CGTownInstance *>{ nearestTown };
@@ -613,6 +617,7 @@ void AINodeStorage::calculateTownPortalTeleportations(
 
 		for(const CGTownInstance * targetTown : towns)
 		{
+			// TODO: allow to hide visiting hero in garrison
 			if(targetTown->visitingHero)
 				continue;
 
@@ -626,9 +631,13 @@ void AINodeStorage::calculateTownPortalTeleportations(
 
 				AIPathNode * node = nodeOptional.get();
 
-				node->theNodeBefore = source.node;
-				node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown));
-				node->moveRemains = source.node->moveRemains;
+				if(node->action == CGPathNode::UNKNOWN || node->cost > movementCost)
+				{
+					node->theNodeBefore = source.node;
+					node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown));
+					node->moveRemains = source.node->moveRemains + movementNeeded;
+					node->cost = movementCost;
+				}
 				
 				neighbours.push_back(node);
 			}
@@ -764,6 +773,7 @@ void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path, int pa
 			AIPathNodeInfo pathNode;
 			pathNode.cost = node->cost;
 			pathNode.targetHero = node->actor->hero;
+			pathNode.specialAction = node->specialAction;
 			pathNode.turns = node->turns;
 			pathNode.danger = node->danger;
 			pathNode.coord = node->coord;

+ 6 - 2
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -10,7 +10,7 @@
 
 #pragma once
 
-#define VCMI_TRACE_PATHFINDER 1
+#define VCMI_TRACE_PATHFINDER 0
 
 #include "../../../lib/CPathfinder.h"
 #include "../../../lib/mapObjects/CGHeroInstance.h"
@@ -39,6 +39,7 @@ struct AIPathNodeInfo
 	uint64_t danger;
 	const CGHeroInstance * targetHero;
 	int parentIndex;
+	std::shared_ptr<const ISpecialAction> specialAction;
 };
 
 struct AIPath
@@ -163,7 +164,10 @@ private:
 	void cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const;
 	void addHeroChain(const std::vector<ExchangeCandidate> & result);
 
-	void calculateTownPortalTeleportations(const PathNodeInfo & source, std::vector<CGPathNode *> & neighbours);
+	void calculateTownPortalTeleportations(
+		const PathNodeInfo & source,
+		std::vector<CGPathNode *> & neighbours,
+		const CPathfinderHelper * pathfinderHelper);
 	void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const;
 	void commit(
 		AIPathNode * destination, 

+ 11 - 1
AI/Nullkiller/VCAI.cpp

@@ -649,6 +649,12 @@ void VCAI::showBlockingDialog(const std::string & text, const std::vector<Compon
 	if(!selection && cancel) //yes&no -> always answer yes, we are a brave AI :)
 		sel = 1;
 
+	// TODO: Find better way to understand it is Chest of Treasures
+	if(components.size() == 2 && components.front().id == Component::RESOURCE)
+	{
+		sel = 1; // for now lets pick gold from a chest.
+	}
+
 	requestActionASAP([=]()
 	{
 		answerQuery(askID, sel);
@@ -1062,7 +1068,8 @@ void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
 		if(h->visitedTown) //we are inside, not just attacking
 		{
 			townVisitsThisWeek[h].insert(h->visitedTown);
-			if(!h->hasSpellbook() && ah->freeGold() >= GameConstants::SPELLBOOK_GOLD_COST)
+			ah->updateHeroRoles();
+			if(ah->getHeroRole(h) == HeroRole::MAIN && !h->hasSpellbook() && ah->freeGold() >= GameConstants::SPELLBOOK_GOLD_COST)
 			{
 				if(h->visitedTown->hasBuilt(BuildingID::MAGES_GUILD_1))
 					cb->buyArtifact(h.get(), ArtifactID::SPELLBOOK);
@@ -1807,6 +1814,9 @@ bool VCAI::isAccessibleForHero(const int3 & pos, HeroPtr h, bool includeAllies)
 
 bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 {
+	if(h->inTownGarrison && h->visitedTown)
+		cb->swapGarrisonHero(h->visitedTown);
+
 	//TODO: consider if blockVisit objects change something in our checks: AIUtility::isBlockVisitObj()
 
 	auto afterMovementCheck = [&]() -> void