1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

- Various improvements for exploration

* Heroes will try to use SectorMap if there are no accessible tiles
* Remove some loopholes and pitfalls when all the ways are blocked
* Fixed AI not conquering some (reserved) objects
Still missing: SectorMap does not use Subterranean Gates :(

- Improvements for army exchange
* Fixed exchange condition
* Bidirectional exchange is possible
This commit is contained in:
DjWarmonger 2014-02-17 07:36:03 +00:00
parent afacb40f5a
commit 6ee823298a
4 changed files with 203 additions and 32 deletions

View File

@ -486,6 +486,10 @@ TGoalVec Explore::getAllPossibleSubgoals()
heroes = cb->getHeroesInfo();
erase_if (heroes, [](const HeroPtr h)
{
if (vstd::contains(ai->lockedHeroes, h))
if (ai->lockedHeroes[h]->goalType == Goals::EXPLORE) //do not reassign hero who is already explorer
return true;
return !h->movement; //saves time, immobile heroes are useless anyway
});
}
@ -514,7 +518,6 @@ TGoalVec Explore::getAllPossibleSubgoals()
auto t = sm.firstTileToGet(h, obj->visitablePos()); //we assume that no more than one tile on the way is guarded
if (t.valid())
{
assert(cb->isInTheMap(t));
if (isSafeToVisit(h, t))
{
ret.push_back (sptr (Goals::VisitTile(t).sethero(h)));
@ -530,14 +533,49 @@ TGoalVec Explore::getAllPossibleSubgoals()
int3 t = whereToExplore(h);
if (t.valid())
{
assert(cb->isInTheMap(t));
ret.push_back (sptr (Goals::VisitTile(t).sethero(h)));
}
else if (hero.h == h || (!hero && h == ai->primaryHero().h)) //check this only ONCE, high cost
{
t = ai->explorationDesperate(h->getSightRadious(), h);
if (t.valid())
{
if (isSafeToVisit(h, t))
{
ret.push_back (sptr (Goals::VisitTile(t).sethero(h)));
}
else
{
ret.push_back (sptr (Goals::GatherArmy(evaluateDanger(t, h)*SAFE_ATTACK_CONSTANT).
sethero(h).setisAbstract(true)));
}
}
}
}
//we either don't have hero yet or none of heroes can explore
if ((!hero || ret.empty()) && ai->canRecruitAnyHero())
ret.push_back (sptr(Goals::RecruitHero()));
//if (ret.empty())
//{
// for (auto h : heroes) //this is costly function, use only when there is no other way
// {
// auto t = ai->explorationDesperate (h->getSightRadious(), h); //we assume that no more than one tile on the way is guarded
// if (t.valid())
// {
// if (isSafeToVisit(h, t))
// {
// ret.push_back (sptr (Goals::VisitTile(t).sethero(h)));
// }
// else
// {
// ret.push_back (sptr (Goals::GatherArmy(evaluateDanger(t, h)*SAFE_ATTACK_CONSTANT).
// sethero(h).setisAbstract(true)));
// }
// }
// }
//}
if (ret.empty())
{
throw goalFulfilledException (sptr(Goals::Explore().sethero(hero)));
@ -784,11 +822,9 @@ TGoalVec Conquer::getAllPossibleSubgoals()
{
TGoalVec ret;
std::vector<const CGObjectInstance *> objs;
for (auto obj : ai->visitableObjs)
auto conquerable = [](const CGObjectInstance * obj) -> bool
{
if (!vstd::contains (ai->reservedObjs, obj) && //no need to capture same object twice
cb->getPlayerRelations(ai->playerID, obj->tempOwner) == PlayerRelations::ENEMIES) //only enemy objects are interesting
if (cb->getPlayerRelations(ai->playerID, obj->tempOwner) == PlayerRelations::ENEMIES)
{
switch (obj->ID.num)
{
@ -796,15 +832,30 @@ TGoalVec Conquer::getAllPossibleSubgoals()
case Obj::HERO:
case Obj::CREATURE_GENERATOR1:
case Obj::MINE: //TODO: check ai->knownSubterraneanGates
objs.push_back (obj);
return true;
}
}
return false;
};
std::vector<const CGObjectInstance *> objs;
for (auto obj : ai->visitableObjs)
{
if (conquerable(obj))
objs.push_back (obj);
}
for (auto h : cb->getHeroesInfo())
{
SectorMap sm(h);
for (auto obj : objs) //double loop, performance risk?
std::vector<const CGObjectInstance *> ourObjs(objs); //copy common objects
for (auto obj : ai->reservedHeroesMap[h]) //add objects reserved by this hero
{
if (conquerable(obj))
ourObjs.push_back(obj);
}
for (auto obj : ourObjs) //double loop, performance risk?
{
auto t = sm.firstTileToGet(h, obj->visitablePos()); //we assume that no more than one tile on the way is guarded
if (t.valid())
@ -881,11 +932,11 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
auto heroDummy = hero;
erase_if(otherHeroes, [heroDummy](const CGHeroInstance * h)
{
return (h == heroDummy.h || !ai->isAccessibleForHero(heroDummy->visitablePos(), h, true) || !ai->canGetArmy(heroDummy.h, h));
return (h == heroDummy.h || !ai->isAccessibleForHero(heroDummy->visitablePos(), h, true)
|| !ai->canGetArmy(heroDummy.h, h) || ai->getGoal(h)->goalType == Goals::GATHER_ARMY);
});
for (auto h : otherHeroes)
{
ret.push_back (sptr (Goals::VisitHero(h->id.getNum()).setisAbstract(true).sethero(hero)));
//go to the other hero if we are faster
ret.push_back (sptr (Goals::VisitHero(hero->id.getNum()).setisAbstract(true).sethero(h)));
@ -928,7 +979,12 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
}
}
if (ret.empty())
ret.push_back (sptr(Goals::Explore()));
{
if (hero == ai->primaryHero() || value >= 1.1f)
ret.push_back (sptr(Goals::Explore()));
else //workaround to break loop - seemingly there are no ways to explore left
throw goalFulfilledException (sptr(Goals::GatherArmy(0).sethero(hero)));
}
return ret;
}

View File

@ -307,14 +307,30 @@ void VCAI::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, Q
requestActionASAP([=]()
{
if (firstHero->getFightingStrength() > secondHero->getFightingStrength() && canGetArmy (firstHero, secondHero))
pickBestCreatures (firstHero, secondHero);
else if (canGetArmy (secondHero, firstHero))
pickBestCreatures (secondHero, firstHero);
float goalpriority1 = 0, goalpriority2 = 0;
completeGoal(sptr(Goals::VisitHero(firstHero->id.getNum()))); //TODO: what if we were visited by other hero in the meantime?
completeGoal(sptr(Goals::VisitHero(secondHero->id.getNum())));
auto firstGoal = getGoal(firstHero);
if (firstGoal->goalType == Goals::GATHER_ARMY)
goalpriority1 = firstGoal->priority;
auto secondGoal = getGoal(secondHero);
if (secondGoal->goalType == Goals::GATHER_ARMY)
goalpriority2 = secondGoal->priority;
if (goalpriority1 > goalpriority2)
pickBestCreatures (firstHero, secondHero);
else if (goalpriority1 < goalpriority2)
pickBestCreatures (secondHero, firstHero);
else //regular criteria
{
if (firstHero->getFightingStrength() > secondHero->getFightingStrength() && canGetArmy (firstHero, secondHero))
pickBestCreatures (firstHero, secondHero);
else if (canGetArmy (secondHero, firstHero))
pickBestCreatures (secondHero, firstHero);
completeGoal(sptr(Goals::VisitHero(firstHero->id.getNum()))); //TODO: what if we were visited by other hero in the meantime?
completeGoal(sptr(Goals::VisitHero(secondHero->id.getNum())));
//TODO: exchange artifacts
}
answerQuery(query, 0);
});
@ -866,7 +882,7 @@ bool VCAI::canGetArmy (const CGHeroInstance * army, const CGHeroInstance * sourc
for(auto armyPtr : armies)
for (int j = 0; j < GameConstants::ARMY_SIZE; j++)
{
if(armyPtr->getCreature(SlotID(j)) == bestArmy[i] && (i != j || armyPtr != army)) //it's a searched creature not in dst slot
if(armyPtr->getCreature(SlotID(j)) == bestArmy[i] && armyPtr != army) //it's a searched creature not in dst ARMY
if (!(armyPtr->needsLastStack() && armyPtr->Slots().size() == 1 && armyPtr != army)) //can't take away last creature
return true; //at least one exchange will be performed
}
@ -910,7 +926,7 @@ void VCAI::pickBestCreatures(const CArmedInstance * army, const CArmedInstance *
for(auto armyPtr : armies)
for (int j = 0; j < GameConstants::ARMY_SIZE; j++)
{
if(armyPtr->getCreature(SlotID(j)) == bestArmy[i] && (i != j || armyPtr != army)) //it's a searched creature not in dst slot
if(armyPtr->getCreature(SlotID(j)) == bestArmy[i] && (i != j || armyPtr != army)) //it's a searched creature not in dst SLOT
if (!(armyPtr->needsLastStack() && armyPtr->Slots().size() == 1 && armyPtr != army))
cb->mergeOrSwapStacks(armyPtr, army, SlotID(j), SlotID(i));
}
@ -1833,6 +1849,16 @@ const CGTownInstance * VCAI::findTownWithTavern() const
return nullptr;
}
Goals::TSubgoal VCAI::getGoal (HeroPtr h) const
{
auto it = lockedHeroes.find(h);
if (it != lockedHeroes.end())
return it->second;
else
return sptr(Goals::Invalid());
}
std::vector<HeroPtr> VCAI::getUnblockedHeroes() const
{
std::vector<HeroPtr> ret;
@ -2179,7 +2205,7 @@ int3 VCAI::explorationBestNeighbour(int3 hpos, int radius, HeroPtr h)
throw cannotFulfillGoalException("No neighbour will bring new discoveries!");
}
int3 VCAI::explorationNewPoint(int radius, HeroPtr h, bool breakUnsafe)
int3 VCAI::explorationNewPoint(int radius, HeroPtr h)
{
//logAi->debugStream() << "Looking for an another place for exploration...";
cb->setSelection(h.h);
@ -2205,7 +2231,7 @@ int3 VCAI::explorationNewPoint(int radius, HeroPtr h, bool breakUnsafe)
{
if (cb->getTile(tile)->blocked) //does it shorten the time?
continue;
if (!cb->getPathInfo(tile)->reachable())
if (!cb->getPathInfo(tile)->reachable()) //this will remove tiles that are guarded by monsters (or removable objects)
continue;
CGPath path;
@ -2214,14 +2240,101 @@ int3 VCAI::explorationNewPoint(int radius, HeroPtr h, bool breakUnsafe)
if (ourValue > bestValue) //avoid costly checks of tiles that don't reveal much
{
if((isSafeToVisit(h, tile) || breakUnsafe) && !isBlockedBorderGate(tile))
if(isSafeToVisit(h, tile) && !isBlockedBorderGate(tile))
{
bestTile = tile; //return first tile that will discover anything
bestTile = tile;
bestValue = ourValue;
}
}
}
}
//if (!bestValue) //no free spot, we need to fight
//{
// SectorMap sm(h);
// ui64 lowestDanger = -1;
// for (int i = 1; i < radius; i++)
// {
// getVisibleNeighbours(tiles[i-1], tiles[i]);
// removeDuplicates(tiles[i]);
// for(const int3 &tile : tiles[i])
// {
// if (cb->getTile(tile)->blocked) //does it shorten the time?
// continue;
// if (!howManyTilesWillBeDiscovered(tile, radius)) //avoid costly checks of tiles that don't reveal much
// continue;
// auto t = sm.firstTileToGet(h, tile);
// if (t.valid())
// {
// ui64 ourDanger = evaluateDanger(tile, h.h);
// if (ourDanger < lowestDanger)
// {
// if(!isBlockedBorderGate(tile))
// {
// if (!ourDanger) //at least one safe place found
// return tile;
// bestTile = tile;
// lowestDanger = ourDanger;
// }
// }
// }
// }
// }
//}
return bestTile;
}
int3 VCAI::explorationDesperate(int radius, HeroPtr h)
{
//logAi->debugStream() << "Looking for an another place for exploration...";
SectorMap sm(h);
std::vector<std::vector<int3> > tiles; //tiles[distance_to_fow]
tiles.resize(radius);
foreach_tile_pos([&](const int3 &pos)
{
if(!cb->isVisible(pos))
tiles[0].push_back(pos);
});
ui64 lowestDanger = -1;
int3 bestTile(-1,-1,-1);
for (int i = 1; i < radius; i++)
{
getVisibleNeighbours(tiles[i-1], tiles[i]);
removeDuplicates(tiles[i]);
for(const int3 &tile : tiles[i])
{
if (cb->getTile(tile)->blocked) //does it shorten the time?
continue;
if (!howManyTilesWillBeDiscovered(tile, radius)) //avoid costly checks of tiles that don't reveal much
continue;
auto t = sm.firstTileToGet(h, tile);
if (t.valid())
{
ui64 ourDanger = evaluateDanger(t, h.h);
if (ourDanger < lowestDanger)
{
if(!isBlockedBorderGate(t))
{
if (!ourDanger) //at least one safe place found
return t;
bestTile = t;
lowestDanger = ourDanger;
}
}
}
}
}
return bestTile;
}

View File

@ -178,7 +178,8 @@ public:
void tryRealize(Goals::AbstractGoal & g);
int3 explorationBestNeighbour(int3 hpos, int radius, HeroPtr h);
int3 explorationNewPoint(int radius, HeroPtr h, bool breakUnsafe = false);
int3 explorationNewPoint(int radius, HeroPtr h);
int3 explorationDesperate(int radius, HeroPtr h);
void recruitHero();
virtual std::string getBattleAIName() const override;
@ -296,6 +297,7 @@ public:
const CGTownInstance *findTownWithTavern() const;
bool canRecruitAnyHero(const CGTownInstance * t = NULL) const;
Goals::TSubgoal getGoal (HeroPtr h) const;
bool canAct(HeroPtr h) const;
std::vector<HeroPtr> getUnblockedHeroes() const;
HeroPtr primaryHero() const;

View File

@ -964,14 +964,14 @@ namespace Selector
extern DLL_LINKAGE const std::map<std::string, Bonus::BonusType> bonusNameMap;
extern DLL_LINKAGE const std::map<std::string, Bonus::ValueType> bonusValueMap;
extern DLL_LINKAGE const std::map<std::string, Bonus::BonusSource> bonusSourceMap;
extern DLL_LINKAGE const std::map<std::string, ui16> bonusDurationMap;
extern DLL_LINKAGE const std::map<std::string, Bonus::LimitEffect> bonusLimitEffect;
extern DLL_LINKAGE const std::map<std::string, TLimiterPtr> bonusLimiterMap;
extern DLL_LINKAGE const std::map<std::string, TPropagatorPtr> bonusPropagatorMap;
// BonusList template that requires full interface of CBonusSystemNode
extern DLL_LINKAGE const std::map<std::string, Bonus::BonusSource> bonusSourceMap;
extern DLL_LINKAGE const std::map<std::string, ui16> bonusDurationMap;
extern DLL_LINKAGE const std::map<std::string, Bonus::LimitEffect> bonusLimitEffect;
extern DLL_LINKAGE const std::map<std::string, TLimiterPtr> bonusLimiterMap;
extern DLL_LINKAGE const std::map<std::string, TPropagatorPtr> bonusPropagatorMap;
// BonusList template that requires full interface of CBonusSystemNode
template <class InputIterator>
void BonusList::insert(const int position, InputIterator first, InputIterator last)
{