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:
parent
42cbbc8682
commit
1cf99f7be1
@ -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);
|
||||
@ -3184,12 +3240,22 @@ TSubgoal CGoal::whatToDoToAchieve()
|
||||
|| 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;
|
||||
|
@ -403,3 +403,4 @@ bool isWeeklyRevisitable (const CGObjectInstance * obj);
|
||||
bool shouldVisit (HeroPtr h, const CGObjectInstance * obj);
|
||||
|
||||
void makePossibleUpgrades(const CArmedInstance *obj);
|
||||
bool boundaryBetweenTwoPoints (int3 pos1, int3 pos2);
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user