فهرست منبع

- Improved exploration algorithm. AI will avoid dead-end barriers and thus explore much faster.
- Fixed crash when there are no heroes available to recruit in the map. TODO: make AI handle it
- Fixed crash when Chain Lightning has not enough targets
- Fixed crash at Hill Fort (?)
- AI will buy Spellbook for its heroes
- AI will try to use Cartographer and Observatory when exploring
- AI will not visit Prison when heroes are at max
- Experiment: AI will try to capture Mines and Dwellings when there are no enemies around (part of CONQUER goal)

DjWarmonger 13 سال پیش
والد
کامیت
1cf99f7be1
4فایلهای تغییر یافته به همراه90 افزوده شده و 11 حذف شده
  1. 77 6
      AI/VCAI/VCAI.cpp
  2. 2 1
      AI/VCAI/VCAI.h
  3. 2 0
      lib/CBattleCallback.cpp
  4. 9 4
      server/CGameHandler.cpp

+ 77 - 6
AI/VCAI/VCAI.cpp

@@ -1108,9 +1108,14 @@ void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
 			break;
 		case Obj::TOWN:
 			moveCreaturesToHero (dynamic_cast<const CGTownInstance *>(obj));
-			townVisitsThisWeek[h].push_back(h->visitedTown);
+			if (h->visitedTown) //we are inside, not just attacking
+			{
+				townVisitsThisWeek[h].push_back(h->visitedTown);
+				if (!h->hasSpellbook() && cb->getResourceAmount(Res::GOLD) >= GameConstants::SPELLBOOK_GOLD_COST + saving[Res::GOLD] &&
+					h->visitedTown->hasBuilt (EBuilding::MAGES_GUILD_1))
+					cb->buyArtifact(h.get(), 0); //buy spellbook
+			}
 			break;
-		break;
 	}
 }
 
@@ -1825,7 +1830,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 }
 
 int howManyTilesWillBeDiscovered(const int3 &pos, int radious)
-{
+{ //TODO: do not explore dead-end boundaries
 	int ret = 0;
 	for(int x = pos.x - radious; x <= pos.x + radious; x++)
 	{
@@ -1834,7 +1839,8 @@ int howManyTilesWillBeDiscovered(const int3 &pos, int radious)
 			int3 npos = int3(x,y,pos.z);
 			if(cb->isInTheMap(npos) && pos.dist2d(npos) - 0.5 < radious  && !cb->isVisible(npos))
 			{
-				ret++;
+				if (!boundaryBetweenTwoPoints (pos, npos))
+					ret++;
 			}
 		}
 	}
@@ -1842,6 +1848,28 @@ int howManyTilesWillBeDiscovered(const int3 &pos, int radious)
 	return ret;
 }
 
+bool boundaryBetweenTwoPoints (int3 pos1, int3 pos2) //determines if two points are separated by known barrier
+{
+	int xMin = std::min (pos1.x, pos2.x);
+	int xMax = std::max (pos1.x, pos2.x);
+	int yMin = std::min (pos1.y, pos2.y);
+	int yMax = std::max (pos1.y, pos2.y);
+
+	for (int x = xMin; x <= xMax; ++x)
+	{
+		for (int y = yMin; y <= yMax; ++y)
+		{
+			int3 tile = int3(x, y, pos1.z); //use only on same level, ofc
+			if (abs(pos1.dist2d(tile) - pos2.dist2d(tile)) < 1.5)
+			{
+				if (!(cb->isVisible(tile) && cb->getTile(tile)->blocked)) //if there's invisible or unblocked tile inbetween, it's good 
+					return false;
+			}
+		}
+	}
+	return true; //if all are visible and blocked, we're at dead end
+}
+
 int howManyTilesWillBeDiscovered(int radious, int3 pos, crint3 dir)
 {
 	return howManyTilesWillBeDiscovered(pos + dir, radious);
@@ -2918,6 +2946,34 @@ TSubgoal CGoal::whatToDoToAchieve()
 		//return CGoal(EXPLORE); // TODO improve
 	case EXPLORE:
 		{
+			auto objs = ai->visitableObjs; //try to use buildings that uncover map
+			erase_if(objs, [&](const CGObjectInstance *obj)
+			{
+				return (obj->ID != Obj::REDWOOD_OBSERVATORY && obj->ID != Obj::PILLAR_OF_FIRE && obj->ID != Obj::CARTOGRAPHER)
+					|| vstd::contains(ai->alreadyVisited, obj); //TODO: check if object radius is uncovered? worth it?
+			});
+			if (objs.size())
+			{
+				if (hero.get(true))
+				{
+					BOOST_FOREACH (auto obj, objs)
+					{
+						auto pos = obj->visitablePos();
+						if (isSafeToVisit(hero, pos) && ai->isAccessibleForHero(pos, hero))
+							return CGoal(VISIT_TILE).settile(pos).sethero(hero);
+					}
+				}
+				else
+				{
+					BOOST_FOREACH (auto obj, objs)
+					{
+						auto pos = obj->visitablePos();
+						if (ai->isAccessible (pos)) //TODO: check safety?
+							return CGoal(VISIT_TILE).settile(pos).sethero(hero);
+					}
+				}
+			}
+
 			if (hero)
 			{
 				return CGoal(VISIT_TILE).settile(whereToExplore(hero)).sethero(hero);
@@ -3183,13 +3239,23 @@ TSubgoal CGoal::whatToDoToAchieve()
 				return (obj->ID != Obj::TOWN && obj->ID != Obj::HERO) //not town/hero
 					|| cb->getPlayerRelations(ai->playerID, obj->tempOwner) != 0; //not enemy
 			});
+			
+			if (objs.empty()) //experiment - try to conquer dwellings and mines, it should pay off
+			{
+				ai->retreiveVisitableObjs(objs);
+				erase_if(objs, [&](const CGObjectInstance *obj)
+				{
+					return (obj->ID != Obj::CREATURE_GENERATOR1 && obj->ID != Obj::MINE) //not dwelling or mine
+						|| cb->getPlayerRelations(ai->playerID, obj->tempOwner) != 0; //not enemy
+				});
+			}
 
 			if(objs.empty())
 				return CGoal(EXPLORE); //we need to find an enemy
 
 			erase_if(objs,  [&](const CGObjectInstance *obj)
 			{
-				return !isSafeToVisit(h, obj->visitablePos());
+				return !isSafeToVisit(h, obj->visitablePos()) || vstd::contains (ai->reservedObjs, obj); //no need to capture same object twice
 			});
 
 			if(objs.empty())
@@ -3198,8 +3264,10 @@ TSubgoal CGoal::whatToDoToAchieve()
 			boost::sort(objs, isCloser);
 			BOOST_FOREACH(const CGObjectInstance *obj, objs)
 			{
-				if(ai->isAccessibleForHero(obj->visitablePos(), h))
+				if (ai->isAccessibleForHero(obj->visitablePos(), h))
 				{
+					ai->reserveObject(h, obj); //no one else will capture same object until we fail
+
 					if (obj->ID == Obj::HERO)
 						return CGoal(VISIT_HERO).sethero(h).setobjid(obj->id).setisAbstract(true); //track enemy hero
 					else
@@ -3552,6 +3620,9 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
 		case Obj::MAGIC_WELL:
 			return h->mana < h->manaLimit();
 			break;
+		case Obj::PRISON:
+			return ai->myCb->getHeroesInfo().size() < GameConstants::MAX_HEROES_PER_PLAYER;
+			break;
 
 		case Obj::BOAT:
 			return false;

+ 2 - 1
AI/VCAI/VCAI.h

@@ -402,4 +402,5 @@ bool isBlockedBorderGate(int3 tileToHit);
 bool isWeeklyRevisitable (const CGObjectInstance * obj);
 bool shouldVisit (HeroPtr h, const CGObjectInstance * obj);
 
-void makePossibleUpgrades(const CArmedInstance *obj);
+void makePossibleUpgrades(const CArmedInstance *obj);
+bool boundaryBetweenTwoPoints (int3 pos1, int3 pos2);

+ 2 - 0
lib/CBattleCallback.cpp

@@ -1940,6 +1940,8 @@ std::set<const CStack*> CBattleInfoCallback::getAffectedCreatures(const CSpell *
 			{
 				possibleHexes.erase (hex); //can't hit same place twice
 			}
+			if (!possibleHexes.size()) //not enough targets
+				break;
 			lightningHex = BattleHex::getClosestTile (attackerOwner, destinationTile, possibleHexes);
 		}
 	}

+ 9 - 4
server/CGameHandler.cpp

@@ -2664,14 +2664,13 @@ bool CGameHandler::upgradeCreature( ui32 objid, ui8 pos, ui32 upgID )
 	const PlayerState *p = getPlayer(player);
 	int crQuantity = obj->stacks[pos]->count;
 	int newIDpos= vstd::find_pos(ui.newID, upgID);//get position of new id in UpgradeInfo
-	TResources totalCost = ui.cost[newIDpos] * crQuantity;
 
 	//check if upgrade is possible
 	if( (ui.oldID<0 || newIDpos == -1 ) && complain("That upgrade is not possible!"))
 	{
 		return false;
 	}
-
+	TResources totalCost = ui.cost[newIDpos] * crQuantity;
 
 	//check if player has enough resources
 	if(!p->resources.canAfford(totalCost))
@@ -3172,7 +3171,11 @@ bool CGameHandler::hireHero(const CGObjectInstance *obj, ui8 hid, ui8 player)
 
 
 	const CGHeroInstance *nh = p->availableHeroes[hid];
-	assert(nh);
+	if (!nh)
+	{
+		complain ("Hero is not available for hiring!");
+		return false;
+	}
 
 	HeroRecruited hr;
 	hr.tid = obj->id;
@@ -3185,7 +3188,9 @@ bool CGameHandler::hireHero(const CGObjectInstance *obj, ui8 hid, ui8 player)
 	bmap<ui32, ConstTransitivePtr<CGHeroInstance> > pool = gs->unusedHeroesFromPool();
 
 	const CGHeroInstance *theOtherHero = p->availableHeroes[!hid];
-	const CGHeroInstance *newHero = gs->hpool.pickHeroFor(false, player, getNativeTown(player), pool, theOtherHero->type->heroClass);
+	const CGHeroInstance *newHero = NULL;
+	if (theOtherHero) //on XXL maps all heroes can be imprisoned :(
+		newHero = gs->hpool.pickHeroFor(false, player, getNativeTown(player), pool, theOtherHero->type->heroClass);
 
 	SetAvailableHeroes sah;
 	sah.player = player;