diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index 9519019ec..6eabd2763 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -20,6 +20,7 @@ */ #define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter +#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 10 times weaker than us struct BankConfig; class FuzzyEngine; @@ -89,6 +90,7 @@ FuzzyHelper::FuzzyHelper() { engine.hedgeSet().add(new fl::HedgeSomewhat()); engine.hedgeSet().add(new fl::HedgeVery()); + engine.fuzzyOperator().setAggregation(new fl::FuzzyOrSum()); //to consider all cases simultaneously initBank(); initTacticalAdvantage(); @@ -141,8 +143,8 @@ void FuzzyHelper::initTacticalAdvantage() for (auto val : helper) { engine.addInputLVar(val); - val->addTerm (new fl::ShoulderTerm("FEW", 0, 0.75, true)); - val->addTerm (new fl::ShoulderTerm("MANY", 0.25, 1, false)); + val->addTerm (new fl::ShoulderTerm("FEW", 0, 0.6, true)); + val->addTerm (new fl::ShoulderTerm("MANY", 0.4, 1, false)); } helper.clear(); @@ -178,9 +180,12 @@ void FuzzyHelper::initTacticalAdvantage() engine.addRuleBlock (&ta.tacticalAdvantage); - ta.tacticalAdvantage.addRule(new fl::MamdaniRule("if OurShooters is MANY and EnemySpeed is LOW then Threat is very LOW", engine)); - ta.tacticalAdvantage.addRule(new fl::MamdaniRule("if OurSpeed is LOW and OurShooters is FEW and EnemyShooters is MANY then Threat is very HIGH", engine)); - ta.tacticalAdvantage.addRule(new fl::MamdaniRule("if (OurShooters is MANY and OurFlyers is MANY) and EnemyShooters is MANY then Threat is LOW", engine)); + ta.tacticalAdvantage.addRule(new fl::MamdaniRule("if OurShooters is MANY and EnemySpeed is LOW then Threat is LOW", engine)); + ta.tacticalAdvantage.addRule(new fl::MamdaniRule("if OurShooters is MANY and EnemyShooters is FEW then Threat is LOW", engine)); + ta.tacticalAdvantage.addRule(new fl::MamdaniRule("if OurSpeed is LOW and EnemyShooters is MANY then Threat is HIGH", engine)); + ta.tacticalAdvantage.addRule(new fl::MamdaniRule("if OurSpeed is HIGH and EnemyShooters is MANY then Threat is LOW", engine)); + + ta.tacticalAdvantage.addRule(new fl::MamdaniRule("if OurWalkers is FEW and EnemyShooters is MANY then Threat is somewhat LOW", engine)); ta.tacticalAdvantage.addRule(new fl::MamdaniRule("if OurShooters is MANY and EnemySpeed is HIGH then Threat is somewhat HIGH", engine)); //just to cover all cases ta.tacticalAdvantage.addRule(new fl::MamdaniRule("if EnemySpeed is MEDIUM then Threat is MEDIUM", engine)); @@ -340,9 +345,8 @@ FuzzyHelper::EvalVisitTile::~EvalVisitTile() { delete strengthRatio; delete heroStrength; - delete tileDistance; + delete turnDistance; delete missionImportance; - delete movement; } void FuzzyHelper::initVisitTile() @@ -353,12 +357,11 @@ void FuzzyHelper::initVisitTile() vt.strengthRatio = new fl::InputLVar("strengthRatio"); //hero must be strong enough to defeat guards 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.turnDistance = new fl::InputLVar("turnDistance"); //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, vt.movement; + helper += vt.strengthRatio, vt.heroStrength, vt.turnDistance, vt.missionImportance; for (auto val : helper) { engine.addInputLVar(val); @@ -366,27 +369,25 @@ void FuzzyHelper::initVisitTile() engine.addOutputLVar (vt.value); vt.strengthRatio->addTerm (new fl::ShoulderTerm("LOW", 0, SAFE_ATTACK_CONSTANT, true)); - vt.strengthRatio->addTerm (new fl::ShoulderTerm("HIGH", SAFE_ATTACK_CONSTANT, SAFE_ATTACK_CONSTANT * 5, false)); + vt.strengthRatio->addTerm (new fl::ShoulderTerm("HIGH", SAFE_ATTACK_CONSTANT, SAFE_ATTACK_CONSTANT * 3, false)); //strength compared to our main hero vt.heroStrength->addTerm (new fl::ShoulderTerm("LOW", 0, 0.2, true)); vt.heroStrength->addTerm (new fl::TriangularTerm("MEDIUM", 0.2, 0.8)); - vt.heroStrength->addTerm (new fl::ShoulderTerm("HIGH", 0.5, 0.99, false)); + vt.heroStrength->addTerm (new fl::ShoulderTerm("HIGH", 0.5, 1, false)); - vt.tileDistance->addTerm (new fl::ShoulderTerm("SMALL", 0, 3.5, true)); - vt.tileDistance->addTerm (new fl::TriangularTerm("MEDIUM", 3, 10.5)); - vt.tileDistance->addTerm (new fl::ShoulderTerm("LONG", 10, 50, false)); + vt.turnDistance->addTerm (new fl::ShoulderTerm("SMALL", 0, 0.5, true)); + vt.turnDistance->addTerm (new fl::TriangularTerm("MEDIUM", 0.1, 0.8)); + vt.turnDistance->addTerm (new fl::ShoulderTerm("LONG", 0.5, 3, false)); vt.missionImportance->addTerm (new fl::ShoulderTerm("LOW", 0, 2.5, true)); vt.missionImportance->addTerm (new fl::TriangularTerm("MEDIUM", 2, 3)); vt.missionImportance->addTerm (new fl::ShoulderTerm("HIGH", 2.5, 5, false)); - vt.movement->addTerm (new fl::ShoulderTerm("LOW", 0, 600, true)); - vt.movement->addTerm (new fl::TriangularTerm("MEDIUM", 500, 1500)); - vt.movement->addTerm (new fl::ShoulderTerm("HIGH", 1000, 2000, false)); - - vt.value->addTerm (new fl::ShoulderTerm("LOW", 0, 2.5, true)); //should be same as "mission Importance" to keep consistency - vt.value->addTerm (new fl::TriangularTerm("MEDIUM", 2, 3)); + //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::ShoulderTerm("LOW", 0, 2.5, true)); + vt.value->addTerm (new fl::TriangularTerm("MEDIUM", 2, 3)); //can't be center of mass :/ vt.value->addTerm (new fl::ShoulderTerm("HIGH", 2.5, 5, false)); engine.addRuleBlock (&vt.rules); @@ -398,6 +399,8 @@ void FuzzyHelper::initVisitTile() vt.rules.addRule (new fl::MamdaniRule("if strengthRatio is HIGH and heroStrength is HIGH then Value is somewhat LOW", engine)); //don't assign targets to heroes who are too weak, but prefer targets of our main hero (in case we need to gather army) vt.rules.addRule (new fl::MamdaniRule("if strengthRatio is LOW and heroStrength is LOW then Value is very LOW", engine)); + //attempt to arm secondary heroes is not stupid + vt.rules.addRule (new fl::MamdaniRule("if strengthRatio is LOW and heroStrength is MEDIUM then Value is somewhat HIGH", engine)); vt.rules.addRule (new fl::MamdaniRule("if strengthRatio is LOW and heroStrength is HIGH then Value is LOW", engine)); //do not cancel important goals @@ -405,13 +408,9 @@ void FuzzyHelper::initVisitTile() vt.rules.addRule (new fl::MamdaniRule("if lockedMissionImportance is MEDIUM then Value is somewhat LOW", engine)); vt.rules.addRule (new fl::MamdaniRule("if lockedMissionImportance is LOW then Value is HIGH", engine)); //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 MEDIUM then Value is MEDIUM", 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 MEDIUM then Value is MEDIUM", engine)); - vt.rules.addRule (new fl::MamdaniRule("if movement is HIGH then Value is somewhat HIGH", engine)); + vt.rules.addRule (new fl::MamdaniRule("if turnDistance is SMALL then Value is HIGH", engine)); + vt.rules.addRule (new fl::MamdaniRule("if turnDistance is MEDIUM then Value is MEDIUM", engine)); + vt.rules.addRule (new fl::MamdaniRule("if turnDistance is LONG then Value is LOW", engine)); } catch (fl::FuzzyException & fe) { @@ -424,14 +423,25 @@ float FuzzyHelper::evaluate (Goals::VisitTile & g) if (!g.hero) return 0; + //assert(cb->isInTheMap(g.tile)); cb->setSelection (g.hero.h); - int distance = cb->getDistance(g.tile); //at this point we already assume tile is reachable + float turns = 0; + float distance = cb->getMovementCost(g.hero.h, g.tile); + if (!distance) //we stand on that tile + turns = 0; + else + { + if (distance < g.hero->movement) //we can move there within one turn + turns = (fl::flScalar)distance /g.hero->movement; + else + turns = 1 + (fl::flScalar)(distance - g.hero->movement)/g.hero->maxMovePoints(true); //bool on land? + } float missionImportance = 0; if (vstd::contains(ai->lockedHeroes, g.hero)) missionImportance = ai->lockedHeroes[g.hero]->priority; - float strengthRatio = 100; //we are much stronger than enemy + float strengthRatio = 10.0f; //we are much stronger than enemy ui64 danger = evaluateDanger (g.tile, g.hero.h); if (danger) strengthRatio = (fl::flScalar)g.hero.h->getTotalStrength() / danger; @@ -440,9 +450,8 @@ float FuzzyHelper::evaluate (Goals::VisitTile & g) { vt.strengthRatio->setInput (strengthRatio); vt.heroStrength->setInput ((fl::flScalar)g.hero->getTotalStrength()/ai->primaryHero()->getTotalStrength()); - vt.tileDistance->setInput (distance); + vt.turnDistance->setInput (turns); vt.missionImportance->setInput (missionImportance); - vt.movement->setInput(g.hero->movement); //engine.process(); engine.process (VISIT_TILE); diff --git a/AI/VCAI/Fuzzy.h b/AI/VCAI/Fuzzy.h index e06a77094..16b2906af 100644 --- a/AI/VCAI/Fuzzy.h +++ b/AI/VCAI/Fuzzy.h @@ -42,9 +42,8 @@ class FuzzyHelper public: fl::InputLVar * strengthRatio; fl::InputLVar * heroStrength; - fl::InputLVar * tileDistance; + fl::InputLVar * turnDistance; fl::InputLVar * missionImportance; - fl::InputLVar * movement; fl::OutputLVar * value; fl::RuleBlock rules; ~EvalVisitTile(); diff --git a/AI/VCAI/Goals.cpp b/AI/VCAI/Goals.cpp index 74c22a317..9abc0fc25 100644 --- a/AI/VCAI/Goals.cpp +++ b/AI/VCAI/Goals.cpp @@ -522,19 +522,20 @@ TGoalVec Explore::getAllPossibleSubgoals() if ((!hero || ret.empty()) && ai->canRecruitAnyHero()) ret.push_back (sptr(Goals::RecruitHero())); - if (ret.empty()) + if (!hero && ret.empty()) { auto h = ai->primaryHero(); //we may need to gather big army to break! if (h.h) { + //FIXME: it never finds anything :? int3 t = ai->explorationNewPoint(h->getSightRadious(), h, true); if (cb->isInTheMap(t)) - ret.push_back (sptr(ClearWayTo(t).setisAbstract(true).sethero(ai->primaryHero()))); + ret.push_back (sptr(ClearWayTo(t).setisAbstract(true).sethero(h))); } } if (ret.empty()) { - throw goalFulfilledException (sptr(*this)); + throw goalFulfilledException (sptr(Goals::Explore().sethero(hero))); } //throw cannotFulfillGoalException("Cannot explore - no possible ways found!"); diff --git a/AI/VCAI/Goals.h b/AI/VCAI/Goals.h index d5dcf637e..5cb1dc54f 100644 --- a/AI/VCAI/Goals.h +++ b/AI/VCAI/Goals.h @@ -218,7 +218,7 @@ class Explore : public CGoal { public: Explore() : CGoal (Goals::EXPLORE){priority = 1;}; - Explore(HeroPtr h) : CGoal (Goals::EXPLORE){hero = h;}; + Explore(HeroPtr h) : CGoal (Goals::EXPLORE){hero = h; priority = 1;}; TGoalVec getAllPossibleSubgoals() override; TSubgoal whatToDoToAchieve() override; std::string completeMessage() const override; diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 0f6492ce6..fe4524980 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -728,6 +728,7 @@ void VCAI::makeTurnInternal() std::vector > safeCopy; for (auto mission : lockedHeroes) { + //FIXME: for some reason when called here, priority is always the same fh->setPriority (mission.second); //re-evaluate if (canAct(mission.first)) { diff --git a/CCallback.cpp b/CCallback.cpp index 111190dcc..a1487248c 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -332,6 +332,11 @@ bool CCallback::getPath2( int3 dest, CGPath &ret ) return cl->pathInfo->getPath(dest, ret); } +int CCallback::getMovementCost(const CGHeroInstance * hero, int3 dest) +{ + return gs->getMovementCost(hero, hero->visitablePos(), dest, hero->movement); +} + void CCallback::recalculatePaths() { cl->calculatePaths(cl->IGameCallback::getSelectedHero(*player)); diff --git a/CCallback.h b/CCallback.h index be36ac6e5..9f90fc89a 100644 --- a/CCallback.h +++ b/CCallback.h @@ -110,6 +110,7 @@ public: virtual const CGPathNode *getPathInfo(int3 tile); //uses main, client pathfinder info virtual int getDistance(int3 tile); virtual bool getPath2(int3 dest, CGPath &ret); //uses main, client pathfinder info + virtual int getMovementCost(const CGHeroInstance * hero, int3 dest); virtual void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int3 src = int3(-1,-1,-1), int movement = -1); virtual void recalculatePaths(); //updates main, client pathfinder info (should be called when moving hero is over) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 4d688a2e3..fe126df33 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1650,7 +1650,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, Pl } const TerrainTile t = *gs->getTile(hmpos); - const int cost = gs->getMovementCost(h, h->getPosition(false), hmpos, h->movement); + const int cost = gs->getMovementCost(h, h->getPosition(), hmpos, h->movement); const int3 guardPos = gs->guardingCreaturePosition(hmpos); const bool embarking = !h->boat && !t.visitableObjects.empty() && t.visitableObjects.back()->ID == Obj::BOAT;