瀏覽代碼

AI pathfinding: buy boat

Andrii Danylchenko 7 年之前
父節點
當前提交
72aff02418

+ 4 - 4
AI/VCAI/AIhelper.cpp

@@ -29,11 +29,11 @@ bool AIhelper::notifyGoalCompleted(Goals::TSubgoal goal)
 	return resourceManager->notifyGoalCompleted(goal);
 }
 
-void AIhelper::setCB(CPlayerSpecificInfoCallback * CB)
+void AIhelper::init(CPlayerSpecificInfoCallback * CB)
 {
-	resourceManager->setCB(CB);
-	buildingManager->setCB(CB);
-	pathfindingManager->setCB(CB);
+	resourceManager->init(CB);
+	buildingManager->init(CB);
+	pathfindingManager->init(CB);
 }
 
 void AIhelper::setAI(VCAI * AI)

+ 1 - 1
AI/VCAI/AIhelper.h

@@ -63,7 +63,7 @@ public:
 private:
 	bool notifyGoalCompleted(Goals::TSubgoal goal);
 
-	void setCB(CPlayerSpecificInfoCallback * CB) override;
+	void init(CPlayerSpecificInfoCallback * CB) override;
 	void setAI(VCAI * AI) override;
 };
 

+ 1 - 1
AI/VCAI/BuildingManager.cpp

@@ -127,7 +127,7 @@ bool BuildingManager::tryBuildNextStructure(const CGTownInstance * t, std::vecto
 	return false; //Nothing to build
 }
 
-void BuildingManager::setCB(CPlayerSpecificInfoCallback * CB)
+void BuildingManager::init(CPlayerSpecificInfoCallback * CB)
 {
 	cb = CB;
 }

+ 2 - 2
AI/VCAI/BuildingManager.h

@@ -29,7 +29,7 @@ class DLL_EXPORT IBuildingManager //: public: IAbstractManager
 { //info about town development
 public:
 	virtual ~IBuildingManager() = default;
-	virtual void setCB(CPlayerSpecificInfoCallback * CB) = 0;
+	virtual void init(CPlayerSpecificInfoCallback * CB) = 0;
 	virtual void setAI(VCAI * AI) = 0;
 
 	virtual bool getBuildingOptions(const CGTownInstance * t) = 0;
@@ -69,6 +69,6 @@ private:
 	std::vector<PotentialBuilding> immediateBuildings; //what we can build right now in current town
 	std::vector<PotentialBuilding> expensiveBuildings; //what we coudl build but can't afford
 
-	void setCB(CPlayerSpecificInfoCallback * CB) override;
+	void init(CPlayerSpecificInfoCallback * CB) override;
 	void setAI(VCAI * AI) override;
 };

+ 8 - 12
AI/VCAI/FuzzyEngines.cpp

@@ -76,18 +76,14 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army)
 	return as;
 }
 
-float HeroMovementGoalEngineBase::calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const
+float HeroMovementGoalEngineBase::calculateTurnDistanceInputValue(const Goals::AbstractGoal & goal) const
 {
-	float turns = 0.0f;
-	float distance =  distanceToTile(h, tile);
-	if(distance)
+	if(goal.evaluationContext.movementCost != 0)
 	{
-		if(distance < h->movement) //we can move there within one turn
-			turns = (fl::scalar)distance / h->movement;
-		else
-			turns = 1 + (fl::scalar)(distance - h->movement) / h->maxMovePoints(true); //bool on land?
+		return goal.evaluationContext.movementCost / (float)goal.hero->maxMovePoints(true);
 	}
-	return turns;
+
+	return distanceToTile(goal.hero.h, goal.tile) / (float)goal.hero->maxMovePoints(true);
 }
 
 TacticalAdvantageEngine::TacticalAdvantageEngine()
@@ -276,8 +272,8 @@ HeroMovementGoalEngineBase::HeroMovementGoalEngineBase()
 
 		turnDistance->addTerm(new fl::Ramp("SHORT", 0.5, 0));
 		turnDistance->addTerm(new fl::Triangle("MEDIUM", 0.1, 0.8));
-		turnDistance->addTerm(new fl::Ramp("LONG", 0.5, 3));
-		turnDistance->setRange(0.0, 3.0);
+		turnDistance->addTerm(new fl::Ramp("LONG", 0.5, 10));
+		turnDistance->setRange(0.0, 10.0);
 
 		missionImportance->addTerm(new fl::Ramp("LOW", 2.5, 0));
 		missionImportance->addTerm(new fl::Triangle("MEDIUM", 2, 3));
@@ -319,7 +315,7 @@ HeroMovementGoalEngineBase::HeroMovementGoalEngineBase()
 
 void HeroMovementGoalEngineBase::setSharedFuzzyVariables(Goals::AbstractGoal & goal)
 {
-	float turns = calculateTurnDistanceInputValue(goal.hero.h, goal.tile);
+	float turns = calculateTurnDistanceInputValue(goal);
 	float missionImportanceData = 0;
 	if(vstd::contains(ai->lockedHeroes, goal.hero))
 		missionImportanceData = ai->lockedHeroes[goal.hero]->priority;

+ 1 - 1
AI/VCAI/FuzzyEngines.h

@@ -52,7 +52,7 @@ protected:
 	fl::OutputVariable * value;
 
 private:
-	float calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const;
+	float calculateTurnDistanceInputValue(const Goals::AbstractGoal & goal) const;
 };
 
 class VisitTileEngine : public HeroMovementGoalEngineBase

+ 15 - 2
AI/VCAI/FuzzyHelper.cpp

@@ -11,6 +11,7 @@
 #include "FuzzyHelper.h"
 
 #include "../../lib/mapObjects/CommonConstructors.h"
+#include "Goals.h"
 #include "VCAI.h"
 
 FuzzyHelper * fh;
@@ -79,6 +80,19 @@ float FuzzyHelper::evaluate(Goals::VisitTile & g)
 {
 	return visitTileEngine.evaluate(g);
 }
+
+float FuzzyHelper::evaluate(Goals::BuildBoat & g)
+{
+	const float buildBoatPenalty = 0.25;
+
+	if(!g.parent)
+	{
+		return 0;
+	}
+
+	return g.parent->accept(this) - buildBoatPenalty;
+}
+
 float FuzzyHelper::evaluate(Goals::VisitObj & g)
 {
 	return visitObjEngine.evaluate(g);
@@ -90,8 +104,7 @@ float FuzzyHelper::evaluate(Goals::VisitHero & g)
 		return -100; //hero died in the meantime
 	else
 	{
-		auto dummyGoal = Goals::VisitTile(obj->visitablePos()).sethero(g.hero).setisAbstract(g.isAbstract);
-		g.setpriority(dummyGoal.accept(this));
+		g.setpriority(Goals::VisitTile(obj->visitablePos()).sethero(g.hero).accept(this));
 	}
 	return g.priority;
 }

+ 1 - 0
AI/VCAI/FuzzyHelper.h

@@ -29,6 +29,7 @@ public:
 	float evaluate(Goals::CollectRes & g);
 	float evaluate(Goals::Build & g);
 	float evaluate(Goals::BuyArmy & g);
+	float evaluate(Goals::BuildBoat & g);
 	float evaluate(Goals::GatherArmy & g);
 	float evaluate(Goals::ClearWayTo & g);
 	float evaluate(Goals::Invalid & g);

+ 130 - 127
AI/VCAI/Goals.cpp

@@ -115,70 +115,94 @@ std::string Goals::AbstractGoal::name() const //TODO: virtualize
 	return desc;
 }
 
-bool Goals::AbstractGoal::operator==(AbstractGoal & g)
+bool Goals::BuildBoat::operator==(const BuildBoat & other) const
 {
-	/*this operator checks if goals are EQUIVALENT, ie. if they represent same objective
-	it does not not check isAbstract or isElementar, as this is up to VCAI decomposition logic
-	*/
-	if(g.goalType != goalType)
-		return false;
+	return shipyard->o->id == other.shipyard->o->id;
+}
 
-	switch(goalType)
-	{
-	//no parameters
-	case INVALID:
-	case WIN:
-	case DO_NOT_LOSE:
-	case RECRUIT_HERO: //overloaded
-		return true;
-		break;
+bool Goals::Explore::operator==(const Explore & other) const
+{
+	return other.hero.h == hero.h;
+}
 
-	//assigned to hero, no parameters
-	case CONQUER:
-	case EXPLORE:
-	case BOOST_HERO:
-		return g.hero.h == hero.h; //how comes HeroPtrs are equal for different heroes?
-		break;
+bool Goals::Conquer::operator==(const Conquer & other) const
+{
+	return other.hero.h == hero.h;
+}
 
-	case GATHER_ARMY: //actual value is indifferent
-		return (g.hero.h == hero.h || town == g.town); //TODO: gather army for town maybe?
-		break;
+bool Goals::GatherArmy::operator==(const GatherArmy & other) const
+{
+	return other.hero.h == hero.h || town == other.town;
+}
 
-	//assigned hero and tile
-	case VISIT_TILE:
-	case CLEAR_WAY_TO:
-	case DIG_AT_TILE:
-		return (g.hero.h == hero.h && g.tile == tile);
-		break;
+bool Goals::BuyArmy::operator==(const BuyArmy & other) const
+{
+	return town == other.town;
+}
 
-	//assigned hero and object
-	case VISIT_OBJ:
-	case FIND_OBJ: //TODO: use subtype?
-	case VISIT_HERO:
-	case GET_ART_TYPE:
-		return (g.hero.h == hero.h && g.objid == objid);
-		break;
+bool Goals::BoostHero::operator==(const BoostHero & other) const
+{
+	return other.hero.h == hero.h;
+}
 
-	case BUILD_STRUCTURE:
-		return (town == g.town && bid == g.bid); //build specific structure in specific town
-		break;
+bool Goals::BuildThis::operator==(const BuildThis & other) const
+{
+	return town == other.town && bid == other.bid;
+}
 
-	case BUY_ARMY:
-		return town == g.town;
+bool Goals::CollectRes::operator==(const CollectRes & other) const
+{
+	return resID == other.resID;
+}
 
-	//no check atm
-	case COLLECT_RES:
-	case TRADE: //TODO
-		return (resID == g.resID); //every hero may collect resources
-		break;
-	case BUILD: //abstract build is indentical, TODO: consider building anything in town
-		return true;
-		break;
-	case GATHER_TROOPS:
-	case ISSUE_COMMAND:
-	default:
-		return false;
-	}
+bool Goals::Trade::operator==(const Trade & other) const
+{
+	return resID == other.resID;
+}
+
+bool Goals::GatherTroops::operator==(const GatherTroops & other) const
+{
+	return objid == other.objid;
+}
+
+bool Goals::VisitObj::operator==(const VisitObj & other) const
+{
+	return other.hero.h == hero.h && other.objid == objid;
+}
+
+bool Goals::FindObj::operator==(const FindObj & other) const
+{
+	return other.hero.h == hero.h && other.objid == objid;
+}
+
+bool Goals::VisitHero::operator==(const VisitHero & other) const
+{
+	return other.hero.h == hero.h && other.objid == objid;
+}
+
+bool Goals::GetArtOfType::operator==(const GetArtOfType & other) const
+{
+	return other.hero.h == hero.h && other.objid == objid;
+}
+
+bool Goals::VisitTile::operator==(const VisitTile & other) const
+{
+	return other.hero.h == hero.h && other.tile == tile;
+}
+
+bool Goals::ClearWayTo::operator==(const ClearWayTo & other) const
+{
+	return other.hero.h == hero.h && other.tile == tile;
+}
+
+bool Goals::DigAtTile::operator==(const DigAtTile & other) const
+{
+	return other.hero.h == hero.h && other.tile == tile;
+}
+
+bool Goals::AbstractGoal::operator==(const AbstractGoal & g) const
+{
+	return false;
 }
 
 bool Goals::AbstractGoal::operator<(AbstractGoal & g) //for std::unique
@@ -263,14 +287,6 @@ namespace Goals
 		return get() < rhs.get(); //compae by value
 	}
 
-	bool BuyArmy::operator==(AbstractGoal & g)
-	{
-		if (g.goalType != goalType)
-			return false;
-		//if (hero && hero != g.hero)
-		//	return false;
-		return town == g.town;
-	}
 	bool BuyArmy::fulfillsMe(TSubgoal goal)
 	{
 		//if (hero && hero != goal->hero)
@@ -294,16 +310,6 @@ TSubgoal Trade::whatToDoToAchieve()
 {
 	return iAmElementar();
 }
-bool Trade::operator==(AbstractGoal & g)
-{
-	if (g.goalType != goalType)
-		return false;
-	if (g.resID == resID)
-		if (g.value == value) //TODO: not sure if that logic is consitent
-			return true;
-
-	return false;
-}
 
 //TSubgoal AbstractGoal::whatToDoToAchieve()
 //{
@@ -465,6 +471,57 @@ TSubgoal Win::whatToDoToAchieve()
 	return sptr(Goals::Invalid());
 }
 
+TSubgoal BuildBoat::whatToDoToAchieve()
+{
+	if(cb->getPlayerRelations(ai->playerID, shipyard->o->tempOwner) == PlayerRelations::ENEMIES)
+	{
+		return fh->chooseSolution(ai->ah->howToVisitObj(shipyard->o));
+	}
+
+	if(shipyard->shipyardStatus() != IShipyard::GOOD)
+	{
+		throw cannotFulfillGoalException("Shipyard is busy.");
+	}
+
+	TResources boatCost;
+	shipyard->getBoatCost(boatCost);
+
+	return ai->ah->whatToDo(boatCost, this->iAmElementar());
+}
+
+void BuildBoat::accept(VCAI * ai)
+{
+	TResources boatCost;
+	shipyard->getBoatCost(boatCost);
+
+	if(!cb->getResourceAmount().canAfford(boatCost))
+	{
+		throw cannotFulfillGoalException("Can not afford boat");
+	}
+
+	if(cb->getPlayerRelations(ai->playerID, shipyard->o->tempOwner) == PlayerRelations::ENEMIES)
+	{
+		throw cannotFulfillGoalException("Can not build boat in enemy shipyard");
+	}
+
+	if(shipyard->shipyardStatus() != IShipyard::GOOD)
+	{
+		throw cannotFulfillGoalException("Shipyard is busy.");
+	}
+
+	cb->buildBoat(shipyard);
+}
+
+std::string BuildBoat::name() const
+{
+	return "BuildBoat";
+}
+
+std::string BuildBoat::completeMessage() const
+{
+	return "Boat have been built at " + shipyard->o->visitablePos().toString();
+}
+
 TSubgoal FindObj::whatToDoToAchieve()
 {
 	const CGObjectInstance * o = nullptr;
@@ -575,13 +632,6 @@ Goals::VisitObj::VisitObj(int Objid) : CGoal(Goals::VISIT_OBJ)
 	priority = 3;
 }
 
-bool Goals::VisitObj::operator==(AbstractGoal & g)
-{
-	if (g.goalType != goalType)
-		return false;
-	return g.objid == objid;
-}
-
 bool VisitObj::fulfillsMe(TSubgoal goal)
 {
 	if(goal->goalType == Goals::VISIT_TILE)
@@ -622,13 +672,6 @@ TSubgoal VisitHero::whatToDoToAchieve()
 	return sptr(Goals::Invalid());
 }
 
-bool Goals::VisitHero::operator==(AbstractGoal & g)
-{
-	if (g.goalType != goalType)
-		return false;
-	return g.hero == hero && g.objid == objid;
-}
-
 bool VisitHero::fulfillsMe(TSubgoal goal)
 {
 	//TODO: VisitObj shoudl not be used for heroes, but...
@@ -665,13 +708,6 @@ TSubgoal ClearWayTo::whatToDoToAchieve()
 	return (fh->chooseSolution(getAllPossibleSubgoals()));
 }
 
-bool Goals::ClearWayTo::operator==(AbstractGoal & g)
-{
-	if (g.goalType != goalType)
-		return false;
-	return g.goalType == goalType && g.tile == tile;
-}
-
 bool Goals::ClearWayTo::fulfillsMe(TSubgoal goal)
 {
 	if (goal->goalType == Goals::VISIT_TILE)
@@ -876,14 +912,6 @@ TSubgoal RecruitHero::whatToDoToAchieve()
 	return ai->ah->whatToDo(res, iAmElementar()); //either buy immediately, or collect res
 }
 
-bool Goals::RecruitHero::operator==(AbstractGoal & g)
-{
-	if (g.goalType != goalType)
-		return false;
-	//TODO: check town and hero
-	return true; //for now, recruiting any hero will do
-}
-
 std::string VisitTile::completeMessage() const
 {
 	return "Hero " + hero.get()->name + " visited tile " + tile.toString();
@@ -909,13 +937,6 @@ TSubgoal VisitTile::whatToDoToAchieve()
 	return ret;
 }
 
-bool Goals::VisitTile::operator==(AbstractGoal & g)
-{
-	if (g.goalType != goalType)
-		return false;
-	return g.goalType == goalType && g.tile == tile;
-}
-
 TGoalVec VisitTile::getAllPossibleSubgoals()
 {
 	assert(cb->isInTheMap(tile));
@@ -970,13 +991,6 @@ TSubgoal DigAtTile::whatToDoToAchieve()
 	return sptr(Goals::VisitTile(tile));
 }
 
-bool Goals::DigAtTile::operator==(AbstractGoal & g)
-{
-	if (g.goalType != goalType)
-		return false;
-	return g.goalType == goalType && g.tile == tile;
-}
-
 TSubgoal BuildThis::whatToDoToAchieve()
 {
 	auto b = BuildingID(bid);
@@ -1195,17 +1209,6 @@ bool CollectRes::fulfillsMe(TSubgoal goal)
 	return false;
 }
 
-bool Goals::CollectRes::operator==(AbstractGoal & g)
-{
-	if (g.goalType != goalType)
-		return false;
-	if (g.resID == resID)
-		if (g.value == value) //TODO: not sure if that logic is consitent
-			return true;
-
-	return false;
-}
-
 TSubgoal GatherTroops::whatToDoToAchieve()
 {
 	std::vector<const CGDwelling *> dwellings;

+ 99 - 35
AI/VCAI/Goals.h

@@ -59,7 +59,8 @@ enum EGoals
 	CLEAR_WAY_TO,
 	DIG_AT_TILE,//elementar with hero on tile
 	BUY_ARMY, //at specific town
-	TRADE //val resID at object objid
+	TRADE, //val resID at object objid
+	BUILD_BOAT
 };
 
 	//method chaining + clone pattern
@@ -74,6 +75,18 @@ enum {LOW_PR = -1};
 
 DLL_EXPORT TSubgoal sptr(const AbstractGoal & tmp);
 
+struct DLL_EXPORT EvaluationContext
+{
+	uint64_t movementCost;
+	int manaCost;
+	uint64_t danger;
+
+	EvaluationContext()
+		:movementCost(0), danger(0), manaCost(0)
+	{
+	}
+};
+
 class DLL_EXPORT AbstractGoal
 {
 public:
@@ -88,9 +101,11 @@ public:
 	HeroPtr hero; VSETTER(HeroPtr, hero)
 	const CGTownInstance *town; VSETTER(CGTownInstance *, town)
 	int bid; VSETTER(int, bid)
+	TSubgoal parent; VSETTER(TSubgoal, parent)
+	EvaluationContext evaluationContext; VSETTER(EvaluationContext, evaluationContext)
 
 	AbstractGoal(EGoals goal = INVALID)
-		: goalType (goal)
+		: goalType (goal), evaluationContext()
 	{
 		priority = 0;
 		isElementar = false;
@@ -120,7 +135,7 @@ public:
 
 	EGoals goalType;
 
-	std::string name() const;
+	virtual std::string name() const;
 	virtual std::string completeMessage() const
 	{
 		return "This goal is unspecified!";
@@ -130,20 +145,24 @@ public:
 
 	static TSubgoal goVisitOrLookFor(const CGObjectInstance * obj); //if obj is nullptr, then we'll explore
 	static TSubgoal lookForArtSmart(int aid); //checks non-standard ways of obtaining art (merchants, quests, etc.)
-	static TSubgoal tryRecruitHero();
 
 	///Visitor pattern
 	//TODO: make accept work for std::shared_ptr... somehow
 	virtual void accept(VCAI * ai); //unhandled goal will report standard error
 	virtual float accept(FuzzyHelper * f);
 
-	virtual bool operator==(AbstractGoal & g);
+	virtual bool operator==(const AbstractGoal & g) const;
 	bool operator<(AbstractGoal & g); //final
 	virtual bool fulfillsMe(Goals::TSubgoal goal) //TODO: multimethod instead of type check
 	{
 		return false; //use this method to check if goal is fulfilled by another (not equal) goal, operator == is handled spearately
 	}
 
+	bool operator!=(const AbstractGoal & g) const
+	{
+		return !(*this == g);
+	}
+
 	template<typename Handler> void serialize(Handler & h, const int version)
 	{
 		h & goalType;
@@ -209,6 +228,16 @@ public:
 		//h & goalType & isElementar & isAbstract & priority;
 		//h & value & resID & objid & aid & tile & hero & town & bid;
 	}
+
+	virtual bool operator==(const AbstractGoal & g) const override
+	{
+		if(goalType != g.goalType)
+			return false;
+
+		return (*this) == (dynamic_cast<const T &>(g));
+	}
+
+	virtual bool operator==(const T & other) const = 0;
 };
 
 class DLL_EXPORT Invalid : public CGoal<Invalid>
@@ -224,6 +253,11 @@ public:
 		return TGoalVec();
 	}
 	TSubgoal whatToDoToAchieve() override;
+
+	virtual bool operator==(const Invalid & other) const override
+	{
+		return true;
+	}
 };
 
 class DLL_EXPORT Win : public CGoal<Win>
@@ -239,6 +273,11 @@ public:
 		return TGoalVec();
 	}
 	TSubgoal whatToDoToAchieve() override;
+
+	virtual bool operator==(const Win & other) const override
+	{
+		return true;
+	}
 };
 
 class DLL_EXPORT NotLose : public CGoal<NotLose>
@@ -254,6 +293,11 @@ public:
 		return TGoalVec();
 	}
 	//TSubgoal whatToDoToAchieve() override;
+
+	virtual bool operator==(const NotLose & other) const override
+	{
+		return true;
+	}
 };
 
 class DLL_EXPORT Conquer : public CGoal<Conquer>
@@ -266,6 +310,7 @@ public:
 	}
 	TGoalVec getAllPossibleSubgoals() override;
 	TSubgoal whatToDoToAchieve() override;
+	virtual bool operator==(const Conquer & other) const override;
 };
 
 class DLL_EXPORT Build : public CGoal<Build>
@@ -279,6 +324,33 @@ public:
 	TGoalVec getAllPossibleSubgoals() override;
 	TSubgoal whatToDoToAchieve() override;
 	bool fulfillsMe(TSubgoal goal) override;
+
+	virtual bool operator==(const Build & other) const override
+	{
+		return true;
+	}
+};
+
+class DLL_EXPORT BuildBoat : public CGoal<BuildBoat>
+{
+private:
+	const IShipyard * shipyard;
+
+public:
+	BuildBoat(const IShipyard * shipyard)
+		: CGoal(Goals::BUILD_BOAT), shipyard(shipyard)
+	{
+		priority = 0;
+	}
+	TGoalVec getAllPossibleSubgoals() override
+	{
+		return TGoalVec();
+	}
+	TSubgoal whatToDoToAchieve() override;
+	void accept(VCAI * ai) override;
+	std::string name() const override;
+	std::string completeMessage() const override;
+	virtual bool operator==(const BuildBoat & other) const override;
 };
 
 class DLL_EXPORT Explore : public CGoal<Explore>
@@ -299,6 +371,7 @@ public:
 	TSubgoal whatToDoToAchieve() override;
 	std::string completeMessage() const override;
 	bool fulfillsMe(TSubgoal goal) override;
+	virtual bool operator==(const Explore & other) const override;
 };
 
 class DLL_EXPORT GatherArmy : public CGoal<GatherArmy>
@@ -317,6 +390,7 @@ public:
 	TGoalVec getAllPossibleSubgoals() override;
 	TSubgoal whatToDoToAchieve() override;
 	std::string completeMessage() const override;
+	virtual bool operator==(const GatherArmy & other) const override;
 };
 
 class DLL_EXPORT BuyArmy : public CGoal<BuyArmy>
@@ -333,11 +407,11 @@ public:
 		value = val; //expressed in AI unit strength
 		priority = 3;//TODO: evaluate?
 	}
-	bool operator==(AbstractGoal & g) override;
 	bool fulfillsMe(TSubgoal goal) override;
 
 	TSubgoal whatToDoToAchieve() override;
 	std::string completeMessage() const override;
+	virtual bool operator==(const BuyArmy & other) const override;
 };
 
 class DLL_EXPORT BoostHero : public CGoal<BoostHero>
@@ -352,6 +426,7 @@ public:
 	{
 		return TGoalVec();
 	}
+	virtual bool operator==(const BoostHero & other) const override;
 	//TSubgoal whatToDoToAchieve() override {return sptr(Invalid());};
 };
 
@@ -363,12 +438,18 @@ public:
 	{
 		priority = 1;
 	}
+
 	TGoalVec getAllPossibleSubgoals() override
 	{
 		return TGoalVec();
 	}
+
 	TSubgoal whatToDoToAchieve() override;
-	bool operator==(AbstractGoal & g) override;
+
+	virtual bool operator==(const RecruitHero & other) const override
+	{
+		return true;
+	}
 };
 
 class DLL_EXPORT BuildThis : public CGoal<BuildThis>
@@ -396,6 +477,7 @@ public:
 	}
 	TSubgoal whatToDoToAchieve() override;
 	//bool fulfillsMe(TSubgoal goal) override;
+	virtual bool operator==(const BuildThis & other) const override;
 };
 
 class DLL_EXPORT CollectRes : public CGoal<CollectRes>
@@ -416,7 +498,7 @@ public:
 	TSubgoal whatToDoToAchieve() override;
 	TSubgoal whatToDoToTrade();
 	bool fulfillsMe(TSubgoal goal) override; //TODO: Trade
-	bool operator==(AbstractGoal & g) override;
+	virtual bool operator==(const CollectRes & other) const override;
 };
 
 class DLL_EXPORT Trade : public CGoal<Trade>
@@ -435,7 +517,7 @@ public:
 		priority = 3; //trading is instant, but picking resources is free
 	}
 	TSubgoal whatToDoToAchieve() override;
-	bool operator==(AbstractGoal & g) override;
+	virtual bool operator==(const Trade & other) const override;
 };
 
 class DLL_EXPORT GatherTroops : public CGoal<GatherTroops>
@@ -459,6 +541,7 @@ public:
 	}
 	TSubgoal whatToDoToAchieve() override;
 	bool fulfillsMe(TSubgoal goal) override;
+	virtual bool operator==(const GatherTroops & other) const override;
 };
 
 class DLL_EXPORT VisitObj : public CGoal<VisitObj> //this goal was previously known as GetObj
@@ -469,9 +552,9 @@ public:
 
 	TGoalVec getAllPossibleSubgoals() override;
 	TSubgoal whatToDoToAchieve() override;
-	bool operator==(AbstractGoal & g) override;
 	bool fulfillsMe(TSubgoal goal) override;
 	std::string completeMessage() const override;
+	virtual bool operator==(const VisitObj & other) const override;
 };
 
 class DLL_EXPORT FindObj : public CGoal<FindObj>
@@ -499,6 +582,7 @@ public:
 	}
 	TSubgoal whatToDoToAchieve() override;
 	bool fulfillsMe(TSubgoal goal) override;
+	virtual bool operator==(const FindObj & other) const override;
 };
 
 class DLL_EXPORT VisitHero : public CGoal<VisitHero>
@@ -519,9 +603,9 @@ public:
 		return TGoalVec();
 	}
 	TSubgoal whatToDoToAchieve() override;
-	bool operator==(AbstractGoal & g) override;
 	bool fulfillsMe(TSubgoal goal) override;
 	std::string completeMessage() const override;
+	virtual bool operator==(const VisitHero & other) const override;
 };
 
 class DLL_EXPORT GetArtOfType : public CGoal<GetArtOfType>
@@ -542,6 +626,7 @@ public:
 		return TGoalVec();
 	}
 	TSubgoal whatToDoToAchieve() override;
+	virtual bool operator==(const GetArtOfType & other) const override;
 };
 
 class DLL_EXPORT VisitTile : public CGoal<VisitTile>
@@ -558,8 +643,8 @@ public:
 	}
 	TGoalVec getAllPossibleSubgoals() override;
 	TSubgoal whatToDoToAchieve() override;
-	bool operator==(AbstractGoal & g) override;
 	std::string completeMessage() const override;
+	virtual bool operator==(const VisitTile & other) const override;
 };
 
 class DLL_EXPORT ClearWayTo : public CGoal<ClearWayTo>
@@ -584,8 +669,8 @@ public:
 	}
 	TGoalVec getAllPossibleSubgoals() override;
 	TSubgoal whatToDoToAchieve() override;
-	bool operator==(AbstractGoal & g) override;
 	bool fulfillsMe(TSubgoal goal) override;
+	virtual bool operator==(const ClearWayTo & other) const override;
 };
 
 class DLL_EXPORT DigAtTile : public CGoal<DigAtTile>
@@ -607,28 +692,7 @@ public:
 		return TGoalVec();
 	}
 	TSubgoal whatToDoToAchieve() override;
-	bool operator==(AbstractGoal & g) override;
-};
-
-class DLL_EXPORT CIssueCommand : public CGoal<CIssueCommand>
-{
-	std::function<bool()> command;
-
-public:
-	CIssueCommand()
-		: CGoal(ISSUE_COMMAND)
-	{
-	}
-	CIssueCommand(std::function<bool()> _command)
-		: CGoal(ISSUE_COMMAND), command(_command)
-	{
-		priority = 1e10;
-	}
-	TGoalVec getAllPossibleSubgoals() override
-	{
-		return TGoalVec();
-	}
-	//TSubgoal whatToDoToAchieve() override {return sptr(Invalid());}
+	virtual bool operator==(const DigAtTile & other) const override;
 };
 
 }

+ 36 - 9
AI/VCAI/Pathfinding/AINodeStorage.cpp

@@ -38,15 +38,34 @@ bool AINodeStorage::isBattleNode(const CGPathNode * node) const
 	return (getAINode(node)->chainMask & BATTLE_CHAIN) > 0;
 }
 
-AIPathNode * AINodeStorage::getNode(const int3 & coord, const EPathfindingLayer layer, int chainNumber)
+boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(const int3 & pos, const EPathfindingLayer layer, int chainNumber)
 {
-	return &nodes[coord.x][coord.y][coord.z][layer][chainNumber];
+	auto chains = nodes[pos.x][pos.y][pos.z][layer];
+
+	for(AIPathNode & node : chains)
+	{
+		if(node.chainMask == chainNumber)
+		{
+			return &node;
+		}
+
+		if(node.chainMask == 0)
+		{
+			node.chainMask = chainNumber;
+
+			return &node;
+		}
+	}
+
+	return boost::none;
 }
 
 CGPathNode * AINodeStorage::getInitialNode()
 {
 	auto hpos = hero->getPosition(false);
-	auto initialNode = getNode(hpos, hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND, 0);
+	auto initialNode = 
+		getOrCreateNode(hpos, hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND, NORMAL_CHAIN)
+		.get();
 
 	initialNode->turns = 0;
 	initialNode->moveRemains = hero->movement;
@@ -61,7 +80,9 @@ void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPat
 	{
 		AIPathNode & heroNode = nodes[coord.x][coord.y][coord.z][layer][i];
 
-		heroNode.chainMask = i;
+		heroNode.chainMask = 0;
+		heroNode.danger = 0;
+		heroNode.specialAction.reset();
 		heroNode.update(coord, layer, accessibility);
 	}
 }
@@ -92,12 +113,12 @@ std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
 	{
 		for(EPathfindingLayer i = EPathfindingLayer::LAND; i <= EPathfindingLayer::AIR; i.advance(1))
 		{
-			auto nextNode = getNode(neighbour, i, srcNode->chainMask);
+			auto nextNode = getOrCreateNode(neighbour, i, srcNode->chainMask);
 
-			if(nextNode->accessible == CGPathNode::NOT_SET)
+			if(!nextNode || nextNode.get()->accessible == CGPathNode::NOT_SET)
 				continue;
 
-			neighbours.push_back(nextNode);
+			neighbours.push_back(nextNode.get());
 		}
 	}
 
@@ -115,9 +136,12 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
 
 	for(auto & neighbour : accessibleExits)
 	{
-		auto node = getNode(neighbour, source.node->layer, srcNode->chainMask);
+		auto node = getOrCreateNode(neighbour, source.node->layer, srcNode->chainMask);
 
-		neighbours.push_back(node);
+		if(!node)
+			continue;
+
+		neighbours.push_back(node.get());
 	}
 
 	return neighbours;
@@ -183,8 +207,11 @@ std::vector<AIPath> AINodeStorage::getChainInfo(int3 pos) const
 			pathNode.coord = current->coord;
 
 			path.nodes.push_back(pathNode);
+			path.specialAction = current->specialAction;
+
 			current = getAINode(current->theNodeBefore);
 		}
+
 		paths.push_back(path);
 	}
 

+ 13 - 6
AI/VCAI/Pathfinding/AINodeStorage.h

@@ -13,17 +13,19 @@
 #include "../../../lib/CPathfinder.h"
 #include "../../../lib/mapObjects/CGHeroInstance.h"
 #include "../AIUtility.h"
+#include "../Goals.h"
 
-class IVirtualObject
+class ISpecialAction
 {
 public:
-	virtual void materialize();
+	virtual Goals::TSubgoal whatToDo(HeroPtr hero) const = 0;
 };
 
 struct AIPathNode : public CGPathNode
 {
 	uint32_t chainMask;
 	uint64_t danger;
+	std::shared_ptr<const ISpecialAction> specialAction;
 };
 
 struct AIPathNodeInfo
@@ -38,6 +40,7 @@ struct AIPathNodeInfo
 struct AIPath
 {
 	std::vector<AIPathNodeInfo> nodes;
+	std::shared_ptr<const ISpecialAction> specialAction;
 
 	AIPath();
 
@@ -63,9 +66,13 @@ private:
 
 public:
 	/// more than 1 chain layer allows us to have more than 1 path to each tile so we can chose more optimal one.
-	static const int NUM_CHAINS = 2;
-	static const int NORMAL_CHAIN = 0;
-	static const int BATTLE_CHAIN = 1;
+	static const int NUM_CHAINS = 3;
+
+	// chain flags, can be combined
+	static const int NORMAL_CHAIN = 1;
+	static const int BATTLE_CHAIN = 2;
+	static const int CAST_CHAIN = 4;
+	static const int RESOURCE_CHAIN = 8;
 
 	AINodeStorage(const int3 & sizes);
 	~AINodeStorage();
@@ -90,7 +97,7 @@ public:
 
 	bool isBattleNode(const CGPathNode * node) const;
 	bool hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const;
-	AIPathNode * getNode(const int3 & coord, const EPathfindingLayer layer, int chainNumber);
+	boost::optional<AIPathNode *> getOrCreateNode(const int3 & coord, const EPathfindingLayer layer, int chainNumber);
 	std::vector<AIPath> getChainInfo(int3 pos) const;
 
 	void setHero(HeroPtr heroPtr)

+ 3 - 3
AI/VCAI/Pathfinding/AIPathfinder.cpp

@@ -16,8 +16,8 @@ std::vector<std::shared_ptr<AINodeStorage>> AIPathfinder::storagePool;
 std::map<HeroPtr, std::shared_ptr<AINodeStorage>> AIPathfinder::storageMap;
 boost::mutex AIPathfinder::storageMutex;
 
-AIPathfinder::AIPathfinder(CPlayerSpecificInfoCallback * cb)
-	:cb(cb)
+AIPathfinder::AIPathfinder(CPlayerSpecificInfoCallback * cb, VCAI * ai)
+	:cb(cb), ai(ai)
 {
 }
 
@@ -48,7 +48,7 @@ std::vector<AIPath> AIPathfinder::getPathInfo(HeroPtr hero, int3 tile)
 
 		storageMap[hero] = nodeStorage;
 		
-		auto config = std::make_shared<AIPathfinderConfig>(cb, nodeStorage);
+		auto config = std::make_shared<AIPathfinderConfig>(cb, ai, nodeStorage);
 
 		nodeStorage->setHero(hero.get());
 		cb->calculatePaths(config, hero.get());

+ 4 - 2
AI/VCAI/Pathfinding/AIPathfinder.h

@@ -10,8 +10,9 @@
 
 #pragma once
 
-#include "../AIUtility.h"
 #include "AINodeStorage.h"
+#include "../AIUtility.h"
+#include "../VCAI.h"
 
 class AIPathfinder
 {
@@ -20,9 +21,10 @@ private:
 	static std::map<HeroPtr, std::shared_ptr<AINodeStorage>> storageMap;
 	static boost::mutex storageMutex;
 	CPlayerSpecificInfoCallback * cb;
+	VCAI * ai;
 
 public:
-	AIPathfinder(CPlayerSpecificInfoCallback * cb);
+	AIPathfinder(CPlayerSpecificInfoCallback * cb, VCAI * ai);
 	std::vector<AIPath> getPathInfo(HeroPtr hero, int3 tile);
 	void clear();
 };

+ 155 - 4
AI/VCAI/Pathfinding/AIPathfinderConfig.cpp

@@ -10,6 +10,119 @@
 #include "StdInc.h"
 #include "AIPathfinderConfig.h"
 #include "../../../CCallback.h"
+#include "../../../lib/mapping/CMap.h"
+#include "../../../lib/mapObjects/MapObjects.h"
+
+class BuildBoatAction : public ISpecialAction
+{
+private:
+	const IShipyard * shipyard;
+
+public:
+	BuildBoatAction(const IShipyard * shipyard)
+		:shipyard(shipyard)
+	{
+	}
+
+	virtual Goals::TSubgoal whatToDo(HeroPtr hero) const override
+	{
+		return sptr(Goals::BuildBoat(shipyard));
+	}
+};
+
+class AILayerTransitionRule : public LayerTransitionRule
+{
+private:
+	CPlayerSpecificInfoCallback * cb;
+	VCAI * ai;
+	std::map<int3, std::shared_ptr<const BuildBoatAction>> virtualBoats;
+	std::shared_ptr<AINodeStorage> nodeStorage;
+
+public:
+	AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, VCAI * ai, std::shared_ptr<AINodeStorage> nodeStorage)
+		:cb(cb), ai(ai), nodeStorage(nodeStorage)
+	{
+		setup();
+	}
+
+	virtual void process(
+		const PathNodeInfo & source,
+		CDestinationNodeInfo & destination,
+		const PathfinderConfig * pathfinderConfig,
+		CPathfinderHelper * pathfinderHelper) const override
+	{
+		LayerTransitionRule::process(source, destination, pathfinderConfig, pathfinderHelper);
+
+		if(!destination.blocked)
+		{
+			return;
+		}
+
+		if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::SAIL
+			&& vstd::contains(virtualBoats, destination.coord))
+		{
+			logAi->trace("Bypassing virtual boat at %s!", destination.coord.toString());
+			
+			nodeStorage->updateAINode(destination.node, [&](AIPathNode * node)
+			{
+				std::shared_ptr<const BuildBoatAction> virtualBoat = virtualBoats.at(destination.coord);
+
+				auto boatNodeOptional = nodeStorage->getOrCreateNode(
+					node->coord,
+					node->layer,
+					node->chainMask | AINodeStorage::RESOURCE_CHAIN);
+
+				if(boatNodeOptional)
+				{
+					AIPathNode * boatNode = boatNodeOptional.get();
+
+					boatNode->specialAction = virtualBoat;
+					destination.blocked = false;
+					destination.action = CGPathNode::ENodeAction::EMBARK;
+					destination.node = boatNode;
+				}
+				else
+				{
+					logAi->trace(
+						"Can not allocate boat node while moving %s -> %s",
+						source.coord.toString(),
+						destination.coord.toString());
+				}
+			});
+		}
+	}
+
+private:
+	void setup()
+	{
+		std::vector<const IShipyard *> shipyards;
+
+		for(const CGTownInstance * t : cb->getTownsInfo())
+		{
+			if(t->hasBuilt(BuildingID::SHIPYARD))
+				shipyards.push_back(t);
+		}
+
+		for(const CGObjectInstance * obj : ai->visitableObjs)
+		{
+			if(obj->ID != Obj::TOWN) //towns were handled in the previous loop
+			{
+				if(const IShipyard * shipyard = IShipyard::castFrom(obj))
+					shipyards.push_back(shipyard);
+			}
+		}
+
+		for(const IShipyard * shipyard : shipyards)
+		{
+			if(shipyard->shipyardStatus() == IShipyard::GOOD)
+			{
+				int3 boatLocation = shipyard->bestLocation();
+				virtualBoats[boatLocation] = std::make_shared<BuildBoatAction>(shipyard);
+				logAi->debug("Virtual boat added at %s", boatLocation.toString());
+			}
+		}
+	}
+};
 
 class AIMovementAfterDestinationRule : public MovementAfterDestinationRule
 {
@@ -88,8 +201,25 @@ public:
 				return;
 			}
 
-			auto destNode = nodeStorage->getAINode(destination.node);
-			auto battleNode = nodeStorage->getNode(destination.coord, destination.node->layer, destNode->chainMask | AINodeStorage::BATTLE_CHAIN);
+			const AIPathNode * destNode = nodeStorage->getAINode(destination.node);
+			auto battleNodeOptional = nodeStorage->getOrCreateNode(
+				destination.coord, 
+				destination.node->layer, 
+				destNode->chainMask | AINodeStorage::BATTLE_CHAIN);
+
+			if(!battleNodeOptional)
+			{
+				logAi->trace(
+					"Can not allocate battle node while moving %s -> %s",
+					source.coord.toString(),
+					destination.coord.toString());
+
+				destination.blocked = true;
+
+				return;
+			}
+
+			AIPathNode *  battleNode = battleNodeOptional.get();
 
 			if(battleNode->locked)
 			{
@@ -150,6 +280,13 @@ public:
 		if(blocker == BlockingReason::NONE)
 			return;
 
+		if(blocker == BlockingReason::DESTINATION_BLOCKED
+			&& destination.action == CGPathNode::EMBARK
+			&& nodeStorage->getAINode(destination.node)->specialAction)
+		{
+			return;
+		}
+		
 		if(blocker == BlockingReason::SOURCE_GUARDED && nodeStorage->isBattleNode(source.node))
 		{
 			auto srcGuardians = cb->getGuardingCreatures(source.coord);
@@ -216,6 +353,8 @@ public:
 					"Link src node %s to destination node %s while bypassing guard",
 					source.coord.toString(),
 					destination.coord.toString());
+
+				return;
 			}
 		}
 
@@ -228,16 +367,27 @@ public:
 				"Link src node %s to destination node %s while bypassing visitable obj",
 				source.coord.toString(),
 				destination.coord.toString());
+
+			return;
+		}
+
+		auto aiSourceNode = nodeStorage->getAINode(source.node);
+
+		if(aiSourceNode->specialAction)
+		{
+			// there is some action on source tile which should be performed before we can bypass it
+			destination.node->theNodeBefore = source.node;
 		}
 	}
 };
 
 std::vector<std::shared_ptr<IPathfindingRule>> makeRuleset(
 	CPlayerSpecificInfoCallback * cb,
+	VCAI * ai,
 	std::shared_ptr<AINodeStorage> nodeStorage)
 {
 	std::vector<std::shared_ptr<IPathfindingRule>> rules = {
-		std::make_shared<LayerTransitionRule>(),
+		std::make_shared<AILayerTransitionRule>(cb, ai, nodeStorage),
 		std::make_shared<DestinationActionRule>(),
 		std::make_shared<AIMovementToDestinationRule>(cb, nodeStorage),
 		std::make_shared<MovementCostRule>(),
@@ -250,7 +400,8 @@ std::vector<std::shared_ptr<IPathfindingRule>> makeRuleset(
 
 AIPathfinderConfig::AIPathfinderConfig(
 	CPlayerSpecificInfoCallback * cb,
+	VCAI * ai,
 	std::shared_ptr<AINodeStorage> nodeStorage)
-	:PathfinderConfig(nodeStorage, makeRuleset(cb, nodeStorage))
+	:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage))
 {
 }

+ 5 - 1
AI/VCAI/Pathfinding/AIPathfinderConfig.h

@@ -11,9 +11,13 @@
 #pragma once
 
 #include "AINodeStorage.h"
+#include "../VCAI.h"
 
 class AIPathfinderConfig : public PathfinderConfig
 {
 public:
-	AIPathfinderConfig(CPlayerSpecificInfoCallback * cb, std::shared_ptr<AINodeStorage> nodeStorage);
+	AIPathfinderConfig(
+		CPlayerSpecificInfoCallback * cb,
+		VCAI * ai, 
+		std::shared_ptr<AINodeStorage> nodeStorage);
 };

+ 36 - 7
AI/VCAI/Pathfinding/PathfindingManager.cpp

@@ -19,10 +19,10 @@ PathfindingManager::PathfindingManager(CPlayerSpecificInfoCallback * CB, VCAI *
 {
 }
 
-void PathfindingManager::setCB(CPlayerSpecificInfoCallback * CB)
+void PathfindingManager::init(CPlayerSpecificInfoCallback * CB)
 {
 	cb = CB;
-	pathfinder.reset(new AIPathfinder(cb));
+	pathfinder.reset(new AIPathfinder(cb, ai));
 }
 
 void PathfindingManager::setAI(VCAI * AI)
@@ -60,10 +60,17 @@ Goals::TGoalVec PathfindingManager::howToVisitObj(ObjectIdRef obj)
 
 Goals::TGoalVec PathfindingManager::howToVisitTile(HeroPtr hero, int3 tile, bool allowGatherArmy)
 {
-	return findPath(hero, tile, allowGatherArmy, [&](int3 firstTileToGet) -> Goals::TSubgoal
+	auto result = findPath(hero, tile, allowGatherArmy, [&](int3 firstTileToGet) -> Goals::TSubgoal
 	{
 		return sptr(Goals::VisitTile(firstTileToGet).sethero(hero).setisAbstract(true));
 	});
+
+	for(Goals::TSubgoal solution : result)
+	{
+		solution->setparent(sptr(Goals::VisitTile(tile).sethero(hero).setevaluationContext(solution->evaluationContext)));
+	}
+
+	return result;
 }
 
 Goals::TGoalVec PathfindingManager::howToVisitObj(HeroPtr hero, ObjectIdRef obj, bool allowGatherArmy)
@@ -75,13 +82,20 @@ Goals::TGoalVec PathfindingManager::howToVisitObj(HeroPtr hero, ObjectIdRef obj,
 
 	int3 dest = obj->visitablePos();
 
-	return findPath(hero, dest, allowGatherArmy, [&](int3 firstTileToGet) -> Goals::TSubgoal
+	auto result = findPath(hero, dest, allowGatherArmy, [&](int3 firstTileToGet) -> Goals::TSubgoal
 	{
 		if(obj->ID.num == Obj::HERO && obj->getOwner() == hero->getOwner())
 			return sptr(Goals::VisitHero(obj->id.getNum()).sethero(hero).setisAbstract(true));
 		else
 			return sptr(Goals::VisitObj(obj->id.getNum()).sethero(hero).setisAbstract(true));
 	});
+
+	for(Goals::TSubgoal solution : result)
+	{
+		solution->setparent(sptr(Goals::VisitObj(obj->id.getNum()).sethero(hero).setevaluationContext(solution->evaluationContext)));
+	}
+
+	return result;
 }
 
 std::vector<AIPath> PathfindingManager::getPathsToTile(HeroPtr hero, int3 tile)
@@ -117,9 +131,24 @@ Goals::TGoalVec PathfindingManager::findPath(
 			{
 				logAi->trace("It's safe for %s to visit tile %s with danger %s", hero->name, dest.toString(), std::to_string(danger));
 
-				auto solution = dest == firstTileToGet
-					? doVisitTile(firstTileToGet)
-					: clearWayTo(hero, firstTileToGet);
+				Goals::TSubgoal solution;
+
+				if(path.specialAction)
+				{
+					solution = path.specialAction->whatToDo(hero);
+				}
+				else
+				{
+					solution = dest == firstTileToGet
+						? doVisitTile(firstTileToGet)
+						: clearWayTo(hero, firstTileToGet);
+				}
+
+				if(solution->evaluationContext.danger < danger)
+					solution->evaluationContext.danger = danger;
+				
+				solution->evaluationContext.movementCost += path.movementCost();
+
 				result.push_back(solution);
 
 				continue;

+ 2 - 2
AI/VCAI/Pathfinding/PathfindingManager.h

@@ -17,7 +17,7 @@ class IPathfindingManager
 {
 public:
 	virtual ~IPathfindingManager() = default;
-	virtual void setCB(CPlayerSpecificInfoCallback * CB) = 0;
+	virtual void init(CPlayerSpecificInfoCallback * CB) = 0;
 	virtual void setAI(VCAI * AI) = 0;
 
 	virtual void resetPaths() = 0;
@@ -49,7 +49,7 @@ public:
 	void resetPaths() override;
 
 private:
-	void setCB(CPlayerSpecificInfoCallback * CB) override;
+	void init(CPlayerSpecificInfoCallback * CB) override;
 	void setAI(VCAI * AI) override;
 
 	Goals::TGoalVec findPath(

+ 1 - 1
AI/VCAI/ResourceManager.cpp

@@ -30,7 +30,7 @@ ResourceManager::ResourceManager(CPlayerSpecificInfoCallback * CB, VCAI * AI)
 {
 }
 
-void ResourceManager::setCB(CPlayerSpecificInfoCallback * CB)
+void ResourceManager::init(CPlayerSpecificInfoCallback * CB)
 {
 	cb = CB;
 }

+ 2 - 2
AI/VCAI/ResourceManager.h

@@ -40,7 +40,7 @@ class IResourceManager //: public: IAbstractManager
 {
 public:
 	virtual ~IResourceManager() = default;
-	virtual void setCB(CPlayerSpecificInfoCallback * CB) = 0;
+	virtual void init(CPlayerSpecificInfoCallback * CB) = 0;
 	virtual void setAI(VCAI * AI) = 0;
 
 	virtual TResources reservedResources() const = 0;
@@ -94,7 +94,7 @@ protected: //not-const actions only for AI
 	virtual TResources estimateIncome() const;
 	virtual Goals::TSubgoal collectResourcesForOurGoal(ResourceObjective &o) const;
 
-	void setCB(CPlayerSpecificInfoCallback * CB) override;
+	void init(CPlayerSpecificInfoCallback * CB) override;
 	void setAI(VCAI * AI) override;
 
 private:

+ 10 - 1
AI/VCAI/VCAI.cpp

@@ -613,7 +613,7 @@ void VCAI::init(std::shared_ptr<CCallback> CB)
 	myCb = CB;
 	cbc = CB;
 
-	ah->setCB(CB.get());
+	ah->init(CB.get());
 
 	NET_EVENT_HANDLER; //sets ah->rm->cb
 	playerID = *myCb->getMyColor();
@@ -907,6 +907,14 @@ void VCAI::mainLoop()
 					completeGoal(e.goal); //put in goalsToRemove
 					break;
 				}
+				catch(cannotFulfillGoalException & e)
+				{
+					//it is impossible to continue some goals (like exploration, for example)
+					//complete abstract goal for now, but maybe main goal finds another path
+					goalsToRemove.push_back(basicGoal);
+					logAi->debug("Goal %s decomposition failed: %s", goalToDecompose->name(), e.what());
+					break;
+				}
 				catch (std::exception & e) //decomposition failed, which means we can't decompose entire tree
 				{
 					goalsToRemove.push_back(basicGoal);
@@ -2397,6 +2405,7 @@ Goals::TSubgoal VCAI::decomposeGoal(Goals::TSubgoal ultimateGoal)
 	while (maxGoals)
 	{
 		boost::this_thread::interruption_point();
+
 		goal = goal->whatToDoToAchieve(); //may throw if decomposition fails
 		--maxGoals;
 		if (goal == ultimateGoal) //compare objects by value

+ 1 - 0
lib/CPathfinder.cpp

@@ -696,6 +696,7 @@ void DestinationActionRule::process(
 {
 	if(destination.action != CGPathNode::ENodeAction::UNKNOWN)
 	{
+		logAi->trace("Accepted precalculated action at %s", destination.coord.toString());
 		return;
 	}