Selaa lähdekoodia

Nullkiller: stabilization+clasterization improvements+fuzzy fear

Andrii Danylchenko 4 vuotta sitten
vanhempi
sitoutus
32fb465823

+ 6 - 0
AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp

@@ -99,6 +99,12 @@ uint64_t DangerHitMapAnalyzer::enemyCanKillOurHeroesAlongThePath(const AIPath &
 const HitMapNode & DangerHitMapAnalyzer::getObjectTreat(const CGObjectInstance * obj) const
 {
 	auto tile = obj->visitablePos();
+
+	return getTileTreat(tile);
+}
+
+const HitMapNode & DangerHitMapAnalyzer::getTileTreat(const int3 & tile) const
+{
 	const HitMapNode & info = hitMap[tile.x][tile.y][tile.z];
 
 	return info;

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

@@ -51,6 +51,7 @@ public:
 	void updateHitMap();
 	uint64_t enemyCanKillOurHeroesAlongThePath(const AIPath & path) const;
 	const HitMapNode & getObjectTreat(const CGObjectInstance * obj) const;
+	const HitMapNode & getTileTreat(const int3 & tile) const;
 	const std::set<const CGObjectInstance *> & getOneTurnAccessibleObjects(const CGHeroInstance * enemy) const;
 	void reset();
 };

+ 25 - 5
AI/Nullkiller/Analyzers/ObjectClusterizer.cpp

@@ -92,7 +92,7 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons
 	{
 		auto guardPos = ai->cb->getGuardingCreaturePosition(node->coord);
 		auto blockers = ai->cb->getVisitableObjs(node->coord);
-
+		
 		if(guardPos.valid())
 		{
 			auto guard = ai->cb->getTopObj(ai->cb->getGuardingCreaturePosition(node->coord));
@@ -103,6 +103,16 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons
 			}
 		}
 
+		if(node->specialAction && node->actionIsBlocked)
+		{
+			auto blockerObject = node->specialAction->targetObject();
+
+			if(blockerObject)
+			{
+				blockers.push_back(blockerObject);
+			}
+		}
+
 		if(blockers.empty())
 			continue;
 
@@ -113,7 +123,8 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons
 			|| blocker->ID == Obj::GARRISON2
 			|| blocker->ID == Obj::BORDERGUARD
 			|| blocker->ID == Obj::QUEST_GUARD
-			|| blocker->ID == Obj::BORDER_GATE)
+			|| blocker->ID == Obj::BORDER_GATE
+			|| blocker->ID == Obj::SHIPYARD)
 		{
 			if(!isObjectPassable(blocker))
 				return blocker;
@@ -201,9 +212,15 @@ void ObjectClusterizer::clusterize()
 		
 		bool added = false;
 		bool directlyAccessible = false;
+		std::set<const CGHeroInstance *> heroesProcessed;
 
 		for(auto & path : paths)
 		{
+			if(vstd::contains(heroesProcessed, path.targetHero))
+				continue;
+
+			heroesProcessed.insert(path.targetHero);
+
 			if(path.nodes.size() > 1)
 			{
 				auto blocker = getBlocker(path);
@@ -236,10 +253,13 @@ void ObjectClusterizer::clusterize()
 
 		if(!added || directlyAccessible)
 		{
-			if(paths.front().turn() <= 2)
-				nearObjects.addObject(obj, paths.front(), 0);
+			AIPath & shortestPath = paths.front();
+			float priority = ai->priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(shortestPath, obj)));
+
+			if(shortestPath.turn() <= 2 || priority > 0.6f)
+				nearObjects.addObject(obj, shortestPath, 0);
 			else
-				farObjects.addObject(obj, paths.front(), 0);
+				farObjects.addObject(obj, shortestPath, 0);
 		}
 	}
 

+ 36 - 21
AI/Nullkiller/Behaviors/DefenceBehavior.cpp

@@ -60,7 +60,7 @@ uint64_t townArmyIncome(const CGTownInstance * town)
 
 void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town) const
 {
-	auto basicPriority = 0.3f + std::sqrt(townArmyIncome(town) / 20000.0f)
+	auto basicPriority = 0.3f + std::sqrt(townArmyIncome(town) / 40000.0f)
 		+ town->dailyIncome()[Res::GOLD] / 10000.0f;
 
 	logAi->debug("Evaluating defence for %s, basic priority %f", town->name, basicPriority);
@@ -124,7 +124,8 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 			{
 				if(path.turn() <= treat.turn && dayOfWeek + treat.turn < 6 && isSafeToVisit(path.targetHero, path.heroArmy, treat.danger)
 					|| path.exchangeCount == 1 && path.turn() < treat.turn
-					|| path.turn() < treat.turn - 1)
+					|| path.turn() < treat.turn - 1
+					|| path.turn() < treat.turn && treat.turn >= 2)
 				{
 					logAi->debug(
 						"Hero %s can eliminate danger for town %s using path %s.",
@@ -194,6 +195,9 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 			continue;
 		}
 
+		std::vector<Goals::ExecuteHeroChain> pathsToDefend;
+		std::map<const CGHeroInstance *, std::vector<AIPath>> defferedPaths;
+
 		for(AIPath & path : paths)
 		{
 #if AI_TRACE_LEVEL >= 1
@@ -211,11 +215,16 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 					town->name,
 					path.targetHero->name);
 
+				defferedPaths[path.targetHero].push_back(path);
+
 				continue;
 			}
 
-			float priority = basicPriority
-				+ std::min(SAFE_ATTACK_CONSTANT, (float)path.getHeroStrength() / treat.danger) / (treat.turn + 1);
+			float priority = basicPriority * std::min(SAFE_ATTACK_CONSTANT, (float)path.getHeroStrength() / treat.danger) 
+				- treat.turn * 0.2f;
+
+			if(treat.turn < path.turn())
+				priority /= (path.turn() - treat.turn) * 2;
 
 			if(path.targetHero == town->visitingHero && path.exchangeCount == 1)
 			{
@@ -231,7 +240,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 				continue;
 			}
 				
-			if(path.turn() <= treat.turn && path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= treat.danger)
+			if(treat.turn == 0 || path.turn() <= treat.turn && path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= treat.danger)
 			{
 				if(ai->nullkiller->arePathHeroesLocked(path))
 				{
@@ -245,27 +254,33 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 					continue;
 				}
 
+				pathsToDefend.push_back(Goals::ExecuteHeroChain(path, town).setpriority(priority));
+			}
+		}
+
+		for(Goals::ExecuteHeroChain & chain : pathsToDefend)
+		{
+			auto path = chain.getPath();
+
+			for(AIPath & defferedPath : defferedPaths[path.targetHero])
+			{
+				if(defferedPath.getHeroStrength() >= path.getHeroStrength()
+					&& defferedPath.turn() <= path.turn())
+				{
+					continue;
+				}
+			}
+
 #if AI_TRACE_LEVEL >= 1
-				logAi->trace("Move %s to defend town %s with priority %f",
-					path.targetHero->name,
-					town->name,
-					priority);
+			logAi->trace("Move %s to defend town %s with priority %f",
+				path.targetHero->name,
+				town->name,
+				chain.priority);
 #endif
 
-				tasks.push_back(Goals::sptr(Goals::ExecuteHeroChain(path, town).setpriority(priority)));
-			}
+			tasks.push_back(Goals::sptr(chain));
 		}
 	}
 
 	logAi->debug("Found %d tasks", tasks.size());
-
-	/*for(auto & treat : treats)
-	{
-		auto paths = ai->nullkiller->pathfinder->getPathInfo(treat.hero->visitablePos());
-
-		for(AIPath & path : paths)
-		{
-			tasks.push_back(Goals::sptr(Goals::ExecuteHeroChain(path)));
-		}
-	}*/
 }

+ 1 - 1
AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp

@@ -90,7 +90,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
 		if(path.getFirstBlockedAction())
 		{
 #if AI_TRACE_LEVEL >= 2
-			// TODO: decomposition?
+			// TODO: decomposition
 			logAi->trace("Ignore path. Action is blocked.");
 #endif
 			continue;

+ 9 - 0
AI/Nullkiller/Behaviors/StartupBehavior.cpp

@@ -11,6 +11,7 @@
 #include "StartupBehavior.h"
 #include "../VCAI.h"
 #include "../AIUtility.h"
+#include "../Goals/BuildThis.h"
 #include "../Goals/RecruitHero.h"
 #include "../Goals/ExecuteHeroChain.h"
 #include "../Goals/ExchangeSwapTownHeroes.h"
@@ -123,6 +124,14 @@ Goals::TGoalVec StartupBehavior::decompose() const
 		});
 	}
 
+	if(!startupTown->hasBuilt(BuildingID::TAVERN)
+		&& cb->canBuildStructure(startupTown, BuildingID::TAVERN) == EBuildingState::ALLOWED)
+	{
+		tasks.push_back(Goals::sptr(Goals::BuildThis(BuildingID::TAVERN, startupTown).setpriority(100)));
+
+		return tasks;
+	}
+
 	bool canRecruitHero = needToRecruitHero(startupTown);
 	auto closestHero = getNearestHero(startupTown);
 

+ 20 - 2
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -43,7 +43,8 @@ EvaluationContext::EvaluationContext(const Nullkiller * ai)
 	heroRole(HeroRole::SCOUT),
 	turn(0),
 	strategicalValue(0),
-	evaluator(ai)
+	evaluator(ai),
+	enemyHeroDangerRatio(0)
 {
 }
 
@@ -71,6 +72,7 @@ void PriorityEvaluator::initVisitTile()
 	strategicalValueVariable = engine->getInputVariable("strategicalValue");
 	goldPreasureVariable = engine->getInputVariable("goldPreasure");
 	goldCostVariable = engine->getInputVariable("goldCost");
+	fearVariable = engine->getInputVariable("fear");
 	value = engine->getOutputVariable("Value");
 }
 
@@ -381,6 +383,19 @@ float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGH
 	}
 }
 
+uint64_t RewardEvaluator::getEnemyHeroDanger(const AIPath & path) const
+{
+	auto & treatNode = ai->dangerHitMap->getTileTreat(path.targetTile());
+
+	if(treatNode.maximumDanger.danger == 0)
+		return 0;
+	
+	if(treatNode.maximumDanger.turn <= path.turn())
+		return treatNode.maximumDanger.danger;
+
+	return treatNode.fastestDanger.turn <= path.turn() ? treatNode.fastestDanger.danger : 0;
+}
+
 int32_t getArmyCost(const CArmedInstance * army)
 {
 	int32_t value = 0;
@@ -485,6 +500,7 @@ public:
 		evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, evaluationContext.heroRole);
 		evaluationContext.strategicalValue += evaluationContext.evaluator.getStrategicalValue(target);
 		evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army);
+		vstd::amax(evaluationContext.enemyHeroDangerRatio, evaluationContext.evaluator.getEnemyHeroDanger(path) / (double)path.getHeroStrength());
 		vstd::amax(evaluationContext.turn, path.turn());
 	}
 };
@@ -659,6 +675,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
 		goldPreasureVariable->setValue(ai->buildAnalyzer->getGoldPreasure());
 		goldCostVariable->setValue(evaluationContext.goldCost / ((float)cb->getResourceAmount(Res::GOLD) + (float)ai->buildAnalyzer->getDailyIncome()[Res::GOLD] + 1.0f));
 		turnVariable->setValue(evaluationContext.turn);
+		fearVariable->setValue(evaluationContext.enemyHeroDangerRatio);
 
 		engine->process();
 		//engine.process(VISIT_TILE); //TODO: Process only Visit_Tile
@@ -671,7 +688,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
 	assert(result >= 0);
 
 #ifdef AI_TRACE_LEVEL >= 1
-	logAi->trace("Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %d, cost: %d, army gain: %d, danger: %d, role: %s, strategical value: %f, cwr: %f, result %f",
+	logAi->trace("Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %d, cost: %d, army gain: %d, danger: %d, role: %s, strategical value: %f, cwr: %f, fear: %f, result %f",
 		task->toString(),
 		evaluationContext.armyLossPersentage,
 		(int)evaluationContext.turn,
@@ -684,6 +701,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
 		evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout",
 		evaluationContext.strategicalValue,
 		evaluationContext.closestWayRatio,
+		evaluationContext.enemyHeroDangerRatio,
 		result);
 #endif
 

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

@@ -30,6 +30,7 @@ public:
 	float getSkillReward(const CGObjectInstance * target, const CGHeroInstance * hero, HeroRole role) const;
 	int32_t getGoldReward(const CGObjectInstance * target, const CGHeroInstance * hero) const;
 	uint64_t getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const;
+	uint64_t getEnemyHeroDanger(const AIPath & path) const;
 };
 
 struct DLL_EXPORT EvaluationContext
@@ -48,6 +49,7 @@ struct DLL_EXPORT EvaluationContext
 	HeroRole heroRole;
 	uint8_t turn;
 	RewardEvaluator evaluator;
+	float enemyHeroDangerRatio;
 
 	EvaluationContext(const Nullkiller * ai);
 };
@@ -87,6 +89,7 @@ private:
 	fl::InputVariable * closestHeroRatioVariable;
 	fl::InputVariable * goldPreasureVariable;
 	fl::InputVariable * goldCostVariable;
+	fl::InputVariable * fearVariable;
 	fl::OutputVariable * value;
 	std::vector<std::shared_ptr<IEvaluationContextBuilder>> evaluationContextBuilders;
 

+ 1 - 1
AI/Nullkiller/Goals/ExecuteHeroChain.cpp

@@ -92,7 +92,7 @@ void ExecuteHeroChain::accept(VCAI * ai)
 					}
 				}
 
-					if(node.turns == 0 && node.coord != hero->visitablePos())
+				if(node.turns == 0 && node.coord != hero->visitablePos())
 				{
 					auto targetNode = cb->getPathsInfo(hero)->getPathInfo(node.coord);
 

+ 31 - 26
AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp

@@ -36,32 +36,6 @@ namespace AIPathfinding
 		return sptr(Goals::Invalid());
 	}
 
-	const ChainActor * BuildBoatAction::getActor(const ChainActor * sourceActor) const
-	{
-		return sourceActor->resourceActor;
-	}
-
-	void SummonBoatAction::execute(const CGHeroInstance * hero) const
-	{
-		Goals::AdventureSpellCast(hero, SpellID::SUMMON_BOAT).accept(ai.get());
-	}
-
-	const ChainActor * SummonBoatAction::getActor(const ChainActor * sourceActor) const
-	{
-		return sourceActor->castActor;
-	}
-
-	void SummonBoatAction::applyOnDestination(
-		const CGHeroInstance * hero,
-		CDestinationNodeInfo & destination,
-		const PathNodeInfo & source,
-		AIPathNode * dstMode,
-		const AIPathNode * srcNode) const
-	{
-		dstMode->manaCost = srcNode->manaCost + getManaCost(hero);
-		dstMode->theNodeBefore = source.node;
-	}
-
 	bool BuildBoatAction::canAct(const AIPathNode * source) const
 	{
 		auto hero = source->actor->hero;
@@ -90,6 +64,37 @@ namespace AIPathfinding
 		return true;
 	}
 
+	const CGObjectInstance * BuildBoatAction::targetObject() const
+	{
+		return shipyard->o;
+	}
+
+	const ChainActor * BuildBoatAction::getActor(const ChainActor * sourceActor) const
+	{
+		return sourceActor->resourceActor;
+	}
+
+	void SummonBoatAction::execute(const CGHeroInstance * hero) const
+	{
+		Goals::AdventureSpellCast(hero, SpellID::SUMMON_BOAT).accept(ai.get());
+	}
+
+	const ChainActor * SummonBoatAction::getActor(const ChainActor * sourceActor) const
+	{
+		return sourceActor->castActor;
+	}
+
+	void SummonBoatAction::applyOnDestination(
+		const CGHeroInstance * hero,
+		CDestinationNodeInfo & destination,
+		const PathNodeInfo & source,
+		AIPathNode * dstMode,
+		const AIPathNode * srcNode) const
+	{
+		dstMode->manaCost = srcNode->manaCost + getManaCost(hero);
+		dstMode->theNodeBefore = source.node;
+	}
+
 	std::string BuildBoatAction::toString() const
 	{
 		return "Build Boat at " + shipyard->o->getObjectName();

+ 2 - 0
AI/Nullkiller/Pathfinding/Actions/BoatActions.h

@@ -67,5 +67,7 @@ namespace AIPathfinding
 		virtual const ChainActor * getActor(const ChainActor * sourceActor) const override;
 
 		virtual std::string toString() const override;
+
+		virtual const CGObjectInstance * targetObject() const override;
 	};
 }

+ 2 - 0
AI/Nullkiller/Pathfinding/Actions/SpecialAction.h

@@ -37,4 +37,6 @@ public:
 	}
 
 	virtual std::string toString() const = 0;
+
+	virtual const CGObjectInstance * targetObject() const { return nullptr; }
 };