mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-16 10:19:47 +02:00
211 lines
5.3 KiB
C++
211 lines
5.3 KiB
C++
/*
|
|
* CollectRes.cpp, part of VCMI engine
|
|
*
|
|
* Authors: listed in file AUTHORS in main folder
|
|
*
|
|
* License: GNU General Public License v2.0 or later
|
|
* Full text of license available in license.txt file, in main folder
|
|
*
|
|
*/
|
|
#include "StdInc.h"
|
|
#include "Goals.h"
|
|
#include "../VCAI.h"
|
|
#include "../AIUtility.h"
|
|
#include "../AIhelper.h"
|
|
#include "../FuzzyHelper.h"
|
|
#include "../ResourceManager.h"
|
|
#include "../BuildingManager.h"
|
|
#include "../../../lib/mapObjects/CGMarket.h"
|
|
#include "../../../lib/constants/StringConstants.h"
|
|
|
|
using namespace Goals;
|
|
|
|
bool CollectRes::operator==(const CollectRes & other) const
|
|
{
|
|
return resID == other.resID;
|
|
}
|
|
|
|
TGoalVec CollectRes::getAllPossibleSubgoals()
|
|
{
|
|
TGoalVec ret;
|
|
|
|
auto givesResource = [this](const CGObjectInstance * obj) -> bool
|
|
{
|
|
//TODO: move this logic to object side
|
|
//TODO: remember mithril exists
|
|
//TODO: water objects
|
|
//TODO: Creature banks
|
|
|
|
//return false first from once-visitable, before checking if they were even visited
|
|
switch (obj->ID.num)
|
|
{
|
|
case Obj::TREASURE_CHEST:
|
|
return resID == GameResID(EGameResID::GOLD);
|
|
break;
|
|
case Obj::RESOURCE:
|
|
return dynamic_cast<const CGResource*>(obj)->resourceID() == GameResID(resID);
|
|
break;
|
|
case Obj::MINE:
|
|
return (dynamic_cast<const CGMine*>(obj)->producedResource == GameResID(resID) &&
|
|
(cb->getPlayerRelations(obj->tempOwner, ai->playerID) == PlayerRelations::ENEMIES)); //don't capture our mines
|
|
break;
|
|
case Obj::CAMPFIRE:
|
|
return true; //contains all resources
|
|
break;
|
|
case Obj::WINDMILL:
|
|
switch (GameResID(resID).toEnum())
|
|
{
|
|
case EGameResID::GOLD:
|
|
case EGameResID::WOOD:
|
|
return false;
|
|
}
|
|
break;
|
|
case Obj::MYSTICAL_GARDEN:
|
|
if ((resID != GameResID(EGameResID::GOLD)) && (resID != GameResID(EGameResID::GEMS)))
|
|
return false;
|
|
break;
|
|
case Obj::WATER_WHEEL:
|
|
case Obj::LEAN_TO:
|
|
case Obj::WAGON:
|
|
if (resID != GameResID(EGameResID::GOLD))
|
|
return false;
|
|
break;
|
|
default:
|
|
return false;
|
|
break;
|
|
}
|
|
return !vstd::contains(ai->alreadyVisited, obj); //for weekly / once visitable
|
|
};
|
|
|
|
std::vector<const CGObjectInstance *> objs;
|
|
for (auto obj : ai->visitableObjs)
|
|
{
|
|
if (givesResource(obj))
|
|
objs.push_back(obj);
|
|
}
|
|
for (auto h : cb->getHeroesInfo())
|
|
{
|
|
std::vector<const CGObjectInstance *> ourObjs(objs); //copy common objects
|
|
|
|
for (auto obj : ai->reservedHeroesMap[h]) //add objects reserved by this hero
|
|
{
|
|
if (givesResource(obj))
|
|
ourObjs.push_back(obj);
|
|
}
|
|
|
|
for (auto obj : ourObjs)
|
|
{
|
|
auto waysToGo = ai->ah->howToVisitObj(h, ObjectIdRef(obj));
|
|
|
|
vstd::concatenate(ret, waysToGo);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
TSubgoal CollectRes::whatToDoToAchieve()
|
|
{
|
|
auto goals = getAllPossibleSubgoals();
|
|
auto trade = whatToDoToTrade();
|
|
if (!trade->invalid())
|
|
goals.push_back(trade);
|
|
|
|
if (goals.empty())
|
|
return sptr(Explore()); //we can always do that
|
|
else
|
|
return fh->chooseSolution(goals); //TODO: evaluate trading
|
|
}
|
|
|
|
TSubgoal CollectRes::whatToDoToTrade()
|
|
{
|
|
std::vector<const IMarket *> markets;
|
|
|
|
std::vector<const CGObjectInstance *> visObjs;
|
|
ai->retrieveVisitableObjs(visObjs, true);
|
|
for(const CGObjectInstance * obj : visObjs)
|
|
{
|
|
const auto * m = dynamic_cast<const IMarket*>(obj);
|
|
|
|
if(m && m->allowsTrade(EMarketMode::RESOURCE_RESOURCE))
|
|
{
|
|
if(obj->ID == Obj::TOWN)
|
|
{
|
|
if(obj->tempOwner == ai->playerID)
|
|
markets.push_back(m);
|
|
}
|
|
else
|
|
markets.push_back(m);
|
|
}
|
|
}
|
|
|
|
boost::sort(markets, [](const IMarket * m1, const IMarket * m2) -> bool
|
|
{
|
|
return m1->getMarketEfficiency() < m2->getMarketEfficiency();
|
|
});
|
|
|
|
markets.erase(boost::remove_if(markets, [](const IMarket * market) -> bool
|
|
{
|
|
auto * o = dynamic_cast<const CGObjectInstance *>(market);
|
|
// FIXME: disabled broken visitation of external markets
|
|
//if(o && !(o->ID == Obj::TOWN && o->tempOwner == ai->playerID))
|
|
|
|
if(o && o->ID == Obj::TOWN)
|
|
{
|
|
if(!ai->isAccessible(o->visitablePos()))
|
|
return true;
|
|
}
|
|
return false;
|
|
}), markets.end());
|
|
|
|
if (!markets.size())
|
|
{
|
|
for (const CGTownInstance * t : cb->getTownsInfo())
|
|
{
|
|
if (cb->canBuildStructure(t, BuildingID::MARKETPLACE) == EBuildingState::ALLOWED)
|
|
return sptr(BuildThis(BuildingID::MARKETPLACE, t).setpriority(2));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const IMarket * m = markets.back();
|
|
//attempt trade at back (best prices)
|
|
int howManyCanWeBuy = 0;
|
|
for (GameResID i = EGameResID::WOOD; i <= EGameResID::GOLD; ++i)
|
|
{
|
|
if (i.getNum() == resID)
|
|
continue;
|
|
int toGive = -1;
|
|
int toReceive = -1;
|
|
m->getOffer(i, resID, toGive, toReceive, EMarketMode::RESOURCE_RESOURCE);
|
|
assert(toGive > 0 && toReceive > 0);
|
|
howManyCanWeBuy += toReceive * (ai->ah->freeResources()[i] / toGive);
|
|
}
|
|
|
|
if (howManyCanWeBuy >= value)
|
|
{
|
|
auto * o = dynamic_cast<const CGObjectInstance *>(m);
|
|
auto backObj = cb->getTopObj(o->visitablePos()); //it'll be a hero if we have one there; otherwise marketplace
|
|
assert(backObj);
|
|
auto objid = o->id.getNum();
|
|
if (backObj->tempOwner != ai->playerID) //top object not owned
|
|
{
|
|
return sptr(VisitObj(objid)); //just go there
|
|
}
|
|
else //either it's our town, or we have hero there
|
|
{
|
|
return sptr(Trade(static_cast<EGameResID>(resID), value, objid).setisElementar(true)); //we can do this immediately
|
|
}
|
|
}
|
|
}
|
|
return sptr(Invalid()); //cannot trade
|
|
}
|
|
|
|
bool CollectRes::fulfillsMe(TSubgoal goal)
|
|
{
|
|
if (goal->resID == resID)
|
|
if (goal->value >= value)
|
|
return true;
|
|
|
|
return false;
|
|
}
|