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:
parent
afacb40f5a
commit
6ee823298a
@ -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;
|
||||
}
|
||||
|
137
AI/VCAI/VCAI.cpp
137
AI/VCAI/VCAI.cpp
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user