Prechádzať zdrojové kódy

- 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 rokov pred
rodič
commit
1cf99f7be1
4 zmenil súbory, kde vykonal 90 pridanie a 11 odobranie
  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;