1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-12 02:28:11 +02:00

- 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)
This commit is contained in:
DjWarmonger 2012-10-01 18:25:43 +00:00
parent 42cbbc8682
commit 1cf99f7be1
4 changed files with 90 additions and 11 deletions

View File

@ -1108,8 +1108,13 @@ void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
break; break;
case Obj::TOWN: case Obj::TOWN:
moveCreaturesToHero (dynamic_cast<const CGTownInstance *>(obj)); moveCreaturesToHero (dynamic_cast<const CGTownInstance *>(obj));
if (h->visitedTown) //we are inside, not just attacking
{
townVisitsThisWeek[h].push_back(h->visitedTown); townVisitsThisWeek[h].push_back(h->visitedTown);
break; 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) int howManyTilesWillBeDiscovered(const int3 &pos, int radious)
{ { //TODO: do not explore dead-end boundaries
int ret = 0; int ret = 0;
for(int x = pos.x - radious; x <= pos.x + radious; x++) for(int x = pos.x - radious; x <= pos.x + radious; x++)
{ {
@ -1834,6 +1839,7 @@ int howManyTilesWillBeDiscovered(const int3 &pos, int radious)
int3 npos = int3(x,y,pos.z); int3 npos = int3(x,y,pos.z);
if(cb->isInTheMap(npos) && pos.dist2d(npos) - 0.5 < radious && !cb->isVisible(npos)) if(cb->isInTheMap(npos) && pos.dist2d(npos) - 0.5 < radious && !cb->isVisible(npos))
{ {
if (!boundaryBetweenTwoPoints (pos, npos))
ret++; ret++;
} }
} }
@ -1842,6 +1848,28 @@ int howManyTilesWillBeDiscovered(const int3 &pos, int radious)
return ret; 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) int howManyTilesWillBeDiscovered(int radious, int3 pos, crint3 dir)
{ {
return howManyTilesWillBeDiscovered(pos + dir, radious); return howManyTilesWillBeDiscovered(pos + dir, radious);
@ -2918,6 +2946,34 @@ TSubgoal CGoal::whatToDoToAchieve()
//return CGoal(EXPLORE); // TODO improve //return CGoal(EXPLORE); // TODO improve
case EXPLORE: 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) if (hero)
{ {
return CGoal(VISIT_TILE).settile(whereToExplore(hero)).sethero(hero); return CGoal(VISIT_TILE).settile(whereToExplore(hero)).sethero(hero);
@ -3184,12 +3240,22 @@ TSubgoal CGoal::whatToDoToAchieve()
|| cb->getPlayerRelations(ai->playerID, obj->tempOwner) != 0; //not enemy || 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()) if(objs.empty())
return CGoal(EXPLORE); //we need to find an enemy return CGoal(EXPLORE); //we need to find an enemy
erase_if(objs, [&](const CGObjectInstance *obj) 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()) if(objs.empty())
@ -3200,6 +3266,8 @@ TSubgoal CGoal::whatToDoToAchieve()
{ {
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) if (obj->ID == Obj::HERO)
return CGoal(VISIT_HERO).sethero(h).setobjid(obj->id).setisAbstract(true); //track enemy hero return CGoal(VISIT_HERO).sethero(h).setobjid(obj->id).setisAbstract(true); //track enemy hero
else else
@ -3552,6 +3620,9 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
case Obj::MAGIC_WELL: case Obj::MAGIC_WELL:
return h->mana < h->manaLimit(); return h->mana < h->manaLimit();
break; break;
case Obj::PRISON:
return ai->myCb->getHeroesInfo().size() < GameConstants::MAX_HEROES_PER_PLAYER;
break;
case Obj::BOAT: case Obj::BOAT:
return false; return false;

View File

@ -403,3 +403,4 @@ bool isWeeklyRevisitable (const CGObjectInstance * obj);
bool shouldVisit (HeroPtr h, 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);

View File

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

View File

@ -2664,14 +2664,13 @@ bool CGameHandler::upgradeCreature( ui32 objid, ui8 pos, ui32 upgID )
const PlayerState *p = getPlayer(player); const PlayerState *p = getPlayer(player);
int crQuantity = obj->stacks[pos]->count; int crQuantity = obj->stacks[pos]->count;
int newIDpos= vstd::find_pos(ui.newID, upgID);//get position of new id in UpgradeInfo 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 //check if upgrade is possible
if( (ui.oldID<0 || newIDpos == -1 ) && complain("That upgrade is not possible!")) if( (ui.oldID<0 || newIDpos == -1 ) && complain("That upgrade is not possible!"))
{ {
return false; return false;
} }
TResources totalCost = ui.cost[newIDpos] * crQuantity;
//check if player has enough resources //check if player has enough resources
if(!p->resources.canAfford(totalCost)) 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]; const CGHeroInstance *nh = p->availableHeroes[hid];
assert(nh); if (!nh)
{
complain ("Hero is not available for hiring!");
return false;
}
HeroRecruited hr; HeroRecruited hr;
hr.tid = obj->id; 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(); bmap<ui32, ConstTransitivePtr<CGHeroInstance> > pool = gs->unusedHeroesFromPool();
const CGHeroInstance *theOtherHero = p->availableHeroes[!hid]; 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; SetAvailableHeroes sah;
sah.player = player; sah.player = player;