Pārlūkot izejas kodu

- AI will now use SectorMap to find a way to guarded / covered objects.
- Improvements to SectorMap needed for use of multiple heroes

DjWarmonger 11 gadi atpakaļ
vecāks
revīzija
d8933b5c36
4 mainītis faili ar 97 papildinājumiem un 54 dzēšanām
  1. 1 1
      AI/VCAI/Fuzzy.cpp
  2. 57 44
      AI/VCAI/Goals.cpp
  3. 35 9
      AI/VCAI/VCAI.cpp
  4. 4 0
      lib/int3.h

+ 1 - 1
AI/VCAI/Fuzzy.cpp

@@ -476,7 +476,7 @@ float FuzzyHelper::evaluate (Goals::GatherArmy & g)
 	//the more army we need, the more important goal
 	//the more army we lack, the less important goal
 	float army = g.hero->getArmyStrength();
-	return g.value / std::min(g.value - army, 1000.0f);
+	return g.value / std::max(g.value - army, 1000.0f);
 }
 float FuzzyHelper::evaluate (Goals::BuildThis & g)
 {

+ 57 - 44
AI/VCAI/Goals.cpp

@@ -407,11 +407,12 @@ TGoalVec ClearWayTo::getAllPossibleSubgoals()
 			continue;
 
 		cb->setSelection(h);
-
 		SectorMap sm;
 
 		int3 tileToHit = sm.firstTileToGet(hero ? hero : h, tile);
 		//if our hero is trapped, make sure we request clearing the way from OUR perspective
+		if (!tileToHit.valid())
+			continue;
 
 		if (isBlockedBorderGate(tileToHit))
 		{	//FIXME: this way we'll not visit gate and activate quest :?
@@ -510,42 +511,35 @@ TGoalVec Explore::getAllPossibleSubgoals()
 	{
 		for (auto obj : objs) //double loop, performance risk?
 		{
-			if (ai->isAccessibleForHero(obj->visitablePos(), h) && isSafeToVisit(h, obj->visitablePos()))
+			cb->setSelection(h);
+			SectorMap sm; //seems to depend on hero
+			auto t = sm.firstTileToGet(h, obj->visitablePos()); //we assume that no more than one tile on the way is guarded
+			if (t.valid())
 			{
-				ret.push_back (sptr (Goals::VisitTile(obj->visitablePos()).sethero(h)));
+				//auto topObj = backOrNull(cb->getVisitableObjs(t));
+				//if (topObj && topObj->ID == Obj::HERO && topObj != h)
+				//	continue; //if the tile is occupied by another hero, we are not interested in going there
+
+				if (isSafeToVisit(h, t))
+				{
+					ret.push_back (sptr (Goals::VisitTile(t).sethero(h)));
+				}
+				else
+				{
+					ret.push_back (sptr (Goals::GatherArmy(evaluateDanger(t, h)*SAFE_ATTACK_CONSTANT).
+						sethero(h).setisAbstract(true)));
+				}
 			}
 		}
 
 		int3 t = whereToExplore(h);
-		if (cb->isInTheMap(t)) //valid tile was found - could be invalid (none)
+		if (t.valid())
 			ret.push_back (sptr (Goals::VisitTile(t).sethero(h)));
 	}
 	//we either don't have hero yet or none of heroes can explore
 	if ((!hero || ret.empty()) && ai->canRecruitAnyHero())
 		ret.push_back (sptr(Goals::RecruitHero()));
 
-	if (ret.empty())
-	{
-		HeroPtr h;
-		if (hero) //there is some hero set and it's us
-		{
-			 if (hero == ai->primaryHero())
-				h = hero;
-		}
-		else //no hero is set, so we choose our main
-			h = ai->primaryHero();
-		 //we may need to gather big army to break!
-		if (h.h)
-		{
-			//FIXME: it never finds anything :?
-			int3 t = ai->explorationNewPoint(h->getSightRadious(), h, true);
-			if (cb->isInTheMap(t)) 
-				ret.push_back (sptr(ClearWayTo(t).setisAbstract(true).sethero(h)));
-			else //just in case above fails - gather army if no further exploration possible
-				ret.push_back (sptr(GatherArmy(h->getArmyStrength() + 1).sethero(h)));
-			//do not set abstract to keep our hero free once he gets reinforcements
-		}
-	}
 	if (ret.empty())
 	{
 		throw goalFulfilledException (sptr(Goals::Explore().sethero(hero)));
@@ -790,31 +784,50 @@ TGoalVec Conquer::getAllPossibleSubgoals()
 {
 	TGoalVec ret;
 
-	std::vector<const CGObjectInstance *> objs; //here we'll gather enemy towns and heroes
-	ai->retreiveVisitableObjs(objs);
-	erase_if(objs, [&](const CGObjectInstance *obj)
-	{
-		return (obj->ID != Obj::TOWN && obj->ID != Obj::HERO && //not town/hero
-			obj->ID != Obj::CREATURE_GENERATOR1 && obj->ID != Obj::MINE) //not dwelling or mine
-			|| cb->getPlayerRelations(ai->playerID, obj->tempOwner) != PlayerRelations::ENEMIES; //only enemy objects are interesting
-	});
-	erase_if(objs,  [&](const CGObjectInstance *obj)
+	std::vector<const CGObjectInstance *> objs;
+	for (auto obj : ai->visitableObjs)
 	{
-		return vstd::contains (ai->reservedObjs, obj);
-		//no need to capture same object twice
-	});
+		if (!vstd::contains (ai->reservedObjs, obj) && //no need to capture same object twice
+			cb->getPlayerRelations(ai->playerID, obj->tempOwner) == PlayerRelations::ENEMIES) //only enemy objects are interesting
+		{
+			switch (obj->ID.num)
+			{
+				case Obj::TOWN:
+				case Obj::HERO:
+				case Obj::CREATURE_GENERATOR1:
+				case Obj::MINE: //TODO: check ai->knownSubterraneanGates
+					objs.push_back (obj);
+			}
+		}
+	}
 
 	for (auto h : cb->getHeroesInfo())
 	{
 		for (auto obj : objs) //double loop, performance risk?
 		{
-			if (ai->isAccessibleForHero(obj->visitablePos(), h) && isSafeToVisit(h, obj->visitablePos()))
+			cb->setSelection(h);
+			SectorMap sm; //seems to depend on hero
+			auto t = sm.firstTileToGet(h, obj->visitablePos()); //we assume that no more than one tile on the way is guarded
+			if (t.valid())
 			{
-				if (obj->ID == Obj::HERO)
-					ret.push_back (sptr (Goals::VisitHero(obj->id.getNum()).sethero(h).setisAbstract(true)));
-					//track enemy hero
+				//auto topObj = backOrNull(cb->getVisitableObjs(t));
+				//if (topObj && topObj->ID == Obj::HERO && topObj != h)
+				//	continue; //if the tile is occupied by another hero, we are not interested in going there
+
+				//FIXME: should firstTileToGet return position of our other hero?
+
+				if (isSafeToVisit(h, t))
+				{
+					if (obj->ID == Obj::HERO)
+						ret.push_back (sptr (Goals::VisitHero(obj->id.getNum()).sethero(h).setisAbstract(true)));
+						//track enemy hero
+					else
+						ret.push_back (sptr (Goals::VisitTile(t).sethero(h)));
+				}
 				else
-					ret.push_back (sptr (Goals::VisitTile(obj->visitablePos()).sethero(h)));
+				{
+					ret.push_back (sptr (Goals::GatherArmy(evaluateDanger(t,h)*SAFE_ATTACK_CONSTANT).sethero(h).setisAbstract(true)));
+				}
 			}
 		}
 	}
@@ -917,7 +930,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
 		for (auto obj : objs)
 		{ //find safe dwelling
 			auto pos = obj->visitablePos();
-			if (shouldVisit (h, obj) && isSafeToVisit(h, pos) && ai->isAccessibleForHero(pos, h))
+			if (ai->isGoodForVisit(obj, h))
 				ret.push_back (sptr (Goals::VisitTile(pos).sethero(h)));
 		}
 	}

+ 35 - 9
AI/VCAI/VCAI.cpp

@@ -2770,7 +2770,14 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
 }
 
 int3 SectorMap::firstTileToGet(HeroPtr h, crint3 dst)
+/*
+this functions returns one target tile or invalid tile. We will use it to poll possible destinations
+For ship construction etc, another function (goal?) is needed
+*/
 {
+	int3 ret(-1,-1,-1);
+	cb->setSelection(h.h);
+
 	int sourceSector = retreiveTile(h->visitablePos()),
 		destinationSector = retreiveTile(dst);
 
@@ -2806,7 +2813,9 @@ int3 SectorMap::firstTileToGet(HeroPtr h, crint3 dst)
 			write("test.txt");
 			ai->completeGoal (sptr(Goals::Explore(h))); //if we can't find the way, seemingly all tiles were explored
 			//TODO: more organized way?
-            throw cannotFulfillGoalException(boost::str(boost::format("Cannot find connection between sectors %d and %d") % src->id % dst->id));
+
+			return ret;
+            //throw cannotFulfillGoalException(boost::str(boost::format("Cannot find connection between sectors %d and %d") % src->id % dst->id));
 		}
 
 		std::vector<const Sector*> toTraverse;
@@ -2861,7 +2870,9 @@ int3 SectorMap::firstTileToGet(HeroPtr h, crint3 dst)
 					if(!shipyards.size())
 					{
 						//TODO consider possibility of building shipyard in a town
-						throw cannotFulfillGoalException("There is no known shipyard!");
+						return ret;
+
+						//throw cannotFulfillGoalException("There is no known shipyard!");
 					}
 
 					//we have only shipyards that possibly can build ships onto the appropriate EP
@@ -2878,13 +2889,15 @@ int3 SectorMap::firstTileToGet(HeroPtr h, crint3 dst)
 						if(cb->getResourceAmount().canAfford(shipCost))
 						{
 							int3 ret = s->bestLocation();
-							cb->buildBoat(s);
+							cb->buildBoat(s); //TODO: move actions elsewhere
 							return ret;
 						}
 						else
 						{
 							//TODO gather res
-							throw cannotFulfillGoalException("Not enough resources to build a boat");
+							return ret;
+
+							//throw cannotFulfillGoalException("Not enough resources to build a boat");
 						}
 					}
 					else
@@ -2898,17 +2911,20 @@ int3 SectorMap::firstTileToGet(HeroPtr h, crint3 dst)
 			{
 				//TODO
 				//disembark
+				return ret;
 			}
 			else
 			{
 				//TODO
 				//transition between two land/water sectors. Monolith? Whirlpool? ...
-				throw cannotFulfillGoalException("Land-land and water-water inter-sector transitions are not implemented!");
+				return ret;
+				//throw cannotFulfillGoalException("Land-land and water-water inter-sector transitions are not implemented!");
 			}
 		}
 		else
 		{
-			throw cannotFulfillGoalException("Inter-sector route detection failed: not connected sectors?");
+			return ret;
+			//throw cannotFulfillGoalException("Inter-sector route detection failed: not connected sectors?");
 		}
 	}
 	else
@@ -2917,6 +2933,12 @@ int3 SectorMap::firstTileToGet(HeroPtr h, crint3 dst)
 		int3 curtile = dst;
 		while(curtile != h->visitablePos())
 		{
+			auto topObj = backOrNull(cb->getVisitableObjs(curtile));
+			if (topObj && topObj->ID == Obj::HERO && topObj != h.h)
+			{
+				logAi->warnStream() << ("Another allied hero stands in our way");
+				return ret;
+			}
 			if(cb->getPathInfo(curtile)->reachable())
 			{
 				return curtile;
@@ -2930,13 +2952,17 @@ int3 SectorMap::firstTileToGet(HeroPtr h, crint3 dst)
 					curtile = i->second;
 				}
 				else
-					throw cannotFulfillGoalException("Unreachable tile in sector? Should not happen!");
+				{
+					return ret;
+					//throw cannotFulfillGoalException("Unreachable tile in sector? Should not happen!");
+				}
 			}
 		}
 	}
 
-
-	throw cannotFulfillGoalException("Impossible happened.");
+	//FIXME: find out why this line is reached
+	logAi->errorStream() << ("Impossible happened at SectorMap::firstTileToGet");
+	return ret;
 }
 
 void SectorMap::makeParentBFS(crint3 source)

+ 4 - 0
lib/int3.h

@@ -84,6 +84,10 @@ public:
 				" " + boost::lexical_cast<std::string>(y) +
 				" " + boost::lexical_cast<std::string>(z) + ")";
 	}
+	inline bool valid() const
+	{
+		return z >= 0; //minimal condition that needs to be fulfilled for tiles in the map
+	}
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & x & y & z;