Sfoglia il codice sorgente

Nullkiller: allow buy army through pathfinder

Andrii Danylchenko 4 anni fa
parent
commit
3822d788e8

+ 5 - 1
AI/Nullkiller/Analyzers/ArmyManager.cpp

@@ -202,7 +202,11 @@ ui64 ArmyManager::howManyReinforcementsCanBuy(const CCreatureSet * h, const CGDw
 
 std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const
 {
-	auto availableRes = cb->getResourceAmount();
+	return getArmyAvailableToBuy(hero, dwelling, cb->getResourceAmount());
+}
+
+std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling, TResources availableRes) const
+{
 	std::vector<creInfo> creaturesInDwellings;
 	int freeHeroSlots = GameConstants::ARMY_SIZE - hero->stacksCount();
 

+ 3 - 1
AI/Nullkiller/Analyzers/ArmyManager.h

@@ -48,13 +48,14 @@ 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<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const = 0;
+	virtual std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling, TResources availableRes) const = 0;
 	virtual uint64_t evaluateStackPower(const CCreature * creature, int count) const = 0;
 	virtual SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const = 0;
 	virtual ArmyUpgradeInfo calculateCreateresUpgrade(
 		const CCreatureSet * army,
 		const CGObjectInstance * upgrader,
 		const TResources & availableResources) const = 0;
+	virtual std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const = 0;
 };
 
 struct StackUpgradeInfo;
@@ -75,6 +76,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<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling, TResources availableRes) 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;

+ 4 - 0
AI/Nullkiller/CMakeLists.txt

@@ -7,6 +7,8 @@ set(VCAI_SRCS
 		Pathfinding/Actors.cpp
 		Pathfinding/Actions/SpecialAction.cpp
 		Pathfinding/Actions/BattleAction.cpp
+		Pathfinding/Actions/QuestAction.cpp
+		Pathfinding/Actions/BuyArmyAction.cpp
 		Pathfinding/Actions/BoatActions.cpp
 		Pathfinding/Actions/TownPortalAction.cpp
 		Pathfinding/Rules/AILayerTransitionRule.cpp
@@ -62,6 +64,8 @@ set(VCAI_HEADERS
 		Pathfinding/Actors.h
 		Pathfinding/Actions/SpecialAction.h
 		Pathfinding/Actions/BattleAction.h
+		Pathfinding/Actions/QuestAction.h
+		Pathfinding/Actions/BuyArmyAction.h
 		Pathfinding/Actions/BoatActions.h
 		Pathfinding/Actions/TownPortalAction.h
 		Pathfinding/Rules/AILayerTransitionRule.h

+ 6 - 0
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -613,6 +613,12 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
 			exchangeNode->theNodeBefore = carrier;
 		}
 
+		if(exchangeNode->actor->actorAction)
+		{
+			exchangeNode->theNodeBefore = carrier;
+			exchangeNode->specialAction = exchangeNode->actor->actorAction;
+		}
+
 		exchangeNode->chainOther = other;
 		exchangeNode->armyLoss = chainInfo.armyLoss;
 

+ 1 - 1
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -11,7 +11,7 @@
 #pragma once
 
 #define PATHFINDER_TRACE_LEVEL 0
-#define AI_TRACE_LEVEL 0
+#define AI_TRACE_LEVEL 2
 #define SCOUT_TURN_DISTANCE_LIMIT 3
 
 #include "../../../lib/CPathfinder.h"

+ 0 - 26
AI/Nullkiller/Pathfinding/Actions/BattleAction.cpp

@@ -28,30 +28,4 @@ namespace AIPathfinding
 	{
 		return "Battle at " + targetTile.toString();
 	}
-
-	bool QuestAction::canAct(const AIPathNode * node) const
-	{
-		if(questInfo.obj->ID == Obj::BORDER_GATE || questInfo.obj->ID == Obj::BORDERGUARD)
-		{
-			return dynamic_cast<const IQuestObject *>(questInfo.obj)->checkQuest(node->actor->hero);
-		}
-
-		return questInfo.quest->progress == CQuest::NOT_ACTIVE 
-			|| questInfo.quest->checkQuest(node->actor->hero);
-	}
-
-	Goals::TSubgoal QuestAction::decompose(const CGHeroInstance * hero) const
-	{
-		return Goals::sptr(Goals::CompleteQuest(questInfo));
-	}
-
-	void QuestAction::execute(const CGHeroInstance * hero) const
-	{
-		ai->moveHeroToTile(questInfo.obj->visitablePos(), hero);
-	}
-
-	std::string QuestAction::toString() const
-	{
-		return "Complete Quest";
-	}
 }

+ 0 - 20
AI/Nullkiller/Pathfinding/Actions/BattleAction.h

@@ -30,24 +30,4 @@ namespace AIPathfinding
 
 		virtual std::string toString() const override;
 	};
-
-	class QuestAction : public SpecialAction
-	{
-	private:
-		QuestInfo questInfo;
-
-	public:
-		QuestAction(QuestInfo questInfo)
-			:questInfo(questInfo)
-		{
-		}
-
-		virtual bool canAct(const AIPathNode * node) const override;
-
-		virtual Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
-
-		virtual void execute(const CGHeroInstance * hero) const override;
-
-		virtual std::string toString() const override;
-	};
 }

+ 31 - 0
AI/Nullkiller/Pathfinding/Actions/BuyArmyAction.cpp

@@ -0,0 +1,31 @@
+/*
+* BattleAction.cpp, part of VCMI engine
+*
+* Authors: listed in file AUTHORS in main folder
+*
+* License: GNU General Public License v2.0 or later
+* Full text of license available in license.txt file, in main folder
+*
+*/
+
+#include "StdInc.h"
+#include "BuyArmyAction.h"
+#include "../../VCAI.h"
+#include "../../Goals/CompleteQuest.h"
+#include "../../../../lib/mapping/CMap.h" //for victory conditions
+
+extern boost::thread_specific_ptr<CCallback> cb;
+extern boost::thread_specific_ptr<VCAI> ai;
+
+namespace AIPathfinding
+{
+	void BuyArmyAction::execute(const CGHeroInstance * hero) const
+	{
+		ai->recruitCreatures(hero->visitedTown, hero);
+	}
+
+	std::string BuyArmyAction::toString() const
+	{
+		return "Buy Army";
+	}
+}

+ 31 - 0
AI/Nullkiller/Pathfinding/Actions/BuyArmyAction.h

@@ -0,0 +1,31 @@
+/*
+* BuyArmyAction.h, part of VCMI engine
+*
+* Authors: listed in file AUTHORS in main folder
+*
+* License: GNU General Public License v2.0 or later
+* Full text of license available in license.txt file, in main folder
+*
+*/
+
+#pragma once
+
+#include "SpecialAction.h"
+#include "../../../../lib/CGameState.h"
+
+namespace AIPathfinding
+{
+	class BuyArmyAction : public SpecialAction
+	{
+	private:
+
+	public:
+		bool canAct(const AIPathNode * source) const override
+		{
+			return true;
+		}
+
+		void execute(const CGHeroInstance * hero) const override;
+		std::string toString() const override;
+	};
+}

+ 47 - 0
AI/Nullkiller/Pathfinding/Actions/QuestAction.cpp

@@ -0,0 +1,47 @@
+/*
+* QuestAction.cpp, part of VCMI engine
+*
+* Authors: listed in file AUTHORS in main folder
+*
+* License: GNU General Public License v2.0 or later
+* Full text of license available in license.txt file, in main folder
+*
+*/
+
+#include "StdInc.h"
+#include "QuestAction.h"
+#include "../../VCAI.h"
+#include "../../Goals/CompleteQuest.h"
+#include "../../../../lib/mapping/CMap.h" //for victory conditions
+
+extern boost::thread_specific_ptr<CCallback> cb;
+extern boost::thread_specific_ptr<VCAI> ai;
+
+namespace AIPathfinding
+{
+	bool QuestAction::canAct(const AIPathNode * node) const
+	{
+		if(questInfo.obj->ID == Obj::BORDER_GATE || questInfo.obj->ID == Obj::BORDERGUARD)
+		{
+			return dynamic_cast<const IQuestObject *>(questInfo.obj)->checkQuest(node->actor->hero);
+		}
+
+		return questInfo.quest->progress == CQuest::NOT_ACTIVE 
+			|| questInfo.quest->checkQuest(node->actor->hero);
+	}
+
+	Goals::TSubgoal QuestAction::decompose(const CGHeroInstance * hero) const
+	{
+		return Goals::sptr(Goals::CompleteQuest(questInfo));
+	}
+
+	void QuestAction::execute(const CGHeroInstance * hero) const
+	{
+		ai->moveHeroToTile(questInfo.obj->visitablePos(), hero);
+	}
+
+	std::string QuestAction::toString() const
+	{
+		return "Complete Quest";
+	}
+}

+ 37 - 0
AI/Nullkiller/Pathfinding/Actions/QuestAction.h

@@ -0,0 +1,37 @@
+/*
+* QuestAction.h, part of VCMI engine
+*
+* Authors: listed in file AUTHORS in main folder
+*
+* License: GNU General Public License v2.0 or later
+* Full text of license available in license.txt file, in main folder
+*
+*/
+
+#pragma once
+
+#include "SpecialAction.h"
+#include "../../../../lib/CGameState.h"
+
+namespace AIPathfinding
+{
+	class QuestAction : public SpecialAction
+	{
+	private:
+		QuestInfo questInfo;
+
+	public:
+		QuestAction(QuestInfo questInfo)
+			:questInfo(questInfo)
+		{
+		}
+
+		virtual bool canAct(const AIPathNode * node) const override;
+
+		virtual Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
+
+		virtual void execute(const CGHeroInstance * hero) const override;
+
+		virtual std::string toString() const override;
+	};
+}

+ 71 - 15
AI/Nullkiller/Pathfinding/Actors.cpp

@@ -14,6 +14,7 @@
 #include "../../../CCallback.h"
 #include "../../../lib/mapping/CMap.h"
 #include "../../../lib/mapObjects/MapObjects.h"
+#include "Actions/BuyArmyAction.h"
 
 CCreatureSet emptyArmy;
 
@@ -22,9 +23,21 @@ bool HeroExchangeArmy::needsLastStack() const
 	return true;
 }
 
+std::shared_ptr<SpecialAction> HeroExchangeArmy::getActorAction() const
+{
+	std::shared_ptr<SpecialAction> result;
+
+	if(requireBuyArmy)
+	{
+		result.reset(new AIPathfinding::BuyArmyAction());
+	}
+
+	return result;
+}
+
 ChainActor::ChainActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t chainMask)
 	:hero(hero), heroRole(heroRole), isMovable(true), chainMask(chainMask), creatureSet(hero),
-	baseActor(this), carrierParent(nullptr), otherParent(nullptr), actorExchangeCount(1), armyCost()
+	baseActor(this), carrierParent(nullptr), otherParent(nullptr), actorExchangeCount(1), armyCost(), actorAction()
 {
 	initialPosition = hero->visitablePos();
 	layer = hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND;
@@ -37,7 +50,7 @@ ChainActor::ChainActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t
 ChainActor::ChainActor(const ChainActor * carrier, const ChainActor * other, const CCreatureSet * heroArmy)
 	:hero(carrier->hero), heroRole(carrier->heroRole), isMovable(true), creatureSet(heroArmy), chainMask(carrier->chainMask | other->chainMask),
 	baseActor(this), carrierParent(carrier), otherParent(other), heroFightingStrength(carrier->heroFightingStrength),
-	actorExchangeCount(carrier->actorExchangeCount + other->actorExchangeCount), armyCost(carrier->armyCost + other->armyCost)
+	actorExchangeCount(carrier->actorExchangeCount + other->actorExchangeCount), armyCost(carrier->armyCost + other->armyCost), actorAction()
 {
 	armyValue = heroArmy->getArmyStrength();
 }
@@ -45,7 +58,7 @@ ChainActor::ChainActor(const ChainActor * carrier, const ChainActor * other, con
 ChainActor::ChainActor(const CGObjectInstance * obj, const CCreatureSet * creatureSet, uint64_t chainMask, int initialTurn)
 	:hero(nullptr), heroRole(HeroRole::MAIN), isMovable(false), creatureSet(creatureSet), chainMask(chainMask),
 	baseActor(this), carrierParent(nullptr), otherParent(nullptr), initialTurn(initialTurn), initialMovement(0),
-	heroFightingStrength(0), actorExchangeCount(1), armyCost()
+	heroFightingStrength(0), actorExchangeCount(1), armyCost(), actorAction()
 {
 	initialPosition = obj->visitablePos();
 	layer = EPathfindingLayer::LAND;
@@ -82,11 +95,13 @@ HeroActor::HeroActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t ch
 HeroActor::HeroActor(
 	const ChainActor * carrier, 
 	const ChainActor * other, 
-	const CCreatureSet * army, 
+	const HeroExchangeArmy * army, 
 	const Nullkiller * ai)
 	:ChainActor(carrier, other,	army)
 {
 	exchangeMap = new HeroExchangeMap(this, ai);
+	armyCost += army->armyCost;
+	actorAction = army->getActorAction();
 	setupSpecialActors();
 }
 
@@ -104,6 +119,7 @@ void ChainActor::setBaseActor(HeroActor * base)
 	isMovable = base->isMovable;
 	heroFightingStrength = base->heroFightingStrength;
 	armyCost = base->armyCost;
+	actorAction = base->actorAction;
 }
 
 void HeroActor::setupSpecialActors()
@@ -191,6 +207,12 @@ bool HeroExchangeMap::canExchange(const ChainActor * other)
 			
 			if(other->creatureSet->Slots().size())
 				reinforcment += ai->armyManager->howManyReinforcementsCanGet(actor->hero, actor->creatureSet, other->creatureSet);
+			
+			auto obj = other->getActorObject();
+			if(obj && obj->ID == Obj::TOWN)
+			{
+				reinforcment += ai->armyManager->howManyReinforcementsCanBuy(actor->creatureSet, ai->cb->getTown(obj->id));
+			}
 
 #if PATHFINDER_TRACE_LEVEL >= 2
 			logAi->trace(
@@ -242,14 +264,16 @@ HeroActor * HeroExchangeMap::exchange(const ChainActor * other)
 	else 
 	{
 		TResources availableResources = ai->cb->getResourceAmount() - actor->armyCost - other->armyCost;
-		CCreatureSet * upgradedInitialArmy = tryUpgrade(actor->creatureSet, other->getActorObject(), availableResources);
-		CCreatureSet * newArmy;
+		HeroExchangeArmy * upgradedInitialArmy = tryUpgrade(actor->creatureSet, other->getActorObject(), availableResources);
+		HeroExchangeArmy * newArmy;
 		
 		if(other->creatureSet->Slots().size())
 		{
 			if(upgradedInitialArmy)
 			{
 				newArmy = pickBestCreatures(upgradedInitialArmy, other->creatureSet);
+				newArmy->armyCost = upgradedInitialArmy->armyCost;
+
 				delete upgradedInitialArmy;
 			}
 			else
@@ -263,34 +287,66 @@ HeroActor * HeroExchangeMap::exchange(const ChainActor * other)
 		}
 
 		result = new HeroActor(actor, other, newArmy, ai);
+		result->armyCost += newArmy->armyCost;
 		exchangeMap[other] = result;
 	}
 
 	return result;
 }
 
-CCreatureSet * HeroExchangeMap::tryUpgrade(const CCreatureSet * army, const CGObjectInstance * upgrader, TResources resources) const
+HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
+	const CCreatureSet * army,
+	const CGObjectInstance * upgrader,
+	TResources resources) const
 {
+	HeroExchangeArmy * target = new HeroExchangeArmy();
 	auto upgradeInfo = ai->armyManager->calculateCreateresUpgrade(army, upgrader, resources);
 
-	if(!upgradeInfo.upgradeValue)
-		return nullptr;
+	if(upgradeInfo.upgradeValue)
+	{
+		for(auto & slotInfo : upgradeInfo.resultingArmy)
+		{
+			auto targetSlot = target->getFreeSlot();
 
-	CCreatureSet * target = new HeroExchangeArmy();
+			target->addToSlot(targetSlot, slotInfo.creature->idNumber, TQuantity(slotInfo.count));
+		}
 
-	for(auto & slotInfo : upgradeInfo.resultingArmy)
+		resources -= upgradeInfo.upgradeCost;
+		target->armyCost += upgradeInfo.upgradeCost;
+	}
+	else
 	{
-		auto targetSlot = target->getFreeSlot();
+		for(auto slot : army->Slots())
+		{
+			auto targetSlot = target->getSlotFor(slot.second->getCreatureID());
 
-		target->addToSlot(targetSlot, slotInfo.creature->idNumber, TQuantity(slotInfo.count));
+			target->addToSlot(targetSlot, slot.second->getCreatureID(), slot.second->count);
+		}
 	}
 
+	if(upgrader->ID == Obj::TOWN)
+	{
+		auto buyArmy = ai->armyManager->getArmyAvailableToBuy(target, ai->cb->getTown(upgrader->id), resources);
+
+		for(auto creatureToBuy : buyArmy)
+		{
+			auto targetSlot = target->getSlotFor(creatureToBuy.cre);
+
+			target->addToSlot(targetSlot, creatureToBuy.creID, creatureToBuy.count);
+			target->armyCost += creatureToBuy.cre->cost * creatureToBuy.count;
+			target->requireBuyArmy = true;
+		}
+	}
+
+	if(target->getArmyStrength() <= army->getArmyStrength())
+		return nullptr;
+
 	return target;
 }
 
-CCreatureSet * HeroExchangeMap::pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const
+HeroExchangeArmy * HeroExchangeMap::pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const
 {
-	CCreatureSet * target = new HeroExchangeArmy();
+	HeroExchangeArmy * target = new HeroExchangeArmy();
 	auto bestArmy = ai->armyManager->getBestArmy(actor->hero, army1, army2);
 
 	for(auto & slotInfo : bestArmy)

+ 11 - 3
AI/Nullkiller/Pathfinding/Actors.h

@@ -21,7 +21,14 @@ class Nullkiller;
 class HeroExchangeArmy : public CCreatureSet
 {
 public:
+	TResources armyCost;
+	bool requireBuyArmy;
 	virtual bool needsLastStack() const override;
+	std::shared_ptr<SpecialAction> getActorAction() const;
+
+	HeroExchangeArmy() : CCreatureSet(), armyCost(), requireBuyArmy(false)
+	{
+	}
 };
 
 class ChainActor
@@ -37,6 +44,7 @@ public:
 	bool allowUseResources;
 	bool allowBattle;
 	bool allowSpellCast;
+	std::shared_ptr<SpecialAction> actorAction;
 	const CGHeroInstance * hero;
 	HeroRole heroRole;
 	const CCreatureSet * creatureSet;
@@ -87,8 +95,8 @@ public:
 	bool canExchange(const ChainActor * other);
 
 private:
-	CCreatureSet * pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const;
-	CCreatureSet * tryUpgrade(const CCreatureSet * army, const CGObjectInstance * upgrader, TResources resources) const;
+	HeroExchangeArmy * pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const;
+	HeroExchangeArmy * tryUpgrade(const CCreatureSet * army, const CGObjectInstance * upgrader, TResources resources) const;
 };
 
 class HeroActor : public ChainActor
@@ -107,7 +115,7 @@ public:
 	// chain flags, can be combined meaning hero exchange and so on
 
 	HeroActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t chainMask, const Nullkiller * ai);
-	HeroActor(const ChainActor * carrier, const ChainActor * other, const CCreatureSet * army, const Nullkiller * ai);
+	HeroActor(const ChainActor * carrier, const ChainActor * other, const HeroExchangeArmy * army, const Nullkiller * ai);
 
 	virtual bool canExchange(const ChainActor * other) const override;
 

+ 1 - 0
AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp

@@ -10,6 +10,7 @@
 #include "StdInc.h"
 #include "AIMovementAfterDestinationRule.h"
 #include "../Actions/BattleAction.h"
+#include "../Actions/QuestAction.h"
 #include "../../Goals/Invalid.h"
 #include "AIPreviousNodeRule.h"
 

+ 1 - 0
AI/Nullkiller/VCAI.cpp

@@ -526,6 +526,7 @@ void VCAI::heroGotLevel(const CGHeroInstance * hero, PrimarySkill::PrimarySkill
 	{ 
 		if(hPtr.validAndSet())
 		{
+			nullkiller->heroManager->update();
 			answerQuery(queryID, nullkiller->heroManager->selectBestSkill(hPtr, skills));
 		}
 	});