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);
|
boost::sort(nearbyVisitableObjs, isCloser);
|
||||||
if(nearbyVisitableObjs.size())
|
if(nearbyVisitableObjs.size())
|
||||||
return nearbyVisitableObjs.back()->visitablePos();
|
return nearbyVisitableObjs.back()->visitablePos();
|
||||||
@ -247,8 +248,7 @@ int3 whereToExplore(HeroPtr h)
|
|||||||
}
|
}
|
||||||
catch(cannotFulfillGoalException &e)
|
catch(cannotFulfillGoalException &e)
|
||||||
{
|
{
|
||||||
std::vector<std::vector<int3> > tiles; //tiles[distance_to_fow]
|
return ai->explorationNewPoint(radius, h);
|
||||||
return ai->explorationNewPoint(radius, h, tiles);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -337,6 +337,7 @@ FuzzyHelper::EvalVisitTile::~EvalVisitTile()
|
|||||||
delete heroStrength;
|
delete heroStrength;
|
||||||
delete tileDistance;
|
delete tileDistance;
|
||||||
delete missionImportance;
|
delete missionImportance;
|
||||||
|
delete movement;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FuzzyHelper::initVisitTile()
|
void FuzzyHelper::initVisitTile()
|
||||||
@ -347,9 +348,10 @@ void FuzzyHelper::initVisitTile()
|
|||||||
vt.heroStrength = new fl::InputLVar("heroStrength"); //we want to use weakest possible hero
|
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.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.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");
|
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("LOW", 0.9, SAFE_ATTACK_CONSTANT, true));
|
||||||
vt.strengthRatio->addTerm (new fl::ShoulderTerm("HIGH", SAFE_ATTACK_CONSTANT, SAFE_ATTACK_CONSTANT * 3, false));
|
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("LOW", 0, 1.1, true));
|
||||||
vt.value->addTerm (new fl::ShoulderTerm("HIGH", 1, 5, false));
|
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)
|
for (auto val : helper)
|
||||||
{
|
{
|
||||||
engine.addInputLVar(val);
|
engine.addInputLVar(val);
|
||||||
@ -387,6 +392,9 @@ void FuzzyHelper::initVisitTile()
|
|||||||
//pick nearby objects if it's easy, avoid long walks
|
//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 SMALL then Value is HIGH", engine));
|
||||||
vt.rules.addRule (new fl::MamdaniRule("if tileDistance is LONG then Value is LOW", 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);
|
engine.addRuleBlock (&vt.rules);
|
||||||
}
|
}
|
||||||
@ -416,6 +424,7 @@ float FuzzyHelper::evaluate (Goals::VisitTile & g)
|
|||||||
vt.heroStrength->setInput (g.hero->getTotalStrength());
|
vt.heroStrength->setInput (g.hero->getTotalStrength());
|
||||||
vt.tileDistance->setInput (distance);
|
vt.tileDistance->setInput (distance);
|
||||||
vt.missionImportance->setInput (missionImportance);
|
vt.missionImportance->setInput (missionImportance);
|
||||||
|
vt.movement->setInput(g.hero->movement);
|
||||||
|
|
||||||
engine.process (VISIT_TILE);
|
engine.process (VISIT_TILE);
|
||||||
output = vt.value->output().defuzzify();
|
output = vt.value->output().defuzzify();
|
||||||
@ -435,7 +444,7 @@ float FuzzyHelper::evaluate (Goals::VisitHero & g)
|
|||||||
}
|
}
|
||||||
float FuzzyHelper::evaluate (Goals::BuildThis & g)
|
float FuzzyHelper::evaluate (Goals::BuildThis & g)
|
||||||
{
|
{
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
float FuzzyHelper::evaluate (Goals::DigAtTile & g)
|
float FuzzyHelper::evaluate (Goals::DigAtTile & g)
|
||||||
{
|
{
|
||||||
|
@ -47,6 +47,7 @@ class FuzzyHelper
|
|||||||
fl::InputLVar * heroStrength;
|
fl::InputLVar * heroStrength;
|
||||||
fl::InputLVar * tileDistance;
|
fl::InputLVar * tileDistance;
|
||||||
fl::InputLVar * missionImportance;
|
fl::InputLVar * missionImportance;
|
||||||
|
fl::InputLVar * movement;
|
||||||
fl::OutputLVar * value;
|
fl::OutputLVar * value;
|
||||||
fl::RuleBlock rules;
|
fl::RuleBlock rules;
|
||||||
~EvalVisitTile();
|
~EvalVisitTile();
|
||||||
|
@ -29,6 +29,7 @@ TSubgoal Goals::sptr(const AbstractGoal & tmp)
|
|||||||
|
|
||||||
std::string Goals::AbstractGoal::name() const //TODO: virtualize
|
std::string Goals::AbstractGoal::name() const //TODO: virtualize
|
||||||
{
|
{
|
||||||
|
std::string desc;
|
||||||
switch (goalType)
|
switch (goalType)
|
||||||
{
|
{
|
||||||
case INVALID:
|
case INVALID:
|
||||||
@ -42,38 +43,53 @@ std::string Goals::AbstractGoal::name() const //TODO: virtualize
|
|||||||
case BUILD:
|
case BUILD:
|
||||||
return "BUILD";
|
return "BUILD";
|
||||||
case EXPLORE:
|
case EXPLORE:
|
||||||
return "EXPLORE";
|
desc = "EXPLORE";
|
||||||
|
break;
|
||||||
case GATHER_ARMY:
|
case GATHER_ARMY:
|
||||||
return "GATHER ARMY";
|
desc = "GATHER ARMY";
|
||||||
|
break;
|
||||||
case BOOST_HERO:
|
case BOOST_HERO:
|
||||||
return "BOOST_HERO (unsupported)";
|
desc = "BOOST_HERO (unsupported)";
|
||||||
|
break;
|
||||||
case RECRUIT_HERO:
|
case RECRUIT_HERO:
|
||||||
return "RECRUIT HERO";
|
return "RECRUIT HERO";
|
||||||
case BUILD_STRUCTURE:
|
case BUILD_STRUCTURE:
|
||||||
return "BUILD STRUCTURE";
|
return "BUILD STRUCTURE";
|
||||||
case COLLECT_RES:
|
case COLLECT_RES:
|
||||||
return "COLLECT RESOURCE";
|
desc = "COLLECT RESOURCE";
|
||||||
|
break;
|
||||||
case GATHER_TROOPS:
|
case GATHER_TROOPS:
|
||||||
return "GATHER TROOPS";
|
desc = "GATHER TROOPS";
|
||||||
|
break;
|
||||||
case GET_OBJ:
|
case GET_OBJ:
|
||||||
return "GET OBJECT " + boost::lexical_cast<std::string>(objid);
|
desc = "GET OBJ " + cb->getObjInstance(ObjectInstanceID(objid))->getHoverText();
|
||||||
|
break;
|
||||||
case FIND_OBJ:
|
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:
|
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:
|
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:
|
case ISSUE_COMMAND:
|
||||||
return "ISSUE COMMAND (unsupported)";
|
return "ISSUE COMMAND (unsupported)";
|
||||||
case VISIT_TILE:
|
case VISIT_TILE:
|
||||||
return "VISIT TILE " + tile();
|
desc = "VISIT TILE " + tile();
|
||||||
|
break;
|
||||||
case CLEAR_WAY_TO:
|
case CLEAR_WAY_TO:
|
||||||
return "CLEAR WAY TO " + tile();
|
desc = "CLEAR WAY TO " + tile();
|
||||||
|
break;
|
||||||
case DIG_AT_TILE:
|
case DIG_AT_TILE:
|
||||||
return "DIG AT TILE " + tile();
|
desc = "DIG AT TILE " + tile();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return boost::lexical_cast<std::string>(goalType);
|
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?
|
//TODO: find out why the following are not generated automatically on MVS?
|
||||||
@ -312,52 +328,56 @@ float GetArtOfType::importanceWhenLocked() const
|
|||||||
|
|
||||||
TSubgoal ClearWayTo::whatToDoToAchieve()
|
TSubgoal ClearWayTo::whatToDoToAchieve()
|
||||||
{
|
{
|
||||||
assert(tile.x >= 0); //set tile
|
assert(cb->isInTheMap(tile)); //set tile
|
||||||
if(!cb->isVisible(tile))
|
if(!cb->isVisible(tile))
|
||||||
{
|
{
|
||||||
logAi->errorStream() << "Clear way should be used with visible tiles!";
|
logAi->errorStream() << "Clear way should be used with visible tiles!";
|
||||||
return sptr (Goals::Explore());
|
return sptr (Goals::Explore());
|
||||||
}
|
}
|
||||||
|
|
||||||
HeroPtr h = hero ? hero : ai->primaryHero();
|
return (fh->chooseSolution(getAllPossibleSubgoals()));
|
||||||
if(!h)
|
}
|
||||||
return sptr (Goals::RecruitHero());
|
|
||||||
|
|
||||||
cb->setSelection(*h);
|
TGoalVec ClearWayTo::getAllPossibleSubgoals()
|
||||||
|
{
|
||||||
|
TGoalVec ret;
|
||||||
|
for (auto h : cb->getHeroesInfo())
|
||||||
|
{
|
||||||
|
cb->setSelection(h);
|
||||||
|
|
||||||
SectorMap sm;
|
SectorMap sm;
|
||||||
bool dropToFile = false;
|
|
||||||
if(dropToFile) //for debug purposes
|
|
||||||
sm.write("test.txt");
|
|
||||||
|
|
||||||
int3 tileToHit = sm.firstTileToGet(h, tile);
|
int3 tileToHit = sm.firstTileToGet(hero ? hero : h, tile);
|
||||||
//if(isSafeToVisit(h, tileToHit))
|
//if our hero is trapped, make sure we request clearing the way from OUR perspective
|
||||||
if(isBlockedBorderGate(tileToHit))
|
|
||||||
|
if (isBlockedBorderGate(tileToHit))
|
||||||
{ //FIXME: this way we'll not visit gate and activate quest :?
|
{ //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
|
////FIXME: this code shouldn't be necessary
|
||||||
if(tileToHit == tile)
|
//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")
|
// 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();
|
// % tileToHit % tile % h->name % h->visitablePos();
|
||||||
throw cannotFulfillGoalException("Retrieving first tile to hit failed (probably)!");
|
// throw cannotFulfillGoalException("Retrieving first tile to hit failed (probably)!");
|
||||||
}
|
//}
|
||||||
|
|
||||||
auto topObj = backOrNull(cb->getVisitableObjs(tileToHit));
|
auto topObj = backOrNull(cb->getVisitableObjs(tileToHit));
|
||||||
if(topObj && topObj->ID == Obj::HERO && cb->getPlayerRelations(h->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES)
|
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());
|
logAi->errorStream() << boost::format("%s stands in the way of %s.\n") % topObj->getHoverText() % h->getHoverText();
|
||||||
throw cannotFulfillGoalException(problem);
|
|
||||||
}
|
}
|
||||||
|
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));
|
if (ret.empty())
|
||||||
//FIXME:: attempts to visit completely unreachable tile with hero results in stall
|
throw cannotFulfillGoalException("There is no known way to clear the way to tile " + tile());
|
||||||
|
|
||||||
//TODO czy istnieje lepsza droga?
|
return ret;
|
||||||
|
|
||||||
throw cannotFulfillGoalException("Cannot reach given tile!"); //how and when could this be used?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float ClearWayTo::importanceWhenLocked() const
|
float ClearWayTo::importanceWhenLocked() const
|
||||||
@ -387,12 +407,15 @@ TSubgoal Explore::whatToDoToAchieve()
|
|||||||
TGoalVec Explore::getAllPossibleSubgoals()
|
TGoalVec Explore::getAllPossibleSubgoals()
|
||||||
{
|
{
|
||||||
TGoalVec ret;
|
TGoalVec ret;
|
||||||
std::vector<HeroPtr> heroes;
|
std::vector<const CGHeroInstance *> heroes;
|
||||||
|
//std::vector<HeroPtr> heroes;
|
||||||
if (hero)
|
if (hero)
|
||||||
heroes.push_back(hero);
|
//heroes.push_back(hero);
|
||||||
|
heroes.push_back(hero.h);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
heroes = ai->getUnblockedHeroes();
|
//heroes = ai->getUnblockedHeroes();
|
||||||
|
heroes = cb->getHeroesInfo();
|
||||||
erase_if (heroes, [](const HeroPtr h)
|
erase_if (heroes, [](const HeroPtr h)
|
||||||
{
|
{
|
||||||
return !h->movement; //saves time, immobile heroes are useless anyway
|
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)
|
if (cb->isInTheMap(t)) //valid tile was found - could be invalid (none)
|
||||||
ret.push_back (sptr (Goals::VisitTile(t).sethero(h)));
|
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()));
|
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())
|
if (ret.empty())
|
||||||
throw cannotFulfillGoalException("Cannot explore - no possible ways found!");
|
throw cannotFulfillGoalException("Cannot explore - no possible ways found!");
|
||||||
|
|
||||||
@ -473,7 +507,8 @@ TSubgoal VisitTile::whatToDoToAchieve()
|
|||||||
}
|
}
|
||||||
else
|
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;
|
return ret;
|
||||||
@ -507,6 +542,7 @@ TGoalVec VisitTile::getAllPossibleSubgoals()
|
|||||||
}
|
}
|
||||||
if (ret.empty())
|
if (ret.empty())
|
||||||
ret.push_back (sptr(Goals::ClearWayTo(tile)));
|
ret.push_back (sptr(Goals::ClearWayTo(tile)));
|
||||||
|
|
||||||
//important - at least one sub-goal must handle case which is impossible to fulfill (unreachable tile)
|
//important - at least one sub-goal must handle case which is impossible to fulfill (unreachable tile)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -746,68 +782,54 @@ std::string GatherArmy::completeMessage() const
|
|||||||
TSubgoal GatherArmy::whatToDoToAchieve()
|
TSubgoal GatherArmy::whatToDoToAchieve()
|
||||||
{
|
{
|
||||||
//TODO: find hero if none set
|
//TODO: find hero if none set
|
||||||
assert(hero);
|
assert(hero.h);
|
||||||
|
|
||||||
cb->setSelection(*hero);
|
return fh->chooseSolution (getAllPossibleSubgoals()); //find dwelling. use current hero to prevent him from doing nothing.
|
||||||
auto compareReinforcements = [this](const CGTownInstance *lhs, const CGTownInstance *rhs) -> bool
|
}
|
||||||
|
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);
|
auto pos = t->visitablePos();
|
||||||
};
|
if (ai->isAccessibleForHero(pos, hero))
|
||||||
|
|
||||||
std::vector<const CGTownInstance *> townsReachable;
|
|
||||||
for(const CGTownInstance *t : cb->getTownsInfo())
|
|
||||||
{
|
{
|
||||||
if(!t->visitingHero && howManyReinforcementsCanGet(hero,t))
|
if(!t->visitingHero && howManyReinforcementsCanGet(hero,t))
|
||||||
{
|
{
|
||||||
if (ai->isAccessibleForHero(t->pos, hero) && !vstd::contains (ai->townVisitsThisWeek[hero], t))
|
if (!vstd::contains (ai->townVisitsThisWeek[hero], t))
|
||||||
townsReachable.push_back(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 otherHeroes = cb->getHeroesInfo();
|
||||||
auto heroDummy = hero;
|
auto heroDummy = hero;
|
||||||
erase_if(otherHeroes, [heroDummy](const CGHeroInstance * h)
|
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));
|
||||||
});
|
});
|
||||||
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)
|
ret.push_back (sptr (Goals::VisitHero(h->id.getNum()).setisAbstract(true).sethero(hero)));
|
||||||
return sptr (Goals::VisitHero(h->id.getNum()).setisAbstract(true).sethero(hero));
|
|
||||||
//go to the other hero if we are faster
|
//go to the other hero if we are faster
|
||||||
else
|
ret.push_back (sptr (Goals::VisitHero(hero->id.getNum()).setisAbstract(true).sethero(h)));
|
||||||
return sptr (Goals::VisitHero(hero->id.getNum()).setisAbstract(true).sethero(h));
|
|
||||||
//let the other hero come to us
|
//let the other hero come to us
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<const CGObjectInstance *> objs; //here we'll gather all dwellings
|
std::vector <const CGObjectInstance *> objs;
|
||||||
ai->retreiveVisitableObjs(objs, true);
|
for (auto obj : ai->visitableObjs)
|
||||||
erase_if(objs, [&](const CGObjectInstance *obj)
|
{
|
||||||
|
if(obj->ID == Obj::CREATURE_GENERATOR1)
|
||||||
{
|
{
|
||||||
if(obj->ID != Obj::CREATURE_GENERATOR1)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
auto relationToOwner = cb->getPlayerRelations(obj->getOwner(), ai->playerID);
|
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
|
//Use flagged dwellings only when there are available creatures that we can afford
|
||||||
if(relationToOwner == PlayerRelations::SAME_PLAYER)
|
if(relationToOwner == PlayerRelations::SAME_PLAYER)
|
||||||
@ -820,42 +842,27 @@ TSubgoal GatherArmy::whatToDoToAchieve()
|
|||||||
for(auto & creatureID : creLevel.second)
|
for(auto & creatureID : creLevel.second)
|
||||||
{
|
{
|
||||||
auto creature = VLC->creh->creatures[creatureID];
|
auto creature = VLC->creh->creatures[creatureID];
|
||||||
if(ai->freeResources().canAfford(creature->cost))
|
if (ai->freeResources().canAfford(creature->cost))
|
||||||
return false;
|
objs.push_back(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return true;
|
}
|
||||||
});
|
for(auto h : cb->getHeroesInfo())
|
||||||
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
|
|
||||||
{
|
{
|
||||||
boost::sort(objs, isCloser);
|
for (auto obj : objs)
|
||||||
HeroPtr h = nullptr;
|
|
||||||
for(const CGObjectInstance *obj : objs)
|
|
||||||
{ //find safe dwelling
|
{ //find safe dwelling
|
||||||
auto pos = obj->visitablePos();
|
auto pos = obj->visitablePos();
|
||||||
if (shouldVisit (hero, obj)) //creatures fit in army
|
if (shouldVisit (h, obj) && isSafeToVisit(h, pos) && ai->isAccessibleForHero(pos, h))
|
||||||
h = hero;
|
ret.push_back (sptr (Goals::VisitTile(pos).sethero(h)));
|
||||||
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 (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
|
float GatherArmy::importanceWhenLocked() const
|
||||||
|
@ -240,7 +240,7 @@ private:
|
|||||||
GatherArmy() : CGoal (Goals::GATHER_ARMY){};
|
GatherArmy() : CGoal (Goals::GATHER_ARMY){};
|
||||||
public:
|
public:
|
||||||
GatherArmy(int val) : CGoal (Goals::GATHER_ARMY){value = val;};
|
GatherArmy(int val) : CGoal (Goals::GATHER_ARMY){value = val;};
|
||||||
TGoalVec getAllPossibleSubgoals() override {return TGoalVec();};
|
TGoalVec getAllPossibleSubgoals() override;
|
||||||
TSubgoal whatToDoToAchieve() override;
|
TSubgoal whatToDoToAchieve() override;
|
||||||
std::string completeMessage() const override;
|
std::string completeMessage() const override;
|
||||||
float importanceWhenLocked() const override;
|
float importanceWhenLocked() const override;
|
||||||
@ -356,7 +356,7 @@ class ClearWayTo : public CGoal<ClearWayTo>
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ClearWayTo(int3 Tile) : CGoal (Goals::CLEAR_WAY_TO) {tile = Tile;};
|
ClearWayTo(int3 Tile) : CGoal (Goals::CLEAR_WAY_TO) {tile = Tile;};
|
||||||
TGoalVec getAllPossibleSubgoals() override {return TGoalVec();};
|
TGoalVec getAllPossibleSubgoals() override;
|
||||||
TSubgoal whatToDoToAchieve() override;
|
TSubgoal whatToDoToAchieve() override;
|
||||||
bool operator== (ClearWayTo &g) {return g.tile == tile;}
|
bool operator== (ClearWayTo &g) {return g.tile == tile;}
|
||||||
float importanceWhenLocked() const override;
|
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;
|
NET_EVENT_HANDLER;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VCAI::heroCreated(const CGHeroInstance*)
|
void VCAI::heroCreated(const CGHeroInstance* h)
|
||||||
{
|
{
|
||||||
LOG_TRACE(logAi);
|
LOG_TRACE(logAi);
|
||||||
|
if (h->visitedTown)
|
||||||
|
townVisitsThisWeek[HeroPtr(h)].push_back(h->visitedTown);
|
||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -523,6 +525,9 @@ void VCAI::init(shared_ptr<CCallback> CB)
|
|||||||
fh = new FuzzyHelper();
|
fh = new FuzzyHelper();
|
||||||
|
|
||||||
retreiveVisitableObjs(visitableObjs);
|
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()
|
void VCAI::yourTurn()
|
||||||
@ -651,30 +656,6 @@ void VCAI::makeTurn()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
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())
|
if(cb->getSelectedHero())
|
||||||
cb->recalculatePaths();
|
cb->recalculatePaths();
|
||||||
@ -1006,6 +987,84 @@ bool VCAI::tryBuildStructure(const CGTownInstance * t, BuildingID building, unsi
|
|||||||
return false;
|
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)
|
bool VCAI::tryBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays)
|
||||||
{
|
{
|
||||||
for(const auto & building : buildList)
|
for(const auto & building : buildList)
|
||||||
@ -1018,6 +1077,18 @@ bool VCAI::tryBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID
|
|||||||
return false; //Can't build anything
|
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)
|
bool VCAI::tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays)
|
||||||
{
|
{
|
||||||
for(const auto & building : buildList)
|
for(const auto & building : buildList)
|
||||||
@ -1036,20 +1107,6 @@ void VCAI::buildStructure(const CGTownInstance * t)
|
|||||||
//TODO: build resource silo, defences when needed
|
//TODO: build resource silo, defences when needed
|
||||||
//Possible - allow "locking" on specific building (build prerequisites and then building itself)
|
//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 currentRes = cb->getResourceAmount();
|
||||||
TResources income = estimateIncome();
|
TResources income = estimateIncome();
|
||||||
|
|
||||||
@ -1147,6 +1204,7 @@ bool VCAI::canRecruitAnyHero (const CGTownInstance * t) const
|
|||||||
|
|
||||||
void VCAI::wander(HeroPtr h)
|
void VCAI::wander(HeroPtr h)
|
||||||
{
|
{
|
||||||
|
TimeCheck tc("looking for wander destination");
|
||||||
while(1)
|
while(1)
|
||||||
{
|
{
|
||||||
validateVisitableObjs();
|
validateVisitableObjs();
|
||||||
@ -1164,6 +1222,9 @@ void VCAI::wander(HeroPtr h)
|
|||||||
|
|
||||||
if(!dests.size())
|
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
|
auto compareReinforcements = [h](const CGTownInstance *lhs, const CGTownInstance *rhs) -> bool
|
||||||
{
|
{
|
||||||
return howManyReinforcementsCanGet(h, lhs) < howManyReinforcementsCanGet(h, rhs);
|
return howManyReinforcementsCanGet(h, lhs) < howManyReinforcementsCanGet(h, rhs);
|
||||||
@ -1193,7 +1254,9 @@ void VCAI::wander(HeroPtr h)
|
|||||||
const CGTownInstance *t = townsNotReachable.back();
|
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();
|
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;
|
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 (pos1 == h->pos && h == primaryHero()) //hero can't move
|
||||||
{
|
{
|
||||||
if (canRecruitAnyHero(t))
|
if (canRecruitAnyHero(t))
|
||||||
@ -1233,10 +1296,6 @@ void VCAI::wander(HeroPtr h)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
logAi->debugStream() << boost::format("Hero %s apparently used all MPs (%d left)") % h->name % h->movement;
|
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;
|
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());
|
//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));
|
//throw goalFulfilledException (CGoal(GET_OBJ).setobjid(visitedObject->id));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(h) //we could have lost hero after last move
|
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();
|
cb->recalculatePaths();
|
||||||
if (startHpos == h->visitablePos() && !ret) //we didn't move and didn't reach the target
|
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!");
|
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...";
|
logAi->debugStream() << "Looking for an another place for exploration...";
|
||||||
|
|
||||||
|
std::vector<std::vector<int3> > tiles; //tiles[distance_to_fow]
|
||||||
tiles.resize(radius);
|
tiles.resize(radius);
|
||||||
|
|
||||||
foreach_tile_pos([&](const int3 &pos)
|
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);
|
tiles[0].push_back(pos);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
int bestValue = 0;
|
||||||
|
int3 bestTile(-1,-1,-1);
|
||||||
|
|
||||||
for (int i = 1; i < radius; i++)
|
for (int i = 1; i < radius; i++)
|
||||||
{
|
{
|
||||||
getVisibleNeighbours(tiles[i-1], tiles[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?
|
if (cb->getTile(tile)->blocked) //does it shorten the time?
|
||||||
continue;
|
continue;
|
||||||
if(cb->getPathInfo(tile)->reachable() && howManyTilesWillBeDiscovered(tile, radius) &&
|
int ourValue = howManyTilesWillBeDiscovered(tile, radius);
|
||||||
isSafeToVisit(h, tile) && !isBlockedBorderGate(tile))
|
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
|
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();
|
logAi->debugStream() << boost::format("Trying to recruit a hero in %s at %s") % t->name % t->visitablePos();
|
||||||
|
|
||||||
if(auto availableHero = frontOrNull(cb->getAvailableHeroes(t)))
|
auto heroes = cb->getAvailableHeroes(t);
|
||||||
cb->recruitHero(t, availableHero);
|
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)
|
else if(throwing)
|
||||||
throw cannotFulfillGoalException("No available heroes in tavern in " + t->nodeName());
|
throw cannotFulfillGoalException("No available heroes in tavern in " + t->nodeName());
|
||||||
}
|
}
|
||||||
@ -2496,6 +2578,9 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
|
|||||||
{
|
{
|
||||||
switch (obj->ID)
|
switch (obj->ID)
|
||||||
{
|
{
|
||||||
|
case Obj::TOWN:
|
||||||
|
return obj->tempOwner != h->tempOwner; //do not visit our towns at random
|
||||||
|
break;
|
||||||
case Obj::BORDER_GATE:
|
case Obj::BORDER_GATE:
|
||||||
{
|
{
|
||||||
for (auto q : ai->myCb->getMyQuests())
|
for (auto q : ai->myCb->getMyQuests())
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include "../../lib/CThreadHelper.h"
|
#include "../../lib/CThreadHelper.h"
|
||||||
|
|
||||||
|
#include "../../lib/GameConstants.h"
|
||||||
#include "../../lib/VCMI_Lib.h"
|
#include "../../lib/VCMI_Lib.h"
|
||||||
#include "../../lib/CBuildingHandler.h"
|
#include "../../lib/CBuildingHandler.h"
|
||||||
#include "../../lib/CCreatureHandler.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?
|
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
|
class VCAI : public CAdventureAI
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
//internal methods for town development
|
//internal methods for town development
|
||||||
|
|
||||||
//try build an unbuilt structure in maxDays at most (0 = indefinite)
|
//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);
|
bool tryBuildStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays=7);
|
||||||
//try build ANY unbuilt structure
|
//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);
|
bool tryBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays=7);
|
||||||
//try build first unbuilt structure
|
//try build first unbuilt structure
|
||||||
bool tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays=7);
|
bool tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays=7);
|
||||||
|
|
||||||
public:
|
|
||||||
friend class FuzzyHelper;
|
friend class FuzzyHelper;
|
||||||
|
|
||||||
std::map<const CGObjectInstance *, const CGObjectInstance *> knownSubterraneanGates;
|
std::map<const CGObjectInstance *, const CGObjectInstance *> knownSubterraneanGates;
|
||||||
@ -160,7 +176,7 @@ public:
|
|||||||
void tryRealize(Goals::AbstractGoal & g);
|
void tryRealize(Goals::AbstractGoal & g);
|
||||||
|
|
||||||
int3 explorationBestNeighbour(int3 hpos, int radius, HeroPtr h);
|
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();
|
void recruitHero();
|
||||||
|
|
||||||
virtual std::string getBattleAIName() const override;
|
virtual std::string getBattleAIName() const override;
|
||||||
@ -259,7 +275,7 @@ public:
|
|||||||
|
|
||||||
void addVisitableObj(const CGObjectInstance *obj);
|
void addVisitableObj(const CGObjectInstance *obj);
|
||||||
void markObjectVisited (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 removeVisitableObj(const CGObjectInstance *obj);
|
||||||
void validateObject(const CGObjectInstance *obj); //checks if object is still visible and if not, removes references to it
|
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
|
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