2
0
Эх сурвалжийг харах

- Fixed heroes stalling. Locked heroes now try to decompose their goals exhaustively, which FINALLY works as intended.
- Fixed multiple issues with ClearWayTo goal.

DjWarmonger 11 жил өмнө
parent
commit
e5b011abe0

+ 2 - 2
AI/VCAI/AIUtility.cpp

@@ -214,7 +214,6 @@ bool canBeEmbarkmentPoint(const TerrainTile *t)
 int3 whereToExplore(HeroPtr h)
 {
 	TimeCheck tc ("where to explore");
-	//TODO it's stupid and ineffective, write sth better
 	cb->setSelection(*h);
 	int radius = h->getSightRadious();
 	int3 hpos = h->visitablePos();
@@ -242,12 +241,13 @@ int3 whereToExplore(HeroPtr h)
 	if(nearbyVisitableObjs.size())
 		return nearbyVisitableObjs.back()->visitablePos();
 
-	try
+	try //check if nearby tiles allow us to reveal anything - this is quick
 	{
 		return ai->explorationBestNeighbour(hpos, radius, h);
 	}
 	catch(cannotFulfillGoalException &e)
 	{
+		//perform exhaustive search
 		return ai->explorationNewPoint(radius, h);
 	}
 }

+ 2 - 2
AI/VCAI/Fuzzy.cpp

@@ -459,6 +459,6 @@ float FuzzyHelper::evaluate (Goals::Invalid & g)
 }
 float FuzzyHelper::evaluate (Goals::AbstractGoal & g)
 {
-	logAi->debugStream() << boost::format("Cannot evaluate goal %s") % g.name();
-	return -1e10;
+	logAi->warnStream() << boost::format("Cannot evaluate goal %s") % g.name();
+	return g.priority;
 }

+ 41 - 7
AI/VCAI/Goals.cpp

@@ -62,13 +62,20 @@ std::string Goals::AbstractGoal::name() const //TODO: virtualize
 			desc = "GATHER TROOPS";
 			break;
 		case GET_OBJ:
-			desc = "GET OBJ " + cb->getObjInstance(ObjectInstanceID(objid))->getHoverText();
-			break;
+		{
+			auto obj = cb->getObjInstance(ObjectInstanceID(objid));
+			if (obj)
+				desc = "GET OBJ " + obj->getHoverText();
+		}
 		case FIND_OBJ:
 			desc = "FIND OBJ " + boost::lexical_cast<std::string>(objid);
 			break;
 		case VISIT_HERO:
-			desc = "VISIT HERO " + cb->getObjInstance(ObjectInstanceID(objid))->getHoverText();
+		{
+			auto obj = cb->getObjInstance(ObjectInstanceID(objid));
+			if (obj)
+				desc = "VISIT HERO " + obj->getHoverText();
+		}
 			break;
 		case GET_ART_TYPE:
 			desc = "GET ARTIFACT OF TYPE " + VLC->arth->artifacts[aid]->Name();
@@ -337,6 +344,10 @@ TGoalVec ClearWayTo::getAllPossibleSubgoals()
 	TGoalVec ret;
 	for (auto h : cb->getHeroesInfo())
 	{
+		if ((hero && hero->visitablePos() == tile && hero == *h) || //we can't free the way ourselves
+			h->visitablePos() == tile) //we are already on that tile! what does it mean?
+			continue;
+
 		cb->setSelection(h);
 
 		SectorMap sm;
@@ -353,7 +364,8 @@ TGoalVec ClearWayTo::getAllPossibleSubgoals()
 		if(topObj)
 		{
 			if (topObj->ID == Obj::HERO && cb->getPlayerRelations(h->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES)
-				logAi->errorStream() << boost::format("%s stands in the way of %s") % topObj->getHoverText()  % h->getHoverText();
+				if (topObj != hero.get(true)) //the hero we wnat to free
+					logAi->errorStream() << boost::format("%s stands in the way of %s") % topObj->getHoverText()  % h->getHoverText();
 			if (topObj->ID == Obj::QUEST_GUARD || topObj->ID == Obj::BORDERGUARD)
 			{
 				if (shouldVisit(h, topObj))
@@ -465,11 +477,27 @@ TGoalVec Explore::getAllPossibleSubgoals()
 		}
 	}
 	if (ret.empty())
-		throw cannotFulfillGoalException("Cannot explore - no possible ways found!");
+	{
+		throw goalFulfilledException (sptr(*this));
+	}
+	//throw cannotFulfillGoalException("Cannot explore - no possible ways found!");
 
 	return ret;
 };
 
+bool Explore::fulfillsMe (TSubgoal goal)
+{
+	if (goal->goalType == Goals::EXPLORE)
+	{
+		if (goal->hero)
+			return hero == goal->hero;
+		else
+			return true; //cancel ALL exploration
+	}
+	return false;
+}
+
+
 TSubgoal RecruitHero::whatToDoToAchieve()
 {
 	const CGTownInstance *t = ai->findTownWithTavern();
@@ -493,7 +521,7 @@ TSubgoal VisitTile::whatToDoToAchieve()
 
 	if (ret->hero)
 	{
-		if (isSafeToVisit(ret->hero, tile))
+		if (isSafeToVisit(ret->hero, tile) && ai->isAccessibleForHero(tile, ret->hero))
 		{
 			ret->setisElementar(true);
 			return ret;
@@ -529,7 +557,13 @@ TGoalVec VisitTile::getAllPossibleSubgoals()
 			ret.push_back (sptr(Goals::RecruitHero()));
 	}
 	if (ret.empty())
-		ret.push_back (sptr(Goals::ClearWayTo(tile)));
+	{
+		auto obj = frontOrNull(cb->getVisitableObjs(tile));
+		if (obj && obj->ID == Obj::HERO && obj->tempOwner == ai->playerID) //our own hero stands on that tile
+			ret.push_back (sptr(Goals::VisitTile(tile).sethero(dynamic_cast<const CGHeroInstance *>(obj)).setisElementar(true)));
+		else
+			ret.push_back (sptr(Goals::ClearWayTo(tile)));
+	}
 
 	//important - at least one sub-goal must handle case which is impossible to fulfill (unreachable tile)
 	return ret;

+ 1 - 0
AI/VCAI/Goals.h

@@ -221,6 +221,7 @@ class Explore : public CGoal<Explore>
 	TGoalVec getAllPossibleSubgoals() override;
 	TSubgoal whatToDoToAchieve() override;
 	std::string completeMessage() const override;
+	bool fulfillsMe (TSubgoal goal) override;
 };
 class GatherArmy : public CGoal<GatherArmy>
 {

+ 97 - 51
AI/VCAI/VCAI.cpp

@@ -525,9 +525,6 @@ void VCAI::init(shared_ptr<CCallback> CB)
 		fh = new FuzzyHelper();
 
 	retreiveVisitableObjs(visitableObjs);
-	//for (auto h : myCb->getHeroesInfo()) //make sure heroes won't try to revisit town in first move
-	//	if (h->visitedTown)
-	//		markObjectVisited(h->visitedTown);
 }
 
 void VCAI::yourTurn()
@@ -704,27 +701,51 @@ void VCAI::makeTurnInternal()
 
 		//finally, continue our abstract long-term goals
 
-		//heroes tend to die in the process and loose their goals, unsafe to iterate it
-		std::vector<std::pair<HeroPtr, Goals::TSubgoal> > safeCopy;
-		boost::copy(lockedHeroes, std::back_inserter(safeCopy));
-
-		typedef std::pair<HeroPtr, Goals::TSubgoal> TItrType;
-
-		auto lockedHeroesSorter = [](TItrType h1, TItrType h2) -> bool
-		{
-			return compareMovement (h1.first, h2.first);
-		};
-		boost::sort(safeCopy, lockedHeroesSorter);
-
-		while (safeCopy.size()) //continue our goals
+		//int i = 100;
+		int oldMovement = 0;
+		int newMovement = 0;
+		while (true)
 		{
-			auto it = safeCopy.begin();
-			if (it->first && it->first->tempOwner == playerID && vstd::contains(lockedHeroes, it->first)) //make sure hero still has his goal
+			//if (!i)
+			//{
+			//	logAi->warnStream() << "Locked heroes: exhaustive decomposition failed!";
+			//	break;
+			//	/*If we are here, it can mean two things:
+			//	1. W are striving to impossible goal (bug!)
+			//	2. Our strategy seems perfect and no move can bring improvement
+			//	*/
+			//}
+			//--i;
+			oldMovement = newMovement; //remember old value
+			newMovement = 0;
+			std::vector<std::pair<HeroPtr, Goals::TSubgoal> > safeCopy;
+			for (auto mission : lockedHeroes)
+			{
+				mission.second->accept(fh); //re-evaluate
+				if (canAct(mission.first))
+				{
+					newMovement += mission.first->movement;
+					safeCopy.push_back (mission);
+				}
+			}
+			if (newMovement == oldMovement) //means our heroes didn't move or didn't re-assign their goals
 			{
-				cb->setSelection(*it->first);
-				striveToGoal (it->second);
+				logAi->warnStream() << "Our heroes don't move anymore, exhaustive decomposition failed";
+			}
+				break;
+			if (safeCopy.empty())
+				break; //all heroes exhausted their locked goals
+			else
+			{
+				typedef std::pair<HeroPtr, Goals::TSubgoal> TItrType;
+
+				auto lockedHeroesSorter = [](TItrType m1, TItrType m2) -> bool
+				{
+					return m1.second->priority < m2.second->priority;
+				};
+				boost::sort(safeCopy, lockedHeroesSorter);
+				striveToGoal (safeCopy.back().second);
 			}
-			safeCopy.erase(it);
 		}
 
 		auto quests = myCb->getMyQuests();
@@ -734,6 +755,14 @@ void VCAI::makeTurnInternal()
 		}
 
 		striveToGoal(sptr(Goals::Build())); //TODO: smarter building management
+		performTypicalActions();
+
+		//for debug purpose
+		for (auto h : cb->getHeroesInfo())
+		{
+			if (h->movement)
+				logAi->warnStream() << boost::format("hero %s has %d MP left") % h->name % h->movement;
+		}	
 	}
 	catch(boost::thread_interrupted &e)
 	{
@@ -1544,10 +1573,8 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 		if(path.nodes.empty())
 		{
             logAi->errorStream() << "Hero " << h->name << " cannot reach " << dst;
-			//setGoal(h, INVALID);
-			completeGoal (sptr(Goals::VisitTile(dst).sethero(h))); //TODO: better mechanism to determine goal
 			cb->recalculatePaths();
-			throw std::runtime_error("Wrong move order!");
+			throw goalFulfilledException (sptr(Goals::VisitTile(dst).sethero(h)));
 		}
 
 		int i=path.nodes.size()-1;
@@ -1586,23 +1613,26 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 	}
 	if (auto visitedObject = frontOrNull(cb->getVisitableObjs(h->visitablePos()))) //we stand on something interesting
 	{
-		performObjectInteraction (visitedObject, h);
+		if (visitedObject != *h)
+			performObjectInteraction (visitedObject, h);
 		//BNLOG("Hero %s moved from %s to %s at %s", h->name % startHpos % visitedObject->hoverName % h->visitablePos());
 		//throw goalFulfilledException (CGoal(GET_OBJ).setobjid(visitedObject->id));
 	}
 	if(h) //we could have lost hero after last move
 	{
+		completeGoal (sptr(Goals::VisitTile(dst).sethero(h))); //we stepped on some tile, anyway
 		if (!ret) //reserve object we are heading towards
 		{
 			auto obj = frontOrNull(cb->getVisitableObjs(dst));
-			if (obj)
+			if (obj && obj != *h)
 				reserveObject(h, obj);
 		}
 
 		cb->recalculatePaths();
 		if (startHpos == h->visitablePos() && !ret) //we didn't move and didn't reach the target
 		{
-			throw cannotFulfillGoalException("Invalid path found!");
+			erase_if_present (lockedHeroes, h); //hero seemingly is confused
+			throw cannotFulfillGoalException("Invalid path found!"); //FIXME: should never happen
 		}
 	}
     logAi->debugStream() << boost::format("Hero %s moved from %s to %s. Returning %d.") % h->name % startHpos % h->visitablePos() % ret;
@@ -1684,7 +1714,7 @@ void VCAI::tryRealize(Goals::DigAtTile & g)
 	if (g.hero->diggingStatus() == CGHeroInstance::CAN_DIG)
 	{
 		cb->dig(g.hero.get());
-		setGoal(g.hero, sptr(Goals::Invalid())); // finished digging
+		completeGoal(sptr(g)); // finished digging
 	}
 	else
 	{
@@ -1730,7 +1760,20 @@ void VCAI::tryRealize(Goals::CollectRes & g)
 
 void VCAI::tryRealize(Goals::Build & g)
 {
-	performTypicalActions(); //TODO: separate build and wander
+	for(const CGTownInstance *t : cb->getTownsInfo())
+	{
+        logAi->debugStream() << boost::format("Looking into %s") % t->name;
+		buildStructure(t);
+		buildArmyIn(t);
+
+		if(!ai->primaryHero() ||
+			(t->getArmyStrength() > ai->primaryHero()->getArmyStrength() * 2 && !isAccessibleForHero(t->visitablePos(), ai->primaryHero())))
+		{
+			recruitHero(t);
+			buildArmyIn(t);
+		}
+	}
+
 	throw cannotFulfillGoalException("BUILD has been realized as much as possible.");
 }
 void VCAI::tryRealize(Goals::Invalid & g)
@@ -1756,17 +1799,31 @@ const CGTownInstance * VCAI::findTownWithTavern() const
 std::vector<HeroPtr> VCAI::getUnblockedHeroes() const
 {
 	std::vector<HeroPtr> ret;
-	boost::copy(cb->getHeroesInfo(), std::back_inserter(ret));
-
-	for(auto h : lockedHeroes)
+	for (auto h : cb->getHeroesInfo())
 	{
-		//if (!h.second.invalid()) //we can use heroes without valid goal
-		if (h.second->goalType == Goals::DIG_AT_TILE || !h.first->movement) //experiment: use all heroes that have movement left, TODO: unlock heroes that couldn't realize their goals 
-			erase_if_present(ret, h.first);
+		//&& !vstd::contains(lockedHeroes, h)
+		//at this point we assume heroes exhausted their locked goals
+		if (canAct(h))
+			ret.push_back(h);
 	}
 	return ret;
 }
 
+bool VCAI::canAct (HeroPtr h) const
+{
+	bool digsTile = false;
+	
+	auto mission = lockedHeroes.find(h);
+	if (mission != lockedHeroes.end())
+	{
+		//FIXME: I'm afraid there can be other conditions when heroes can act but not move :?
+		if (mission->second->goalType == Goals::DIG_AT_TILE && !mission->second->isElementar)
+			return false;
+	}
+
+	return h->movement;
+}
+
 HeroPtr VCAI::primaryHero() const
 {
 	auto hs = cb->getHeroesInfo();
@@ -1856,7 +1913,7 @@ Goals::TSubgoal VCAI::striveToGoalInternal(Goals::TSubgoal ultimateGoal, bool on
 				}
 				else
 				{
-					setGoal(goal->hero, sptr(Goals::Invalid())); // we seemingly don't know what to do with hero
+					erase_if_present (lockedHeroes, goal->hero); // we seemingly don't know what to do with hero
 				}
 			}
 
@@ -2031,20 +2088,6 @@ void VCAI::striveToQuest (const QuestInfo &q)
 
 void VCAI::performTypicalActions()
 {
-	for(const CGTownInstance *t : cb->getTownsInfo())
-	{
-        logAi->debugStream() << boost::format("Looking into %s") % t->name;
-		buildStructure(t);
-		buildArmyIn(t);
-
-		if(!ai->primaryHero() ||
-			(t->getArmyStrength() > ai->primaryHero()->getArmyStrength() * 2 && !isAccessibleForHero(t->visitablePos(), ai->primaryHero())))
-		{
-			recruitHero(t);
-			buildArmyIn(t);
-		}
-	}
-
 	for(auto h : getUnblockedHeroes())
 	{
         logAi->debugStream() << boost::format("Looking into %s, MP=%d") % h->name.c_str() % h->movement;
@@ -2083,7 +2126,7 @@ int3 VCAI::explorationBestNeighbour(int3 hpos, int radius, HeroPtr h)
 	{
 		const CGPathNode *pn = cb->getPathInfo(i->first);
 		//const TerrainTile *t = cb->getTile(i->first);
-		if(best->second < i->second  && i->second && pn->reachable() && pn->accessible == CGPathNode::ACCESSIBLE)
+		if(best->second < i->second && pn->reachable() && pn->accessible == CGPathNode::ACCESSIBLE)
 			best = i;
 	}
 
@@ -2096,6 +2139,7 @@ int3 VCAI::explorationBestNeighbour(int3 hpos, int radius, HeroPtr h)
 int3 VCAI::explorationNewPoint(int radius, HeroPtr h, bool breakUnsafe)
 {
     logAi->debugStream() << "Looking for an another place for exploration...";
+	cb->setSelection(h.h);
 
 	std::vector<std::vector<int3> > tiles; //tiles[distance_to_fow]
 	tiles.resize(radius);
@@ -2584,6 +2628,7 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
 	switch (obj->ID)
 	{	
 		case Obj::TOWN:
+		case Obj::HERO: //never visit our heroes at random
 			return obj->tempOwner != h->tempOwner; //do not visit our towns at random
 			break;
 		case Obj::BORDER_GATE:
@@ -2847,6 +2892,7 @@ int3 SectorMap::firstTileToGet(HeroPtr h, crint3 dst)
 		}
 	}
 
+
 	throw cannotFulfillGoalException("Impossible happened.");
 }
 

+ 1 - 0
AI/VCAI/VCAI.h

@@ -295,6 +295,7 @@ public:
 	const CGTownInstance *findTownWithTavern() const;
 	bool canRecruitAnyHero(const CGTownInstance * t = NULL) const;
 
+	bool VCAI::canAct (HeroPtr h) const;
 	std::vector<HeroPtr> getUnblockedHeroes() const;
 	HeroPtr primaryHero() const;
 	TResources freeResources() const; //owned resources minus gold reserve