1
0
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:
DjWarmonger 2013-12-23 20:46:01 +00:00
parent 7806bda663
commit aec04d920e
7 changed files with 326 additions and 208 deletions

View File

@ -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);
}
}

View File

@ -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));
@ -368,6 +370,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)
{
@ -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)
{

View File

@ -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();

View File

@ -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);
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))
{ //FIXME: this way we'll not visit gate and activate quest :?
return sptr (Goals::FindObj (Obj::KEYMASTER, cb->getTile(tileToHit)->visitableObjects.back()->subID));
}
//FIXME: this code shouldn't be necessary
if(tileToHit == tile)
TGoalVec ClearWayTo::getAllPossibleSubgoals()
{
TGoalVec ret;
for (auto h : cb->getHeroesInfo())
{
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)!");
cb->setSelection(h);
SectorMap sm;
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 :?
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)!");
//}
auto topObj = backOrNull(cb->getVisitableObjs(tileToHit));
if(topObj && topObj->ID == Obj::HERO && cb->getPlayerRelations(h->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES)
{
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()));
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);
}
if (ret.empty())
throw cannotFulfillGoalException("There is no known way to clear the way to tile " + tile());
return sptr (Goals::VisitTile(tileToHit).sethero(h));
//FIXME:: attempts to visit completely unreachable tile with hero results in stall
//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())
{
if(!t->visitingHero && howManyReinforcementsCanGet(hero,t))
auto pos = t->visitablePos();
if (ai->isAccessibleForHero(pos, hero))
{
if (ai->isAccessibleForHero(t->pos, hero) && !vstd::contains (ai->townVisitsThisWeek[hero], t))
townsReachable.push_back(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)
if(!t->visitingHero && howManyReinforcementsCanGet(hero,t))
{
return (h == heroDummy.h || !ai->isAccessibleForHero(heroDummy->visitablePos(), h, true) || !ai->canGetArmy(heroDummy.h, h));
});
if (otherHeroes.size())
{
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));
//go to the other hero if we are faster
else
return sptr (Goals::VisitHero(hero->id.getNum()).setisAbstract(true).sethero(h));
//let the other hero come to us
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)));
}
}
std::vector<const CGObjectInstance *> objs; //here we'll gather all dwellings
ai->retreiveVisitableObjs(objs, true);
erase_if(objs, [&](const CGObjectInstance *obj)
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));
});
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)));
//let the other hero come to us
}
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
{
boost::sort(objs, isCloser);
HeroPtr h = nullptr;
for(const CGObjectInstance *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));
}
}
}
for(auto h : cb->getHeroesInfo())
{
for (auto obj : objs)
{ //find safe dwelling
auto pos = obj->visitablePos();
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

View File

@ -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;

View File

@ -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);
@ -1189,20 +1250,22 @@ void VCAI::wander(HeroPtr h)
else if(townsNotReachable.size())
{
boost::sort(townsNotReachable, compareReinforcements);
//TODO pick the truly best
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();
//TODO pick the truly best
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))
recruitHero(t);
}
break;
break;
}
else if(cb->getResourceAmount(Res::GOLD) >= HERO_GOLD_COST)
{
{
std::vector<const CGTownInstance *> towns = cb->getTownsInfo();
erase_if(towns, [](const CGTownInstance *t) -> bool
{
@ -1212,13 +1275,13 @@ void VCAI::wander(HeroPtr h)
return false;
});
boost::sort(towns, compareArmyStrength);
if(towns.size())
recruitHero(towns.back());
break;
}
else
{
logAi->debugStream() << "Nowhere more to go...";
if(towns.size())
recruitHero(towns.back());
break;
}
else
{
logAi->debugStream() << "Nowhere more to go...";
break;
}
}
@ -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())

View File

@ -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