mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-24 22:14:36 +02:00
VCAI tweaks (#311)
* Add extra priority support for town capture evaluation * Improve building algorithm * GatherArmy: check free gold instead of total for when hiring heroes
This commit is contained in:
parent
98140aab6b
commit
0cb6515ae8
@ -338,6 +338,7 @@ FuzzyHelper::EvalVisitTile::~EvalVisitTile()
|
||||
delete heroStrength;
|
||||
delete turnDistance;
|
||||
delete missionImportance;
|
||||
delete estimatedReward;
|
||||
}
|
||||
|
||||
void FuzzyHelper::initVisitTile()
|
||||
@ -348,11 +349,12 @@ void FuzzyHelper::initVisitTile()
|
||||
vt.heroStrength = new fl::InputVariable("heroStrength"); //we want to use weakest possible hero
|
||||
vt.turnDistance = new fl::InputVariable("turnDistance"); //we want to use hero who is near
|
||||
vt.missionImportance = new fl::InputVariable("lockedMissionImportance"); //we may want to preempt hero with low-priority mission
|
||||
vt.estimatedReward = new fl::InputVariable("estimatedReward"); //indicate AI that content of the file is important or it is probably bad
|
||||
vt.value = new fl::OutputVariable("Value");
|
||||
vt.value->setMinimum(0);
|
||||
vt.value->setMaximum(5);
|
||||
|
||||
std::vector<fl::InputVariable*> helper = {vt.strengthRatio, vt.heroStrength, vt.turnDistance, vt.missionImportance};
|
||||
std::vector<fl::InputVariable*> helper = {vt.strengthRatio, vt.heroStrength, vt.turnDistance, vt.missionImportance, vt.estimatedReward};
|
||||
for (auto val : helper)
|
||||
{
|
||||
vt.engine.addInputVariable(val);
|
||||
@ -379,6 +381,10 @@ void FuzzyHelper::initVisitTile()
|
||||
vt.missionImportance->addTerm(new fl::Ramp("HIGH", 2.5, 5));
|
||||
vt.missionImportance->setRange(0.0, 5.0);
|
||||
|
||||
vt.estimatedReward->addTerm(new fl::Ramp("LOW", 2.5, 0));
|
||||
vt.estimatedReward->addTerm(new fl::Ramp("HIGH", 2.5, 5));
|
||||
vt.estimatedReward->setRange(0.0, 5.0);
|
||||
|
||||
//an issue: in 99% cases this outputs center of mass (2.5) regardless of actual input :/
|
||||
//should be same as "mission Importance" to keep consistency
|
||||
vt.value->addTerm(new fl::Ramp("LOW", 2.5, 0));
|
||||
@ -405,6 +411,9 @@ void FuzzyHelper::initVisitTile()
|
||||
vt.addRule("if turnDistance is SMALL then Value is HIGH");
|
||||
vt.addRule("if turnDistance is MEDIUM then Value is MEDIUM");
|
||||
vt.addRule("if turnDistance is LONG then Value is LOW");
|
||||
//some goals are more rewarding by definition f.e. capturing town is more important than collecting resource - experimental
|
||||
vt.addRule("if estimatedReward is HIGH then Value is very HIGH");
|
||||
vt.addRule("if estimatedReward is LOW then Value is somewhat LOW");
|
||||
}
|
||||
catch (fl::Exception & fe)
|
||||
{
|
||||
@ -440,12 +449,22 @@ float FuzzyHelper::evaluate (Goals::VisitTile & g)
|
||||
if (danger)
|
||||
strengthRatio = (fl::scalar)g.hero.h->getTotalStrength() / danger;
|
||||
|
||||
float tilePriority = 0;
|
||||
if(g.objid == -1)
|
||||
vt.estimatedReward->setEnabled(false);
|
||||
else if(g.objid == Obj::TOWN) //TODO: move to getObj eventually and add appropiate logic there
|
||||
{
|
||||
vt.estimatedReward->setEnabled(true);
|
||||
tilePriority = 5;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
vt.strengthRatio->setInputValue(strengthRatio);
|
||||
vt.heroStrength->setInputValue((fl::scalar)g.hero->getTotalStrength() / ai->primaryHero()->getTotalStrength());
|
||||
vt.turnDistance->setInputValue(turns);
|
||||
vt.missionImportance->setInputValue(missionImportance);
|
||||
vt.estimatedReward->setInputValue(tilePriority);
|
||||
|
||||
vt.engine.process();
|
||||
//engine.process(VISIT_TILE); //TODO: Process only Visit_Tile
|
||||
|
@ -49,6 +49,7 @@ class FuzzyHelper
|
||||
fl::InputVariable * heroStrength;
|
||||
fl::InputVariable * turnDistance;
|
||||
fl::InputVariable * missionImportance;
|
||||
fl::InputVariable * estimatedReward;
|
||||
fl::OutputVariable * value;
|
||||
fl::RuleBlock rules;
|
||||
~EvalVisitTile();
|
||||
|
@ -719,10 +719,12 @@ TSubgoal VisitTile::whatToDoToAchieve()
|
||||
{
|
||||
auto ret = fh->chooseSolution(getAllPossibleSubgoals());
|
||||
|
||||
if (ret->hero)
|
||||
if(ret->hero)
|
||||
{
|
||||
if (isSafeToVisit(ret->hero, tile) && ai->isAccessibleForHero(tile, ret->hero))
|
||||
if(isSafeToVisit(ret->hero, tile) && ai->isAccessibleForHero(tile, ret->hero))
|
||||
{
|
||||
if(cb->getTile(tile)->topVisitableId().num == Obj::TOWN) //if target is town, fuzzy system will use additional "estimatedReward" variable to increase priority a bit
|
||||
ret->objid = Obj::TOWN; //TODO: move to getObj eventually and add appropiate logic there
|
||||
ret->setisElementar(true);
|
||||
return ret;
|
||||
}
|
||||
@ -973,40 +975,44 @@ TGoalVec Conquer::getAllPossibleSubgoals()
|
||||
};
|
||||
|
||||
std::vector<const CGObjectInstance *> objs;
|
||||
for (auto obj : ai->visitableObjs)
|
||||
for(auto obj : ai->visitableObjs)
|
||||
{
|
||||
if (conquerable(obj))
|
||||
if(conquerable(obj))
|
||||
objs.push_back (obj);
|
||||
}
|
||||
|
||||
for (auto h : cb->getHeroesInfo())
|
||||
for(auto h : cb->getHeroesInfo())
|
||||
{
|
||||
auto sm = ai->getCachedSectorMap(h);
|
||||
std::vector<const CGObjectInstance *> ourObjs(objs); //copy common objects
|
||||
|
||||
for (auto obj : ai->reservedHeroesMap[h]) //add objects reserved by this hero
|
||||
for(auto obj : ai->reservedHeroesMap[h]) //add objects reserved by this hero
|
||||
{
|
||||
if (conquerable(obj))
|
||||
if(conquerable(obj))
|
||||
ourObjs.push_back(obj);
|
||||
}
|
||||
for (auto obj : ourObjs)
|
||||
for(auto obj : ourObjs)
|
||||
{
|
||||
int3 dest = obj->visitablePos();
|
||||
auto t = sm->firstTileToGet(h, dest); //we assume that no more than one tile on the way is guarded
|
||||
if (t.valid()) //we know any path at all
|
||||
if(t.valid()) //we know any path at all
|
||||
{
|
||||
if (ai->isTileNotReserved(h, t)) //no other hero wants to conquer that tile
|
||||
if(ai->isTileNotReserved(h, t)) //no other hero wants to conquer that tile
|
||||
{
|
||||
if (isSafeToVisit(h, dest))
|
||||
if(isSafeToVisit(h, dest))
|
||||
{
|
||||
if (dest != t) //there is something blocking our way
|
||||
if(dest != t) //there is something blocking our way
|
||||
ret.push_back(sptr(Goals::ClearWayTo(dest, h).setisAbstract(true)));
|
||||
else
|
||||
{
|
||||
if (obj->ID.num == Obj::HERO) //enemy hero may move to other position
|
||||
if(obj->ID.num == Obj::HERO) //enemy hero may move to other position
|
||||
ret.push_back(sptr(Goals::VisitHero(obj->id.getNum()).sethero(h).setisAbstract(true)));
|
||||
else //just visit that tile
|
||||
ret.push_back(sptr(Goals::VisitTile(dest).sethero(h).setisAbstract(true)));
|
||||
if(obj->ID.num == Obj::TOWN)
|
||||
//if target is town, fuzzy system will use additional "estimatedReward" variable to increase priority a bit
|
||||
ret.push_back(sptr(Goals::VisitTile(dest).sethero(h).setobjid(obj->ID.num).setisAbstract(true))); //TODO: change to getObj eventually and and move appropiate logic there
|
||||
else
|
||||
ret.push_back(sptr(Goals::VisitTile(dest).sethero(h).setisAbstract(true)));
|
||||
}
|
||||
}
|
||||
else //we need to get army in order to conquer that place
|
||||
@ -1124,13 +1130,13 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
|
||||
}
|
||||
}
|
||||
|
||||
if (ai->canRecruitAnyHero()) //this is not stupid in early phase of game
|
||||
if(ai->canRecruitAnyHero() && ai->freeResources()[Res::GOLD] > GameConstants::HERO_GOLD_COST) //this is not stupid in early phase of game
|
||||
{
|
||||
if (auto t = ai->findTownWithTavern())
|
||||
if(auto t = ai->findTownWithTavern())
|
||||
{
|
||||
for (auto h : cb->getAvailableHeroes(t)) //we assume that all towns have same set of heroes
|
||||
{
|
||||
if (h && h->getTotalStrength() > 500) //do not buy heroes with single creatures for GatherArmy
|
||||
if(h && h->getTotalStrength() > 500) //do not buy heroes with single creatures for GatherArmy
|
||||
{
|
||||
ret.push_back(sptr(Goals::RecruitHero()));
|
||||
break;
|
||||
|
@ -1352,6 +1352,7 @@ bool VCAI::tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingI
|
||||
//Set of buildings for different goals. Does not include any prerequisites.
|
||||
static const BuildingID essential[] = {BuildingID::TAVERN, BuildingID::TOWN_HALL};
|
||||
static const BuildingID goldSource[] = {BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL};
|
||||
static const BuildingID capitolRequirements[] = { BuildingID::FORT, BuildingID::CITADEL };
|
||||
static 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};
|
||||
static const BuildingID unitsUpgrade[] = { BuildingID::DWELL_LVL_1_UP, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP,
|
||||
@ -1370,55 +1371,64 @@ 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)
|
||||
|
||||
//below algorithm focuses on economy growth at start of the game.
|
||||
TResources currentRes = cb->getResourceAmount();
|
||||
TResources currentIncome = t->dailyIncome();
|
||||
int townIncome = currentIncome[Res::GOLD];
|
||||
|
||||
if (tryBuildAnyStructure(t, std::vector<BuildingID>(essential, essential + ARRAY_COUNT(essential))))
|
||||
if(tryBuildAnyStructure(t, std::vector<BuildingID>(essential, essential + ARRAY_COUNT(essential))))
|
||||
return;
|
||||
|
||||
//we're running out of gold - try to build something gold-producing. Multiplier can be tweaked, 6 is minimum due to buildings costs
|
||||
if (currentRes[Res::GOLD] < townIncome * 6)
|
||||
if (tryBuildNextStructure(t, std::vector<BuildingID>(goldSource, goldSource + ARRAY_COUNT(goldSource))))
|
||||
//the more gold the better and less problems later
|
||||
if(tryBuildNextStructure(t, std::vector<BuildingID>(goldSource, goldSource + ARRAY_COUNT(goldSource))))
|
||||
return;
|
||||
|
||||
//workaround for mantis #2696 - build fort and citadel - building castle will be handled without bug
|
||||
if(vstd::contains(t->builtBuildings, BuildingID::CITY_HALL) && cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::HAVE_CAPITAL &&
|
||||
cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::FORBIDDEN)
|
||||
if(tryBuildNextStructure(t, std::vector<BuildingID>(capitolRequirements, capitolRequirements + ARRAY_COUNT(capitolRequirements))))
|
||||
return;
|
||||
|
||||
if (cb->getDate(Date::DAY_OF_WEEK) > 6)// last 2 days of week - try to focus on growth
|
||||
if((!vstd::contains(t->builtBuildings, BuildingID::CAPITOL) && cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::HAVE_CAPITAL && cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::FORBIDDEN)
|
||||
|| (!vstd::contains(t->builtBuildings, BuildingID::CITY_HALL) && cb->canBuildStructure(t, BuildingID::CAPITOL) == EBuildingState::HAVE_CAPITAL && cb->canBuildStructure(t, BuildingID::CITY_HALL) != EBuildingState::FORBIDDEN)
|
||||
|| (!vstd::contains(t->builtBuildings, BuildingID::TOWN_HALL) && cb->canBuildStructure(t, BuildingID::TOWN_HALL) != EBuildingState::FORBIDDEN))
|
||||
return; //save money for capitol or city hall if capitol unavailable, do not build other things (unless gold source buildings are disabled in map editor)
|
||||
|
||||
if(cb->getDate(Date::DAY_OF_WEEK) > 6)// last 2 days of week - try to focus on growth
|
||||
{
|
||||
if (tryBuildNextStructure(t, std::vector<BuildingID>(unitGrowth, unitGrowth + ARRAY_COUNT(unitGrowth)), 2))
|
||||
if(tryBuildNextStructure(t, std::vector<BuildingID>(unitGrowth, unitGrowth + ARRAY_COUNT(unitGrowth)), 2))
|
||||
return;
|
||||
}
|
||||
|
||||
// first in-game week or second half of any week: try build dwellings
|
||||
if (cb->getDate(Date::DAY) < 7 || cb->getDate(Date::DAY_OF_WEEK) > 3)
|
||||
if (tryBuildAnyStructure(t, std::vector<BuildingID>(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 8 - cb->getDate(Date::DAY_OF_WEEK)))
|
||||
if(cb->getDate(Date::DAY) < 7 || cb->getDate(Date::DAY_OF_WEEK) > 3)
|
||||
if(tryBuildAnyStructure(t, std::vector<BuildingID>(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 8 - cb->getDate(Date::DAY_OF_WEEK)))
|
||||
return;
|
||||
|
||||
//try to upgrade dwelling
|
||||
for(int i = 0; i < ARRAY_COUNT(unitsUpgrade); i++)
|
||||
{
|
||||
if (t->hasBuilt(unitsSource[i]) && !t->hasBuilt(unitsUpgrade[i]))
|
||||
if(t->hasBuilt(unitsSource[i]) && !t->hasBuilt(unitsUpgrade[i]))
|
||||
{
|
||||
if (tryBuildStructure(t, unitsUpgrade[i]))
|
||||
if(tryBuildStructure(t, unitsUpgrade[i]))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//remaining tasks
|
||||
if (tryBuildNextStructure(t, std::vector<BuildingID>(goldSource, goldSource + ARRAY_COUNT(goldSource))))
|
||||
if(tryBuildNextStructure(t, std::vector<BuildingID>(spells, spells + ARRAY_COUNT(spells))))
|
||||
return;
|
||||
if (tryBuildNextStructure(t, std::vector<BuildingID>(spells, spells + ARRAY_COUNT(spells))))
|
||||
return;
|
||||
if (tryBuildAnyStructure(t, std::vector<BuildingID>(extra, extra + ARRAY_COUNT(extra))))
|
||||
if(tryBuildAnyStructure(t, std::vector<BuildingID>(extra, extra + ARRAY_COUNT(extra))))
|
||||
return;
|
||||
|
||||
//at the end, try to get and build any extra buildings with nonstandard slots (for example HotA 3rd level dwelling)
|
||||
std::vector<BuildingID> extraBuildings;
|
||||
|
||||
for (auto buildingInfo : t->town->buildings)
|
||||
if (buildingInfo.first > 43)
|
||||
for(auto buildingInfo : t->town->buildings)
|
||||
if(buildingInfo.first > 43)
|
||||
extraBuildings.push_back(buildingInfo.first);
|
||||
|
||||
if (tryBuildAnyStructure(t, extraBuildings))
|
||||
if(tryBuildAnyStructure(t, extraBuildings))
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2865,7 +2875,13 @@ void VCAI::validateObject(ObjectIdRef obj)
|
||||
TResources VCAI::freeResources() const
|
||||
{
|
||||
TResources myRes = cb->getResourceAmount();
|
||||
myRes[Res::GOLD] -= GOLD_RESERVE;
|
||||
auto iterator = cb->getTownsInfo();
|
||||
if(std::none_of(iterator.begin(), iterator.end(), [](const CGTownInstance * x)->bool
|
||||
{
|
||||
return x->builtBuildings.find(BuildingID::CAPITOL) != x->builtBuildings.end();
|
||||
})
|
||||
/*|| std::all_of(iterator.begin(), iterator.end(), [](const CGTownInstance * x) -> bool { return x->forbiddenBuildings.find(BuildingID::CAPITOL) != x->forbiddenBuildings.end(); })*/ )
|
||||
myRes[Res::GOLD] -= GOLD_RESERVE; //what if capitol is blocked from building in all possessed towns (set in map editor)? What about reserve for city hall or something similar in that case?
|
||||
vstd::amax(myRes[Res::GOLD], 0);
|
||||
return myRes;
|
||||
}
|
||||
|
@ -866,6 +866,11 @@ void CGHeroInstance::setPropertyDer( ui8 what, ui32 val )
|
||||
setStackCount(SlotID(0), val);
|
||||
}
|
||||
|
||||
TFaction CGHeroInstance::getFaction() const
|
||||
{
|
||||
return type->heroClass->faction;
|
||||
}
|
||||
|
||||
double CGHeroInstance::getFightingStrength() const
|
||||
{
|
||||
return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::ATTACK)) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::DEFENSE)));
|
||||
|
@ -147,6 +147,7 @@ public:
|
||||
EAlignment::EAlignment getAlignment() const;
|
||||
const std::string &getBiography() const;
|
||||
bool needsLastStack()const override;
|
||||
TFaction getFaction() const;
|
||||
ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from, const TurnInfo * ti) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling
|
||||
int getNativeTerrain() const;
|
||||
ui32 getLowestCreatureSpeed() const;
|
||||
|
Loading…
Reference in New Issue
Block a user