mirror of
https://github.com/vcmi/vcmi.git
synced 2025-02-03 13:01:33 +02:00
Endless crusade against AI issues and loopholes!
- Fixed #1126 - ClearWayTo and GatherArmy goals will also consider multiple subgoals - GatherArmy may include building dwellings in town (experimental)
This commit is contained in:
parent
7806bda663
commit
aec04d920e
@ -237,6 +237,7 @@ int3 whereToExplore(HeroPtr h)
|
||||
}
|
||||
}
|
||||
}
|
||||
removeDuplicates (nearbyVisitableObjs); //one object may occupy multiple tiles
|
||||
boost::sort(nearbyVisitableObjs, isCloser);
|
||||
if(nearbyVisitableObjs.size())
|
||||
return nearbyVisitableObjs.back()->visitablePos();
|
||||
@ -247,8 +248,7 @@ int3 whereToExplore(HeroPtr h)
|
||||
}
|
||||
catch(cannotFulfillGoalException &e)
|
||||
{
|
||||
std::vector<std::vector<int3> > tiles; //tiles[distance_to_fow]
|
||||
return ai->explorationNewPoint(radius, h, tiles);
|
||||
return ai->explorationNewPoint(radius, h);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -337,6 +337,7 @@ FuzzyHelper::EvalVisitTile::~EvalVisitTile()
|
||||
delete heroStrength;
|
||||
delete tileDistance;
|
||||
delete missionImportance;
|
||||
delete movement;
|
||||
}
|
||||
|
||||
void FuzzyHelper::initVisitTile()
|
||||
@ -347,9 +348,10 @@ void FuzzyHelper::initVisitTile()
|
||||
vt.heroStrength = new fl::InputLVar("heroStrength"); //we want to use weakest possible hero
|
||||
vt.tileDistance = new fl::InputLVar("tileDistance"); //we want to use hero who is near
|
||||
vt.missionImportance = new fl::InputLVar("lockedMissionImportance"); //we may want to preempt hero with low-priority mission
|
||||
vt.movement = new fl::InputLVar("movement");
|
||||
vt.value = new fl::OutputLVar("Value");
|
||||
|
||||
helper += vt.strengthRatio, vt.heroStrength, vt.tileDistance, vt.missionImportance;
|
||||
helper += vt.strengthRatio, vt.heroStrength, vt.tileDistance, vt.missionImportance, vt.movement;
|
||||
|
||||
vt.strengthRatio->addTerm (new fl::ShoulderTerm("LOW", 0.9, SAFE_ATTACK_CONSTANT, true));
|
||||
vt.strengthRatio->addTerm (new fl::ShoulderTerm("HIGH", SAFE_ATTACK_CONSTANT, SAFE_ATTACK_CONSTANT * 3, false));
|
||||
@ -369,6 +371,9 @@ void FuzzyHelper::initVisitTile()
|
||||
vt.value->addTerm (new fl::ShoulderTerm("LOW", 0, 1.1, true));
|
||||
vt.value->addTerm (new fl::ShoulderTerm("HIGH", 1, 5, false));
|
||||
|
||||
vt.movement->addTerm (new fl::ShoulderTerm("LOW", 1, 200, true));
|
||||
vt.movement->addTerm (new fl::ShoulderTerm("HIGH", 1000, 2000, false));
|
||||
|
||||
for (auto val : helper)
|
||||
{
|
||||
engine.addInputLVar(val);
|
||||
@ -387,6 +392,9 @@ void FuzzyHelper::initVisitTile()
|
||||
//pick nearby objects if it's easy, avoid long walks
|
||||
vt.rules.addRule (new fl::MamdaniRule("if tileDistance is SMALL then Value is HIGH", engine));
|
||||
vt.rules.addRule (new fl::MamdaniRule("if tileDistance is LONG then Value is LOW", engine));
|
||||
//use heroes with movement points first
|
||||
vt.rules.addRule (new fl::MamdaniRule("if movement is LOW then Value is somewhat LOW", engine));
|
||||
vt.rules.addRule (new fl::MamdaniRule("if movement is HIGH then Value is somewhat HIGH", engine));
|
||||
|
||||
engine.addRuleBlock (&vt.rules);
|
||||
}
|
||||
@ -416,6 +424,7 @@ float FuzzyHelper::evaluate (Goals::VisitTile & g)
|
||||
vt.heroStrength->setInput (g.hero->getTotalStrength());
|
||||
vt.tileDistance->setInput (distance);
|
||||
vt.missionImportance->setInput (missionImportance);
|
||||
vt.movement->setInput(g.hero->movement);
|
||||
|
||||
engine.process (VISIT_TILE);
|
||||
output = vt.value->output().defuzzify();
|
||||
@ -435,7 +444,7 @@ float FuzzyHelper::evaluate (Goals::VisitHero & g)
|
||||
}
|
||||
float FuzzyHelper::evaluate (Goals::BuildThis & g)
|
||||
{
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
float FuzzyHelper::evaluate (Goals::DigAtTile & g)
|
||||
{
|
||||
|
@ -47,6 +47,7 @@ class FuzzyHelper
|
||||
fl::InputLVar * heroStrength;
|
||||
fl::InputLVar * tileDistance;
|
||||
fl::InputLVar * missionImportance;
|
||||
fl::InputLVar * movement;
|
||||
fl::OutputLVar * value;
|
||||
fl::RuleBlock rules;
|
||||
~EvalVisitTile();
|
||||
|
@ -29,6 +29,7 @@ TSubgoal Goals::sptr(const AbstractGoal & tmp)
|
||||
|
||||
std::string Goals::AbstractGoal::name() const //TODO: virtualize
|
||||
{
|
||||
std::string desc;
|
||||
switch (goalType)
|
||||
{
|
||||
case INVALID:
|
||||
@ -42,38 +43,53 @@ std::string Goals::AbstractGoal::name() const //TODO: virtualize
|
||||
case BUILD:
|
||||
return "BUILD";
|
||||
case EXPLORE:
|
||||
return "EXPLORE";
|
||||
desc = "EXPLORE";
|
||||
break;
|
||||
case GATHER_ARMY:
|
||||
return "GATHER ARMY";
|
||||
desc = "GATHER ARMY";
|
||||
break;
|
||||
case BOOST_HERO:
|
||||
return "BOOST_HERO (unsupported)";
|
||||
desc = "BOOST_HERO (unsupported)";
|
||||
break;
|
||||
case RECRUIT_HERO:
|
||||
return "RECRUIT HERO";
|
||||
case BUILD_STRUCTURE:
|
||||
return "BUILD STRUCTURE";
|
||||
case COLLECT_RES:
|
||||
return "COLLECT RESOURCE";
|
||||
desc = "COLLECT RESOURCE";
|
||||
break;
|
||||
case GATHER_TROOPS:
|
||||
return "GATHER TROOPS";
|
||||
desc = "GATHER TROOPS";
|
||||
break;
|
||||
case GET_OBJ:
|
||||
return "GET OBJECT " + boost::lexical_cast<std::string>(objid);
|
||||
desc = "GET OBJ " + cb->getObjInstance(ObjectInstanceID(objid))->getHoverText();
|
||||
break;
|
||||
case FIND_OBJ:
|
||||
return "FIND OBJECT " + boost::lexical_cast<std::string>(objid);
|
||||
desc = "FIND OBJ " + boost::lexical_cast<std::string>(objid);
|
||||
break;
|
||||
case VISIT_HERO:
|
||||
return "VISIT HERO " + boost::lexical_cast<std::string>(objid);
|
||||
desc = "VISIT HERO " + cb->getObjInstance(ObjectInstanceID(objid))->getHoverText();
|
||||
break;
|
||||
case GET_ART_TYPE:
|
||||
return "GET ARTIFACT OF TYPE " + VLC->arth->artifacts[aid]->Name();
|
||||
desc = "GET ARTIFACT OF TYPE " + VLC->arth->artifacts[aid]->Name();
|
||||
break;
|
||||
case ISSUE_COMMAND:
|
||||
return "ISSUE COMMAND (unsupported)";
|
||||
case VISIT_TILE:
|
||||
return "VISIT TILE " + tile();
|
||||
desc = "VISIT TILE " + tile();
|
||||
break;
|
||||
case CLEAR_WAY_TO:
|
||||
return "CLEAR WAY TO " + tile();
|
||||
desc = "CLEAR WAY TO " + tile();
|
||||
break;
|
||||
case DIG_AT_TILE:
|
||||
return "DIG AT TILE " + tile();
|
||||
desc = "DIG AT TILE " + tile();
|
||||
break;
|
||||
default:
|
||||
return boost::lexical_cast<std::string>(goalType);
|
||||
}
|
||||
if (hero.h)
|
||||
desc += " (" + hero->name + ")";
|
||||
return desc;
|
||||
}
|
||||
|
||||
//TODO: find out why the following are not generated automatically on MVS?
|
||||
@ -312,52 +328,56 @@ float GetArtOfType::importanceWhenLocked() const
|
||||
|
||||
TSubgoal ClearWayTo::whatToDoToAchieve()
|
||||
{
|
||||
assert(tile.x >= 0); //set tile
|
||||
assert(cb->isInTheMap(tile)); //set tile
|
||||
if(!cb->isVisible(tile))
|
||||
{
|
||||
logAi->errorStream() << "Clear way should be used with visible tiles!";
|
||||
return sptr (Goals::Explore());
|
||||
}
|
||||
|
||||
HeroPtr h = hero ? hero : ai->primaryHero();
|
||||
if(!h)
|
||||
return sptr (Goals::RecruitHero());
|
||||
return (fh->chooseSolution(getAllPossibleSubgoals()));
|
||||
}
|
||||
|
||||
cb->setSelection(*h);
|
||||
TGoalVec ClearWayTo::getAllPossibleSubgoals()
|
||||
{
|
||||
TGoalVec ret;
|
||||
for (auto h : cb->getHeroesInfo())
|
||||
{
|
||||
cb->setSelection(h);
|
||||
|
||||
SectorMap sm;
|
||||
bool dropToFile = false;
|
||||
if(dropToFile) //for debug purposes
|
||||
sm.write("test.txt");
|
||||
|
||||
int3 tileToHit = sm.firstTileToGet(h, tile);
|
||||
//if(isSafeToVisit(h, tileToHit))
|
||||
if(isBlockedBorderGate(tileToHit))
|
||||
int3 tileToHit = sm.firstTileToGet(hero ? hero : h, tile);
|
||||
//if our hero is trapped, make sure we request clearing the way from OUR perspective
|
||||
|
||||
if (isBlockedBorderGate(tileToHit))
|
||||
{ //FIXME: this way we'll not visit gate and activate quest :?
|
||||
return sptr (Goals::FindObj (Obj::KEYMASTER, cb->getTile(tileToHit)->visitableObjects.back()->subID));
|
||||
ret.push_back (sptr (Goals::FindObj (Obj::KEYMASTER, cb->getTile(tileToHit)->visitableObjects.back()->subID)));
|
||||
}
|
||||
|
||||
//FIXME: this code shouldn't be necessary
|
||||
if(tileToHit == tile)
|
||||
{
|
||||
logAi->errorStream() << boost::format("Very strange, tile to hit is %s and tile is also %s, while hero %s is at %s\n")
|
||||
% tileToHit % tile % h->name % h->visitablePos();
|
||||
throw cannotFulfillGoalException("Retrieving first tile to hit failed (probably)!");
|
||||
}
|
||||
////FIXME: this code shouldn't be necessary
|
||||
//if(tileToHit == tile)
|
||||
//{
|
||||
// logAi->errorStream() << boost::format("Very strange, tile to hit is %s and tile is also %s, while hero %s is at %s\n")
|
||||
// % tileToHit % tile % h->name % h->visitablePos();
|
||||
// throw cannotFulfillGoalException("Retrieving first tile to hit failed (probably)!");
|
||||
//}
|
||||
|
||||
auto topObj = backOrNull(cb->getVisitableObjs(tileToHit));
|
||||
if(topObj && topObj->ID == Obj::HERO && cb->getPlayerRelations(h->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES)
|
||||
{
|
||||
std::string problem = boost::str(boost::format("%s stands in the way of %s.\n") % topObj->getHoverText() % h->getHoverText());
|
||||
throw cannotFulfillGoalException(problem);
|
||||
logAi->errorStream() << boost::format("%s stands in the way of %s.\n") % topObj->getHoverText() % h->getHoverText();
|
||||
}
|
||||
else
|
||||
ret.push_back (sptr (Goals::VisitTile(tileToHit).sethero(h)));
|
||||
}
|
||||
if (ai->canRecruitAnyHero())
|
||||
ret.push_back (sptr (Goals::RecruitHero()));
|
||||
|
||||
return sptr (Goals::VisitTile(tileToHit).sethero(h));
|
||||
//FIXME:: attempts to visit completely unreachable tile with hero results in stall
|
||||
if (ret.empty())
|
||||
throw cannotFulfillGoalException("There is no known way to clear the way to tile " + tile());
|
||||
|
||||
//TODO czy istnieje lepsza droga?
|
||||
|
||||
throw cannotFulfillGoalException("Cannot reach given tile!"); //how and when could this be used?
|
||||
return ret;
|
||||
}
|
||||
|
||||
float ClearWayTo::importanceWhenLocked() const
|
||||
@ -387,12 +407,15 @@ TSubgoal Explore::whatToDoToAchieve()
|
||||
TGoalVec Explore::getAllPossibleSubgoals()
|
||||
{
|
||||
TGoalVec ret;
|
||||
std::vector<HeroPtr> heroes;
|
||||
std::vector<const CGHeroInstance *> heroes;
|
||||
//std::vector<HeroPtr> heroes;
|
||||
if (hero)
|
||||
heroes.push_back(hero);
|
||||
//heroes.push_back(hero);
|
||||
heroes.push_back(hero.h);
|
||||
else
|
||||
{
|
||||
heroes = ai->getUnblockedHeroes();
|
||||
//heroes = ai->getUnblockedHeroes();
|
||||
heroes = cb->getHeroesInfo();
|
||||
erase_if (heroes, [](const HeroPtr h)
|
||||
{
|
||||
return !h->movement; //saves time, immobile heroes are useless anyway
|
||||
@ -429,9 +452,20 @@ TGoalVec Explore::getAllPossibleSubgoals()
|
||||
if (cb->isInTheMap(t)) //valid tile was found - could be invalid (none)
|
||||
ret.push_back (sptr (Goals::VisitTile(t).sethero(h)));
|
||||
}
|
||||
if (!hero && ai->canRecruitAnyHero())//if hero is assigned to that goal, no need to buy another one yet
|
||||
//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())
|
||||
{
|
||||
auto h = ai->primaryHero(); //we may need to gather big army to break!
|
||||
if (h.h)
|
||||
{
|
||||
int3 t = ai->explorationNewPoint(h->getSightRadious(), h, true);
|
||||
if (cb->isInTheMap(t))
|
||||
ret.push_back (sptr(ClearWayTo(t).setisAbstract(true).sethero(ai->primaryHero())));
|
||||
}
|
||||
}
|
||||
if (ret.empty())
|
||||
throw cannotFulfillGoalException("Cannot explore - no possible ways found!");
|
||||
|
||||
@ -473,7 +507,8 @@ TSubgoal VisitTile::whatToDoToAchieve()
|
||||
}
|
||||
else
|
||||
{
|
||||
return sptr (Goals::GatherArmy(evaluateDanger(tile, *ret->hero) * SAFE_ATTACK_CONSTANT).sethero(ret->hero));
|
||||
return sptr (Goals::GatherArmy(evaluateDanger(tile, *ret->hero) * SAFE_ATTACK_CONSTANT)
|
||||
.sethero(ret->hero).setisAbstract(true));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
@ -507,6 +542,7 @@ TGoalVec VisitTile::getAllPossibleSubgoals()
|
||||
}
|
||||
if (ret.empty())
|
||||
ret.push_back (sptr(Goals::ClearWayTo(tile)));
|
||||
|
||||
//important - at least one sub-goal must handle case which is impossible to fulfill (unreachable tile)
|
||||
return ret;
|
||||
}
|
||||
@ -746,68 +782,54 @@ std::string GatherArmy::completeMessage() const
|
||||
TSubgoal GatherArmy::whatToDoToAchieve()
|
||||
{
|
||||
//TODO: find hero if none set
|
||||
assert(hero);
|
||||
assert(hero.h);
|
||||
|
||||
cb->setSelection(*hero);
|
||||
auto compareReinforcements = [this](const CGTownInstance *lhs, const CGTownInstance *rhs) -> bool
|
||||
return fh->chooseSolution (getAllPossibleSubgoals()); //find dwelling. use current hero to prevent him from doing nothing.
|
||||
}
|
||||
TGoalVec GatherArmy::getAllPossibleSubgoals()
|
||||
{
|
||||
//get all possible towns, heroes and dwellings we may use
|
||||
TGoalVec ret;
|
||||
|
||||
//TODO: include evaluation of monsters gather in calculation
|
||||
for (auto t : cb->getTownsInfo())
|
||||
{
|
||||
return howManyReinforcementsCanGet(hero, lhs) < howManyReinforcementsCanGet(hero, rhs);
|
||||
};
|
||||
|
||||
std::vector<const CGTownInstance *> townsReachable;
|
||||
for(const CGTownInstance *t : cb->getTownsInfo())
|
||||
auto pos = t->visitablePos();
|
||||
if (ai->isAccessibleForHero(pos, hero))
|
||||
{
|
||||
if(!t->visitingHero && howManyReinforcementsCanGet(hero,t))
|
||||
{
|
||||
if (ai->isAccessibleForHero(t->pos, hero) && !vstd::contains (ai->townVisitsThisWeek[hero], t))
|
||||
townsReachable.push_back(t);
|
||||
if (!vstd::contains (ai->townVisitsThisWeek[hero], t))
|
||||
ret.push_back (sptr (Goals::VisitTile(pos).sethero(hero)));
|
||||
}
|
||||
auto bid = ai->canBuildAnyStructure(t, std::vector<BuildingID>
|
||||
(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 8 - cb->getDate(Date::DAY_OF_WEEK));
|
||||
if (bid != BuildingID::NONE)
|
||||
ret.push_back (sptr(BuildThis(bid, t)));
|
||||
}
|
||||
}
|
||||
|
||||
if(townsReachable.size()) //try towns first
|
||||
{
|
||||
boost::sort(townsReachable, compareReinforcements);
|
||||
return sptr (Goals::VisitTile(townsReachable.back()->visitablePos()).sethero(hero));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hero == ai->primaryHero()) //we can get army from other heroes
|
||||
{
|
||||
auto otherHeroes = cb->getHeroesInfo();
|
||||
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));
|
||||
});
|
||||
if (otherHeroes.size())
|
||||
for (auto h : otherHeroes)
|
||||
{
|
||||
boost::sort(otherHeroes, compareArmyStrength); //TODO: check if hero has at least one stack more powerful than ours? not likely to fail
|
||||
int primaryPath, secondaryPath;
|
||||
auto h = otherHeroes.back();
|
||||
cb->setSelection(hero.h);
|
||||
primaryPath = cb->getPathInfo(h->visitablePos())->turns;
|
||||
cb->setSelection(h);
|
||||
secondaryPath = cb->getPathInfo(hero->visitablePos())->turns;
|
||||
|
||||
if (primaryPath < secondaryPath)
|
||||
return sptr (Goals::VisitHero(h->id.getNum()).setisAbstract(true).sethero(hero));
|
||||
ret.push_back (sptr (Goals::VisitHero(h->id.getNum()).setisAbstract(true).sethero(hero)));
|
||||
//go to the other hero if we are faster
|
||||
else
|
||||
return sptr (Goals::VisitHero(hero->id.getNum()).setisAbstract(true).sethero(h));
|
||||
ret.push_back (sptr (Goals::VisitHero(hero->id.getNum()).setisAbstract(true).sethero(h)));
|
||||
//let the other hero come to us
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<const CGObjectInstance *> objs; //here we'll gather all dwellings
|
||||
ai->retreiveVisitableObjs(objs, true);
|
||||
erase_if(objs, [&](const CGObjectInstance *obj)
|
||||
std::vector <const CGObjectInstance *> objs;
|
||||
for (auto obj : ai->visitableObjs)
|
||||
{
|
||||
if(obj->ID == Obj::CREATURE_GENERATOR1)
|
||||
{
|
||||
if(obj->ID != Obj::CREATURE_GENERATOR1)
|
||||
return true;
|
||||
|
||||
auto relationToOwner = cb->getPlayerRelations(obj->getOwner(), ai->playerID);
|
||||
if(relationToOwner == PlayerRelations::ALLIES)
|
||||
return true;
|
||||
|
||||
//Use flagged dwellings only when there are available creatures that we can afford
|
||||
if(relationToOwner == PlayerRelations::SAME_PLAYER)
|
||||
@ -820,42 +842,27 @@ TSubgoal GatherArmy::whatToDoToAchieve()
|
||||
for(auto & creatureID : creLevel.second)
|
||||
{
|
||||
auto creature = VLC->creh->creatures[creatureID];
|
||||
if(ai->freeResources().canAfford(creature->cost))
|
||||
return false;
|
||||
if (ai->freeResources().canAfford(creature->cost))
|
||||
objs.push_back(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
if(objs.empty()) //no possible objects, we did eveyrthing already
|
||||
return sptr (Goals::Explore(hero));
|
||||
//TODO: check if we can recruit any creatures there, evaluate army
|
||||
else
|
||||
}
|
||||
}
|
||||
for(auto h : cb->getHeroesInfo())
|
||||
{
|
||||
boost::sort(objs, isCloser);
|
||||
HeroPtr h = nullptr;
|
||||
for(const CGObjectInstance *obj : objs)
|
||||
for (auto obj : objs)
|
||||
{ //find safe dwelling
|
||||
auto pos = obj->visitablePos();
|
||||
if (shouldVisit (hero, obj)) //creatures fit in army
|
||||
h = hero;
|
||||
else
|
||||
{
|
||||
for(auto ourHero : cb->getHeroesInfo()) //make use of multiple heroes
|
||||
{
|
||||
if (shouldVisit(ourHero, obj))
|
||||
h = ourHero;
|
||||
}
|
||||
}
|
||||
if (h && isSafeToVisit(h, pos) && ai->isAccessibleForHero(pos, h))
|
||||
return sptr (Goals::VisitTile(pos).sethero(h));
|
||||
}
|
||||
if (shouldVisit (h, obj) && isSafeToVisit(h, pos) && ai->isAccessibleForHero(pos, h))
|
||||
ret.push_back (sptr (Goals::VisitTile(pos).sethero(h)));
|
||||
}
|
||||
}
|
||||
if (ret.empty())
|
||||
ret.push_back (sptr(Goals::Explore()));
|
||||
|
||||
return sptr (Goals::Explore(hero)); //find dwelling. use current hero to prevent him from doing nothing.
|
||||
return ret;
|
||||
}
|
||||
|
||||
float GatherArmy::importanceWhenLocked() const
|
||||
|
@ -240,7 +240,7 @@ private:
|
||||
GatherArmy() : CGoal (Goals::GATHER_ARMY){};
|
||||
public:
|
||||
GatherArmy(int val) : CGoal (Goals::GATHER_ARMY){value = val;};
|
||||
TGoalVec getAllPossibleSubgoals() override {return TGoalVec();};
|
||||
TGoalVec getAllPossibleSubgoals() override;
|
||||
TSubgoal whatToDoToAchieve() override;
|
||||
std::string completeMessage() const override;
|
||||
float importanceWhenLocked() const override;
|
||||
@ -356,7 +356,7 @@ class ClearWayTo : public CGoal<ClearWayTo>
|
||||
{
|
||||
public:
|
||||
ClearWayTo(int3 Tile) : CGoal (Goals::CLEAR_WAY_TO) {tile = Tile;};
|
||||
TGoalVec getAllPossibleSubgoals() override {return TGoalVec();};
|
||||
TGoalVec getAllPossibleSubgoals() override;
|
||||
TSubgoal whatToDoToAchieve() override;
|
||||
bool operator== (ClearWayTo &g) {return g.tile == tile;}
|
||||
float importanceWhenLocked() const override;
|
||||
|
191
AI/VCAI/VCAI.cpp
191
AI/VCAI/VCAI.cpp
@ -406,9 +406,11 @@ void VCAI::newStackInserted(const StackLocation &location, const CStackInstance
|
||||
NET_EVENT_HANDLER;
|
||||
}
|
||||
|
||||
void VCAI::heroCreated(const CGHeroInstance*)
|
||||
void VCAI::heroCreated(const CGHeroInstance* h)
|
||||
{
|
||||
LOG_TRACE(logAi);
|
||||
if (h->visitedTown)
|
||||
townVisitsThisWeek[HeroPtr(h)].push_back(h->visitedTown);
|
||||
NET_EVENT_HANDLER;
|
||||
}
|
||||
|
||||
@ -523,6 +525,9 @@ void VCAI::init(shared_ptr<CCallback> CB)
|
||||
fh = new FuzzyHelper();
|
||||
|
||||
retreiveVisitableObjs(visitableObjs);
|
||||
//for (auto h : myCb->getHeroesInfo()) //make sure heroes won't try to revisit town in first move
|
||||
// if (h->visitedTown)
|
||||
// markObjectVisited(h->visitedTown);
|
||||
}
|
||||
|
||||
void VCAI::yourTurn()
|
||||
@ -651,30 +656,6 @@ void VCAI::makeTurn()
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 7: //reconsider strategy
|
||||
{
|
||||
if(auto h = primaryHero()) //check if our primary hero can handle danger
|
||||
{
|
||||
ui64 totalDanger = 0;
|
||||
int dangerousObjects = 0;
|
||||
std::vector<const CGObjectInstance *> objs;
|
||||
retreiveVisitableObjs(objs, false);
|
||||
for (auto obj : objs)
|
||||
{
|
||||
if (evaluateDanger(obj)) //potentilaly dnagerous
|
||||
{
|
||||
totalDanger += evaluateDanger(obj->visitablePos(), *h);
|
||||
++dangerousObjects;
|
||||
}
|
||||
}
|
||||
ui64 averageDanger = totalDanger / std::max(dangerousObjects, 1);
|
||||
if (dangerousObjects && averageDanger > h->getHeroStrength())
|
||||
{
|
||||
setGoal (h, sptr(Goals::GatherArmy(averageDanger * SAFE_ATTACK_CONSTANT).sethero(h).setisAbstract(true)));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if(cb->getSelectedHero())
|
||||
cb->recalculatePaths();
|
||||
@ -1006,6 +987,84 @@ bool VCAI::tryBuildStructure(const CGTownInstance * t, BuildingID building, unsi
|
||||
return false;
|
||||
}
|
||||
|
||||
//bool VCAI::canBuildStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays=7)
|
||||
//{
|
||||
// if (maxDays == 0)
|
||||
// {
|
||||
// logAi->warnStream() << "Request to build building " << building << " in 0 days!";
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if (!vstd::contains(t->town->buildings, building))
|
||||
// return false; // no such building in town
|
||||
//
|
||||
// if (t->hasBuilt(building)) //Already built? Shouldn't happen in general
|
||||
// return true;
|
||||
//
|
||||
// const CBuilding * buildPtr = t->town->buildings.at(building);
|
||||
//
|
||||
// auto toBuild = buildPtr->requirements.getFulfillmentCandidates([&](const BuildingID & buildID)
|
||||
// {
|
||||
// return t->hasBuilt(buildID);
|
||||
// });
|
||||
// toBuild.push_back(building);
|
||||
//
|
||||
// for(BuildingID buildID : toBuild)
|
||||
// {
|
||||
// EBuildingState::EBuildingState canBuild = cb->canBuildStructure(t, buildID);
|
||||
// if (canBuild == EBuildingState::HAVE_CAPITAL
|
||||
// || canBuild == EBuildingState::FORBIDDEN
|
||||
// || canBuild == EBuildingState::NO_WATER)
|
||||
// return false; //we won't be able to build this
|
||||
// }
|
||||
//
|
||||
// if (maxDays && toBuild.size() > maxDays)
|
||||
// return false;
|
||||
//
|
||||
// TResources currentRes = cb->getResourceAmount();
|
||||
// TResources income = estimateIncome();
|
||||
// //TODO: calculate if we have enough resources to build it in maxDays
|
||||
//
|
||||
// for(const auto & buildID : toBuild)
|
||||
// {
|
||||
// const CBuilding *b = t->town->buildings.at(buildID);
|
||||
//
|
||||
// EBuildingState::EBuildingState canBuild = cb->canBuildStructure(t, buildID);
|
||||
// if(canBuild == EBuildingState::ALLOWED)
|
||||
// {
|
||||
// if(!containsSavedRes(b->resources))
|
||||
// {
|
||||
// logAi->debugStream() << boost::format("Player %d will build %s in town of %s at %s") % playerID % b->Name() % t->name % t->pos;
|
||||
// return true;
|
||||
// }
|
||||
// continue;
|
||||
// }
|
||||
// else if(canBuild == EBuildingState::NO_RESOURCES)
|
||||
// {
|
||||
// TResources cost = t->town->buildings.at(buildID)->resources;
|
||||
// for (int i = 0; i < GameConstants::RESOURCE_QUANTITY; i++)
|
||||
// {
|
||||
// int diff = currentRes[i] - cost[i] + income[i];
|
||||
// if(diff < 0)
|
||||
// saving[i] = 1;
|
||||
// }
|
||||
// continue;
|
||||
// }
|
||||
// else if (canBuild == EBuildingState::PREREQUIRES)
|
||||
// {
|
||||
// // can happen when dependencies have their own missing dependencies
|
||||
// if (canBuildStructure(t, buildID, maxDays - 1))
|
||||
// return true;
|
||||
// }
|
||||
// else if (canBuild == EBuildingState::MISSING_BASE)
|
||||
// {
|
||||
// if (canBuildStructure(t, b->upgrade, maxDays - 1))
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
//}
|
||||
|
||||
bool VCAI::tryBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays)
|
||||
{
|
||||
for(const auto & building : buildList)
|
||||
@ -1018,6 +1077,18 @@ bool VCAI::tryBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID
|
||||
return false; //Can't build anything
|
||||
}
|
||||
|
||||
BuildingID VCAI::canBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays)
|
||||
{
|
||||
for(const auto & building : buildList)
|
||||
{
|
||||
if(t->hasBuilt(building))
|
||||
continue;
|
||||
if (cb->canBuildStructure(t, building))
|
||||
return building;
|
||||
}
|
||||
return BuildingID::NONE; //Can't build anything
|
||||
}
|
||||
|
||||
bool VCAI::tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays)
|
||||
{
|
||||
for(const auto & building : buildList)
|
||||
@ -1036,20 +1107,6 @@ void VCAI::buildStructure(const CGTownInstance * t)
|
||||
//TODO: build resource silo, defences when needed
|
||||
//Possible - allow "locking" on specific building (build prerequisites and then building itself)
|
||||
|
||||
//Set of buildings for different goals. Does not include any prerequisites.
|
||||
const BuildingID essential[] = {BuildingID::TAVERN, BuildingID::TOWN_HALL};
|
||||
const BuildingID goldSource[] = {BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL};
|
||||
const BuildingID unitsSource[] = { BuildingID::DWELL_LVL_1, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3,
|
||||
BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7};
|
||||
const BuildingID unitsUpgrade[] = { BuildingID::DWELL_LVL_1_UP, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP,
|
||||
BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP};
|
||||
const BuildingID unitGrowth[] = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::HORDE_1,
|
||||
BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR};
|
||||
const BuildingID spells[] = {BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3,
|
||||
BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5};
|
||||
const BuildingID extra[] = {BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, BuildingID::SPECIAL_3,
|
||||
BuildingID::SPECIAL_4, BuildingID::SHIPYARD}; // all remaining buildings
|
||||
|
||||
TResources currentRes = cb->getResourceAmount();
|
||||
TResources income = estimateIncome();
|
||||
|
||||
@ -1147,6 +1204,7 @@ bool VCAI::canRecruitAnyHero (const CGTownInstance * t) const
|
||||
|
||||
void VCAI::wander(HeroPtr h)
|
||||
{
|
||||
TimeCheck tc("looking for wander destination");
|
||||
while(1)
|
||||
{
|
||||
validateVisitableObjs();
|
||||
@ -1164,6 +1222,9 @@ void VCAI::wander(HeroPtr h)
|
||||
|
||||
if(!dests.size())
|
||||
{
|
||||
if (cb->getVisitableObjs(h->visitablePos()).size() > 1)
|
||||
moveHeroToTile(h->visitablePos(), h); //just in case we're standing on blocked subterranean gate
|
||||
|
||||
auto compareReinforcements = [h](const CGTownInstance *lhs, const CGTownInstance *rhs) -> bool
|
||||
{
|
||||
return howManyReinforcementsCanGet(h, lhs) < howManyReinforcementsCanGet(h, rhs);
|
||||
@ -1193,7 +1254,9 @@ void VCAI::wander(HeroPtr h)
|
||||
const CGTownInstance *t = townsNotReachable.back();
|
||||
logAi->debugStream() << boost::format("%s can't reach any town, we'll try to make our way to %s at %s") % h->name % t->name % t->visitablePos();
|
||||
int3 pos1 = h->pos;
|
||||
striveToGoal(sptr(Goals::VisitTile(t->visitablePos()).sethero(h)));
|
||||
striveToGoal(sptr(Goals::ClearWayTo(t->visitablePos()).sethero(h)));
|
||||
//if out hero is stuck, we may need to request another hero to clear the way we see
|
||||
|
||||
if (pos1 == h->pos && h == primaryHero()) //hero can't move
|
||||
{
|
||||
if (canRecruitAnyHero(t))
|
||||
@ -1233,10 +1296,6 @@ void VCAI::wander(HeroPtr h)
|
||||
else
|
||||
{
|
||||
logAi->debugStream() << boost::format("Hero %s apparently used all MPs (%d left)") % h->name % h->movement;
|
||||
reserveObject(h, dest); //reserve that object - we predict it will be reached soon
|
||||
|
||||
//removed - do not forget abstract goal so easily
|
||||
//setGoal(h, CGoal(VISIT_TILE).sethero(h).settile(dest->visitablePos()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -1520,9 +1579,15 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
|
||||
//BNLOG("Hero %s moved from %s to %s at %s", h->name % startHpos % visitedObject->hoverName % h->visitablePos());
|
||||
//throw goalFulfilledException (CGoal(GET_OBJ).setobjid(visitedObject->id));
|
||||
}
|
||||
|
||||
if(h) //we could have lost hero after last move
|
||||
{
|
||||
if (!ret) //reserve object we are heading towards
|
||||
{
|
||||
auto obj = frontOrNull(cb->getVisitableObjs(dst));
|
||||
if (obj)
|
||||
reserveObject(h, obj);
|
||||
}
|
||||
|
||||
cb->recalculatePaths();
|
||||
if (startHpos == h->visitablePos() && !ret) //we didn't move and didn't reach the target
|
||||
{
|
||||
@ -2023,9 +2088,11 @@ int3 VCAI::explorationBestNeighbour(int3 hpos, int radius, HeroPtr h)
|
||||
throw cannotFulfillGoalException("No neighbour will bring new discoveries!");
|
||||
}
|
||||
|
||||
int3 VCAI::explorationNewPoint(int radius, HeroPtr h, std::vector<std::vector<int3> > &tiles)
|
||||
int3 VCAI::explorationNewPoint(int radius, HeroPtr h, bool breakUnsafe)
|
||||
{
|
||||
logAi->debugStream() << "Looking for an another place for exploration...";
|
||||
|
||||
std::vector<std::vector<int3> > tiles; //tiles[distance_to_fow]
|
||||
tiles.resize(radius);
|
||||
|
||||
foreach_tile_pos([&](const int3 &pos)
|
||||
@ -2034,6 +2101,9 @@ int3 VCAI::explorationNewPoint(int radius, HeroPtr h, std::vector<std::vector<in
|
||||
tiles[0].push_back(pos);
|
||||
});
|
||||
|
||||
int bestValue = 0;
|
||||
int3 bestTile(-1,-1,-1);
|
||||
|
||||
for (int i = 1; i < radius; i++)
|
||||
{
|
||||
getVisibleNeighbours(tiles[i-1], tiles[i]);
|
||||
@ -2043,15 +2113,18 @@ int3 VCAI::explorationNewPoint(int radius, HeroPtr h, std::vector<std::vector<in
|
||||
{
|
||||
if (cb->getTile(tile)->blocked) //does it shorten the time?
|
||||
continue;
|
||||
if(cb->getPathInfo(tile)->reachable() && howManyTilesWillBeDiscovered(tile, radius) &&
|
||||
isSafeToVisit(h, tile) && !isBlockedBorderGate(tile))
|
||||
int ourValue = howManyTilesWillBeDiscovered(tile, radius);
|
||||
if (ourValue > bestValue) //avoid costly checks of tiles that don't reveal much
|
||||
{
|
||||
return tile; //return first tile that will discover anything
|
||||
if(cb->getPathInfo(tile)->reachable() && (isSafeToVisit(h, tile) || breakUnsafe) && !isBlockedBorderGate(tile))
|
||||
{
|
||||
bestTile = tile; //return first tile that will discover anything
|
||||
bestValue = ourValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return int3 (-1,-1,-1);
|
||||
//throw cannotFulfillGoalException("No accessible tile will bring discoveries!");
|
||||
}
|
||||
return bestTile;
|
||||
}
|
||||
|
||||
TResources VCAI::estimateIncome() const
|
||||
@ -2125,8 +2198,17 @@ void VCAI::recruitHero(const CGTownInstance * t, bool throwing)
|
||||
{
|
||||
logAi->debugStream() << boost::format("Trying to recruit a hero in %s at %s") % t->name % t->visitablePos();
|
||||
|
||||
if(auto availableHero = frontOrNull(cb->getAvailableHeroes(t)))
|
||||
cb->recruitHero(t, availableHero);
|
||||
auto heroes = cb->getAvailableHeroes(t);
|
||||
if(heroes.size())
|
||||
{
|
||||
auto hero = heroes[0];
|
||||
if (heroes.size() >= 2) //makes sense to recruit two heroes with starting amries in first week
|
||||
{
|
||||
if (heroes[1]->getTotalStrength() > hero->getTotalStrength())
|
||||
hero = heroes[1];
|
||||
}
|
||||
cb->recruitHero(t, hero);
|
||||
}
|
||||
else if(throwing)
|
||||
throw cannotFulfillGoalException("No available heroes in tavern in " + t->nodeName());
|
||||
}
|
||||
@ -2496,6 +2578,9 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
|
||||
{
|
||||
switch (obj->ID)
|
||||
{
|
||||
case Obj::TOWN:
|
||||
return obj->tempOwner != h->tempOwner; //do not visit our towns at random
|
||||
break;
|
||||
case Obj::BORDER_GATE:
|
||||
{
|
||||
for (auto q : ai->myCb->getMyQuests())
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "../../lib/CThreadHelper.h"
|
||||
|
||||
#include "../../lib/GameConstants.h"
|
||||
#include "../../lib/VCMI_Lib.h"
|
||||
#include "../../lib/CBuildingHandler.h"
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
@ -109,19 +110,34 @@ struct SectorMap
|
||||
int3 firstTileToGet(HeroPtr h, crint3 dst); //if h wants to reach tile dst, which tile he should visit to clear the way?
|
||||
};
|
||||
|
||||
//Set of buildings for different goals. Does not include any prerequisites.
|
||||
const BuildingID essential[] = {BuildingID::TAVERN, BuildingID::TOWN_HALL};
|
||||
const BuildingID goldSource[] = {BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL};
|
||||
const BuildingID unitsSource[] = { BuildingID::DWELL_LVL_1, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3,
|
||||
BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7};
|
||||
const BuildingID unitsUpgrade[] = { BuildingID::DWELL_LVL_1_UP, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP,
|
||||
BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP};
|
||||
const BuildingID unitGrowth[] = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::HORDE_1,
|
||||
BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR};
|
||||
const BuildingID spells[] = {BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3,
|
||||
BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5};
|
||||
const BuildingID extra[] = {BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, BuildingID::SPECIAL_3,
|
||||
BuildingID::SPECIAL_4, BuildingID::SHIPYARD}; // all remaining buildings
|
||||
|
||||
class VCAI : public CAdventureAI
|
||||
{
|
||||
public:
|
||||
//internal methods for town development
|
||||
|
||||
//try build an unbuilt structure in maxDays at most (0 = indefinite)
|
||||
/*bool canBuildStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays=7);*/
|
||||
bool tryBuildStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays=7);
|
||||
//try build ANY unbuilt structure
|
||||
BuildingID canBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays=7);
|
||||
bool tryBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays=7);
|
||||
//try build first unbuilt structure
|
||||
bool tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays=7);
|
||||
|
||||
public:
|
||||
friend class FuzzyHelper;
|
||||
|
||||
std::map<const CGObjectInstance *, const CGObjectInstance *> knownSubterraneanGates;
|
||||
@ -160,7 +176,7 @@ public:
|
||||
void tryRealize(Goals::AbstractGoal & g);
|
||||
|
||||
int3 explorationBestNeighbour(int3 hpos, int radius, HeroPtr h);
|
||||
int3 explorationNewPoint(int radius, HeroPtr h, std::vector<std::vector<int3> > &tiles);
|
||||
int3 explorationNewPoint(int radius, HeroPtr h, bool breakUnsafe = false);
|
||||
void recruitHero();
|
||||
|
||||
virtual std::string getBattleAIName() const override;
|
||||
@ -259,7 +275,7 @@ public:
|
||||
|
||||
void addVisitableObj(const CGObjectInstance *obj);
|
||||
void markObjectVisited (const CGObjectInstance *obj);
|
||||
void reserveObject (HeroPtr h, const CGObjectInstance *obj);
|
||||
void reserveObject (HeroPtr h, const CGObjectInstance *obj); //TODO: reserve all objects that heroes attempt to visit
|
||||
//void removeVisitableObj(const CGObjectInstance *obj);
|
||||
void validateObject(const CGObjectInstance *obj); //checks if object is still visible and if not, removes references to it
|
||||
void validateObject(ObjectIdRef obj); //checks if object is still visible and if not, removes references to it
|
||||
|
Loading…
x
Reference in New Issue
Block a user