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;
|
break;
|
||||||
case Obj::TOWN:
|
case Obj::TOWN:
|
||||||
moveCreaturesToHero (dynamic_cast<const CGTownInstance *>(obj));
|
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;
|
||||||
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,7 +1839,8 @@ 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))
|
||||||
{
|
{
|
||||||
ret++;
|
if (!boundaryBetweenTwoPoints (pos, npos))
|
||||||
|
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);
|
||||||
@ -3183,13 +3239,23 @@ TSubgoal CGoal::whatToDoToAchieve()
|
|||||||
return (obj->ID != Obj::TOWN && obj->ID != Obj::HERO) //not town/hero
|
return (obj->ID != Obj::TOWN && obj->ID != Obj::HERO) //not town/hero
|
||||||
|| 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())
|
||||||
@ -3198,8 +3264,10 @@ TSubgoal CGoal::whatToDoToAchieve()
|
|||||||
boost::sort(objs, isCloser);
|
boost::sort(objs, isCloser);
|
||||||
BOOST_FOREACH(const CGObjectInstance *obj, objs)
|
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)
|
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;
|
||||||
|
@ -402,4 +402,5 @@ bool isBlockedBorderGate(int3 tileToHit);
|
|||||||
bool isWeeklyRevisitable (const CGObjectInstance * obj);
|
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);
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user