From a8f5882a7769149f9a26bc977c2b05427e55cf92 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sat, 4 Aug 2018 17:36:16 +0200 Subject: [PATCH 01/33] Initial split of classes --- AI/VCAI/Fuzzy.cpp | 60 ++++++++++++++++++++-------------------- AI/VCAI/Fuzzy.h | 70 +++++++++++++++++++++++++++-------------------- 2 files changed, 71 insertions(+), 59 deletions(-) diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index 3db117451..ded588dbe 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -308,7 +308,7 @@ float FuzzyHelper::getWanderTargetObjectValue(const CGHeroInstance & h, const Ob float output = -1.0f; try { - wanderTarget.distance->setValue(distFromObject); + wanderTarget.turnDistance->setValue(distFromObject); wanderTarget.objectValue->setValue(objValue); wanderTarget.engine.process(); output = wanderTarget.visitGain->getValue(); @@ -321,7 +321,7 @@ float FuzzyHelper::getWanderTargetObjectValue(const CGHeroInstance & h, const Ob return output; } -FuzzyHelper::TacticalAdvantage::~TacticalAdvantage() +TacticalAdvantage::~TacticalAdvantage() { //TODO: smart pointers? delete ourWalkers; @@ -373,7 +373,7 @@ float FuzzyHelper::evaluate(Goals::RecruitHero & g) { return 1; } -FuzzyHelper::EvalVisitTile::~EvalVisitTile() +HeroMovementGoalEngine::~HeroMovementGoalEngine() { delete strengthRatio; delete heroStrength; @@ -466,21 +466,21 @@ void FuzzyHelper::initWanderTarget() { try { - wanderTarget.distance = new fl::InputVariable("distance"); //distance on map from object + wanderTarget.turnDistance = new fl::InputVariable("turnDistance"); //distance on map from object wanderTarget.objectValue = new fl::InputVariable("objectValue"); //value of that object type known by AI - wanderTarget.visitGain = new fl::OutputVariable("visitGain"); - wanderTarget.visitGain->setMinimum(0); - wanderTarget.visitGain->setMaximum(10); + wanderTarget.value = new fl::OutputVariable("value"); + wanderTarget.value->setMinimum(0); + wanderTarget.value->setMaximum(10); - wanderTarget.engine.addInputVariable(wanderTarget.distance); + wanderTarget.engine.addInputVariable(wanderTarget.turnDistance); wanderTarget.engine.addInputVariable(wanderTarget.objectValue); wanderTarget.engine.addOutputVariable(wanderTarget.visitGain); //for now distance variable same as in as VisitTile - wanderTarget.distance->addTerm(new fl::Ramp("SHORT", 0.5, 0)); - wanderTarget.distance->addTerm(new fl::Triangle("MEDIUM", 0.1, 0.8)); - wanderTarget.distance->addTerm(new fl::Ramp("LONG", 0.5, 3)); - wanderTarget.distance->setRange(0, 3.0); + wanderTarget.turnDistance->addTerm(new fl::Ramp("SHORT", 0.5, 0)); + wanderTarget.turnDistance->addTerm(new fl::Triangle("MEDIUM", 0.1, 0.8)); + wanderTarget.turnDistance->addTerm(new fl::Ramp("LONG", 0.5, 3)); + wanderTarget.turnDistance->setRange(0, 3.0); //objectValue ranges are based on checking RMG priorities of some objects and trying to guess sane value ranges wanderTarget.objectValue->addTerm(new fl::Ramp("LOW", 3000, 0)); //I have feeling that concave shape might work well instead of ramp for objectValue FL terms @@ -488,22 +488,22 @@ void FuzzyHelper::initWanderTarget() wanderTarget.objectValue->addTerm(new fl::Ramp("HIGH", 5000, 20000)); wanderTarget.objectValue->setRange(0, 20000); //relic artifact value is border value by design, even better things are scaled down. - wanderTarget.visitGain->addTerm(new fl::Ramp("LOW", 5, 0)); - wanderTarget.visitGain->addTerm(new fl::Triangle("MEDIUM", 4, 6)); - wanderTarget.visitGain->addTerm(new fl::Ramp("HIGH", 5, 10)); - wanderTarget.visitGain->setRange(0, 10); + wanderTarget.value->addTerm(new fl::Ramp("LOW", 5, 0)); + wanderTarget.value->addTerm(new fl::Triangle("MEDIUM", 4, 6)); + wanderTarget.value->addTerm(new fl::Ramp("HIGH", 5, 10)); + wanderTarget.value->setRange(0, 10); - wanderTarget.addRule("if distance is LONG and objectValue is HIGH then visitGain is MEDIUM"); - wanderTarget.addRule("if distance is MEDIUM and objectValue is HIGH then visitGain is somewhat HIGH"); - wanderTarget.addRule("if distance is SHORT and objectValue is HIGH then visitGain is HIGH"); + wanderTarget.addRule("if turnDistance is LONG and objectValue is HIGH then value is MEDIUM"); + wanderTarget.addRule("if turnDistance is MEDIUM and objectValue is HIGH then value is somewhat HIGH"); + wanderTarget.addRule("if turnDistance is SHORT and objectValue is HIGH then value is HIGH"); - wanderTarget.addRule("if distance is LONG and objectValue is MEDIUM then visitGain is somewhat LOW"); - wanderTarget.addRule("if distance is MEDIUM and objectValue is MEDIUM then visitGain is MEDIUM"); - wanderTarget.addRule("if distance is SHORT and objectValue is MEDIUM then visitGain is somewhat HIGH"); - - wanderTarget.addRule("if distance is LONG and objectValue is LOW then visitGain is very LOW"); - wanderTarget.addRule("if distance is MEDIUM and objectValue is LOW then visitGain is LOW"); - wanderTarget.addRule("if distance is SHORT and objectValue is LOW then visitGain is MEDIUM"); + wanderTarget.addRule("if turnDistance is LONG and objectValue is MEDIUM then value is somewhat LOW"); + wanderTarget.addRule("if turnDistance is MEDIUM and objectValue is MEDIUM then value is MEDIUM"); + wanderTarget.addRule("if turnDistance is SHORT and objectValue is MEDIUM then value is somewhat HIGH"); + + wanderTarget.addRule("if turnDistance is LONG and objectValue is LOW then value is very LOW"); + wanderTarget.addRule("if turnDistance is MEDIUM and objectValue is LOW then value is LOW"); + wanderTarget.addRule("if turnDistance is SHORT and objectValue is LOW then value is MEDIUM"); } catch(fl::Exception & fe) { @@ -635,9 +635,11 @@ void FuzzyHelper::setPriority(Goals::TSubgoal & g) //calls evaluate - Visitor pa g->setpriority(g->accept(this)); //this enforces returned value is set } -FuzzyHelper::EvalWanderTargetObject::~EvalWanderTargetObject() +EvalWanderTargetObject::~EvalWanderTargetObject() { - delete distance; delete objectValue; - delete visitGain; +} + +EvalVisitTile::~EvalVisitTile() +{ } diff --git a/AI/VCAI/Fuzzy.h b/AI/VCAI/Fuzzy.h index a7709ccc2..5a16beae1 100644 --- a/AI/VCAI/Fuzzy.h +++ b/AI/VCAI/Fuzzy.h @@ -27,42 +27,52 @@ public: void addRule(const std::string & txt); }; +class TacticalAdvantage : public engineBase +{ +public: + fl::InputVariable * ourWalkers, *ourShooters, *ourFlyers, *enemyWalkers, *enemyShooters, *enemyFlyers; + fl::InputVariable * ourSpeed, *enemySpeed; + fl::InputVariable * bankPresent; + fl::InputVariable * castleWalls; + fl::OutputVariable * threat; + ~TacticalAdvantage(); +}; + +class HeroMovementGoalEngine : public engineBase +{ +public: + fl::InputVariable * strengthRatio; + fl::InputVariable * heroStrength; + fl::InputVariable * turnDistance; + fl::InputVariable * missionImportance; + fl::InputVariable * estimatedReward; + fl::OutputVariable * value; + ~HeroMovementGoalEngine(); +}; + +class EvalVisitTile : public HeroMovementGoalEngine +{ +public: + ~EvalVisitTile(); +}; + +class EvalWanderTargetObject : public HeroMovementGoalEngine //designed for use with VCAI::wander() +{ +public: + fl::InputVariable * objectValue; + fl::OutputVariable * visitGain; + ~EvalWanderTargetObject(); +}; + class FuzzyHelper { friend class VCAI; - class TacticalAdvantage : public engineBase - { - public: - fl::InputVariable * ourWalkers, * ourShooters, * ourFlyers, * enemyWalkers, * enemyShooters, * enemyFlyers; - fl::InputVariable * ourSpeed, * enemySpeed; - fl::InputVariable * bankPresent; - fl::InputVariable * castleWalls; - fl::OutputVariable * threat; - ~TacticalAdvantage(); - } ta; + TacticalAdvantage ta; - class EvalVisitTile : public engineBase - { - public: - fl::InputVariable * strengthRatio; - fl::InputVariable * heroStrength; - fl::InputVariable * turnDistance; - fl::InputVariable * missionImportance; - fl::InputVariable * estimatedReward; - fl::OutputVariable * value; - fl::RuleBlock rules; - ~EvalVisitTile(); - } vt; + EvalVisitTile vt; - class EvalWanderTargetObject : public engineBase //designed for use with VCAI::wander() - { - public: - fl::InputVariable * distance; - fl::InputVariable * objectValue; - fl::OutputVariable * visitGain; - ~EvalWanderTargetObject(); - } wanderTarget; + EvalWanderTargetObject wanderTarget; private: float calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const; From e33ef718829e91b460d28474facbc841fc166905 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sat, 4 Aug 2018 18:58:50 +0200 Subject: [PATCH 02/33] FuzzyHelper is on diet... gotta get thinner --- AI/VCAI/Fuzzy.cpp | 255 ++++++++++++++++++++++------------------------ AI/VCAI/Fuzzy.h | 21 ++-- 2 files changed, 134 insertions(+), 142 deletions(-) diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index ded588dbe..810cc20b2 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -202,7 +202,7 @@ void FuzzyHelper::initTacticalAdvantage() } } -float FuzzyHelper::calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const +float HeroMovementGoalEngineBase::calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const { float turns = 0.0f; float distance = CPathfinderHelper::getMovementCost(h, tile); @@ -311,7 +311,7 @@ float FuzzyHelper::getWanderTargetObjectValue(const CGHeroInstance & h, const Ob wanderTarget.turnDistance->setValue(distFromObject); wanderTarget.objectValue->setValue(objValue); wanderTarget.engine.process(); - output = wanderTarget.visitGain->getValue(); + output = wanderTarget.value->getValue(); } catch (fl::Exception & fe) { @@ -373,7 +373,87 @@ float FuzzyHelper::evaluate(Goals::RecruitHero & g) { return 1; } -HeroMovementGoalEngine::~HeroMovementGoalEngine() +HeroMovementGoalEngineBase::HeroMovementGoalEngineBase() +{ + try + { + strengthRatio = new fl::InputVariable("strengthRatio"); //hero must be strong enough to defeat guards + heroStrength = new fl::InputVariable("heroStrength"); //we want to use weakest possible hero + turnDistance = new fl::InputVariable("turnDistance"); //we want to use hero who is near + missionImportance = new fl::InputVariable("lockedMissionImportance"); //we may want to preempt hero with low-priority mission + estimatedReward = new fl::InputVariable("estimatedReward"); //indicate AI that content of the file is important or it is probably bad + value = new fl::OutputVariable("Value"); + value->setMinimum(0); + value->setMaximum(5); + + std::vector helper = { strengthRatio, heroStrength, turnDistance, missionImportance, estimatedReward }; + for(auto val : helper) + { + engine.addInputVariable(val); + } + engine.addOutputVariable(value); + + strengthRatio->addTerm(new fl::Ramp("LOW", SAFE_ATTACK_CONSTANT, 0)); + strengthRatio->addTerm(new fl::Ramp("HIGH", SAFE_ATTACK_CONSTANT, SAFE_ATTACK_CONSTANT * 3)); + strengthRatio->setRange(0, SAFE_ATTACK_CONSTANT * 3); + + //strength compared to our main hero + heroStrength->addTerm(new fl::Ramp("LOW", 0.2, 0)); + heroStrength->addTerm(new fl::Triangle("MEDIUM", 0.2, 0.8)); + heroStrength->addTerm(new fl::Ramp("HIGH", 0.5, 1)); + heroStrength->setRange(0.0, 1.0); + + turnDistance->addTerm(new fl::Ramp("SMALL", 0.5, 0)); + turnDistance->addTerm(new fl::Triangle("MEDIUM", 0.1, 0.8)); + turnDistance->addTerm(new fl::Ramp("LONG", 0.5, 3)); + turnDistance->setRange(0.0, 3.0); + + missionImportance->addTerm(new fl::Ramp("LOW", 2.5, 0)); + missionImportance->addTerm(new fl::Triangle("MEDIUM", 2, 3)); + missionImportance->addTerm(new fl::Ramp("HIGH", 2.5, 5)); + missionImportance->setRange(0.0, 5.0); + + estimatedReward->addTerm(new fl::Ramp("LOW", 2.5, 0)); + estimatedReward->addTerm(new fl::Ramp("HIGH", 2.5, 5)); + 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 + value->addTerm(new fl::Ramp("LOW", 2.5, 0)); + value->addTerm(new fl::Triangle("MEDIUM", 2, 3)); //can't be center of mass :/ + value->addTerm(new fl::Ramp("HIGH", 2.5, 5)); + value->setRange(0.0, 5.0); + + //use unarmed scouts if possible + addRule("if strengthRatio is HIGH and heroStrength is LOW then Value is very HIGH"); + //we may want to use secondary hero(es) rather than main hero + addRule("if strengthRatio is HIGH and heroStrength is MEDIUM then Value is somewhat HIGH"); + addRule("if strengthRatio is HIGH and heroStrength is HIGH then Value is somewhat LOW"); + //don't assign targets to heroes who are too weak, but prefer targets of our main hero (in case we need to gather army) + addRule("if strengthRatio is LOW and heroStrength is LOW then Value is very LOW"); + //attempt to arm secondary heroes is not stupid + addRule("if strengthRatio is LOW and heroStrength is MEDIUM then Value is somewhat HIGH"); + addRule("if strengthRatio is LOW and heroStrength is HIGH then Value is LOW"); + + //do not cancel important goals + addRule("if lockedMissionImportance is HIGH then Value is very LOW"); + addRule("if lockedMissionImportance is MEDIUM then Value is somewhat LOW"); + addRule("if lockedMissionImportance is LOW then Value is HIGH"); + //pick nearby objects if it's easy, avoid long walks + addRule("if turnDistance is SMALL then Value is HIGH"); + addRule("if turnDistance is MEDIUM then Value is MEDIUM"); + 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 + addRule("if estimatedReward is HIGH then Value is very HIGH"); + addRule("if estimatedReward is LOW then Value is somewhat LOW"); + } + catch(fl::Exception & fe) + { + logAi->error("HeroMovementGoalEngineBase: %s", fe.getWhat()); + } +} + +HeroMovementGoalEngineBase::~HeroMovementGoalEngineBase() { delete strengthRatio; delete heroStrength; @@ -382,135 +462,6 @@ HeroMovementGoalEngine::~HeroMovementGoalEngine() delete estimatedReward; } -void FuzzyHelper::initVisitTile() -{ - try - { - vt.strengthRatio = new fl::InputVariable("strengthRatio"); //hero must be strong enough to defeat guards - 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 helper = {vt.strengthRatio, vt.heroStrength, vt.turnDistance, vt.missionImportance, vt.estimatedReward}; - for(auto val : helper) - { - vt.engine.addInputVariable(val); - } - vt.engine.addOutputVariable(vt.value); - - vt.strengthRatio->addTerm(new fl::Ramp("LOW", SAFE_ATTACK_CONSTANT, 0)); - vt.strengthRatio->addTerm(new fl::Ramp("HIGH", SAFE_ATTACK_CONSTANT, SAFE_ATTACK_CONSTANT * 3)); - vt.strengthRatio->setRange(0, SAFE_ATTACK_CONSTANT * 3); - - //strength compared to our main hero - vt.heroStrength->addTerm(new fl::Ramp("LOW", 0.2, 0)); - vt.heroStrength->addTerm(new fl::Triangle("MEDIUM", 0.2, 0.8)); - vt.heroStrength->addTerm(new fl::Ramp("HIGH", 0.5, 1)); - vt.heroStrength->setRange(0.0, 1.0); - - vt.turnDistance->addTerm(new fl::Ramp("SMALL", 0.5, 0)); - vt.turnDistance->addTerm(new fl::Triangle("MEDIUM", 0.1, 0.8)); - vt.turnDistance->addTerm(new fl::Ramp("LONG", 0.5, 3)); - vt.turnDistance->setRange(0.0, 3.0); - - vt.missionImportance->addTerm(new fl::Ramp("LOW", 2.5, 0)); - vt.missionImportance->addTerm(new fl::Triangle("MEDIUM", 2, 3)); - 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)); - vt.value->addTerm(new fl::Triangle("MEDIUM", 2, 3)); //can't be center of mass :/ - vt.value->addTerm(new fl::Ramp("HIGH", 2.5, 5)); - vt.value->setRange(0.0, 5.0); - - //use unarmed scouts if possible - vt.addRule("if strengthRatio is HIGH and heroStrength is LOW then Value is very HIGH"); - //we may want to use secondary hero(es) rather than main hero - vt.addRule("if strengthRatio is HIGH and heroStrength is MEDIUM then Value is somewhat HIGH"); - vt.addRule("if strengthRatio is HIGH and heroStrength is HIGH then Value is somewhat LOW"); - //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.addRule("if strengthRatio is LOW and heroStrength is LOW then Value is very LOW"); - //attempt to arm secondary heroes is not stupid - vt.addRule("if strengthRatio is LOW and heroStrength is MEDIUM then Value is somewhat HIGH"); - vt.addRule("if strengthRatio is LOW and heroStrength is HIGH then Value is LOW"); - - //do not cancel important goals - vt.addRule("if lockedMissionImportance is HIGH then Value is very LOW"); - vt.addRule("if lockedMissionImportance is MEDIUM then Value is somewhat LOW"); - vt.addRule("if lockedMissionImportance is LOW then Value is HIGH"); - //pick nearby objects if it's easy, avoid long walks - 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) - { - logAi->error("visitTile: %s", fe.getWhat()); - } -} - -void FuzzyHelper::initWanderTarget() -{ - try - { - wanderTarget.turnDistance = new fl::InputVariable("turnDistance"); //distance on map from object - wanderTarget.objectValue = new fl::InputVariable("objectValue"); //value of that object type known by AI - wanderTarget.value = new fl::OutputVariable("value"); - wanderTarget.value->setMinimum(0); - wanderTarget.value->setMaximum(10); - - wanderTarget.engine.addInputVariable(wanderTarget.turnDistance); - wanderTarget.engine.addInputVariable(wanderTarget.objectValue); - wanderTarget.engine.addOutputVariable(wanderTarget.visitGain); - - //for now distance variable same as in as VisitTile - wanderTarget.turnDistance->addTerm(new fl::Ramp("SHORT", 0.5, 0)); - wanderTarget.turnDistance->addTerm(new fl::Triangle("MEDIUM", 0.1, 0.8)); - wanderTarget.turnDistance->addTerm(new fl::Ramp("LONG", 0.5, 3)); - wanderTarget.turnDistance->setRange(0, 3.0); - - //objectValue ranges are based on checking RMG priorities of some objects and trying to guess sane value ranges - wanderTarget.objectValue->addTerm(new fl::Ramp("LOW", 3000, 0)); //I have feeling that concave shape might work well instead of ramp for objectValue FL terms - wanderTarget.objectValue->addTerm(new fl::Triangle("MEDIUM", 2500, 6000)); - wanderTarget.objectValue->addTerm(new fl::Ramp("HIGH", 5000, 20000)); - wanderTarget.objectValue->setRange(0, 20000); //relic artifact value is border value by design, even better things are scaled down. - - wanderTarget.value->addTerm(new fl::Ramp("LOW", 5, 0)); - wanderTarget.value->addTerm(new fl::Triangle("MEDIUM", 4, 6)); - wanderTarget.value->addTerm(new fl::Ramp("HIGH", 5, 10)); - wanderTarget.value->setRange(0, 10); - - wanderTarget.addRule("if turnDistance is LONG and objectValue is HIGH then value is MEDIUM"); - wanderTarget.addRule("if turnDistance is MEDIUM and objectValue is HIGH then value is somewhat HIGH"); - wanderTarget.addRule("if turnDistance is SHORT and objectValue is HIGH then value is HIGH"); - - wanderTarget.addRule("if turnDistance is LONG and objectValue is MEDIUM then value is somewhat LOW"); - wanderTarget.addRule("if turnDistance is MEDIUM and objectValue is MEDIUM then value is MEDIUM"); - wanderTarget.addRule("if turnDistance is SHORT and objectValue is MEDIUM then value is somewhat HIGH"); - - wanderTarget.addRule("if turnDistance is LONG and objectValue is LOW then value is very LOW"); - wanderTarget.addRule("if turnDistance is MEDIUM and objectValue is LOW then value is LOW"); - wanderTarget.addRule("if turnDistance is SHORT and objectValue is LOW then value is MEDIUM"); - } - catch(fl::Exception & fe) - { - logAi->error("FindWanderTarget: %s", fe.getWhat()); - } -} - float FuzzyHelper::evaluate(Goals::VisitTile & g) { //we assume that hero is already set and we want to choose most suitable one for the mission @@ -635,11 +586,51 @@ void FuzzyHelper::setPriority(Goals::TSubgoal & g) //calls evaluate - Visitor pa g->setpriority(g->accept(this)); //this enforces returned value is set } +EvalWanderTargetObject::EvalWanderTargetObject() +{ + try + { + objectValue = new fl::InputVariable("objectValue"); //value of that object type known by AI + + engine.addInputVariable(turnDistance); + engine.addInputVariable(objectValue); + engine.addOutputVariable(value); + + //objectValue ranges are based on checking RMG priorities of some objects and trying to guess sane value ranges + objectValue->addTerm(new fl::Ramp("LOW", 3000, 0)); //I have feeling that concave shape might work well instead of ramp for objectValue FL terms + objectValue->addTerm(new fl::Triangle("MEDIUM", 2500, 6000)); + objectValue->addTerm(new fl::Ramp("HIGH", 5000, 20000)); + objectValue->setRange(0, 20000); //relic artifact value is border value by design, even better things are scaled down. + + addRule("if turnDistance is LONG and objectValue is HIGH then value is MEDIUM"); + addRule("if turnDistance is MEDIUM and objectValue is HIGH then value is somewhat HIGH"); + addRule("if turnDistance is SHORT and objectValue is HIGH then value is HIGH"); + + addRule("if turnDistance is LONG and objectValue is MEDIUM then value is somewhat LOW"); + addRule("if turnDistance is MEDIUM and objectValue is MEDIUM then value is MEDIUM"); + addRule("if turnDistance is SHORT and objectValue is MEDIUM then value is somewhat HIGH"); + + addRule("if turnDistance is LONG and objectValue is LOW then value is very LOW"); + addRule("if turnDistance is MEDIUM and objectValue is LOW then value is LOW"); + addRule("if turnDistance is SHORT and objectValue is LOW then value is MEDIUM"); + } + catch(fl::Exception & fe) + { + logAi->error("FindWanderTarget: %s", fe.getWhat()); + } + configure(); +} + EvalWanderTargetObject::~EvalWanderTargetObject() { delete objectValue; } +EvalVisitTile::EvalVisitTile() +{ + configure(); +} + EvalVisitTile::~EvalVisitTile() { } diff --git a/AI/VCAI/Fuzzy.h b/AI/VCAI/Fuzzy.h index 5a16beae1..4a9cfdd67 100644 --- a/AI/VCAI/Fuzzy.h +++ b/AI/VCAI/Fuzzy.h @@ -30,6 +30,7 @@ public: class TacticalAdvantage : public engineBase { public: + TacticalAdvantage(); fl::InputVariable * ourWalkers, *ourShooters, *ourFlyers, *enemyWalkers, *enemyShooters, *enemyFlyers; fl::InputVariable * ourSpeed, *enemySpeed; fl::InputVariable * bankPresent; @@ -38,29 +39,34 @@ public: ~TacticalAdvantage(); }; -class HeroMovementGoalEngine : public engineBase +class HeroMovementGoalEngineBase : public engineBase //abstract class for hero visiting goals, allows evaluating newly added elementar objectives with appropiately broad context data { public: + HeroMovementGoalEngineBase(); fl::InputVariable * strengthRatio; fl::InputVariable * heroStrength; fl::InputVariable * turnDistance; fl::InputVariable * missionImportance; fl::InputVariable * estimatedReward; fl::OutputVariable * value; - ~HeroMovementGoalEngine(); + ~HeroMovementGoalEngineBase(); + +private: + float calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const; }; -class EvalVisitTile : public HeroMovementGoalEngine +class EvalVisitTile : public HeroMovementGoalEngineBase { public: + EvalVisitTile(); ~EvalVisitTile(); }; -class EvalWanderTargetObject : public HeroMovementGoalEngine //designed for use with VCAI::wander() +class EvalWanderTargetObject : public HeroMovementGoalEngineBase //designed for use with VCAI::wander() { public: + EvalWanderTargetObject(); fl::InputVariable * objectValue; - fl::OutputVariable * visitGain; ~EvalWanderTargetObject(); }; @@ -74,17 +80,12 @@ class FuzzyHelper EvalWanderTargetObject wanderTarget; -private: - float calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const; - public: enum RuleBlocks {BANK_DANGER, TACTICAL_ADVANTAGE, VISIT_TILE}; //blocks should be initialized in this order, which may be confusing :/ FuzzyHelper(); void initTacticalAdvantage(); - void initVisitTile(); - void initWanderTarget(); float evaluate(Goals::Explore & g); float evaluate(Goals::RecruitHero & g); From 37b2dbcbba480a702a44724116026935d031efc0 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sun, 5 Aug 2018 15:50:56 +0200 Subject: [PATCH 03/33] Finish moving init functions to goal engine constructors --- AI/VCAI/Fuzzy.cpp | 210 ++++++++++++++++++++++------------------------ AI/VCAI/Fuzzy.h | 8 +- 2 files changed, 101 insertions(+), 117 deletions(-) diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index 810cc20b2..92dddb03c 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -92,116 +92,6 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army) return as; } -FuzzyHelper::FuzzyHelper() -{ - initTacticalAdvantage(); - ta.configure(); - initVisitTile(); - vt.configure(); - initWanderTarget(); - wanderTarget.configure(); -} - - -void FuzzyHelper::initTacticalAdvantage() -{ - try - { - ta.ourShooters = new fl::InputVariable("OurShooters"); - ta.ourWalkers = new fl::InputVariable("OurWalkers"); - ta.ourFlyers = new fl::InputVariable("OurFlyers"); - ta.enemyShooters = new fl::InputVariable("EnemyShooters"); - ta.enemyWalkers = new fl::InputVariable("EnemyWalkers"); - ta.enemyFlyers = new fl::InputVariable("EnemyFlyers"); - - //Tactical advantage calculation - std::vector helper = - { - ta.ourShooters, ta.ourWalkers, ta.ourFlyers, ta.enemyShooters, ta.enemyWalkers, ta.enemyFlyers - }; - - for(auto val : helper) - { - ta.engine.addInputVariable(val); - val->addTerm(new fl::Ramp("FEW", 0.6, 0.0)); - val->addTerm(new fl::Ramp("MANY", 0.4, 1)); - val->setRange(0.0, 1.0); - } - - ta.ourSpeed = new fl::InputVariable("OurSpeed"); - ta.enemySpeed = new fl::InputVariable("EnemySpeed"); - - helper = {ta.ourSpeed, ta.enemySpeed}; - - for(auto val : helper) - { - ta.engine.addInputVariable(val); - val->addTerm(new fl::Ramp("LOW", 6.5, 3)); - val->addTerm(new fl::Triangle("MEDIUM", 5.5, 10.5)); - val->addTerm(new fl::Ramp("HIGH", 8.5, 16)); - val->setRange(0, 25); - } - - ta.castleWalls = new fl::InputVariable("CastleWalls"); - ta.engine.addInputVariable(ta.castleWalls); - { - fl::Rectangle * none = new fl::Rectangle("NONE", CGTownInstance::NONE, CGTownInstance::NONE + (CGTownInstance::FORT - CGTownInstance::NONE) * 0.5f); - ta.castleWalls->addTerm(none); - - fl::Trapezoid * medium = new fl::Trapezoid("MEDIUM", (CGTownInstance::FORT - CGTownInstance::NONE) * 0.5f, CGTownInstance::FORT, - CGTownInstance::CITADEL, CGTownInstance::CITADEL + (CGTownInstance::CASTLE - CGTownInstance::CITADEL) * 0.5f); - ta.castleWalls->addTerm(medium); - - fl::Ramp * high = new fl::Ramp("HIGH", CGTownInstance::CITADEL - 0.1, CGTownInstance::CASTLE); - ta.castleWalls->addTerm(high); - - ta.castleWalls->setRange(CGTownInstance::NONE, CGTownInstance::CASTLE); - } - - - ta.bankPresent = new fl::InputVariable("Bank"); - ta.engine.addInputVariable(ta.bankPresent); - { - fl::Rectangle * termFalse = new fl::Rectangle("FALSE", 0.0, 0.5f); - ta.bankPresent->addTerm(termFalse); - fl::Rectangle * termTrue = new fl::Rectangle("TRUE", 0.5f, 1); - ta.bankPresent->addTerm(termTrue); - ta.bankPresent->setRange(0, 1); - } - - ta.threat = new fl::OutputVariable("Threat"); - ta.engine.addOutputVariable(ta.threat); - ta.threat->addTerm(new fl::Ramp("LOW", 1, MIN_AI_STRENGHT)); - ta.threat->addTerm(new fl::Triangle("MEDIUM", 0.8, 1.2)); - ta.threat->addTerm(new fl::Ramp("HIGH", 1, 1.5)); - ta.threat->setRange(MIN_AI_STRENGHT, 1.5); - - ta.addRule("if OurShooters is MANY and EnemySpeed is LOW then Threat is LOW"); - ta.addRule("if OurShooters is MANY and EnemyShooters is FEW then Threat is LOW"); - ta.addRule("if OurSpeed is LOW and EnemyShooters is MANY then Threat is HIGH"); - ta.addRule("if OurSpeed is HIGH and EnemyShooters is MANY then Threat is LOW"); - - ta.addRule("if OurWalkers is FEW and EnemyShooters is MANY then Threat is somewhat LOW"); - ta.addRule("if OurShooters is MANY and EnemySpeed is HIGH then Threat is somewhat HIGH"); - //just to cover all cases - ta.addRule("if OurShooters is FEW and EnemySpeed is HIGH then Threat is MEDIUM"); - ta.addRule("if EnemySpeed is MEDIUM then Threat is MEDIUM"); - ta.addRule("if EnemySpeed is LOW and OurShooters is FEW then Threat is MEDIUM"); - - ta.addRule("if Bank is TRUE and OurShooters is MANY then Threat is somewhat HIGH"); - ta.addRule("if Bank is TRUE and EnemyShooters is MANY then Threat is LOW"); - - ta.addRule("if CastleWalls is HIGH and OurWalkers is MANY then Threat is very HIGH"); - ta.addRule("if CastleWalls is HIGH and OurFlyers is MANY and OurShooters is MANY then Threat is MEDIUM"); - ta.addRule("if CastleWalls is MEDIUM and OurShooters is MANY and EnemyWalkers is MANY then Threat is LOW"); - - } - catch(fl::Exception & pe) - { - logAi->error("initTacticalAdvantage: %s", pe.getWhat()); - } -} - float HeroMovementGoalEngineBase::calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const { float turns = 0.0f; @@ -321,6 +211,106 @@ float FuzzyHelper::getWanderTargetObjectValue(const CGHeroInstance & h, const Ob return output; } +TacticalAdvantage::TacticalAdvantage() +{ + try + { + ourShooters = new fl::InputVariable("OurShooters"); + ourWalkers = new fl::InputVariable("OurWalkers"); + ourFlyers = new fl::InputVariable("OurFlyers"); + enemyShooters = new fl::InputVariable("EnemyShooters"); + enemyWalkers = new fl::InputVariable("EnemyWalkers"); + enemyFlyers = new fl::InputVariable("EnemyFlyers"); + + //Tactical advantage calculation + std::vector helper = + { + ourShooters, ourWalkers, ourFlyers, enemyShooters, enemyWalkers, enemyFlyers + }; + + for(auto val : helper) + { + engine.addInputVariable(val); + val->addTerm(new fl::Ramp("FEW", 0.6, 0.0)); + val->addTerm(new fl::Ramp("MANY", 0.4, 1)); + val->setRange(0.0, 1.0); + } + + ourSpeed = new fl::InputVariable("OurSpeed"); + enemySpeed = new fl::InputVariable("EnemySpeed"); + + helper = { ourSpeed, enemySpeed }; + + for(auto val : helper) + { + engine.addInputVariable(val); + val->addTerm(new fl::Ramp("LOW", 6.5, 3)); + val->addTerm(new fl::Triangle("MEDIUM", 5.5, 10.5)); + val->addTerm(new fl::Ramp("HIGH", 8.5, 16)); + val->setRange(0, 25); + } + + castleWalls = new fl::InputVariable("CastleWalls"); + engine.addInputVariable(castleWalls); + { + fl::Rectangle * none = new fl::Rectangle("NONE", CGTownInstance::NONE, CGTownInstance::NONE + (CGTownInstance::FORT - CGTownInstance::NONE) * 0.5f); + castleWalls->addTerm(none); + + fl::Trapezoid * medium = new fl::Trapezoid("MEDIUM", (CGTownInstance::FORT - CGTownInstance::NONE) * 0.5f, CGTownInstance::FORT, + CGTownInstance::CITADEL, CGTownInstance::CITADEL + (CGTownInstance::CASTLE - CGTownInstance::CITADEL) * 0.5f); + castleWalls->addTerm(medium); + + fl::Ramp * high = new fl::Ramp("HIGH", CGTownInstance::CITADEL - 0.1, CGTownInstance::CASTLE); + castleWalls->addTerm(high); + + castleWalls->setRange(CGTownInstance::NONE, CGTownInstance::CASTLE); + } + + + bankPresent = new fl::InputVariable("Bank"); + engine.addInputVariable(bankPresent); + { + fl::Rectangle * termFalse = new fl::Rectangle("FALSE", 0.0, 0.5f); + bankPresent->addTerm(termFalse); + fl::Rectangle * termTrue = new fl::Rectangle("TRUE", 0.5f, 1); + bankPresent->addTerm(termTrue); + bankPresent->setRange(0, 1); + } + + threat = new fl::OutputVariable("Threat"); + engine.addOutputVariable(threat); + threat->addTerm(new fl::Ramp("LOW", 1, MIN_AI_STRENGHT)); + threat->addTerm(new fl::Triangle("MEDIUM", 0.8, 1.2)); + threat->addTerm(new fl::Ramp("HIGH", 1, 1.5)); + threat->setRange(MIN_AI_STRENGHT, 1.5); + + addRule("if OurShooters is MANY and EnemySpeed is LOW then Threat is LOW"); + addRule("if OurShooters is MANY and EnemyShooters is FEW then Threat is LOW"); + addRule("if OurSpeed is LOW and EnemyShooters is MANY then Threat is HIGH"); + addRule("if OurSpeed is HIGH and EnemyShooters is MANY then Threat is LOW"); + + addRule("if OurWalkers is FEW and EnemyShooters is MANY then Threat is somewhat LOW"); + addRule("if OurShooters is MANY and EnemySpeed is HIGH then Threat is somewhat HIGH"); + //just to cover all cases + addRule("if OurShooters is FEW and EnemySpeed is HIGH then Threat is MEDIUM"); + addRule("if EnemySpeed is MEDIUM then Threat is MEDIUM"); + addRule("if EnemySpeed is LOW and OurShooters is FEW then Threat is MEDIUM"); + + addRule("if Bank is TRUE and OurShooters is MANY then Threat is somewhat HIGH"); + addRule("if Bank is TRUE and EnemyShooters is MANY then Threat is LOW"); + + addRule("if CastleWalls is HIGH and OurWalkers is MANY then Threat is very HIGH"); + addRule("if CastleWalls is HIGH and OurFlyers is MANY and OurShooters is MANY then Threat is MEDIUM"); + addRule("if CastleWalls is MEDIUM and OurShooters is MANY and EnemyWalkers is MANY then Threat is LOW"); + + } + catch(fl::Exception & pe) + { + logAi->error("initTacticalAdvantage: %s", pe.getWhat()); + } + configure(); +} + TacticalAdvantage::~TacticalAdvantage() { //TODO: smart pointers? diff --git a/AI/VCAI/Fuzzy.h b/AI/VCAI/Fuzzy.h index 4a9cfdd67..4549e1d2d 100644 --- a/AI/VCAI/Fuzzy.h +++ b/AI/VCAI/Fuzzy.h @@ -51,7 +51,7 @@ public: fl::OutputVariable * value; ~HeroMovementGoalEngineBase(); -private: +protected: float calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const; }; @@ -81,12 +81,6 @@ class FuzzyHelper EvalWanderTargetObject wanderTarget; public: - enum RuleBlocks {BANK_DANGER, TACTICAL_ADVANTAGE, VISIT_TILE}; - //blocks should be initialized in this order, which may be confusing :/ - - FuzzyHelper(); - void initTacticalAdvantage(); - float evaluate(Goals::Explore & g); float evaluate(Goals::RecruitHero & g); float evaluate(Goals::VisitTile & g); From 64b7c9f0368efd1345fefef6f645584123d998bb Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sun, 5 Aug 2018 19:03:25 +0200 Subject: [PATCH 04/33] VisitTile fuzzy logic separated from FuzzyHelper --- AI/VCAI/Fuzzy.cpp | 210 ++++++++++++++++++++++++---------------------- AI/VCAI/Fuzzy.h | 30 ++++--- 2 files changed, 125 insertions(+), 115 deletions(-) diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index 92dddb03c..cc94b25ba 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -125,60 +125,6 @@ ui64 FuzzyHelper::estimateBankDanger(const CBank * bank) } -float FuzzyHelper::getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy) -{ - float output = 1; - try - { - armyStructure ourStructure = evaluateArmyStructure(we); - armyStructure enemyStructure = evaluateArmyStructure(enemy); - - ta.ourWalkers->setValue(ourStructure.walkers); - ta.ourShooters->setValue(ourStructure.shooters); - ta.ourFlyers->setValue(ourStructure.flyers); - ta.ourSpeed->setValue(ourStructure.maxSpeed); - - ta.enemyWalkers->setValue(enemyStructure.walkers); - ta.enemyShooters->setValue(enemyStructure.shooters); - ta.enemyFlyers->setValue(enemyStructure.flyers); - ta.enemySpeed->setValue(enemyStructure.maxSpeed); - - bool bank = dynamic_cast(enemy); - if(bank) - ta.bankPresent->setValue(1); - else - ta.bankPresent->setValue(0); - - const CGTownInstance * fort = dynamic_cast(enemy); - if(fort) - ta.castleWalls->setValue(fort->fortLevel()); - else - ta.castleWalls->setValue(0); - - //engine.process(TACTICAL_ADVANTAGE);//TODO: Process only Tactical_Advantage - ta.engine.process(); - output = ta.threat->getValue(); - } - catch(fl::Exception & fe) - { - logAi->error("getTacticalAdvantage: %s ", fe.getWhat()); - } - - if(output < 0 || (output != output)) - { - fl::InputVariable * tab[] = {ta.bankPresent, ta.castleWalls, ta.ourWalkers, ta.ourShooters, ta.ourFlyers, ta.ourSpeed, ta.enemyWalkers, ta.enemyShooters, ta.enemyFlyers, ta.enemySpeed}; - std::string names[] = {"bankPresent", "castleWalls", "ourWalkers", "ourShooters", "ourFlyers", "ourSpeed", "enemyWalkers", "enemyShooters", "enemyFlyers", "enemySpeed" }; - std::stringstream log("Warning! Fuzzy engine doesn't cover this set of parameters: "); - - for(int i = 0; i < boost::size(tab); i++) - log << names[i] << ": " << tab[i]->getValue() << " "; - logAi->error(log.str()); - assert(false); - } - - return output; -} - float FuzzyHelper::getWanderTargetObjectValue(const CGHeroInstance & h, const ObjectIdRef & obj) { float distFromObject = calculateTurnDistanceInputValue(&h, obj->pos); @@ -211,7 +157,7 @@ float FuzzyHelper::getWanderTargetObjectValue(const CGHeroInstance & h, const Ob return output; } -TacticalAdvantage::TacticalAdvantage() +TacticalAdvantageEngine::TacticalAdvantageEngine() { try { @@ -311,7 +257,61 @@ TacticalAdvantage::TacticalAdvantage() configure(); } -TacticalAdvantage::~TacticalAdvantage() +float TacticalAdvantageEngine::getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy) +{ + float output = 1; + try + { + armyStructure ourStructure = evaluateArmyStructure(we); + armyStructure enemyStructure = evaluateArmyStructure(enemy); + + ourWalkers->setValue(ourStructure.walkers); + ourShooters->setValue(ourStructure.shooters); + ourFlyers->setValue(ourStructure.flyers); + ourSpeed->setValue(ourStructure.maxSpeed); + + enemyWalkers->setValue(enemyStructure.walkers); + enemyShooters->setValue(enemyStructure.shooters); + enemyFlyers->setValue(enemyStructure.flyers); + enemySpeed->setValue(enemyStructure.maxSpeed); + + bool bank = dynamic_cast(enemy); + if(bank) + bankPresent->setValue(1); + else + bankPresent->setValue(0); + + const CGTownInstance * fort = dynamic_cast(enemy); + if(fort) + castleWalls->setValue(fort->fortLevel()); + else + castleWalls->setValue(0); + + //engine.process(TACTICAL_ADVANTAGE);//TODO: Process only Tactical_Advantage + engine.process(); + output = threat->getValue(); + } + catch(fl::Exception & fe) + { + logAi->error("getTacticalAdvantage: %s ", fe.getWhat()); + } + + if(output < 0 || (output != output)) + { + fl::InputVariable * tab[] = { bankPresent, castleWalls, ourWalkers, ourShooters, ourFlyers, ourSpeed, enemyWalkers, enemyShooters, enemyFlyers, enemySpeed }; + std::string names[] = { "bankPresent", "castleWalls", "ourWalkers", "ourShooters", "ourFlyers", "ourSpeed", "enemyWalkers", "enemyShooters", "enemyFlyers", "enemySpeed" }; + std::stringstream log("Warning! Fuzzy engine doesn't cover this set of parameters: "); + + for(int i = 0; i < boost::size(tab); i++) + log << names[i] << ": " << tab[i]->getValue() << " "; + logAi->error(log.str()); + assert(false); + } + + return output; +} + +TacticalAdvantageEngine::~TacticalAdvantageEngine() { //TODO: smart pointers? delete ourWalkers; @@ -454,50 +454,7 @@ HeroMovementGoalEngineBase::~HeroMovementGoalEngineBase() float FuzzyHelper::evaluate(Goals::VisitTile & g) { - //we assume that hero is already set and we want to choose most suitable one for the mission - if(!g.hero) - return 0; - - //assert(cb->isInTheMap(g.tile)); - float turns = calculateTurnDistanceInputValue(g.hero.h, g.tile); - float missionImportance = 0; - if(vstd::contains(ai->lockedHeroes, g.hero)) - missionImportance = ai->lockedHeroes[g.hero]->priority; - - float strengthRatio = 10.0f; //we are much stronger than enemy - ui64 danger = evaluateDanger(g.tile, g.hero.h); - 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->setValue(strengthRatio); - vt.heroStrength->setValue((fl::scalar)g.hero->getTotalStrength() / ai->primaryHero()->getTotalStrength()); - vt.turnDistance->setValue(turns); - vt.missionImportance->setValue(missionImportance); - vt.estimatedReward->setValue(tilePriority); - - vt.engine.process(); - //engine.process(VISIT_TILE); //TODO: Process only Visit_Tile - g.priority = vt.value->getValue(); - } - catch(fl::Exception & fe) - { - logAi->error("evaluate VisitTile: %s", fe.getWhat()); - } - assert(g.priority >= 0); - return g.priority; + return visitTileEngine.evaluate(g); } float FuzzyHelper::evaluate(Goals::VisitHero & g) { @@ -616,11 +573,60 @@ EvalWanderTargetObject::~EvalWanderTargetObject() delete objectValue; } -EvalVisitTile::EvalVisitTile() +VisitTileEngine::VisitTileEngine() //so far no VisitTile-specific variables that are not shared with HeroMovementGoalEngineBase { configure(); } -EvalVisitTile::~EvalVisitTile() +VisitTileEngine::~VisitTileEngine() { } + +float VisitTileEngine::evaluate(Goals::AbstractGoal & goal) +{ + auto g = dynamic_cast(goal); + //we assume that hero is already set and we want to choose most suitable one for the mission + if(!g.hero) + return 0; + + //assert(cb->isInTheMap(g.tile)); + float turns = calculateTurnDistanceInputValue(g.hero.h, g.tile); + float missionImportanceData = 0; + if(vstd::contains(ai->lockedHeroes, g.hero)) + missionImportanceData = ai->lockedHeroes[g.hero]->priority; + + float strengthRatioData = 10.0f; //we are much stronger than enemy + ui64 danger = evaluateDanger(g.tile, g.hero.h); + if(danger) + strengthRatioData = (fl::scalar)g.hero.h->getTotalStrength() / danger; + + float tilePriority = 0; + if(g.objid == -1) + { + estimatedReward->setEnabled(false); + } + else if(g.objid == Obj::TOWN) //TODO: move to getObj eventually and add appropiate logic there + { + estimatedReward->setEnabled(true); + tilePriority = 5; + } + + try + { + strengthRatio->setValue(strengthRatioData); + heroStrength->setValue((fl::scalar)g.hero->getTotalStrength() / ai->primaryHero()->getTotalStrength()); + turnDistance->setValue(turns); + missionImportance->setValue(missionImportanceData); + estimatedReward->setValue(tilePriority); + + engine.process(); + //engine.process(VISIT_TILE); //TODO: Process only Visit_Tile + g.priority = value->getValue(); + } + catch(fl::Exception & fe) + { + logAi->error("evaluate VisitTile: %s", fe.getWhat()); + } + assert(g.priority >= 0); + return g.priority; +} diff --git a/AI/VCAI/Fuzzy.h b/AI/VCAI/Fuzzy.h index 4549e1d2d..5b39d35e4 100644 --- a/AI/VCAI/Fuzzy.h +++ b/AI/VCAI/Fuzzy.h @@ -23,23 +23,25 @@ public: fl::RuleBlock rules; engineBase(); - void configure(); + virtual void configure(); void addRule(const std::string & txt); }; -class TacticalAdvantage : public engineBase +class TacticalAdvantageEngine : public engineBase { public: - TacticalAdvantage(); + TacticalAdvantageEngine(); fl::InputVariable * ourWalkers, *ourShooters, *ourFlyers, *enemyWalkers, *enemyShooters, *enemyFlyers; fl::InputVariable * ourSpeed, *enemySpeed; fl::InputVariable * bankPresent; fl::InputVariable * castleWalls; fl::OutputVariable * threat; - ~TacticalAdvantage(); + + float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us + ~TacticalAdvantageEngine(); }; -class HeroMovementGoalEngineBase : public engineBase //abstract class for hero visiting goals, allows evaluating newly added elementar objectives with appropiately broad context data +class HeroMovementGoalEngineBase : public engineBase { public: HeroMovementGoalEngineBase(); @@ -49,17 +51,20 @@ public: fl::InputVariable * missionImportance; fl::InputVariable * estimatedReward; fl::OutputVariable * value; + + virtual float evaluate(Goals::AbstractGoal & goal) = 0; ~HeroMovementGoalEngineBase(); protected: float calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const; }; -class EvalVisitTile : public HeroMovementGoalEngineBase +class VisitTileEngine : public HeroMovementGoalEngineBase { public: - EvalVisitTile(); - ~EvalVisitTile(); + VisitTileEngine(); + ~VisitTileEngine(); + float evaluate(Goals::AbstractGoal & goal) override; }; class EvalWanderTargetObject : public HeroMovementGoalEngineBase //designed for use with VCAI::wander() @@ -74,11 +79,11 @@ class FuzzyHelper { friend class VCAI; - TacticalAdvantage ta; + TacticalAdvantageEngine tacticalAdvantageEngine; - EvalVisitTile vt; + VisitTileEngine visitTileEngine; - EvalWanderTargetObject wanderTarget; + EvalWanderTargetObject wanderTargetEngine; public: float evaluate(Goals::Explore & g); @@ -96,8 +101,7 @@ public: float evaluate(Goals::AbstractGoal & g); void setPriority(Goals::TSubgoal & g); - ui64 estimateBankDanger(const CBank * bank); - float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us + ui64 estimateBankDanger(const CBank * bank); //TODO: move to another class? float getWanderTargetObjectValue(const CGHeroInstance & h, const ObjectIdRef & obj); Goals::TSubgoal chooseSolution(Goals::TGoalVec vec); From b29d5bb00175dc1e823c033eb448f2ff98e0c398 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sun, 5 Aug 2018 20:30:08 +0200 Subject: [PATCH 05/33] finished FuzzyHelper logic separation --- AI/VCAI/Fuzzy.cpp | 146 +++++++++++++++++++++++----------------------- AI/VCAI/Fuzzy.h | 34 ++++++----- 2 files changed, 93 insertions(+), 87 deletions(-) diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index cc94b25ba..2c3e84d8a 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -125,38 +125,6 @@ ui64 FuzzyHelper::estimateBankDanger(const CBank * bank) } -float FuzzyHelper::getWanderTargetObjectValue(const CGHeroInstance & h, const ObjectIdRef & obj) -{ - float distFromObject = calculateTurnDistanceInputValue(&h, obj->pos); - boost::optional objValueKnownByAI = MapObjectsEvaluator::getInstance().getObjectValue(obj->ID, obj->subID); - int objValue = 0; - - if(objValueKnownByAI != boost::none) //consider adding value manipulation based on object instances on map - { - objValue = std::min(std::max(objValueKnownByAI.get(), 0), 20000); - } - else - { - MapObjectsEvaluator::getInstance().addObjectData(obj->ID, obj->subID, 0); - logGlobal->warn("AI met object type it doesn't know - ID: " + std::to_string(obj->ID) + ", subID: " + std::to_string(obj->subID) + " - adding to database with value " + std::to_string(objValue)); - } - - float output = -1.0f; - try - { - wanderTarget.turnDistance->setValue(distFromObject); - wanderTarget.objectValue->setValue(objValue); - wanderTarget.engine.process(); - output = wanderTarget.value->getValue(); - } - catch (fl::Exception & fe) - { - logAi->error("evaluate getWanderTargetObjectValue: %s", fe.getWhat()); - } - assert(output >= 0.0f); - return output; -} - TacticalAdvantageEngine::TacticalAdvantageEngine() { try @@ -287,7 +255,6 @@ float TacticalAdvantageEngine::getTacticalAdvantage(const CArmedInstance * we, c else castleWalls->setValue(0); - //engine.process(TACTICAL_ADVANTAGE);//TODO: Process only Tactical_Advantage engine.process(); output = threat->getValue(); } @@ -371,12 +338,11 @@ HeroMovementGoalEngineBase::HeroMovementGoalEngineBase() heroStrength = new fl::InputVariable("heroStrength"); //we want to use weakest possible hero turnDistance = new fl::InputVariable("turnDistance"); //we want to use hero who is near missionImportance = new fl::InputVariable("lockedMissionImportance"); //we may want to preempt hero with low-priority mission - estimatedReward = new fl::InputVariable("estimatedReward"); //indicate AI that content of the file is important or it is probably bad value = new fl::OutputVariable("Value"); value->setMinimum(0); value->setMaximum(5); - std::vector helper = { strengthRatio, heroStrength, turnDistance, missionImportance, estimatedReward }; + std::vector helper = { strengthRatio, heroStrength, turnDistance, missionImportance }; for(auto val : helper) { engine.addInputVariable(val); @@ -403,10 +369,6 @@ HeroMovementGoalEngineBase::HeroMovementGoalEngineBase() missionImportance->addTerm(new fl::Ramp("HIGH", 2.5, 5)); missionImportance->setRange(0.0, 5.0); - estimatedReward->addTerm(new fl::Ramp("LOW", 2.5, 0)); - estimatedReward->addTerm(new fl::Ramp("HIGH", 2.5, 5)); - 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 value->addTerm(new fl::Ramp("LOW", 2.5, 0)); @@ -433,9 +395,6 @@ HeroMovementGoalEngineBase::HeroMovementGoalEngineBase() addRule("if turnDistance is SMALL then Value is HIGH"); addRule("if turnDistance is MEDIUM then Value is MEDIUM"); 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 - addRule("if estimatedReward is HIGH then Value is very HIGH"); - addRule("if estimatedReward is LOW then Value is somewhat LOW"); } catch(fl::Exception & fe) { @@ -443,19 +402,47 @@ HeroMovementGoalEngineBase::HeroMovementGoalEngineBase() } } +void HeroMovementGoalEngineBase::setSharedFuzzyVariables(Goals::AbstractGoal & goal) +{ + float turns = calculateTurnDistanceInputValue(goal.hero.h, goal.tile); + float missionImportanceData = 0; + if(vstd::contains(ai->lockedHeroes, goal.hero)) + missionImportanceData = ai->lockedHeroes[goal.hero]->priority; + + float strengthRatioData = 10.0f; //we are much stronger than enemy + ui64 danger = evaluateDanger(goal.tile, goal.hero.h); + if(danger) + strengthRatioData = (fl::scalar)goal.hero.h->getTotalStrength() / danger; + + try + { + strengthRatio->setValue(strengthRatioData); + heroStrength->setValue((fl::scalar)goal.hero->getTotalStrength() / ai->primaryHero()->getTotalStrength()); + turnDistance->setValue(turns); + missionImportance->setValue(missionImportanceData); + } + catch(fl::Exception & fe) + { + logAi->error("HeroMovementGoalEngineBase::setSharedFuzzyVariables: %s", fe.getWhat()); + } +} + HeroMovementGoalEngineBase::~HeroMovementGoalEngineBase() { delete strengthRatio; delete heroStrength; delete turnDistance; delete missionImportance; - delete estimatedReward; } float FuzzyHelper::evaluate(Goals::VisitTile & g) { return visitTileEngine.evaluate(g); } +float FuzzyHelper::evaluate(Goals::GetObj & g) +{ + return getObjEngine.evaluate(g); +} float FuzzyHelper::evaluate(Goals::VisitHero & g) { auto obj = cb->getObj(ObjectInstanceID(g.objid)); //we assume for now that these goals are similar @@ -533,15 +520,13 @@ void FuzzyHelper::setPriority(Goals::TSubgoal & g) //calls evaluate - Visitor pa g->setpriority(g->accept(this)); //this enforces returned value is set } -EvalWanderTargetObject::EvalWanderTargetObject() +GetObjEngine::GetObjEngine() { try { objectValue = new fl::InputVariable("objectValue"); //value of that object type known by AI - engine.addInputVariable(turnDistance); engine.addInputVariable(objectValue); - engine.addOutputVariable(value); //objectValue ranges are based on checking RMG priorities of some objects and trying to guess sane value ranges objectValue->addTerm(new fl::Ramp("LOW", 3000, 0)); //I have feeling that concave shape might work well instead of ramp for objectValue FL terms @@ -568,11 +553,51 @@ EvalWanderTargetObject::EvalWanderTargetObject() configure(); } -EvalWanderTargetObject::~EvalWanderTargetObject() +GetObjEngine::~GetObjEngine() { delete objectValue; } +float GetObjEngine::evaluate(Goals::AbstractGoal & goal) +{ + auto g = dynamic_cast(goal); + + if(!g.hero) + return 0; + + auto obj = cb->getObj(ObjectInstanceID(g.objid)); + + + boost::optional objValueKnownByAI = MapObjectsEvaluator::getInstance().getObjectValue(obj->ID, obj->subID); + int objValue = 0; + + if(objValueKnownByAI != boost::none) //consider adding value manipulation based on object instances on map + { + objValue = std::min(std::max(objValueKnownByAI.get(), 0), 20000); + } + else + { + MapObjectsEvaluator::getInstance().addObjectData(obj->ID, obj->subID, 0); + logGlobal->warn("AI met object type it doesn't know - ID: " + std::to_string(obj->ID) + ", subID: " + std::to_string(obj->subID) + " - adding to database with value " + std::to_string(objValue)); + } + + setSharedFuzzyVariables(goal); + + float output = -1.0f; + try + { + objectValue->setValue(objValue); + engine.process(); + output = value->getValue(); + } + catch(fl::Exception & fe) + { + logAi->error("evaluate getWanderTargetObjectValue: %s", fe.getWhat()); + } + assert(output >= 0.0f); + return output; +} + VisitTileEngine::VisitTileEngine() //so far no VisitTile-specific variables that are not shared with HeroMovementGoalEngineBase { configure(); @@ -590,37 +615,12 @@ float VisitTileEngine::evaluate(Goals::AbstractGoal & goal) return 0; //assert(cb->isInTheMap(g.tile)); - float turns = calculateTurnDistanceInputValue(g.hero.h, g.tile); - float missionImportanceData = 0; - if(vstd::contains(ai->lockedHeroes, g.hero)) - missionImportanceData = ai->lockedHeroes[g.hero]->priority; - float strengthRatioData = 10.0f; //we are much stronger than enemy - ui64 danger = evaluateDanger(g.tile, g.hero.h); - if(danger) - strengthRatioData = (fl::scalar)g.hero.h->getTotalStrength() / danger; - - float tilePriority = 0; - if(g.objid == -1) - { - estimatedReward->setEnabled(false); - } - else if(g.objid == Obj::TOWN) //TODO: move to getObj eventually and add appropiate logic there - { - estimatedReward->setEnabled(true); - tilePriority = 5; - } + setSharedFuzzyVariables(goal); try { - strengthRatio->setValue(strengthRatioData); - heroStrength->setValue((fl::scalar)g.hero->getTotalStrength() / ai->primaryHero()->getTotalStrength()); - turnDistance->setValue(turns); - missionImportance->setValue(missionImportanceData); - estimatedReward->setValue(tilePriority); - engine.process(); - //engine.process(VISIT_TILE); //TODO: Process only Visit_Tile g.priority = value->getValue(); } catch(fl::Exception & fe) diff --git a/AI/VCAI/Fuzzy.h b/AI/VCAI/Fuzzy.h index 5b39d35e4..875d11f9c 100644 --- a/AI/VCAI/Fuzzy.h +++ b/AI/VCAI/Fuzzy.h @@ -31,31 +31,35 @@ class TacticalAdvantageEngine : public engineBase { public: TacticalAdvantageEngine(); + + float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us + ~TacticalAdvantageEngine(); +private: fl::InputVariable * ourWalkers, *ourShooters, *ourFlyers, *enemyWalkers, *enemyShooters, *enemyFlyers; fl::InputVariable * ourSpeed, *enemySpeed; fl::InputVariable * bankPresent; fl::InputVariable * castleWalls; fl::OutputVariable * threat; - - float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us - ~TacticalAdvantageEngine(); }; -class HeroMovementGoalEngineBase : public engineBase +class HeroMovementGoalEngineBase : public engineBase //in future - maybe derive from some (GoalEngineBase : public engineBase) class for handling non-movement goals with common utility for goal engines { public: HeroMovementGoalEngineBase(); + + virtual float evaluate(Goals::AbstractGoal & goal) = 0; + virtual ~HeroMovementGoalEngineBase(); + +protected: + void setSharedFuzzyVariables(Goals::AbstractGoal & goal); + fl::InputVariable * strengthRatio; fl::InputVariable * heroStrength; fl::InputVariable * turnDistance; fl::InputVariable * missionImportance; - fl::InputVariable * estimatedReward; fl::OutputVariable * value; - virtual float evaluate(Goals::AbstractGoal & goal) = 0; - ~HeroMovementGoalEngineBase(); - -protected: +private: float calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const; }; @@ -67,12 +71,14 @@ public: float evaluate(Goals::AbstractGoal & goal) override; }; -class EvalWanderTargetObject : public HeroMovementGoalEngineBase //designed for use with VCAI::wander() +class GetObjEngine : public HeroMovementGoalEngineBase { public: - EvalWanderTargetObject(); + GetObjEngine(); + ~GetObjEngine(); + float evaluate(Goals::AbstractGoal & goal) override; +protected: fl::InputVariable * objectValue; - ~EvalWanderTargetObject(); }; class FuzzyHelper @@ -83,12 +89,13 @@ class FuzzyHelper VisitTileEngine visitTileEngine; - EvalWanderTargetObject wanderTargetEngine; + GetObjEngine getObjEngine; public: float evaluate(Goals::Explore & g); float evaluate(Goals::RecruitHero & g); float evaluate(Goals::VisitTile & g); + float evaluate(Goals::GetObj & g); float evaluate(Goals::VisitHero & g); float evaluate(Goals::BuildThis & g); float evaluate(Goals::DigAtTile & g); @@ -102,7 +109,6 @@ public: void setPriority(Goals::TSubgoal & g); ui64 estimateBankDanger(const CBank * bank); //TODO: move to another class? - float getWanderTargetObjectValue(const CGHeroInstance & h, const ObjectIdRef & obj); Goals::TSubgoal chooseSolution(Goals::TGoalVec vec); //std::shared_ptr chooseSolution (std::vector> & vec); From 12d750a7673eb664aea98ccd0c0dc79c3ab6407a Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sun, 5 Aug 2018 20:59:04 +0200 Subject: [PATCH 06/33] Accessibility tweaks + fix some compile errors --- AI/VCAI/AIUtility.cpp | 6 +++--- AI/VCAI/Fuzzy.h | 10 ++++------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/AI/VCAI/AIUtility.cpp b/AI/VCAI/AIUtility.cpp index 768619da1..311597daf 100644 --- a/AI/VCAI/AIUtility.cpp +++ b/AI/VCAI/AIUtility.cpp @@ -245,7 +245,7 @@ ui64 evaluateDanger(crint3 tile, const CGHeroInstance * visitor) auto armedObj = dynamic_cast(dangerousObject); if(armedObj) { - float tacticalAdvantage = fh->getTacticalAdvantage(visitor, armedObj); + float tacticalAdvantage = fh->tacticalAdvantageEngine.getTacticalAdvantage(visitor, armedObj); objectDanger *= tacticalAdvantage; //this line tends to go infinite for allied towns (?) } } @@ -258,7 +258,7 @@ ui64 evaluateDanger(crint3 tile, const CGHeroInstance * visitor) auto guards = cb->getGuardingCreatures(it->second->visitablePos()); for(auto cre : guards) { - vstd::amax(guardDanger, evaluateDanger(cre) * fh->getTacticalAdvantage(visitor, dynamic_cast(cre))); + vstd::amax(guardDanger, evaluateDanger(cre) * fh->tacticalAdvantageEngine.getTacticalAdvantage(visitor, dynamic_cast(cre))); } } } @@ -267,7 +267,7 @@ ui64 evaluateDanger(crint3 tile, const CGHeroInstance * visitor) auto guards = cb->getGuardingCreatures(tile); for(auto cre : guards) { - vstd::amax(guardDanger, evaluateDanger(cre) * fh->getTacticalAdvantage(visitor, dynamic_cast(cre))); //we are interested in strongest monster around + vstd::amax(guardDanger, evaluateDanger(cre) * fh->tacticalAdvantageEngine.getTacticalAdvantage(visitor, dynamic_cast(cre))); //we are interested in strongest monster around } //TODO mozna odwiedzic blockvis nie ruszajac straznika diff --git a/AI/VCAI/Fuzzy.h b/AI/VCAI/Fuzzy.h index 875d11f9c..6fd969605 100644 --- a/AI/VCAI/Fuzzy.h +++ b/AI/VCAI/Fuzzy.h @@ -18,13 +18,13 @@ struct SectorMap; class engineBase { -public: +protected: fl::Engine engine; fl::RuleBlock rules; - - engineBase(); virtual void configure(); void addRule(const std::string & txt); +public: + engineBase(); }; class TacticalAdvantageEngine : public engineBase @@ -85,13 +85,11 @@ class FuzzyHelper { friend class VCAI; +public: TacticalAdvantageEngine tacticalAdvantageEngine; - VisitTileEngine visitTileEngine; - GetObjEngine getObjEngine; -public: float evaluate(Goals::Explore & g); float evaluate(Goals::RecruitHero & g); float evaluate(Goals::VisitTile & g); From 67439bdd3660f20667ebabece69de721c86189f4 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Mon, 6 Aug 2018 19:20:36 +0200 Subject: [PATCH 07/33] use smart pointers for fl variables --- AI/VCAI/Fuzzy.cpp | 97 ++++++++++++++--------------------------------- AI/VCAI/Fuzzy.h | 26 ++++++------- 2 files changed, 39 insertions(+), 84 deletions(-) diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index 2c3e84d8a..1fbe2aa6e 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -129,20 +129,14 @@ TacticalAdvantageEngine::TacticalAdvantageEngine() { try { - ourShooters = new fl::InputVariable("OurShooters"); - ourWalkers = new fl::InputVariable("OurWalkers"); - ourFlyers = new fl::InputVariable("OurFlyers"); - enemyShooters = new fl::InputVariable("EnemyShooters"); - enemyWalkers = new fl::InputVariable("EnemyWalkers"); - enemyFlyers = new fl::InputVariable("EnemyFlyers"); + ourShooters = vstd::make_unique(fl::InputVariable("OurShooters")); + ourWalkers = vstd::make_unique(fl::InputVariable("OurWalkers")); + ourFlyers = vstd::make_unique(fl::InputVariable("OurFlyers")); + enemyShooters = vstd::make_unique(fl::InputVariable("EnemyShooters")); + enemyWalkers = vstd::make_unique(fl::InputVariable("EnemyWalkers")); + enemyFlyers = vstd::make_unique(fl::InputVariable("EnemyFlyers")); - //Tactical advantage calculation - std::vector helper = - { - ourShooters, ourWalkers, ourFlyers, enemyShooters, enemyWalkers, enemyFlyers - }; - - for(auto val : helper) + for(auto val : {ourShooters.get(), ourWalkers.get(), ourFlyers.get(), enemyShooters.get(), enemyWalkers.get(), enemyFlyers.get()}) { engine.addInputVariable(val); val->addTerm(new fl::Ramp("FEW", 0.6, 0.0)); @@ -150,12 +144,10 @@ TacticalAdvantageEngine::TacticalAdvantageEngine() val->setRange(0.0, 1.0); } - ourSpeed = new fl::InputVariable("OurSpeed"); - enemySpeed = new fl::InputVariable("EnemySpeed"); + ourSpeed = vstd::make_unique(fl::InputVariable("OurSpeed")); + enemySpeed = vstd::make_unique(fl::InputVariable("EnemySpeed")); - helper = { ourSpeed, enemySpeed }; - - for(auto val : helper) + for(auto val : {ourSpeed.get(), enemySpeed.get()}) { engine.addInputVariable(val); val->addTerm(new fl::Ramp("LOW", 6.5, 3)); @@ -164,8 +156,8 @@ TacticalAdvantageEngine::TacticalAdvantageEngine() val->setRange(0, 25); } - castleWalls = new fl::InputVariable("CastleWalls"); - engine.addInputVariable(castleWalls); + castleWalls = vstd::make_unique(fl::InputVariable("CastleWalls")); + engine.addInputVariable(castleWalls.get()); { fl::Rectangle * none = new fl::Rectangle("NONE", CGTownInstance::NONE, CGTownInstance::NONE + (CGTownInstance::FORT - CGTownInstance::NONE) * 0.5f); castleWalls->addTerm(none); @@ -181,8 +173,8 @@ TacticalAdvantageEngine::TacticalAdvantageEngine() } - bankPresent = new fl::InputVariable("Bank"); - engine.addInputVariable(bankPresent); + bankPresent = vstd::make_unique(fl::InputVariable("Bank")); + engine.addInputVariable(bankPresent.get()); { fl::Rectangle * termFalse = new fl::Rectangle("FALSE", 0.0, 0.5f); bankPresent->addTerm(termFalse); @@ -191,8 +183,8 @@ TacticalAdvantageEngine::TacticalAdvantageEngine() bankPresent->setRange(0, 1); } - threat = new fl::OutputVariable("Threat"); - engine.addOutputVariable(threat); + threat = vstd::make_unique(fl::OutputVariable("Threat")); + engine.addOutputVariable(threat.get()); threat->addTerm(new fl::Ramp("LOW", 1, MIN_AI_STRENGHT)); threat->addTerm(new fl::Triangle("MEDIUM", 0.8, 1.2)); threat->addTerm(new fl::Ramp("HIGH", 1, 1.5)); @@ -265,12 +257,13 @@ float TacticalAdvantageEngine::getTacticalAdvantage(const CArmedInstance * we, c if(output < 0 || (output != output)) { - fl::InputVariable * tab[] = { bankPresent, castleWalls, ourWalkers, ourShooters, ourFlyers, ourSpeed, enemyWalkers, enemyShooters, enemyFlyers, enemySpeed }; + fl::scalar tab[] = { bankPresent->getValue(), castleWalls->getValue(), ourWalkers->getValue(), ourShooters->getValue(), ourFlyers->getValue(), ourSpeed->getValue(), + enemyWalkers->getValue(), enemyShooters->getValue(), enemyFlyers->getValue(), enemySpeed->getValue() }; std::string names[] = { "bankPresent", "castleWalls", "ourWalkers", "ourShooters", "ourFlyers", "ourSpeed", "enemyWalkers", "enemyShooters", "enemyFlyers", "enemySpeed" }; std::stringstream log("Warning! Fuzzy engine doesn't cover this set of parameters: "); for(int i = 0; i < boost::size(tab); i++) - log << names[i] << ": " << tab[i]->getValue() << " "; + log << names[i] << ": " << tab[i] << " "; logAi->error(log.str()); assert(false); } @@ -278,22 +271,6 @@ float TacticalAdvantageEngine::getTacticalAdvantage(const CArmedInstance * we, c return output; } -TacticalAdvantageEngine::~TacticalAdvantageEngine() -{ - //TODO: smart pointers? - delete ourWalkers; - delete ourShooters; - delete ourFlyers; - delete enemyWalkers; - delete enemyShooters; - delete enemyFlyers; - delete ourSpeed; - delete enemySpeed; - delete bankPresent; - delete castleWalls; - delete threat; -} - //std::shared_ptr chooseSolution (std::vector> & vec) Goals::TSubgoal FuzzyHelper::chooseSolution(Goals::TGoalVec vec) @@ -334,20 +311,19 @@ HeroMovementGoalEngineBase::HeroMovementGoalEngineBase() { try { - strengthRatio = new fl::InputVariable("strengthRatio"); //hero must be strong enough to defeat guards - heroStrength = new fl::InputVariable("heroStrength"); //we want to use weakest possible hero - turnDistance = new fl::InputVariable("turnDistance"); //we want to use hero who is near - missionImportance = new fl::InputVariable("lockedMissionImportance"); //we may want to preempt hero with low-priority mission - value = new fl::OutputVariable("Value"); + strengthRatio = vstd::make_unique(fl::InputVariable("strengthRatio")); //hero must be strong enough to defeat guards + heroStrength = vstd::make_unique(fl::InputVariable("heroStrength")); //we want to use weakest possible hero + turnDistance = vstd::make_unique(fl::InputVariable("turnDistance")); //we want to use hero who is near + missionImportance = vstd::make_unique(fl::InputVariable("lockedMissionImportance")); //we may want to preempt hero with low-priority mission + value = vstd::make_unique(fl::OutputVariable("Value")); value->setMinimum(0); value->setMaximum(5); - std::vector helper = { strengthRatio, heroStrength, turnDistance, missionImportance }; - for(auto val : helper) + for(auto val : { strengthRatio.get(), heroStrength.get(), turnDistance.get(), missionImportance.get() }) { engine.addInputVariable(val); } - engine.addOutputVariable(value); + engine.addOutputVariable(value.get()); strengthRatio->addTerm(new fl::Ramp("LOW", SAFE_ATTACK_CONSTANT, 0)); strengthRatio->addTerm(new fl::Ramp("HIGH", SAFE_ATTACK_CONSTANT, SAFE_ATTACK_CONSTANT * 3)); @@ -427,14 +403,6 @@ void HeroMovementGoalEngineBase::setSharedFuzzyVariables(Goals::AbstractGoal & g } } -HeroMovementGoalEngineBase::~HeroMovementGoalEngineBase() -{ - delete strengthRatio; - delete heroStrength; - delete turnDistance; - delete missionImportance; -} - float FuzzyHelper::evaluate(Goals::VisitTile & g) { return visitTileEngine.evaluate(g); @@ -524,9 +492,9 @@ GetObjEngine::GetObjEngine() { try { - objectValue = new fl::InputVariable("objectValue"); //value of that object type known by AI + objectValue = vstd::make_unique(fl::InputVariable("objectValue")); //value of that object type known by AI - engine.addInputVariable(objectValue); + engine.addInputVariable(objectValue.get()); //objectValue ranges are based on checking RMG priorities of some objects and trying to guess sane value ranges objectValue->addTerm(new fl::Ramp("LOW", 3000, 0)); //I have feeling that concave shape might work well instead of ramp for objectValue FL terms @@ -553,11 +521,6 @@ GetObjEngine::GetObjEngine() configure(); } -GetObjEngine::~GetObjEngine() -{ - delete objectValue; -} - float GetObjEngine::evaluate(Goals::AbstractGoal & goal) { auto g = dynamic_cast(goal); @@ -603,10 +566,6 @@ VisitTileEngine::VisitTileEngine() //so far no VisitTile-specific variables that configure(); } -VisitTileEngine::~VisitTileEngine() -{ -} - float VisitTileEngine::evaluate(Goals::AbstractGoal & goal) { auto g = dynamic_cast(goal); diff --git a/AI/VCAI/Fuzzy.h b/AI/VCAI/Fuzzy.h index 6fd969605..2460a99a0 100644 --- a/AI/VCAI/Fuzzy.h +++ b/AI/VCAI/Fuzzy.h @@ -33,13 +33,12 @@ public: TacticalAdvantageEngine(); float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us - ~TacticalAdvantageEngine(); private: - fl::InputVariable * ourWalkers, *ourShooters, *ourFlyers, *enemyWalkers, *enemyShooters, *enemyFlyers; - fl::InputVariable * ourSpeed, *enemySpeed; - fl::InputVariable * bankPresent; - fl::InputVariable * castleWalls; - fl::OutputVariable * threat; + std::unique_ptr ourWalkers, ourShooters, ourFlyers, enemyWalkers, enemyShooters, enemyFlyers; + std::unique_ptr ourSpeed, enemySpeed; + std::unique_ptr bankPresent; + std::unique_ptr castleWalls; + std::unique_ptr threat; }; class HeroMovementGoalEngineBase : public engineBase //in future - maybe derive from some (GoalEngineBase : public engineBase) class for handling non-movement goals with common utility for goal engines @@ -48,16 +47,15 @@ public: HeroMovementGoalEngineBase(); virtual float evaluate(Goals::AbstractGoal & goal) = 0; - virtual ~HeroMovementGoalEngineBase(); protected: void setSharedFuzzyVariables(Goals::AbstractGoal & goal); - fl::InputVariable * strengthRatio; - fl::InputVariable * heroStrength; - fl::InputVariable * turnDistance; - fl::InputVariable * missionImportance; - fl::OutputVariable * value; + std::unique_ptr strengthRatio; + std::unique_ptr heroStrength; + std::unique_ptr turnDistance; + std::unique_ptr missionImportance; + std::unique_ptr value; private: float calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const; @@ -67,7 +65,6 @@ class VisitTileEngine : public HeroMovementGoalEngineBase { public: VisitTileEngine(); - ~VisitTileEngine(); float evaluate(Goals::AbstractGoal & goal) override; }; @@ -75,10 +72,9 @@ class GetObjEngine : public HeroMovementGoalEngineBase { public: GetObjEngine(); - ~GetObjEngine(); float evaluate(Goals::AbstractGoal & goal) override; protected: - fl::InputVariable * objectValue; + std::unique_ptr objectValue; }; class FuzzyHelper From 2079ae6190f3e0df02ac8435ece45d5ad82fbd5a Mon Sep 17 00:00:00 2001 From: Dydzio Date: Mon, 6 Aug 2018 19:38:39 +0200 Subject: [PATCH 08/33] Revert "use smart pointers for fl variables" This reverts commit d30b76bd7b47d1bfd197e80a7b0675bd7815db03. --- AI/VCAI/Fuzzy.cpp | 97 +++++++++++++++++++++++++++++++++-------------- AI/VCAI/Fuzzy.h | 26 +++++++------ 2 files changed, 84 insertions(+), 39 deletions(-) diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index 1fbe2aa6e..2c3e84d8a 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -129,14 +129,20 @@ TacticalAdvantageEngine::TacticalAdvantageEngine() { try { - ourShooters = vstd::make_unique(fl::InputVariable("OurShooters")); - ourWalkers = vstd::make_unique(fl::InputVariable("OurWalkers")); - ourFlyers = vstd::make_unique(fl::InputVariable("OurFlyers")); - enemyShooters = vstd::make_unique(fl::InputVariable("EnemyShooters")); - enemyWalkers = vstd::make_unique(fl::InputVariable("EnemyWalkers")); - enemyFlyers = vstd::make_unique(fl::InputVariable("EnemyFlyers")); + ourShooters = new fl::InputVariable("OurShooters"); + ourWalkers = new fl::InputVariable("OurWalkers"); + ourFlyers = new fl::InputVariable("OurFlyers"); + enemyShooters = new fl::InputVariable("EnemyShooters"); + enemyWalkers = new fl::InputVariable("EnemyWalkers"); + enemyFlyers = new fl::InputVariable("EnemyFlyers"); - for(auto val : {ourShooters.get(), ourWalkers.get(), ourFlyers.get(), enemyShooters.get(), enemyWalkers.get(), enemyFlyers.get()}) + //Tactical advantage calculation + std::vector helper = + { + ourShooters, ourWalkers, ourFlyers, enemyShooters, enemyWalkers, enemyFlyers + }; + + for(auto val : helper) { engine.addInputVariable(val); val->addTerm(new fl::Ramp("FEW", 0.6, 0.0)); @@ -144,10 +150,12 @@ TacticalAdvantageEngine::TacticalAdvantageEngine() val->setRange(0.0, 1.0); } - ourSpeed = vstd::make_unique(fl::InputVariable("OurSpeed")); - enemySpeed = vstd::make_unique(fl::InputVariable("EnemySpeed")); + ourSpeed = new fl::InputVariable("OurSpeed"); + enemySpeed = new fl::InputVariable("EnemySpeed"); - for(auto val : {ourSpeed.get(), enemySpeed.get()}) + helper = { ourSpeed, enemySpeed }; + + for(auto val : helper) { engine.addInputVariable(val); val->addTerm(new fl::Ramp("LOW", 6.5, 3)); @@ -156,8 +164,8 @@ TacticalAdvantageEngine::TacticalAdvantageEngine() val->setRange(0, 25); } - castleWalls = vstd::make_unique(fl::InputVariable("CastleWalls")); - engine.addInputVariable(castleWalls.get()); + castleWalls = new fl::InputVariable("CastleWalls"); + engine.addInputVariable(castleWalls); { fl::Rectangle * none = new fl::Rectangle("NONE", CGTownInstance::NONE, CGTownInstance::NONE + (CGTownInstance::FORT - CGTownInstance::NONE) * 0.5f); castleWalls->addTerm(none); @@ -173,8 +181,8 @@ TacticalAdvantageEngine::TacticalAdvantageEngine() } - bankPresent = vstd::make_unique(fl::InputVariable("Bank")); - engine.addInputVariable(bankPresent.get()); + bankPresent = new fl::InputVariable("Bank"); + engine.addInputVariable(bankPresent); { fl::Rectangle * termFalse = new fl::Rectangle("FALSE", 0.0, 0.5f); bankPresent->addTerm(termFalse); @@ -183,8 +191,8 @@ TacticalAdvantageEngine::TacticalAdvantageEngine() bankPresent->setRange(0, 1); } - threat = vstd::make_unique(fl::OutputVariable("Threat")); - engine.addOutputVariable(threat.get()); + threat = new fl::OutputVariable("Threat"); + engine.addOutputVariable(threat); threat->addTerm(new fl::Ramp("LOW", 1, MIN_AI_STRENGHT)); threat->addTerm(new fl::Triangle("MEDIUM", 0.8, 1.2)); threat->addTerm(new fl::Ramp("HIGH", 1, 1.5)); @@ -257,13 +265,12 @@ float TacticalAdvantageEngine::getTacticalAdvantage(const CArmedInstance * we, c if(output < 0 || (output != output)) { - fl::scalar tab[] = { bankPresent->getValue(), castleWalls->getValue(), ourWalkers->getValue(), ourShooters->getValue(), ourFlyers->getValue(), ourSpeed->getValue(), - enemyWalkers->getValue(), enemyShooters->getValue(), enemyFlyers->getValue(), enemySpeed->getValue() }; + fl::InputVariable * tab[] = { bankPresent, castleWalls, ourWalkers, ourShooters, ourFlyers, ourSpeed, enemyWalkers, enemyShooters, enemyFlyers, enemySpeed }; std::string names[] = { "bankPresent", "castleWalls", "ourWalkers", "ourShooters", "ourFlyers", "ourSpeed", "enemyWalkers", "enemyShooters", "enemyFlyers", "enemySpeed" }; std::stringstream log("Warning! Fuzzy engine doesn't cover this set of parameters: "); for(int i = 0; i < boost::size(tab); i++) - log << names[i] << ": " << tab[i] << " "; + log << names[i] << ": " << tab[i]->getValue() << " "; logAi->error(log.str()); assert(false); } @@ -271,6 +278,22 @@ float TacticalAdvantageEngine::getTacticalAdvantage(const CArmedInstance * we, c return output; } +TacticalAdvantageEngine::~TacticalAdvantageEngine() +{ + //TODO: smart pointers? + delete ourWalkers; + delete ourShooters; + delete ourFlyers; + delete enemyWalkers; + delete enemyShooters; + delete enemyFlyers; + delete ourSpeed; + delete enemySpeed; + delete bankPresent; + delete castleWalls; + delete threat; +} + //std::shared_ptr chooseSolution (std::vector> & vec) Goals::TSubgoal FuzzyHelper::chooseSolution(Goals::TGoalVec vec) @@ -311,19 +334,20 @@ HeroMovementGoalEngineBase::HeroMovementGoalEngineBase() { try { - strengthRatio = vstd::make_unique(fl::InputVariable("strengthRatio")); //hero must be strong enough to defeat guards - heroStrength = vstd::make_unique(fl::InputVariable("heroStrength")); //we want to use weakest possible hero - turnDistance = vstd::make_unique(fl::InputVariable("turnDistance")); //we want to use hero who is near - missionImportance = vstd::make_unique(fl::InputVariable("lockedMissionImportance")); //we may want to preempt hero with low-priority mission - value = vstd::make_unique(fl::OutputVariable("Value")); + strengthRatio = new fl::InputVariable("strengthRatio"); //hero must be strong enough to defeat guards + heroStrength = new fl::InputVariable("heroStrength"); //we want to use weakest possible hero + turnDistance = new fl::InputVariable("turnDistance"); //we want to use hero who is near + missionImportance = new fl::InputVariable("lockedMissionImportance"); //we may want to preempt hero with low-priority mission + value = new fl::OutputVariable("Value"); value->setMinimum(0); value->setMaximum(5); - for(auto val : { strengthRatio.get(), heroStrength.get(), turnDistance.get(), missionImportance.get() }) + std::vector helper = { strengthRatio, heroStrength, turnDistance, missionImportance }; + for(auto val : helper) { engine.addInputVariable(val); } - engine.addOutputVariable(value.get()); + engine.addOutputVariable(value); strengthRatio->addTerm(new fl::Ramp("LOW", SAFE_ATTACK_CONSTANT, 0)); strengthRatio->addTerm(new fl::Ramp("HIGH", SAFE_ATTACK_CONSTANT, SAFE_ATTACK_CONSTANT * 3)); @@ -403,6 +427,14 @@ void HeroMovementGoalEngineBase::setSharedFuzzyVariables(Goals::AbstractGoal & g } } +HeroMovementGoalEngineBase::~HeroMovementGoalEngineBase() +{ + delete strengthRatio; + delete heroStrength; + delete turnDistance; + delete missionImportance; +} + float FuzzyHelper::evaluate(Goals::VisitTile & g) { return visitTileEngine.evaluate(g); @@ -492,9 +524,9 @@ GetObjEngine::GetObjEngine() { try { - objectValue = vstd::make_unique(fl::InputVariable("objectValue")); //value of that object type known by AI + objectValue = new fl::InputVariable("objectValue"); //value of that object type known by AI - engine.addInputVariable(objectValue.get()); + engine.addInputVariable(objectValue); //objectValue ranges are based on checking RMG priorities of some objects and trying to guess sane value ranges objectValue->addTerm(new fl::Ramp("LOW", 3000, 0)); //I have feeling that concave shape might work well instead of ramp for objectValue FL terms @@ -521,6 +553,11 @@ GetObjEngine::GetObjEngine() configure(); } +GetObjEngine::~GetObjEngine() +{ + delete objectValue; +} + float GetObjEngine::evaluate(Goals::AbstractGoal & goal) { auto g = dynamic_cast(goal); @@ -566,6 +603,10 @@ VisitTileEngine::VisitTileEngine() //so far no VisitTile-specific variables that configure(); } +VisitTileEngine::~VisitTileEngine() +{ +} + float VisitTileEngine::evaluate(Goals::AbstractGoal & goal) { auto g = dynamic_cast(goal); diff --git a/AI/VCAI/Fuzzy.h b/AI/VCAI/Fuzzy.h index 2460a99a0..6fd969605 100644 --- a/AI/VCAI/Fuzzy.h +++ b/AI/VCAI/Fuzzy.h @@ -33,12 +33,13 @@ public: TacticalAdvantageEngine(); float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us + ~TacticalAdvantageEngine(); private: - std::unique_ptr ourWalkers, ourShooters, ourFlyers, enemyWalkers, enemyShooters, enemyFlyers; - std::unique_ptr ourSpeed, enemySpeed; - std::unique_ptr bankPresent; - std::unique_ptr castleWalls; - std::unique_ptr threat; + fl::InputVariable * ourWalkers, *ourShooters, *ourFlyers, *enemyWalkers, *enemyShooters, *enemyFlyers; + fl::InputVariable * ourSpeed, *enemySpeed; + fl::InputVariable * bankPresent; + fl::InputVariable * castleWalls; + fl::OutputVariable * threat; }; class HeroMovementGoalEngineBase : public engineBase //in future - maybe derive from some (GoalEngineBase : public engineBase) class for handling non-movement goals with common utility for goal engines @@ -47,15 +48,16 @@ public: HeroMovementGoalEngineBase(); virtual float evaluate(Goals::AbstractGoal & goal) = 0; + virtual ~HeroMovementGoalEngineBase(); protected: void setSharedFuzzyVariables(Goals::AbstractGoal & goal); - std::unique_ptr strengthRatio; - std::unique_ptr heroStrength; - std::unique_ptr turnDistance; - std::unique_ptr missionImportance; - std::unique_ptr value; + fl::InputVariable * strengthRatio; + fl::InputVariable * heroStrength; + fl::InputVariable * turnDistance; + fl::InputVariable * missionImportance; + fl::OutputVariable * value; private: float calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const; @@ -65,6 +67,7 @@ class VisitTileEngine : public HeroMovementGoalEngineBase { public: VisitTileEngine(); + ~VisitTileEngine(); float evaluate(Goals::AbstractGoal & goal) override; }; @@ -72,9 +75,10 @@ class GetObjEngine : public HeroMovementGoalEngineBase { public: GetObjEngine(); + ~GetObjEngine(); float evaluate(Goals::AbstractGoal & goal) override; protected: - std::unique_ptr objectValue; + fl::InputVariable * objectValue; }; class FuzzyHelper From e89c7eeba4cdac9d4d2944898a1049726807a878 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Mon, 6 Aug 2018 19:47:08 +0200 Subject: [PATCH 09/33] Remove incorrect destructors - fl::Engine is responsible for these --- AI/VCAI/Fuzzy.cpp | 33 --------------------------------- AI/VCAI/Fuzzy.h | 7 +------ 2 files changed, 1 insertion(+), 39 deletions(-) diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index 2c3e84d8a..051f34d0b 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -278,22 +278,6 @@ float TacticalAdvantageEngine::getTacticalAdvantage(const CArmedInstance * we, c return output; } -TacticalAdvantageEngine::~TacticalAdvantageEngine() -{ - //TODO: smart pointers? - delete ourWalkers; - delete ourShooters; - delete ourFlyers; - delete enemyWalkers; - delete enemyShooters; - delete enemyFlyers; - delete ourSpeed; - delete enemySpeed; - delete bankPresent; - delete castleWalls; - delete threat; -} - //std::shared_ptr chooseSolution (std::vector> & vec) Goals::TSubgoal FuzzyHelper::chooseSolution(Goals::TGoalVec vec) @@ -427,14 +411,6 @@ void HeroMovementGoalEngineBase::setSharedFuzzyVariables(Goals::AbstractGoal & g } } -HeroMovementGoalEngineBase::~HeroMovementGoalEngineBase() -{ - delete strengthRatio; - delete heroStrength; - delete turnDistance; - delete missionImportance; -} - float FuzzyHelper::evaluate(Goals::VisitTile & g) { return visitTileEngine.evaluate(g); @@ -553,11 +529,6 @@ GetObjEngine::GetObjEngine() configure(); } -GetObjEngine::~GetObjEngine() -{ - delete objectValue; -} - float GetObjEngine::evaluate(Goals::AbstractGoal & goal) { auto g = dynamic_cast(goal); @@ -603,10 +574,6 @@ VisitTileEngine::VisitTileEngine() //so far no VisitTile-specific variables that configure(); } -VisitTileEngine::~VisitTileEngine() -{ -} - float VisitTileEngine::evaluate(Goals::AbstractGoal & goal) { auto g = dynamic_cast(goal); diff --git a/AI/VCAI/Fuzzy.h b/AI/VCAI/Fuzzy.h index 6fd969605..efa18e9f0 100644 --- a/AI/VCAI/Fuzzy.h +++ b/AI/VCAI/Fuzzy.h @@ -16,7 +16,7 @@ class CArmedInstance; class CBank; struct SectorMap; -class engineBase +class engineBase //subclasses create fuzzylite variables with "new" that are not freed - this is desired as fl::Engine wants to destroy these... { protected: fl::Engine engine; @@ -31,9 +31,7 @@ class TacticalAdvantageEngine : public engineBase { public: TacticalAdvantageEngine(); - float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us - ~TacticalAdvantageEngine(); private: fl::InputVariable * ourWalkers, *ourShooters, *ourFlyers, *enemyWalkers, *enemyShooters, *enemyFlyers; fl::InputVariable * ourSpeed, *enemySpeed; @@ -48,7 +46,6 @@ public: HeroMovementGoalEngineBase(); virtual float evaluate(Goals::AbstractGoal & goal) = 0; - virtual ~HeroMovementGoalEngineBase(); protected: void setSharedFuzzyVariables(Goals::AbstractGoal & goal); @@ -67,7 +64,6 @@ class VisitTileEngine : public HeroMovementGoalEngineBase { public: VisitTileEngine(); - ~VisitTileEngine(); float evaluate(Goals::AbstractGoal & goal) override; }; @@ -75,7 +71,6 @@ class GetObjEngine : public HeroMovementGoalEngineBase { public: GetObjEngine(); - ~GetObjEngine(); float evaluate(Goals::AbstractGoal & goal) override; protected: fl::InputVariable * objectValue; From 2365946b6280fd13c43dc24d4462036be3d885df Mon Sep 17 00:00:00 2001 From: Dydzio Date: Tue, 7 Aug 2018 00:32:12 +0200 Subject: [PATCH 10/33] Elementar GetObj - first attempt --- AI/VCAI/Goals.cpp | 34 +++++++++++++++++++++------------- AI/VCAI/VCAI.cpp | 16 ++++++++++++++++ AI/VCAI/VCAI.h | 1 + 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/AI/VCAI/Goals.cpp b/AI/VCAI/Goals.cpp index 228826d5d..472734b1f 100644 --- a/AI/VCAI/Goals.cpp +++ b/AI/VCAI/Goals.cpp @@ -517,21 +517,35 @@ TSubgoal GetObj::whatToDoToAchieve() const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(objid)); if(!obj) return sptr(Goals::Explore()); - if(obj->tempOwner == ai->playerID) //we can't capture our own object -> move to Win codition - throw cannotFulfillGoalException("Cannot capture my own object " + obj->getObjectName()); int3 pos = obj->visitablePos(); if(hero) { if(ai->isAccessibleForHero(pos, hero)) - return sptr(Goals::VisitTile(pos).sethero(hero)); + { + if(isSafeToVisit(hero, pos)) + return sptr(Goals::GetObj(obj->id.getNum()).sethero(hero).setisElementar(true)); + else + return sptr(Goals::GatherArmy(evaluateDanger(pos, hero.h) * SAFE_ATTACK_CONSTANT).sethero(hero).setisAbstract(true)); + } } else { + TGoalVec goalVersionsWithAllPossibleHeroes; for(auto h : cb->getHeroesInfo()) - { + { if(ai->isAccessibleForHero(pos, h)) - return sptr(Goals::VisitTile(pos).sethero(h)); //we must visit object with same hero, if any + { + if(isSafeToVisit(hero, pos)) + goalVersionsWithAllPossibleHeroes.push_back(sptr(Goals::GetObj(obj->id.getNum()).sethero(h))); + else + return sptr(Goals::GatherArmy(evaluateDanger(pos, h) * SAFE_ATTACK_CONSTANT).sethero(hero).setisAbstract(true)); + } + } + if(!goalVersionsWithAllPossibleHeroes.empty()) + { + auto bestCandidate = fh->chooseSolution(goalVersionsWithAllPossibleHeroes); + return bestCandidate; } } return sptr(Goals::ClearWayTo(pos).sethero(hero)); @@ -912,8 +926,6 @@ TSubgoal VisitTile::whatToDoToAchieve() { 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; } @@ -1386,13 +1398,9 @@ TGoalVec Conquer::getAllPossibleSubgoals() { ret.push_back(sptr(Goals::VisitHero(obj->id.getNum()).sethero(h).setisAbstract(true))); } - else //just visit that tile + else //just get that object { - 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))); + ret.push_back(sptr(Goals::GetObj(obj->id.getNum()).sethero(h).setisAbstract(true))); } } } diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 35b269aae..1f20abf64 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -2006,6 +2006,22 @@ void VCAI::tryRealize(Goals::VisitTile & g) } } +void VCAI::tryRealize(Goals::GetObj & g) +{ + auto position = cb->getObjInstance((ObjectInstanceID)g.objid)->pos; + if(!g.hero->movement) + throw cannotFulfillGoalException("Cannot visit tile: hero is out of MPs!"); + if(position == g.hero->visitablePos() && cb->getVisitableObjs(g.hero->visitablePos()).size() < 2) + { + logAi->warn("Why do I want to move hero %s to tile %s? Already standing on that tile! ", g.hero->name, g.tile.toString()); + throw goalFulfilledException(sptr(g)); + } + if(ai->moveHeroToTile(position, g.hero.get())) + { + throw goalFulfilledException(sptr(g)); + } +} + void VCAI::tryRealize(Goals::VisitHero & g) { if(!g.hero->movement) diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index 7981b78d4..df23911f6 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -121,6 +121,7 @@ public: void tryRealize(Goals::Explore & g); void tryRealize(Goals::RecruitHero & g); void tryRealize(Goals::VisitTile & g); + void tryRealize(Goals::GetObj & g); void tryRealize(Goals::VisitHero & g); void tryRealize(Goals::BuildThis & g); void tryRealize(Goals::DigAtTile & g); From 41df9d82834c12cb04fa349957f6bc866e16f295 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Tue, 7 Aug 2018 14:33:45 +0200 Subject: [PATCH 11/33] Move away subgoal collection logic for GetObj --- AI/VCAI/Goals.cpp | 40 ++++++++++++++++++++++++++++------------ AI/VCAI/Goals.h | 5 +---- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/AI/VCAI/Goals.cpp b/AI/VCAI/Goals.cpp index 472734b1f..6bdca1c0f 100644 --- a/AI/VCAI/Goals.cpp +++ b/AI/VCAI/Goals.cpp @@ -512,11 +512,15 @@ std::string GetObj::completeMessage() const return "hero " + hero.get()->name + " captured Object ID = " + boost::lexical_cast(objid); } -TSubgoal GetObj::whatToDoToAchieve() +TGoalVec GetObj::getAllPossibleSubgoals() { + TGoalVec goalList; const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(objid)); if(!obj) - return sptr(Goals::Explore()); + { + goalList.push_back(sptr(Goals::Explore())); + return goalList; + } int3 pos = obj->visitablePos(); if(hero) @@ -524,31 +528,43 @@ TSubgoal GetObj::whatToDoToAchieve() if(ai->isAccessibleForHero(pos, hero)) { if(isSafeToVisit(hero, pos)) - return sptr(Goals::GetObj(obj->id.getNum()).sethero(hero).setisElementar(true)); + goalList.push_back(sptr(Goals::GetObj(obj->id.getNum()).sethero(hero))); else - return sptr(Goals::GatherArmy(evaluateDanger(pos, hero.h) * SAFE_ATTACK_CONSTANT).sethero(hero).setisAbstract(true)); + goalList.push_back(sptr(Goals::GatherArmy(evaluateDanger(pos, hero.h) * SAFE_ATTACK_CONSTANT).sethero(hero).setisAbstract(true))); + + return goalList; } } else { - TGoalVec goalVersionsWithAllPossibleHeroes; for(auto h : cb->getHeroesInfo()) - { + { if(ai->isAccessibleForHero(pos, h)) { if(isSafeToVisit(hero, pos)) - goalVersionsWithAllPossibleHeroes.push_back(sptr(Goals::GetObj(obj->id.getNum()).sethero(h))); + goalList.push_back(sptr(Goals::GetObj(obj->id.getNum()).sethero(h))); else - return sptr(Goals::GatherArmy(evaluateDanger(pos, h) * SAFE_ATTACK_CONSTANT).sethero(hero).setisAbstract(true)); + goalList.push_back(sptr(Goals::GatherArmy(evaluateDanger(pos, h) * SAFE_ATTACK_CONSTANT).sethero(hero).setisAbstract(true))); } } - if(!goalVersionsWithAllPossibleHeroes.empty()) + if(!goalList.empty()) { - auto bestCandidate = fh->chooseSolution(goalVersionsWithAllPossibleHeroes); - return bestCandidate; + return goalList; } } - return sptr(Goals::ClearWayTo(pos).sethero(hero)); + + goalList.push_back(sptr(Goals::ClearWayTo(pos).sethero(hero))); + return goalList; +} + +TSubgoal GetObj::whatToDoToAchieve() +{ + auto bestGoal = fh->chooseSolution(getAllPossibleSubgoals()); + + if(bestGoal->goalType == Goals::GET_OBJ && bestGoal->hero) + bestGoal->setisElementar(true); + + return bestGoal; } bool Goals::GetObj::operator==(AbstractGoal & g) diff --git a/AI/VCAI/Goals.h b/AI/VCAI/Goals.h index 6adcea7b4..1cb8e2833 100644 --- a/AI/VCAI/Goals.h +++ b/AI/VCAI/Goals.h @@ -472,10 +472,7 @@ public: objid = Objid; priority = 3; } - TGoalVec getAllPossibleSubgoals() override - { - return TGoalVec(); - } + TGoalVec getAllPossibleSubgoals() override; TSubgoal whatToDoToAchieve() override; bool operator==(AbstractGoal & g) override; bool fulfillsMe(TSubgoal goal) override; From 01709bd19672cf11cdc8b15df64f6d2208221b9c Mon Sep 17 00:00:00 2001 From: Dydzio Date: Tue, 7 Aug 2018 19:47:02 +0200 Subject: [PATCH 12/33] Dwelling aiValue set: cre.aiValue * growth --- config/objects/dwellings.json | 84 ++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/config/objects/dwellings.json b/config/objects/dwellings.json index 39bf11ac3..abda39893 100644 --- a/config/objects/dwellings.json +++ b/config/objects/dwellings.json @@ -15,6 +15,7 @@ "types" : { "basiliskPit": { "index": 0, + "aiValue" : 2208, "creatures": [["basilisk"]], "sounds": { "ambient": ["LOOPMONS"] @@ -22,6 +23,7 @@ }, "behemothCrag": { "index": 1, + "aiValue" : 3162, "creatures": [["behemoth"]], "sounds": { "ambient": ["LOOPBEHE"] @@ -29,6 +31,7 @@ }, "pillarOfEyes": { "index": 2, + "aiValue" : 2352, "creatures": [["beholder"]], "sounds": { "ambient": ["LOOPCAVE"] @@ -36,6 +39,7 @@ }, "hallOfDarkness": { "index": 3, + "aiValue" : 4174, "creatures": [["blackKnight"]], "sounds": { "ambient": ["LOOPDEAD"] @@ -43,6 +47,7 @@ }, "dragonVault": { "index": 4, + "aiValue" : 3388, "creatures": [["boneDragon"]], "sounds": { "ambient": ["LOOPDRAG"] @@ -50,6 +55,7 @@ }, "trainingGrounds": { "index": 5, + "aiValue" : 3892, "creatures": [["cavalier"]], "sounds": { "ambient": ["LOOPHORS"] @@ -57,6 +63,7 @@ }, "centaurStables": { "index": 6, + "aiValue" : 1400, "creatures": [["centaur"]], "sounds": { "ambient": ["LOOPHORS"] @@ -64,6 +71,7 @@ }, "airConflux": { "index": 7, + "aiValue" : 2136, "creatures": [["airElemental"]], "sounds": { "ambient": ["LOOPAIR"] @@ -71,6 +79,7 @@ }, "portalOfGlory": { "index": 8, + "aiValue" : 5019, "creatures": [["angel"]], "sounds": { "ambient": ["LOOPSANC"] @@ -78,6 +87,7 @@ }, "cyclopsCave": { "index": 9, + "aiValue" : 2532, "creatures": [["cyclop"]], "sounds": { "ambient": ["LOOPCAVE"] @@ -85,6 +95,7 @@ }, "forsakenPalace": { "index": 10, + "aiValue" : 5101, "creatures": [["devil"]], "sounds": { "ambient": ["LOOPDEVL"] @@ -92,6 +103,7 @@ }, "serpentFlyHive": { "index": 11, + "aiValue" : 2144, "creatures": [["serpentFly"]], "sounds": { "ambient": ["LOOPLEAR"] @@ -99,6 +111,7 @@ }, "dwarfCottage": { "index": 12, + "aiValue" : 1104, "creatures": [["dwarf"]], "sounds": { "ambient": ["LOOPDWAR"] @@ -106,6 +119,7 @@ }, "earthConflux": { "index": 13, + "aiValue" : 1320, "creatures": [["earthElemental"]], "sounds": { "ambient": ["LOOPEART"] @@ -113,6 +127,7 @@ }, "fireLake": { "index": 14, + "aiValue" : 3340, "creatures": [["efreet"]], "sounds": { "ambient": ["LOOPVENT"] @@ -120,6 +135,7 @@ }, "homestead": { "index": 15, + "aiValue" : 1638, "creatures": [["woodElf"]], "sounds": { "ambient": ["LOOPELF"] @@ -127,6 +143,7 @@ }, "fireConflux": { "index": 16, + "aiValue" : 1725, "creatures": [["fireElemental"]], "sounds": { "ambient": ["LOOPFIRE"] @@ -134,6 +151,7 @@ }, "parapet": { "index": 17, + "aiValue" : 1485, "creatures": [["stoneGargoyle"]], "sounds": { "ambient": ["LOOPGRIF"] @@ -141,6 +159,7 @@ }, "altarOfWishes": { "index": 18, + "aiValue" : 2652, "creatures": [["genie"]], "sounds": { "ambient": ["LOOPMAGI"] @@ -148,6 +167,7 @@ }, "wolfPen": { "index": 19, + "aiValue" : 1170, "creatures": [["goblinWolfRider"]], "sounds": { "ambient": ["LOOPWOLF"] @@ -155,6 +175,7 @@ }, "gnollHut": { "index": 20, + "aiValue" : 672, "creatures": [["gnoll"]], "sounds": { "ambient": ["LOOPORC"] @@ -162,6 +183,7 @@ }, "goblinBarracks": { "index": 21, + "aiValue" : 900, "creatures": [["goblin"]], "sounds": { "ambient": ["LOOPGOBL"] @@ -169,6 +191,7 @@ }, "hallOfSins": { "index": 22, + "aiValue" : 1272, "creatures": [["gog"]], "sounds": { "ambient": ["LOOPVENT"] @@ -176,6 +199,7 @@ }, "gorgonLair": { "index": 23, + "aiValue" : 2670, "creatures": [["gorgon"]], "sounds": { "ambient": ["LOOPBEHE"] @@ -183,6 +207,7 @@ }, "dragonCliffs": { "index": 24, + "aiValue" : 4872, "creatures": [["greenDragon"]], "sounds": { "ambient": ["LOOPDRAG"] @@ -190,6 +215,7 @@ }, "griffinTower": { "index": 25, + "aiValue" : 2457, "creatures": [["griffin"]], "sounds": { "ambient": ["LOOPGRIF"] @@ -197,6 +223,7 @@ }, "harpyLoft": { "index": 26, + "aiValue" : 1232, "creatures": [["harpy"]], "sounds": { "ambient": ["LOOPHARP"] @@ -204,6 +231,7 @@ }, "kennels": { "index": 27, + "aiValue" : 1785, "creatures": [["hellHound"]], "sounds": { "ambient": ["LOOPDOG"] @@ -211,6 +239,7 @@ }, "hydraPond": { "index": 28, + "aiValue" : 4120, "creatures": [["hydra"]], "sounds": { "ambient": ["LOOPHYDR"] @@ -218,6 +247,7 @@ }, "impCrucible": { "index": 29, + "aiValue" : 750, "creatures": [["imp"]], "sounds": { "ambient": ["LOOPFIRE"] @@ -225,6 +255,7 @@ }, "lizardDen": { "index": 30, + "aiValue" : 1134, "creatures": [["lizardman"]], "sounds": { "ambient": ["LOOPARCH"] @@ -232,6 +263,7 @@ }, "mageTower": { "index": 31, + "aiValue" : 2280, "creatures": [["mage"]], "sounds": { "ambient": ["LOOPMAGI"] @@ -239,6 +271,7 @@ }, "manticoreLair": { "index": 32, + "aiValue" : 3094, "creatures": [["manticore"]], "sounds": { "ambient": ["LOOPMANT"] @@ -246,6 +279,7 @@ }, "medusaChapel": { "index": 33, + "aiValue" : 2068, "creatures": [["medusa"]], "sounds": { "ambient": ["LOOPMEDU"] @@ -253,6 +287,7 @@ }, "labyrinth": { "index": 34, + "aiValue" : 2505, "creatures": [["minotaur"]], "sounds": { "ambient": ["LOOPANIM"] @@ -260,6 +295,7 @@ }, "monastery": { "index": 35, + "aiValue" : 1455, "creatures": [["monk"]], "sounds": { "ambient": ["LOOPMONK"] @@ -267,6 +303,7 @@ }, "goldenPavilion": { "index": 36, + "aiValue" : 4032, "creatures": [["naga"]], "sounds": { "ambient": ["LOOPNAGA"] @@ -274,6 +311,7 @@ }, "demonGate": { "index": 37, + "aiValue" : 1780, "creatures": [["demon"]], "sounds": { "ambient": ["LOOPCAVE"] @@ -281,6 +319,7 @@ }, "ogreFort": { "index": 38, + "aiValue" : 1664, "creatures": [["ogre"]], "sounds": { "ambient": ["LOOPOGRE"] @@ -288,6 +327,7 @@ }, "orcTower": { "index": 39, + "aiValue" : 1344, "creatures": [["orc"]], "sounds": { "ambient": ["LOOPORC"] @@ -295,6 +335,7 @@ }, "hellHole": { "index": 40, + "aiValue" : 2295, "creatures": [["pitFiend"]], "sounds": { "ambient": ["LOOPFIRE"] @@ -302,6 +343,7 @@ }, "dragonCave": { "index": 41, + "aiValue" : 4702, "creatures": [["redDragon"]], "sounds": { "ambient": ["LOOPDRAG"] @@ -309,6 +351,7 @@ }, "cliffNest": { "index": 42, + "aiValue" : 3081, "creatures": [["roc"]], "sounds": { "ambient": ["LOOPBIRD"] @@ -316,6 +359,7 @@ }, "workshop": { "index": 43, + "aiValue" : 704, "creatures": [["gremlin"]], "sounds": { "ambient": ["LOOPGREM"] @@ -323,6 +367,7 @@ }, "cloudTemple": { "index": 44, + "aiValue" : 3718, "creatures": [["giant"]], "sounds": { "ambient": ["LOOPTITA"] @@ -330,6 +375,7 @@ }, "dendroidArches": { "index": 45, + "aiValue" : 1551, "creatures": [["dendroidGuard"]], "sounds": { "ambient": ["LOOPGARD"] @@ -337,6 +383,7 @@ }, "warren": { "index": 46, + "aiValue" : 826, "creatures": [["troglodyte"]], "sounds": { "ambient": ["LOOPCAVE"] @@ -344,6 +391,7 @@ }, "waterConflux": { "index": 47, + "aiValue" : 1890, "creatures": [["waterElemental"]], "sounds": { "ambient": ["LOOPFOUN"] @@ -351,6 +399,7 @@ }, "tombOfSouls": { "index": 48, + "aiValue" : 1764, "creatures": [["wight"]], "sounds": { "ambient": ["LOOPDEAD"] @@ -358,6 +407,7 @@ }, "wyvernNest": { "index": 49, + "aiValue" : 2700, "creatures": [["wyvern"]], "sounds": { "ambient": ["LOOPMONS"] @@ -365,6 +415,7 @@ }, "enchantedSpring": { "index": 50, + "aiValue" : 2590, "creatures": [["pegasus"]], "sounds": { "ambient": ["LOOPPEGA"] @@ -372,6 +423,7 @@ }, "unicornGladeBig": { "index": 51, + "aiValue" : 3612, "creatures": [["unicorn"]], "sounds": { "ambient": ["LOOPUNIC"] @@ -379,6 +431,7 @@ }, "mausoleum": { "index": 52, + "aiValue" : 2544, "creatures": [["lich"]], "sounds": { "ambient": ["LOOPDEAD"] @@ -386,6 +439,7 @@ }, "estate": { "index": 53, + "aiValue" : 2220, "creatures": [["vampire"]], "sounds": { "ambient": ["LOOPDEAD"] @@ -393,6 +447,7 @@ }, "cursedTemple": { "index": 54, + "aiValue" : 720, "creatures": [["skeleton"]], "sounds": { "ambient": ["LOOPSKEL"] @@ -400,6 +455,7 @@ }, "graveyard": { "index": 55, + "aiValue" : 784, "creatures": [["walkingDead"]], "sounds": { "ambient": ["LOOPDEAD"] @@ -407,6 +463,7 @@ }, "guardhouse": { "index": 56, + "aiValue" : 1120, "creatures": [["pikeman"]], "sounds": { "ambient": ["LOOPPIKE"] @@ -414,6 +471,7 @@ }, "archersTower": { "index": 57, + "aiValue" : 1134, "creatures": [["archer"]], "sounds": { "ambient": ["LOOPARCH"] @@ -421,46 +479,57 @@ }, "barracks": { "index": 58, + "aiValue" : 1780, "creatures": [["swordsman"]] }, "magicLantern": { "index": 59, + "aiValue" : 1100, "creatures": [["pixie"]] }, "altarOfThought": { "index": 60, + "aiValue" : 3338, "creatures": [["psychicElemental"]] }, "pyre": { "index": 61, + "aiValue" : 9094, "creatures": [["firebird"]] }, "frozenCliffs": { "index": 62, + "aiValue" : 78845, "creatures": [["azureDragon"]] }, "crystalCavern": { "index": 63, + "aiValue" : 39338, "creatures": [["crystalDragon"]] }, "magicForest": { "index": 64, + "aiValue" : 19580, "creatures": [["fairieDragon"]] }, "sulfurousLair": { "index": 65, + "aiValue" : 26433, "creatures": [["rustDragon"]] }, "enchantersHollow": { "index": 66, + "aiValue" : 2420, "creatures": [["enchanter"]] }, "treetopTower": { "index": 67, + "aiValue" : 2340, "creatures": [["sharpshooter"]] }, "unicornGlade": { "index": 68, + "aiValue" : 3612, "creatures": [["unicorn"]], "sounds": { "ambient": ["LOOPUNIC"] @@ -468,6 +537,7 @@ }, "altarOfAir": { "index": 69, + "aiValue" : 2136, "creatures": [["airElemental"]], "sounds": { "ambient": ["LOOPAIR"] @@ -475,6 +545,7 @@ }, "altarOfEarth": { "index": 70, + "aiValue" : 1320, "creatures": [["earthElemental"]], "sounds": { "ambient": ["LOOPEART"] @@ -482,6 +553,7 @@ }, "altarOfFire": { "index": 71, + "aiValue" : 1725, "creatures": [["fireElemental"]], "sounds": { "ambient": ["LOOPFIRE"] @@ -489,6 +561,7 @@ }, "altarOfWater": { "index": 72, + "aiValue" : 1890, "creatures": [["waterElemental"]], "sounds": { "ambient": ["LOOPFOUN"] @@ -496,30 +569,37 @@ }, "thatchedHut": { "index": 73, + "aiValue" : 1125, "creatures": [["halfling"]] }, "hovel": { "index": 74, + "aiValue" : 375, "creatures": [["peasant"]] }, "boarGlen": { "index": 75, + "aiValue" : 1160, "creatures": [["boar"]] }, "tombOfCurses": { "index": 76, + "aiValue" : 1890, "creatures": [["mummy"]] }, "nomadTent": { "index": 77, + "aiValue" : 2415, "creatures": [["nomad"]] }, "rogueCavern": { "index": 78, + "aiValue" : 1080, "creatures": [["rogue"]] }, "trollBridge": { "index": 79, + "aiValue" : 3072, "creatures": [["troll"]] } } @@ -531,6 +611,7 @@ "types" : { "elementalConflux" : { "index" : 0, + "aiValue" : 7071, "creatures" : [ // 4 separate "levels" to give them separate growth [ "airElemental" ], [ "waterElemental" ], @@ -546,6 +627,7 @@ }, "golemFactory" : { "index" : 1, + "aiValue" : 7322, "creatures" : [ // 4 separate "levels" to give them separate growth [ "ironGolem" ], [ "stoneGolem" ], @@ -561,5 +643,5 @@ } } } - }, + } } From 616a6bbdf74d13de4cefe3a95c96f9327ca6c33c Mon Sep 17 00:00:00 2001 From: Dydzio Date: Thu, 9 Aug 2018 16:28:15 +0200 Subject: [PATCH 13/33] New object target handling in wander --- AI/VCAI/Goals.cpp | 2 +- AI/VCAI/VCAI.cpp | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/AI/VCAI/Goals.cpp b/AI/VCAI/Goals.cpp index 6bdca1c0f..16561de7d 100644 --- a/AI/VCAI/Goals.cpp +++ b/AI/VCAI/Goals.cpp @@ -1142,7 +1142,7 @@ TGoalVec Goals::CollectRes::getAllPossibleSubgoals() if (dest != t) //there is something blocking our way ret.push_back(sptr(Goals::ClearWayTo(dest, h).setisAbstract(true))); else - ret.push_back(sptr(Goals::VisitTile(dest).sethero(h).setisAbstract(true))); + ret.push_back(sptr(Goals::GetObj(obj->id.getNum()).sethero(h).setisAbstract(true))); } else //we need to get army in order to pick that object ret.push_back(sptr(Goals::GatherArmy(evaluateDanger(dest, h) * SAFE_ATTACK_CONSTANT).sethero(h).setisAbstract(true))); diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 1f20abf64..1d532879c 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -1460,17 +1460,18 @@ void VCAI::wander(HeroPtr h) //end of objs empty if(dests.size()) //performance improvement - { - auto fuzzyLogicSorter = [h](const ObjectIdRef & l, const ObjectIdRef & r) -> bool //TODO: create elementar GetObj goal usable for goal decomposition and Wander based on VisitTile logic and object value on top of it + { + Goals::TGoalVec targetObjectGoals; + for(auto destination : dests) { - return fh->getWanderTargetObjectValue( *h.get(), l) < fh->getWanderTargetObjectValue(*h.get(), r); - }; - - const ObjectIdRef & dest = *boost::max_element(dests, fuzzyLogicSorter); //find best object to visit based on fuzzy logic evaluation, TODO: use elementar version of GetObj here in future + targetObjectGoals.push_back(sptr(Goals::GetObj(destination.id.getNum()).sethero(h).setisAbstract(true))); + } + auto bestObjectGoal = fh->chooseSolution(targetObjectGoals); + decomposeGoal(bestObjectGoal)->accept(this); //wander should not cause heroes to be reserved - they are always considered free - logAi->debug("Of all %d destinations, object oid=%d seems nice", dests.size(), dest.id.getNum()); - if (!goVisitObj(dest, h)) + logAi->debug("Of all %d destinations, object oid=%d seems nice", dests.size(), bestObjectGoal->objid); + /*if (!goVisitObj(dest, h)) { if (!dest) { @@ -1483,7 +1484,7 @@ void VCAI::wander(HeroPtr h) } } else //we reached our destination - visitTownIfAny(h); + visitTownIfAny(h);*/ } } visitTownIfAny(h); //in case hero is just sitting in town From 1377338a41fbc18604e363562e7b51558dac4142 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Thu, 9 Aug 2018 17:45:58 +0200 Subject: [PATCH 14/33] Restore old code line --- AI/VCAI/VCAI.cpp | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 1d532879c..e71ef20b0 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -1471,20 +1471,8 @@ void VCAI::wander(HeroPtr h) //wander should not cause heroes to be reserved - they are always considered free logAi->debug("Of all %d destinations, object oid=%d seems nice", dests.size(), bestObjectGoal->objid); - /*if (!goVisitObj(dest, h)) - { - if (!dest) - { - logAi->debug("Visit attempt made the object (id=%d) gone...", dest.id.getNum()); - } - else - { - logAi->debug("Hero %s apparently used all MPs (%d left)", h->name, h->movement); - break; - } - } - else //we reached our destination - visitTownIfAny(h);*/ + + visitTownIfAny(h); } } visitTownIfAny(h); //in case hero is just sitting in town From ff2ac5b34ba307e5223733913ea814403c54057d Mon Sep 17 00:00:00 2001 From: Dydzio Date: Thu, 9 Aug 2018 18:04:49 +0200 Subject: [PATCH 15/33] Minor fixes --- AI/VCAI/VCAI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index e71ef20b0..895eb94a0 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -1322,7 +1322,6 @@ bool VCAI::canRecruitAnyHero(const CGTownInstance * t) const void VCAI::wander(HeroPtr h) { - auto visitTownIfAny = [this](HeroPtr h) -> bool { if (h->visitedTown) @@ -1331,6 +1330,7 @@ void VCAI::wander(HeroPtr h) buildArmyIn(h->visitedTown); return true; } + return false; }; //unclaim objects that are now dangerous for us From fa97940b4f0e6be75f63d36d3c3bf1cd291085c7 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Thu, 9 Aug 2018 18:24:04 +0200 Subject: [PATCH 16/33] Improve wander logging --- AI/VCAI/VCAI.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 895eb94a0..da65c8835 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -1470,7 +1470,8 @@ void VCAI::wander(HeroPtr h) decomposeGoal(bestObjectGoal)->accept(this); //wander should not cause heroes to be reserved - they are always considered free - logAi->debug("Of all %d destinations, object oid=%d seems nice", dests.size(), bestObjectGoal->objid); + auto chosenObject = cb->getObjInstance(ObjectInstanceID(bestObjectGoal->objid)); + logAi->debug("Of all %d destinations, object %s at pos=%s seems nice", dests.size(), chosenObject->getObjectName(), chosenObject->pos.toString()); visitTownIfAny(h); } From 4b776db1a2b98f9c823e816abbb6c79667b1b118 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Thu, 9 Aug 2018 20:24:19 +0200 Subject: [PATCH 17/33] Set destination tile in GetObj - needed for priority evaluation --- AI/VCAI/Goals.h | 1 + 1 file changed, 1 insertion(+) diff --git a/AI/VCAI/Goals.h b/AI/VCAI/Goals.h index 1cb8e2833..a742323c6 100644 --- a/AI/VCAI/Goals.h +++ b/AI/VCAI/Goals.h @@ -470,6 +470,7 @@ public: : CGoal(Goals::GET_OBJ) { objid = Objid; + tile = cb->getObjInstance(ObjectInstanceID(objid))->visitablePos(); priority = 3; } TGoalVec getAllPossibleSubgoals() override; From c0821495e5e8b7d85b35dd31e21ee00ff450d42a Mon Sep 17 00:00:00 2001 From: Dydzio Date: Thu, 9 Aug 2018 22:07:02 +0200 Subject: [PATCH 18/33] GetObj tweak --- AI/VCAI/Goals.cpp | 6 ++++++ AI/VCAI/Goals.h | 10 ++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/AI/VCAI/Goals.cpp b/AI/VCAI/Goals.cpp index 16561de7d..cd479247f 100644 --- a/AI/VCAI/Goals.cpp +++ b/AI/VCAI/Goals.cpp @@ -286,6 +286,12 @@ namespace Goals { return boost::format("Bought army of value %d in town of %s") % value, town->name; } + GetObj::GetObj(int Objid): CGoal(Goals::GET_OBJ) + { + objid = Objid; + tile = ai->myCb->getObjInstance(ObjectInstanceID(objid))->pos; + priority = 3; + } } TSubgoal Trade::whatToDoToAchieve() diff --git a/AI/VCAI/Goals.h b/AI/VCAI/Goals.h index a742323c6..7a97a7f6f 100644 --- a/AI/VCAI/Goals.h +++ b/AI/VCAI/Goals.h @@ -464,15 +464,9 @@ public: class DLL_EXPORT GetObj : public CGoal { public: - GetObj() {} // empty constructor not allowed + GetObj() = delete; // empty constructor not allowed + GetObj(int Objid); - GetObj(int Objid) - : CGoal(Goals::GET_OBJ) - { - objid = Objid; - tile = cb->getObjInstance(ObjectInstanceID(objid))->visitablePos(); - priority = 3; - } TGoalVec getAllPossibleSubgoals() override; TSubgoal whatToDoToAchieve() override; bool operator==(AbstractGoal & g) override; From 5065f5a104085149ccbe28d7c168bf1b3822beaa Mon Sep 17 00:00:00 2001 From: Dydzio Date: Fri, 10 Aug 2018 00:33:18 +0200 Subject: [PATCH 19/33] Fixes --- AI/VCAI/Goals.cpp | 21 +++++++++++---------- AI/VCAI/VCAI.cpp | 13 +++++++++---- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/AI/VCAI/Goals.cpp b/AI/VCAI/Goals.cpp index cd479247f..c48ae4ba1 100644 --- a/AI/VCAI/Goals.cpp +++ b/AI/VCAI/Goals.cpp @@ -286,12 +286,6 @@ namespace Goals { return boost::format("Bought army of value %d in town of %s") % value, town->name; } - GetObj::GetObj(int Objid): CGoal(Goals::GET_OBJ) - { - objid = Objid; - tile = ai->myCb->getObjInstance(ObjectInstanceID(objid))->pos; - priority = 3; - } } TSubgoal Trade::whatToDoToAchieve() @@ -521,7 +515,7 @@ std::string GetObj::completeMessage() const TGoalVec GetObj::getAllPossibleSubgoals() { TGoalVec goalList; - const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(objid)); + const CGObjectInstance * obj = cb->getObjInstance(ObjectInstanceID(objid)); if(!obj) { goalList.push_back(sptr(Goals::Explore())); @@ -573,6 +567,13 @@ TSubgoal GetObj::whatToDoToAchieve() return bestGoal; } +Goals::GetObj::GetObj(int Objid) : CGoal(Goals::GET_OBJ) +{ + objid = Objid; + tile = ai->myCb->getObjInstance(ObjectInstanceID(objid))->visitablePos(); + priority = 3; +} + bool Goals::GetObj::operator==(AbstractGoal & g) { if (g.goalType != goalType) @@ -582,11 +583,11 @@ bool Goals::GetObj::operator==(AbstractGoal & g) bool GetObj::fulfillsMe(TSubgoal goal) { - if(goal->goalType == Goals::VISIT_TILE) //visiting tile visits object at same time + if(goal->goalType == Goals::GET_OBJ) { if (!hero || hero == goal->hero) { - auto obj = cb->getObj(ObjectInstanceID(objid)); + auto obj = cb->getObjInstance(ObjectInstanceID(objid)); if (obj && obj->visitablePos() == goal->tile) //object could be removed return true; } @@ -731,7 +732,7 @@ TGoalVec ClearWayTo::getAllPossibleSubgoals() if(shouldVisit(h, topObj)) { //do NOT use VISIT_TILE, as tile with quets guard can't be visited - ret.push_back(sptr(Goals::GetObj(topObj->id.getNum()).sethero(h))); + ret.push_back(sptr(Goals::GetObj(topObj->id.getNum()).sethero(h))); //TODO: Recheck this code - object visit became elementar goal continue; //do not try to visit tile or gather army } else diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index da65c8835..14716da56 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -1470,8 +1470,13 @@ void VCAI::wander(HeroPtr h) decomposeGoal(bestObjectGoal)->accept(this); //wander should not cause heroes to be reserved - they are always considered free - auto chosenObject = cb->getObjInstance(ObjectInstanceID(bestObjectGoal->objid)); - logAi->debug("Of all %d destinations, object %s at pos=%s seems nice", dests.size(), chosenObject->getObjectName(), chosenObject->pos.toString()); + if(bestObjectGoal->goalType == Goals::GET_OBJ) + { + auto chosenObject = cb->getObjInstance(ObjectInstanceID(bestObjectGoal->objid)); + logAi->debug("Of all %d destinations, object %s at pos=%s seems nice", dests.size(), chosenObject->getObjectName(), chosenObject->pos.toString()); + } + else + logAi->debug("Trying to realize goal of type %d as part of wandering.", bestObjectGoal->goalType); visitTownIfAny(h); } @@ -1998,9 +2003,9 @@ void VCAI::tryRealize(Goals::VisitTile & g) void VCAI::tryRealize(Goals::GetObj & g) { - auto position = cb->getObjInstance((ObjectInstanceID)g.objid)->pos; + auto position = g.tile; if(!g.hero->movement) - throw cannotFulfillGoalException("Cannot visit tile: hero is out of MPs!"); + throw cannotFulfillGoalException("Cannot visit object: hero is out of MPs!"); if(position == g.hero->visitablePos() && cb->getVisitableObjs(g.hero->visitablePos()).size() < 2) { logAi->warn("Why do I want to move hero %s to tile %s? Already standing on that tile! ", g.hero->name, g.tile.toString()); From f91c85a90034ff588a8d5be36ae0ce27e826a744 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Fri, 10 Aug 2018 01:04:31 +0200 Subject: [PATCH 20/33] Crash fix --- AI/VCAI/VCAI.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 14716da56..9bf388287 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -1473,7 +1473,8 @@ void VCAI::wander(HeroPtr h) if(bestObjectGoal->goalType == Goals::GET_OBJ) { auto chosenObject = cb->getObjInstance(ObjectInstanceID(bestObjectGoal->objid)); - logAi->debug("Of all %d destinations, object %s at pos=%s seems nice", dests.size(), chosenObject->getObjectName(), chosenObject->pos.toString()); + if(chosenObject != nullptr) + logAi->debug("Of all %d destinations, object %s at pos=%s seems nice", dests.size(), chosenObject->getObjectName(), chosenObject->pos.toString()); } else logAi->debug("Trying to realize goal of type %d as part of wandering.", bestObjectGoal->goalType); From 787f4032b413d30b955f532286dc273a3335ceb7 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Fri, 10 Aug 2018 13:42:15 +0200 Subject: [PATCH 21/33] fulfillsMe change revert, tweaks --- AI/VCAI/Fuzzy.cpp | 18 +++++------------- AI/VCAI/Goals.cpp | 5 ++--- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index 051f34d0b..2c879a625 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -343,7 +343,7 @@ HeroMovementGoalEngineBase::HeroMovementGoalEngineBase() heroStrength->addTerm(new fl::Ramp("HIGH", 0.5, 1)); heroStrength->setRange(0.0, 1.0); - turnDistance->addTerm(new fl::Ramp("SMALL", 0.5, 0)); + turnDistance->addTerm(new fl::Ramp("SHORT", 0.5, 0)); turnDistance->addTerm(new fl::Triangle("MEDIUM", 0.1, 0.8)); turnDistance->addTerm(new fl::Ramp("LONG", 0.5, 3)); turnDistance->setRange(0.0, 3.0); @@ -376,7 +376,7 @@ HeroMovementGoalEngineBase::HeroMovementGoalEngineBase() addRule("if lockedMissionImportance is MEDIUM then Value is somewhat LOW"); addRule("if lockedMissionImportance is LOW then Value is HIGH"); //pick nearby objects if it's easy, avoid long walks - addRule("if turnDistance is SMALL then Value is HIGH"); + addRule("if turnDistance is SHORT then Value is HIGH"); addRule("if turnDistance is MEDIUM then Value is MEDIUM"); addRule("if turnDistance is LONG then Value is LOW"); } @@ -510,17 +510,9 @@ GetObjEngine::GetObjEngine() objectValue->addTerm(new fl::Ramp("HIGH", 5000, 20000)); objectValue->setRange(0, 20000); //relic artifact value is border value by design, even better things are scaled down. - addRule("if turnDistance is LONG and objectValue is HIGH then value is MEDIUM"); - addRule("if turnDistance is MEDIUM and objectValue is HIGH then value is somewhat HIGH"); - addRule("if turnDistance is SHORT and objectValue is HIGH then value is HIGH"); - - addRule("if turnDistance is LONG and objectValue is MEDIUM then value is somewhat LOW"); - addRule("if turnDistance is MEDIUM and objectValue is MEDIUM then value is MEDIUM"); - addRule("if turnDistance is SHORT and objectValue is MEDIUM then value is somewhat HIGH"); - - addRule("if turnDistance is LONG and objectValue is LOW then value is very LOW"); - addRule("if turnDistance is MEDIUM and objectValue is LOW then value is LOW"); - addRule("if turnDistance is SHORT and objectValue is LOW then value is MEDIUM"); + addRule("if objectValue is HIGH then value is HIGH"); + addRule("if objectValue is MEDIUM then value is MEDIUM"); + addRule("if objectValue is LOW then value is LOW"); } catch(fl::Exception & fe) { diff --git a/AI/VCAI/Goals.cpp b/AI/VCAI/Goals.cpp index c48ae4ba1..18aa3640d 100644 --- a/AI/VCAI/Goals.cpp +++ b/AI/VCAI/Goals.cpp @@ -518,8 +518,7 @@ TGoalVec GetObj::getAllPossibleSubgoals() const CGObjectInstance * obj = cb->getObjInstance(ObjectInstanceID(objid)); if(!obj) { - goalList.push_back(sptr(Goals::Explore())); - return goalList; + throw cannotFulfillGoalException("Object is missing - goal is invalid now!"); } int3 pos = obj->visitablePos(); @@ -583,7 +582,7 @@ bool Goals::GetObj::operator==(AbstractGoal & g) bool GetObj::fulfillsMe(TSubgoal goal) { - if(goal->goalType == Goals::GET_OBJ) + if(goal->goalType == Goals::VISIT_TILE) { if (!hero || hero == goal->hero) { From f2ba500e909812c6d4b622e0c0c142ec0a6cca36 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Fri, 10 Aug 2018 16:36:45 +0200 Subject: [PATCH 22/33] Autocalculate default AI value for dwellings --- AI/VCAI/MapObjectsEvaluator.cpp | 25 +++++++++- config/objects/dwellings.json | 84 +-------------------------------- 2 files changed, 24 insertions(+), 85 deletions(-) diff --git a/AI/VCAI/MapObjectsEvaluator.cpp b/AI/VCAI/MapObjectsEvaluator.cpp index 7783f68dd..b6525e72e 100644 --- a/AI/VCAI/MapObjectsEvaluator.cpp +++ b/AI/VCAI/MapObjectsEvaluator.cpp @@ -2,6 +2,9 @@ #include "MapObjectsEvaluator.h" #include "../../lib/GameConstants.h" #include "../../lib/VCMI_Lib.h" +#include "../../lib/CCreatureHandler.h" +#include "../../lib/mapObjects/CGTownInstance.h" +#include "../../lib/CRandomGenerator.h" MapObjectsEvaluator & MapObjectsEvaluator::getInstance() { @@ -29,9 +32,27 @@ MapObjectsEvaluator::MapObjectsEvaluator() { objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = VLC->objtypeh->getObjGroupAiValue(primaryID).get(); } - else + else //some default handling when aiValue not found { - objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = 0; //some default handling when aiValue not found + if(primaryID == Obj::CREATURE_GENERATOR1 || primaryID == Obj::CREATURE_GENERATOR4) + { + int aiValue = 0; + CGDwelling dwellingMock; + CRandomGenerator rngMock; + handler->configureObject(&dwellingMock, rngMock); + + for(auto & creLevel : dwellingMock.creatures) + { + for(auto & creatureID : creLevel.second) + { + auto creature = VLC->creh->creatures[creatureID]; + aiValue += (creature->AIValue * creature->growth); + } + } + objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = aiValue; + } + else + objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = 0; } } } diff --git a/config/objects/dwellings.json b/config/objects/dwellings.json index abda39893..39bf11ac3 100644 --- a/config/objects/dwellings.json +++ b/config/objects/dwellings.json @@ -15,7 +15,6 @@ "types" : { "basiliskPit": { "index": 0, - "aiValue" : 2208, "creatures": [["basilisk"]], "sounds": { "ambient": ["LOOPMONS"] @@ -23,7 +22,6 @@ }, "behemothCrag": { "index": 1, - "aiValue" : 3162, "creatures": [["behemoth"]], "sounds": { "ambient": ["LOOPBEHE"] @@ -31,7 +29,6 @@ }, "pillarOfEyes": { "index": 2, - "aiValue" : 2352, "creatures": [["beholder"]], "sounds": { "ambient": ["LOOPCAVE"] @@ -39,7 +36,6 @@ }, "hallOfDarkness": { "index": 3, - "aiValue" : 4174, "creatures": [["blackKnight"]], "sounds": { "ambient": ["LOOPDEAD"] @@ -47,7 +43,6 @@ }, "dragonVault": { "index": 4, - "aiValue" : 3388, "creatures": [["boneDragon"]], "sounds": { "ambient": ["LOOPDRAG"] @@ -55,7 +50,6 @@ }, "trainingGrounds": { "index": 5, - "aiValue" : 3892, "creatures": [["cavalier"]], "sounds": { "ambient": ["LOOPHORS"] @@ -63,7 +57,6 @@ }, "centaurStables": { "index": 6, - "aiValue" : 1400, "creatures": [["centaur"]], "sounds": { "ambient": ["LOOPHORS"] @@ -71,7 +64,6 @@ }, "airConflux": { "index": 7, - "aiValue" : 2136, "creatures": [["airElemental"]], "sounds": { "ambient": ["LOOPAIR"] @@ -79,7 +71,6 @@ }, "portalOfGlory": { "index": 8, - "aiValue" : 5019, "creatures": [["angel"]], "sounds": { "ambient": ["LOOPSANC"] @@ -87,7 +78,6 @@ }, "cyclopsCave": { "index": 9, - "aiValue" : 2532, "creatures": [["cyclop"]], "sounds": { "ambient": ["LOOPCAVE"] @@ -95,7 +85,6 @@ }, "forsakenPalace": { "index": 10, - "aiValue" : 5101, "creatures": [["devil"]], "sounds": { "ambient": ["LOOPDEVL"] @@ -103,7 +92,6 @@ }, "serpentFlyHive": { "index": 11, - "aiValue" : 2144, "creatures": [["serpentFly"]], "sounds": { "ambient": ["LOOPLEAR"] @@ -111,7 +99,6 @@ }, "dwarfCottage": { "index": 12, - "aiValue" : 1104, "creatures": [["dwarf"]], "sounds": { "ambient": ["LOOPDWAR"] @@ -119,7 +106,6 @@ }, "earthConflux": { "index": 13, - "aiValue" : 1320, "creatures": [["earthElemental"]], "sounds": { "ambient": ["LOOPEART"] @@ -127,7 +113,6 @@ }, "fireLake": { "index": 14, - "aiValue" : 3340, "creatures": [["efreet"]], "sounds": { "ambient": ["LOOPVENT"] @@ -135,7 +120,6 @@ }, "homestead": { "index": 15, - "aiValue" : 1638, "creatures": [["woodElf"]], "sounds": { "ambient": ["LOOPELF"] @@ -143,7 +127,6 @@ }, "fireConflux": { "index": 16, - "aiValue" : 1725, "creatures": [["fireElemental"]], "sounds": { "ambient": ["LOOPFIRE"] @@ -151,7 +134,6 @@ }, "parapet": { "index": 17, - "aiValue" : 1485, "creatures": [["stoneGargoyle"]], "sounds": { "ambient": ["LOOPGRIF"] @@ -159,7 +141,6 @@ }, "altarOfWishes": { "index": 18, - "aiValue" : 2652, "creatures": [["genie"]], "sounds": { "ambient": ["LOOPMAGI"] @@ -167,7 +148,6 @@ }, "wolfPen": { "index": 19, - "aiValue" : 1170, "creatures": [["goblinWolfRider"]], "sounds": { "ambient": ["LOOPWOLF"] @@ -175,7 +155,6 @@ }, "gnollHut": { "index": 20, - "aiValue" : 672, "creatures": [["gnoll"]], "sounds": { "ambient": ["LOOPORC"] @@ -183,7 +162,6 @@ }, "goblinBarracks": { "index": 21, - "aiValue" : 900, "creatures": [["goblin"]], "sounds": { "ambient": ["LOOPGOBL"] @@ -191,7 +169,6 @@ }, "hallOfSins": { "index": 22, - "aiValue" : 1272, "creatures": [["gog"]], "sounds": { "ambient": ["LOOPVENT"] @@ -199,7 +176,6 @@ }, "gorgonLair": { "index": 23, - "aiValue" : 2670, "creatures": [["gorgon"]], "sounds": { "ambient": ["LOOPBEHE"] @@ -207,7 +183,6 @@ }, "dragonCliffs": { "index": 24, - "aiValue" : 4872, "creatures": [["greenDragon"]], "sounds": { "ambient": ["LOOPDRAG"] @@ -215,7 +190,6 @@ }, "griffinTower": { "index": 25, - "aiValue" : 2457, "creatures": [["griffin"]], "sounds": { "ambient": ["LOOPGRIF"] @@ -223,7 +197,6 @@ }, "harpyLoft": { "index": 26, - "aiValue" : 1232, "creatures": [["harpy"]], "sounds": { "ambient": ["LOOPHARP"] @@ -231,7 +204,6 @@ }, "kennels": { "index": 27, - "aiValue" : 1785, "creatures": [["hellHound"]], "sounds": { "ambient": ["LOOPDOG"] @@ -239,7 +211,6 @@ }, "hydraPond": { "index": 28, - "aiValue" : 4120, "creatures": [["hydra"]], "sounds": { "ambient": ["LOOPHYDR"] @@ -247,7 +218,6 @@ }, "impCrucible": { "index": 29, - "aiValue" : 750, "creatures": [["imp"]], "sounds": { "ambient": ["LOOPFIRE"] @@ -255,7 +225,6 @@ }, "lizardDen": { "index": 30, - "aiValue" : 1134, "creatures": [["lizardman"]], "sounds": { "ambient": ["LOOPARCH"] @@ -263,7 +232,6 @@ }, "mageTower": { "index": 31, - "aiValue" : 2280, "creatures": [["mage"]], "sounds": { "ambient": ["LOOPMAGI"] @@ -271,7 +239,6 @@ }, "manticoreLair": { "index": 32, - "aiValue" : 3094, "creatures": [["manticore"]], "sounds": { "ambient": ["LOOPMANT"] @@ -279,7 +246,6 @@ }, "medusaChapel": { "index": 33, - "aiValue" : 2068, "creatures": [["medusa"]], "sounds": { "ambient": ["LOOPMEDU"] @@ -287,7 +253,6 @@ }, "labyrinth": { "index": 34, - "aiValue" : 2505, "creatures": [["minotaur"]], "sounds": { "ambient": ["LOOPANIM"] @@ -295,7 +260,6 @@ }, "monastery": { "index": 35, - "aiValue" : 1455, "creatures": [["monk"]], "sounds": { "ambient": ["LOOPMONK"] @@ -303,7 +267,6 @@ }, "goldenPavilion": { "index": 36, - "aiValue" : 4032, "creatures": [["naga"]], "sounds": { "ambient": ["LOOPNAGA"] @@ -311,7 +274,6 @@ }, "demonGate": { "index": 37, - "aiValue" : 1780, "creatures": [["demon"]], "sounds": { "ambient": ["LOOPCAVE"] @@ -319,7 +281,6 @@ }, "ogreFort": { "index": 38, - "aiValue" : 1664, "creatures": [["ogre"]], "sounds": { "ambient": ["LOOPOGRE"] @@ -327,7 +288,6 @@ }, "orcTower": { "index": 39, - "aiValue" : 1344, "creatures": [["orc"]], "sounds": { "ambient": ["LOOPORC"] @@ -335,7 +295,6 @@ }, "hellHole": { "index": 40, - "aiValue" : 2295, "creatures": [["pitFiend"]], "sounds": { "ambient": ["LOOPFIRE"] @@ -343,7 +302,6 @@ }, "dragonCave": { "index": 41, - "aiValue" : 4702, "creatures": [["redDragon"]], "sounds": { "ambient": ["LOOPDRAG"] @@ -351,7 +309,6 @@ }, "cliffNest": { "index": 42, - "aiValue" : 3081, "creatures": [["roc"]], "sounds": { "ambient": ["LOOPBIRD"] @@ -359,7 +316,6 @@ }, "workshop": { "index": 43, - "aiValue" : 704, "creatures": [["gremlin"]], "sounds": { "ambient": ["LOOPGREM"] @@ -367,7 +323,6 @@ }, "cloudTemple": { "index": 44, - "aiValue" : 3718, "creatures": [["giant"]], "sounds": { "ambient": ["LOOPTITA"] @@ -375,7 +330,6 @@ }, "dendroidArches": { "index": 45, - "aiValue" : 1551, "creatures": [["dendroidGuard"]], "sounds": { "ambient": ["LOOPGARD"] @@ -383,7 +337,6 @@ }, "warren": { "index": 46, - "aiValue" : 826, "creatures": [["troglodyte"]], "sounds": { "ambient": ["LOOPCAVE"] @@ -391,7 +344,6 @@ }, "waterConflux": { "index": 47, - "aiValue" : 1890, "creatures": [["waterElemental"]], "sounds": { "ambient": ["LOOPFOUN"] @@ -399,7 +351,6 @@ }, "tombOfSouls": { "index": 48, - "aiValue" : 1764, "creatures": [["wight"]], "sounds": { "ambient": ["LOOPDEAD"] @@ -407,7 +358,6 @@ }, "wyvernNest": { "index": 49, - "aiValue" : 2700, "creatures": [["wyvern"]], "sounds": { "ambient": ["LOOPMONS"] @@ -415,7 +365,6 @@ }, "enchantedSpring": { "index": 50, - "aiValue" : 2590, "creatures": [["pegasus"]], "sounds": { "ambient": ["LOOPPEGA"] @@ -423,7 +372,6 @@ }, "unicornGladeBig": { "index": 51, - "aiValue" : 3612, "creatures": [["unicorn"]], "sounds": { "ambient": ["LOOPUNIC"] @@ -431,7 +379,6 @@ }, "mausoleum": { "index": 52, - "aiValue" : 2544, "creatures": [["lich"]], "sounds": { "ambient": ["LOOPDEAD"] @@ -439,7 +386,6 @@ }, "estate": { "index": 53, - "aiValue" : 2220, "creatures": [["vampire"]], "sounds": { "ambient": ["LOOPDEAD"] @@ -447,7 +393,6 @@ }, "cursedTemple": { "index": 54, - "aiValue" : 720, "creatures": [["skeleton"]], "sounds": { "ambient": ["LOOPSKEL"] @@ -455,7 +400,6 @@ }, "graveyard": { "index": 55, - "aiValue" : 784, "creatures": [["walkingDead"]], "sounds": { "ambient": ["LOOPDEAD"] @@ -463,7 +407,6 @@ }, "guardhouse": { "index": 56, - "aiValue" : 1120, "creatures": [["pikeman"]], "sounds": { "ambient": ["LOOPPIKE"] @@ -471,7 +414,6 @@ }, "archersTower": { "index": 57, - "aiValue" : 1134, "creatures": [["archer"]], "sounds": { "ambient": ["LOOPARCH"] @@ -479,57 +421,46 @@ }, "barracks": { "index": 58, - "aiValue" : 1780, "creatures": [["swordsman"]] }, "magicLantern": { "index": 59, - "aiValue" : 1100, "creatures": [["pixie"]] }, "altarOfThought": { "index": 60, - "aiValue" : 3338, "creatures": [["psychicElemental"]] }, "pyre": { "index": 61, - "aiValue" : 9094, "creatures": [["firebird"]] }, "frozenCliffs": { "index": 62, - "aiValue" : 78845, "creatures": [["azureDragon"]] }, "crystalCavern": { "index": 63, - "aiValue" : 39338, "creatures": [["crystalDragon"]] }, "magicForest": { "index": 64, - "aiValue" : 19580, "creatures": [["fairieDragon"]] }, "sulfurousLair": { "index": 65, - "aiValue" : 26433, "creatures": [["rustDragon"]] }, "enchantersHollow": { "index": 66, - "aiValue" : 2420, "creatures": [["enchanter"]] }, "treetopTower": { "index": 67, - "aiValue" : 2340, "creatures": [["sharpshooter"]] }, "unicornGlade": { "index": 68, - "aiValue" : 3612, "creatures": [["unicorn"]], "sounds": { "ambient": ["LOOPUNIC"] @@ -537,7 +468,6 @@ }, "altarOfAir": { "index": 69, - "aiValue" : 2136, "creatures": [["airElemental"]], "sounds": { "ambient": ["LOOPAIR"] @@ -545,7 +475,6 @@ }, "altarOfEarth": { "index": 70, - "aiValue" : 1320, "creatures": [["earthElemental"]], "sounds": { "ambient": ["LOOPEART"] @@ -553,7 +482,6 @@ }, "altarOfFire": { "index": 71, - "aiValue" : 1725, "creatures": [["fireElemental"]], "sounds": { "ambient": ["LOOPFIRE"] @@ -561,7 +489,6 @@ }, "altarOfWater": { "index": 72, - "aiValue" : 1890, "creatures": [["waterElemental"]], "sounds": { "ambient": ["LOOPFOUN"] @@ -569,37 +496,30 @@ }, "thatchedHut": { "index": 73, - "aiValue" : 1125, "creatures": [["halfling"]] }, "hovel": { "index": 74, - "aiValue" : 375, "creatures": [["peasant"]] }, "boarGlen": { "index": 75, - "aiValue" : 1160, "creatures": [["boar"]] }, "tombOfCurses": { "index": 76, - "aiValue" : 1890, "creatures": [["mummy"]] }, "nomadTent": { "index": 77, - "aiValue" : 2415, "creatures": [["nomad"]] }, "rogueCavern": { "index": 78, - "aiValue" : 1080, "creatures": [["rogue"]] }, "trollBridge": { "index": 79, - "aiValue" : 3072, "creatures": [["troll"]] } } @@ -611,7 +531,6 @@ "types" : { "elementalConflux" : { "index" : 0, - "aiValue" : 7071, "creatures" : [ // 4 separate "levels" to give them separate growth [ "airElemental" ], [ "waterElemental" ], @@ -627,7 +546,6 @@ }, "golemFactory" : { "index" : 1, - "aiValue" : 7322, "creatures" : [ // 4 separate "levels" to give them separate growth [ "ironGolem" ], [ "stoneGolem" ], @@ -643,5 +561,5 @@ } } } - } + }, } From 3961b4ac93f9b89bf489d3cccf7606a3f132c661 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Fri, 10 Aug 2018 16:42:55 +0200 Subject: [PATCH 23/33] Rename GetObj->VisitObj to reflect new functionality --- AI/VCAI/Fuzzy.cpp | 4 ++-- AI/VCAI/Fuzzy.h | 2 +- AI/VCAI/Goals.cpp | 36 ++++++++++++++++++------------------ AI/VCAI/Goals.h | 6 +++--- AI/VCAI/VCAI.cpp | 24 ++++++++++++------------ AI/VCAI/VCAI.h | 4 ++-- 6 files changed, 38 insertions(+), 38 deletions(-) diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index 2c879a625..1feb7b264 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -415,7 +415,7 @@ float FuzzyHelper::evaluate(Goals::VisitTile & g) { return visitTileEngine.evaluate(g); } -float FuzzyHelper::evaluate(Goals::GetObj & g) +float FuzzyHelper::evaluate(Goals::VisitObj & g) { return getObjEngine.evaluate(g); } @@ -523,7 +523,7 @@ GetObjEngine::GetObjEngine() float GetObjEngine::evaluate(Goals::AbstractGoal & goal) { - auto g = dynamic_cast(goal); + auto g = dynamic_cast(goal); if(!g.hero) return 0; diff --git a/AI/VCAI/Fuzzy.h b/AI/VCAI/Fuzzy.h index efa18e9f0..bfe016af6 100644 --- a/AI/VCAI/Fuzzy.h +++ b/AI/VCAI/Fuzzy.h @@ -88,7 +88,7 @@ public: float evaluate(Goals::Explore & g); float evaluate(Goals::RecruitHero & g); float evaluate(Goals::VisitTile & g); - float evaluate(Goals::GetObj & g); + float evaluate(Goals::VisitObj & g); float evaluate(Goals::VisitHero & g); float evaluate(Goals::BuildThis & g); float evaluate(Goals::DigAtTile & g); diff --git a/AI/VCAI/Goals.cpp b/AI/VCAI/Goals.cpp index 18aa3640d..39cc9161c 100644 --- a/AI/VCAI/Goals.cpp +++ b/AI/VCAI/Goals.cpp @@ -350,7 +350,7 @@ TSubgoal Win::whatToDoToAchieve() return sptr(Goals::Conquer()); - return sptr(Goals::GetObj(goal.object->id.getNum())); + return sptr(Goals::VisitObj(goal.object->id.getNum())); } else { @@ -404,7 +404,7 @@ TSubgoal Win::whatToDoToAchieve() return sptr(Goals::DigAtTile(grailPos)); } //TODO: use FIND_OBJ else if(const CGObjectInstance * obj = ai->getUnvisitedObj(objWithID)) //there are unvisited Obelisks - return sptr(Goals::GetObj(obj->id.getNum())); + return sptr(Goals::VisitObj(obj->id.getNum())); else return sptr(Goals::Explore()); } @@ -414,7 +414,7 @@ TSubgoal Win::whatToDoToAchieve() { if(goal.object) { - return sptr(Goals::GetObj(goal.object->id.getNum())); + return sptr(Goals::VisitObj(goal.object->id.getNum())); } else { @@ -489,7 +489,7 @@ TSubgoal FindObj::whatToDoToAchieve() } } if(o && ai->isAccessible(o->pos)) //we don't use isAccessibleForHero as we don't know which hero it is - return sptr(Goals::GetObj(o->id.getNum())); + return sptr(Goals::VisitObj(o->id.getNum())); else return sptr(Goals::Explore()); } @@ -507,12 +507,12 @@ bool Goals::FindObj::fulfillsMe(TSubgoal goal) return false; } -std::string GetObj::completeMessage() const +std::string VisitObj::completeMessage() const { return "hero " + hero.get()->name + " captured Object ID = " + boost::lexical_cast(objid); } -TGoalVec GetObj::getAllPossibleSubgoals() +TGoalVec VisitObj::getAllPossibleSubgoals() { TGoalVec goalList; const CGObjectInstance * obj = cb->getObjInstance(ObjectInstanceID(objid)); @@ -527,7 +527,7 @@ TGoalVec GetObj::getAllPossibleSubgoals() if(ai->isAccessibleForHero(pos, hero)) { if(isSafeToVisit(hero, pos)) - goalList.push_back(sptr(Goals::GetObj(obj->id.getNum()).sethero(hero))); + goalList.push_back(sptr(Goals::VisitObj(obj->id.getNum()).sethero(hero))); else goalList.push_back(sptr(Goals::GatherArmy(evaluateDanger(pos, hero.h) * SAFE_ATTACK_CONSTANT).sethero(hero).setisAbstract(true))); @@ -541,7 +541,7 @@ TGoalVec GetObj::getAllPossibleSubgoals() if(ai->isAccessibleForHero(pos, h)) { if(isSafeToVisit(hero, pos)) - goalList.push_back(sptr(Goals::GetObj(obj->id.getNum()).sethero(h))); + goalList.push_back(sptr(Goals::VisitObj(obj->id.getNum()).sethero(h))); else goalList.push_back(sptr(Goals::GatherArmy(evaluateDanger(pos, h) * SAFE_ATTACK_CONSTANT).sethero(hero).setisAbstract(true))); } @@ -556,7 +556,7 @@ TGoalVec GetObj::getAllPossibleSubgoals() return goalList; } -TSubgoal GetObj::whatToDoToAchieve() +TSubgoal VisitObj::whatToDoToAchieve() { auto bestGoal = fh->chooseSolution(getAllPossibleSubgoals()); @@ -566,21 +566,21 @@ TSubgoal GetObj::whatToDoToAchieve() return bestGoal; } -Goals::GetObj::GetObj(int Objid) : CGoal(Goals::GET_OBJ) +Goals::VisitObj::VisitObj(int Objid) : CGoal(Goals::GET_OBJ) { objid = Objid; tile = ai->myCb->getObjInstance(ObjectInstanceID(objid))->visitablePos(); priority = 3; } -bool Goals::GetObj::operator==(AbstractGoal & g) +bool Goals::VisitObj::operator==(AbstractGoal & g) { if (g.goalType != goalType) return false; return g.objid == objid; } -bool GetObj::fulfillsMe(TSubgoal goal) +bool VisitObj::fulfillsMe(TSubgoal goal) { if(goal->goalType == Goals::VISIT_TILE) { @@ -731,7 +731,7 @@ TGoalVec ClearWayTo::getAllPossibleSubgoals() if(shouldVisit(h, topObj)) { //do NOT use VISIT_TILE, as tile with quets guard can't be visited - ret.push_back(sptr(Goals::GetObj(topObj->id.getNum()).sethero(h))); //TODO: Recheck this code - object visit became elementar goal + ret.push_back(sptr(Goals::VisitObj(topObj->id.getNum()).sethero(h))); //TODO: Recheck this code - object visit became elementar goal continue; //do not try to visit tile or gather army } else @@ -1148,7 +1148,7 @@ TGoalVec Goals::CollectRes::getAllPossibleSubgoals() if (dest != t) //there is something blocking our way ret.push_back(sptr(Goals::ClearWayTo(dest, h).setisAbstract(true))); else - ret.push_back(sptr(Goals::GetObj(obj->id.getNum()).sethero(h).setisAbstract(true))); + ret.push_back(sptr(Goals::VisitObj(obj->id.getNum()).sethero(h).setisAbstract(true))); } else //we need to get army in order to pick that object ret.push_back(sptr(Goals::GatherArmy(evaluateDanger(dest, h) * SAFE_ATTACK_CONSTANT).sethero(h).setisAbstract(true))); @@ -1234,7 +1234,7 @@ TSubgoal Goals::CollectRes::whatToDoToTrade() auto objid = m->o->id.getNum(); if (backObj->tempOwner != ai->playerID) //top object not owned { - return sptr(Goals::GetObj(objid)); //just go there + return sptr(Goals::VisitObj(objid)); //just go there } else //either it's our town, or we have hero there { @@ -1340,7 +1340,7 @@ TSubgoal GatherTroops::whatToDoToAchieve() if(!nearest) throw cannotFulfillGoalException("Cannot find nearest dwelling!"); - return sptr(Goals::GetObj(nearest->id.getNum())); + return sptr(Goals::VisitObj(nearest->id.getNum())); } else return sptr(Goals::Explore()); @@ -1422,7 +1422,7 @@ TGoalVec Conquer::getAllPossibleSubgoals() } else //just get that object { - ret.push_back(sptr(Goals::GetObj(obj->id.getNum()).sethero(h).setisAbstract(true))); + ret.push_back(sptr(Goals::VisitObj(obj->id.getNum()).sethero(h).setisAbstract(true))); } } } @@ -1642,7 +1642,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals() TSubgoal AbstractGoal::goVisitOrLookFor(const CGObjectInstance * obj) { if(obj) - return sptr(Goals::GetObj(obj->id.getNum())); + return sptr(Goals::VisitObj(obj->id.getNum())); else return sptr(Goals::Explore()); } diff --git a/AI/VCAI/Goals.h b/AI/VCAI/Goals.h index 7a97a7f6f..3884748a0 100644 --- a/AI/VCAI/Goals.h +++ b/AI/VCAI/Goals.h @@ -461,11 +461,11 @@ public: bool fulfillsMe(TSubgoal goal) override; }; -class DLL_EXPORT GetObj : public CGoal +class DLL_EXPORT VisitObj : public CGoal //this goal was previously known as GetObj { public: - GetObj() = delete; // empty constructor not allowed - GetObj(int Objid); + VisitObj() = delete; // empty constructor not allowed + VisitObj(int Objid); TGoalVec getAllPossibleSubgoals() override; TSubgoal whatToDoToAchieve() override; diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 9bf388287..98699d662 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -268,7 +268,7 @@ void VCAI::heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * vi { markObjectVisited(visitedObj); unreserveObject(visitor, visitedObj); - completeGoal(sptr(Goals::GetObj(visitedObj->id.getNum()).sethero(visitor))); //we don't need to visit it anymore + completeGoal(sptr(Goals::VisitObj(visitedObj->id.getNum()).sethero(visitor))); //we don't need to visit it anymore //TODO: what if we visited one-time visitable object that was reserved by another hero (shouldn't, but..) if (visitedObj->ID == Obj::HERO) { @@ -1013,7 +1013,7 @@ void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h) } break; } - completeGoal(sptr(Goals::GetObj(obj->id.getNum()).sethero(h))); + completeGoal(sptr(Goals::VisitObj(obj->id.getNum()).sethero(h))); } void VCAI::moveCreaturesToHero(const CGTownInstance * t) @@ -1464,7 +1464,7 @@ void VCAI::wander(HeroPtr h) Goals::TGoalVec targetObjectGoals; for(auto destination : dests) { - targetObjectGoals.push_back(sptr(Goals::GetObj(destination.id.getNum()).sethero(h).setisAbstract(true))); + targetObjectGoals.push_back(sptr(Goals::VisitObj(destination.id.getNum()).sethero(h).setisAbstract(true))); } auto bestObjectGoal = fh->chooseSolution(targetObjectGoals); decomposeGoal(bestObjectGoal)->accept(this); @@ -2002,7 +2002,7 @@ void VCAI::tryRealize(Goals::VisitTile & g) } } -void VCAI::tryRealize(Goals::GetObj & g) +void VCAI::tryRealize(Goals::VisitObj & g) { auto position = g.tile; if(!g.hero->movement) @@ -2374,7 +2374,7 @@ Goals::TSubgoal VCAI::questToGoal(const QuestInfo & q) { if (q.quest->checkQuest(hero)) { - return sptr(Goals::GetObj(q.obj->id.getNum()).sethero(hero)); + return sptr(Goals::VisitObj(q.obj->id.getNum()).sethero(hero)); } } for (auto art : q.quest->m5arts) @@ -2390,7 +2390,7 @@ Goals::TSubgoal VCAI::questToGoal(const QuestInfo & q) { if (q.quest->checkQuest(hero)) { - return sptr(Goals::GetObj(q.obj->id.getNum()).sethero(hero)); + return sptr(Goals::VisitObj(q.obj->id.getNum()).sethero(hero)); } } return sptr(Goals::FindObj(Obj::PRISON)); //rule of a thumb - quest heroes usually are locked in prisons @@ -2403,7 +2403,7 @@ Goals::TSubgoal VCAI::questToGoal(const QuestInfo & q) { if (q.quest->checkQuest(hero)) //very bad info - stacks can be split between multiple heroes :( { - return sptr(Goals::GetObj(q.obj->id.getNum()).sethero(hero)); + return sptr(Goals::VisitObj(q.obj->id.getNum()).sethero(hero)); } } for (auto creature : q.quest->m6creatures) @@ -2420,7 +2420,7 @@ Goals::TSubgoal VCAI::questToGoal(const QuestInfo & q) { if (q.quest->checkQuest(heroes.front())) //it doesn't matter which hero it is { - return sptr(Goals::GetObj(q.obj->id.getNum())); + return sptr(Goals::VisitObj(q.obj->id.getNum())); } else { @@ -2440,9 +2440,9 @@ Goals::TSubgoal VCAI::questToGoal(const QuestInfo & q) { auto obj = cb->getObjByQuestIdentifier(q.quest->m13489val); if (obj) - return sptr(Goals::GetObj(obj->id.getNum())); + return sptr(Goals::VisitObj(obj->id.getNum())); else - return sptr(Goals::GetObj(q.obj->id.getNum())); //visit seer hut + return sptr(Goals::VisitObj(q.obj->id.getNum())); //visit seer hut break; } case CQuest::MISSION_PRIMARY_STAT: @@ -2452,7 +2452,7 @@ Goals::TSubgoal VCAI::questToGoal(const QuestInfo & q) { if (q.quest->checkQuest(hero)) { - return sptr(Goals::GetObj(q.obj->id.getNum()).sethero(hero)); + return sptr(Goals::VisitObj(q.obj->id.getNum()).sethero(hero)); } } for (int i = 0; i < q.quest->m2stats.size(); ++i) @@ -2468,7 +2468,7 @@ Goals::TSubgoal VCAI::questToGoal(const QuestInfo & q) { if (q.quest->checkQuest(hero)) { - return sptr(Goals::GetObj(q.obj->id.getNum()).sethero(hero)); //TODO: causes infinite loop :/ + return sptr(Goals::VisitObj(q.obj->id.getNum()).sethero(hero)); //TODO: causes infinite loop :/ } } logAi->debug("Don't know how to reach hero level %d", q.quest->m13489val); diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index df23911f6..a16c495d4 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -121,7 +121,7 @@ public: void tryRealize(Goals::Explore & g); void tryRealize(Goals::RecruitHero & g); void tryRealize(Goals::VisitTile & g); - void tryRealize(Goals::GetObj & g); + void tryRealize(Goals::VisitObj & g); void tryRealize(Goals::VisitHero & g); void tryRealize(Goals::BuildThis & g); void tryRealize(Goals::DigAtTile & g); @@ -285,7 +285,7 @@ public: h.template registerType(); h.template registerType(); h.template registerType(); - h.template registerType(); + h.template registerType(); h.template registerType(); //h.template registerType(); h.template registerType(); From bd3d27c79b3240a643b19fed61a872829dd64114 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Fri, 10 Aug 2018 16:48:42 +0200 Subject: [PATCH 24/33] Leftover stuff: rename GET_OBJ --- AI/VCAI/Goals.cpp | 8 ++++---- AI/VCAI/Goals.h | 2 +- AI/VCAI/VCAI.cpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/AI/VCAI/Goals.cpp b/AI/VCAI/Goals.cpp index 39cc9161c..1b507ec6f 100644 --- a/AI/VCAI/Goals.cpp +++ b/AI/VCAI/Goals.cpp @@ -77,7 +77,7 @@ std::string Goals::AbstractGoal::name() const //TODO: virtualize case GATHER_TROOPS: desc = "GATHER TROOPS"; break; - case GET_OBJ: + case VISIT_OBJ: { auto obj = cb->getObjInstance(ObjectInstanceID(objid)); if(obj) @@ -153,7 +153,7 @@ bool Goals::AbstractGoal::operator==(AbstractGoal & g) break; //assigned hero and object - case GET_OBJ: + case VISIT_OBJ: case FIND_OBJ: //TODO: use subtype? case VISIT_HERO: case GET_ART_TYPE: @@ -560,13 +560,13 @@ TSubgoal VisitObj::whatToDoToAchieve() { auto bestGoal = fh->chooseSolution(getAllPossibleSubgoals()); - if(bestGoal->goalType == Goals::GET_OBJ && bestGoal->hero) + if(bestGoal->goalType == Goals::VISIT_OBJ && bestGoal->hero) bestGoal->setisElementar(true); return bestGoal; } -Goals::VisitObj::VisitObj(int Objid) : CGoal(Goals::GET_OBJ) +Goals::VisitObj::VisitObj(int Objid) : CGoal(Goals::VISIT_OBJ) { objid = Objid; tile = ai->myCb->getObjInstance(ObjectInstanceID(objid))->visitablePos(); diff --git a/AI/VCAI/Goals.h b/AI/VCAI/Goals.h index 3884748a0..89806f630 100644 --- a/AI/VCAI/Goals.h +++ b/AI/VCAI/Goals.h @@ -46,7 +46,7 @@ enum EGoals GATHER_TROOPS, // val of creatures with objid OBJECT_GOALS_BEGIN, - GET_OBJ, //visit or defeat or collect the object + VISIT_OBJ, //visit or defeat or collect the object FIND_OBJ, //find and visit any obj with objid + resid //TODO: consider universal subid for various types (aid, bid) VISIT_HERO, //heroes can move around - set goal abstract and track hero every turn diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 98699d662..c8845232d 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -1470,7 +1470,7 @@ void VCAI::wander(HeroPtr h) decomposeGoal(bestObjectGoal)->accept(this); //wander should not cause heroes to be reserved - they are always considered free - if(bestObjectGoal->goalType == Goals::GET_OBJ) + if(bestObjectGoal->goalType == Goals::VISIT_OBJ) { auto chosenObject = cb->getObjInstance(ObjectInstanceID(bestObjectGoal->objid)); if(chosenObject != nullptr) From 72b206347f92bdc55eb6a9f4831083b13f80ad2c Mon Sep 17 00:00:00 2001 From: Dydzio Date: Fri, 10 Aug 2018 18:27:57 +0200 Subject: [PATCH 25/33] Split Fuzzy.cpp/h --- AI/VCAI/AIUtility.cpp | 2 +- AI/VCAI/CMakeLists.txt | 6 +- AI/VCAI/{Fuzzy.cpp => FuzzyEngines.cpp} | 1028 ++++++++++------------- AI/VCAI/{Fuzzy.h => FuzzyEngines.h} | 182 ++-- AI/VCAI/FuzzyHelper.cpp | 156 ++++ AI/VCAI/FuzzyHelper.h | 42 + AI/VCAI/Goals.cpp | 2 +- AI/VCAI/VCAI.cpp | 2 +- 8 files changed, 716 insertions(+), 704 deletions(-) rename AI/VCAI/{Fuzzy.cpp => FuzzyEngines.cpp} (74%) rename AI/VCAI/{Fuzzy.h => FuzzyEngines.h} (57%) create mode 100644 AI/VCAI/FuzzyHelper.cpp create mode 100644 AI/VCAI/FuzzyHelper.h diff --git a/AI/VCAI/AIUtility.cpp b/AI/VCAI/AIUtility.cpp index 311597daf..3d614b34c 100644 --- a/AI/VCAI/AIUtility.cpp +++ b/AI/VCAI/AIUtility.cpp @@ -10,7 +10,7 @@ #include "StdInc.h" #include "AIUtility.h" #include "VCAI.h" -#include "Fuzzy.h" +#include "FuzzyHelper.h" #include "../../lib/UnlockGuard.h" #include "../../lib/CConfigHandler.h" diff --git a/AI/VCAI/CMakeLists.txt b/AI/VCAI/CMakeLists.txt index aa26bc10e..9e73e6c56 100644 --- a/AI/VCAI/CMakeLists.txt +++ b/AI/VCAI/CMakeLists.txt @@ -15,7 +15,8 @@ set(VCAI_SRCS SectorMap.cpp BuildingManager.cpp MapObjectsEvaluator.cpp - Fuzzy.cpp + FuzzyEngines.cpp + FuzzyHelper.cpp Goals.cpp main.cpp VCAI.cpp @@ -31,7 +32,8 @@ set(VCAI_HEADERS SectorMap.h BuildingManager.h MapObjectsEvaluator.h - Fuzzy.h + FuzzyEngines.h + FuzzyHelper.h Goals.h VCAI.h ) diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/FuzzyEngines.cpp similarity index 74% rename from AI/VCAI/Fuzzy.cpp rename to AI/VCAI/FuzzyEngines.cpp index 1feb7b264..2adb5ec82 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/FuzzyEngines.cpp @@ -1,591 +1,437 @@ -/* - * Fuzzy.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 "Fuzzy.h" -#include - -#include "../../lib/mapObjects/MapObjects.h" -#include "../../lib/mapObjects/CommonConstructors.h" -#include "../../lib/CCreatureHandler.h" -#include "../../lib/CPathfinder.h" -#include "../../lib/CGameStateFwd.h" -#include "../../lib/VCMI_Lib.h" -#include "../../CCallback.h" -#include "VCAI.h" -#include "MapObjectsEvaluator.h" - -#define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter -#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us - -struct BankConfig; -class CBankInfo; -class Engine; -class InputVariable; -class CGTownInstance; - -FuzzyHelper * fh; - -extern boost::thread_specific_ptr cb; -extern boost::thread_specific_ptr ai; - -engineBase::engineBase() -{ - engine.addRuleBlock(&rules); -} - -void engineBase::configure() -{ - engine.configure("Minimum", "Maximum", "Minimum", "AlgebraicSum", "Centroid", "General"); - logAi->info(engine.toString()); -} - -void engineBase::addRule(const std::string & txt) -{ - rules.addRule(fl::Rule::parse(txt, &engine)); -} - -struct armyStructure -{ - float walkers, shooters, flyers; - ui32 maxSpeed; -}; - -armyStructure evaluateArmyStructure(const CArmedInstance * army) -{ - ui64 totalStrenght = army->getArmyStrength(); - double walkersStrenght = 0; - double flyersStrenght = 0; - double shootersStrenght = 0; - ui32 maxSpeed = 0; - - for(auto s : army->Slots()) - { - bool walker = true; - if(s.second->type->hasBonusOfType(Bonus::SHOOTER)) - { - shootersStrenght += s.second->getPower(); - walker = false; - } - if(s.second->type->hasBonusOfType(Bonus::FLYING)) - { - flyersStrenght += s.second->getPower(); - walker = false; - } - if(walker) - walkersStrenght += s.second->getPower(); - - vstd::amax(maxSpeed, s.second->type->valOfBonuses(Bonus::STACKS_SPEED)); - } - armyStructure as; - as.walkers = walkersStrenght / totalStrenght; - as.shooters = shootersStrenght / totalStrenght; - as.flyers = flyersStrenght / totalStrenght; - as.maxSpeed = maxSpeed; - assert(as.walkers || as.flyers || as.shooters); - return as; -} - -float HeroMovementGoalEngineBase::calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const -{ - float turns = 0.0f; - float distance = CPathfinderHelper::getMovementCost(h, tile); - if(distance) - { - if(distance < h->movement) //we can move there within one turn - turns = (fl::scalar)distance / h->movement; - else - turns = 1 + (fl::scalar)(distance - h->movement) / h->maxMovePoints(true); //bool on land? - } - return turns; -} - -ui64 FuzzyHelper::estimateBankDanger(const CBank * bank) -{ - //this one is not fuzzy anymore, just calculate weighted average - - auto objectInfo = VLC->objtypeh->getHandlerFor(bank->ID, bank->subID)->getObjectInfo(bank->appearance); - - CBankInfo * bankInfo = dynamic_cast(objectInfo.get()); - - ui64 totalStrength = 0; - ui8 totalChance = 0; - for(auto config : bankInfo->getPossibleGuards()) - { - totalStrength += config.second.totalStrength * config.first; - totalChance += config.first; - } - return totalStrength / std::max(totalChance, 1); //avoid division by zero - -} - -TacticalAdvantageEngine::TacticalAdvantageEngine() -{ - try - { - ourShooters = new fl::InputVariable("OurShooters"); - ourWalkers = new fl::InputVariable("OurWalkers"); - ourFlyers = new fl::InputVariable("OurFlyers"); - enemyShooters = new fl::InputVariable("EnemyShooters"); - enemyWalkers = new fl::InputVariable("EnemyWalkers"); - enemyFlyers = new fl::InputVariable("EnemyFlyers"); - - //Tactical advantage calculation - std::vector helper = - { - ourShooters, ourWalkers, ourFlyers, enemyShooters, enemyWalkers, enemyFlyers - }; - - for(auto val : helper) - { - engine.addInputVariable(val); - val->addTerm(new fl::Ramp("FEW", 0.6, 0.0)); - val->addTerm(new fl::Ramp("MANY", 0.4, 1)); - val->setRange(0.0, 1.0); - } - - ourSpeed = new fl::InputVariable("OurSpeed"); - enemySpeed = new fl::InputVariable("EnemySpeed"); - - helper = { ourSpeed, enemySpeed }; - - for(auto val : helper) - { - engine.addInputVariable(val); - val->addTerm(new fl::Ramp("LOW", 6.5, 3)); - val->addTerm(new fl::Triangle("MEDIUM", 5.5, 10.5)); - val->addTerm(new fl::Ramp("HIGH", 8.5, 16)); - val->setRange(0, 25); - } - - castleWalls = new fl::InputVariable("CastleWalls"); - engine.addInputVariable(castleWalls); - { - fl::Rectangle * none = new fl::Rectangle("NONE", CGTownInstance::NONE, CGTownInstance::NONE + (CGTownInstance::FORT - CGTownInstance::NONE) * 0.5f); - castleWalls->addTerm(none); - - fl::Trapezoid * medium = new fl::Trapezoid("MEDIUM", (CGTownInstance::FORT - CGTownInstance::NONE) * 0.5f, CGTownInstance::FORT, - CGTownInstance::CITADEL, CGTownInstance::CITADEL + (CGTownInstance::CASTLE - CGTownInstance::CITADEL) * 0.5f); - castleWalls->addTerm(medium); - - fl::Ramp * high = new fl::Ramp("HIGH", CGTownInstance::CITADEL - 0.1, CGTownInstance::CASTLE); - castleWalls->addTerm(high); - - castleWalls->setRange(CGTownInstance::NONE, CGTownInstance::CASTLE); - } - - - bankPresent = new fl::InputVariable("Bank"); - engine.addInputVariable(bankPresent); - { - fl::Rectangle * termFalse = new fl::Rectangle("FALSE", 0.0, 0.5f); - bankPresent->addTerm(termFalse); - fl::Rectangle * termTrue = new fl::Rectangle("TRUE", 0.5f, 1); - bankPresent->addTerm(termTrue); - bankPresent->setRange(0, 1); - } - - threat = new fl::OutputVariable("Threat"); - engine.addOutputVariable(threat); - threat->addTerm(new fl::Ramp("LOW", 1, MIN_AI_STRENGHT)); - threat->addTerm(new fl::Triangle("MEDIUM", 0.8, 1.2)); - threat->addTerm(new fl::Ramp("HIGH", 1, 1.5)); - threat->setRange(MIN_AI_STRENGHT, 1.5); - - addRule("if OurShooters is MANY and EnemySpeed is LOW then Threat is LOW"); - addRule("if OurShooters is MANY and EnemyShooters is FEW then Threat is LOW"); - addRule("if OurSpeed is LOW and EnemyShooters is MANY then Threat is HIGH"); - addRule("if OurSpeed is HIGH and EnemyShooters is MANY then Threat is LOW"); - - addRule("if OurWalkers is FEW and EnemyShooters is MANY then Threat is somewhat LOW"); - addRule("if OurShooters is MANY and EnemySpeed is HIGH then Threat is somewhat HIGH"); - //just to cover all cases - addRule("if OurShooters is FEW and EnemySpeed is HIGH then Threat is MEDIUM"); - addRule("if EnemySpeed is MEDIUM then Threat is MEDIUM"); - addRule("if EnemySpeed is LOW and OurShooters is FEW then Threat is MEDIUM"); - - addRule("if Bank is TRUE and OurShooters is MANY then Threat is somewhat HIGH"); - addRule("if Bank is TRUE and EnemyShooters is MANY then Threat is LOW"); - - addRule("if CastleWalls is HIGH and OurWalkers is MANY then Threat is very HIGH"); - addRule("if CastleWalls is HIGH and OurFlyers is MANY and OurShooters is MANY then Threat is MEDIUM"); - addRule("if CastleWalls is MEDIUM and OurShooters is MANY and EnemyWalkers is MANY then Threat is LOW"); - - } - catch(fl::Exception & pe) - { - logAi->error("initTacticalAdvantage: %s", pe.getWhat()); - } - configure(); -} - -float TacticalAdvantageEngine::getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy) -{ - float output = 1; - try - { - armyStructure ourStructure = evaluateArmyStructure(we); - armyStructure enemyStructure = evaluateArmyStructure(enemy); - - ourWalkers->setValue(ourStructure.walkers); - ourShooters->setValue(ourStructure.shooters); - ourFlyers->setValue(ourStructure.flyers); - ourSpeed->setValue(ourStructure.maxSpeed); - - enemyWalkers->setValue(enemyStructure.walkers); - enemyShooters->setValue(enemyStructure.shooters); - enemyFlyers->setValue(enemyStructure.flyers); - enemySpeed->setValue(enemyStructure.maxSpeed); - - bool bank = dynamic_cast(enemy); - if(bank) - bankPresent->setValue(1); - else - bankPresent->setValue(0); - - const CGTownInstance * fort = dynamic_cast(enemy); - if(fort) - castleWalls->setValue(fort->fortLevel()); - else - castleWalls->setValue(0); - - engine.process(); - output = threat->getValue(); - } - catch(fl::Exception & fe) - { - logAi->error("getTacticalAdvantage: %s ", fe.getWhat()); - } - - if(output < 0 || (output != output)) - { - fl::InputVariable * tab[] = { bankPresent, castleWalls, ourWalkers, ourShooters, ourFlyers, ourSpeed, enemyWalkers, enemyShooters, enemyFlyers, enemySpeed }; - std::string names[] = { "bankPresent", "castleWalls", "ourWalkers", "ourShooters", "ourFlyers", "ourSpeed", "enemyWalkers", "enemyShooters", "enemyFlyers", "enemySpeed" }; - std::stringstream log("Warning! Fuzzy engine doesn't cover this set of parameters: "); - - for(int i = 0; i < boost::size(tab); i++) - log << names[i] << ": " << tab[i]->getValue() << " "; - logAi->error(log.str()); - assert(false); - } - - return output; -} - -//std::shared_ptr chooseSolution (std::vector> & vec) - -Goals::TSubgoal FuzzyHelper::chooseSolution(Goals::TGoalVec vec) -{ - if(vec.empty()) //no possibilities found - return sptr(Goals::Invalid()); - - ai->cachedSectorMaps.clear(); - - //a trick to switch between heroes less often - calculatePaths is costly - auto sortByHeroes = [](const Goals::TSubgoal & lhs, const Goals::TSubgoal & rhs) -> bool - { - return lhs->hero.h < rhs->hero.h; - }; - boost::sort(vec, sortByHeroes); - - for(auto g : vec) - { - setPriority(g); - } - - auto compareGoals = [](const Goals::TSubgoal & lhs, const Goals::TSubgoal & rhs) -> bool - { - return lhs->priority < rhs->priority; - }; - return *boost::max_element(vec, compareGoals); -} - -float FuzzyHelper::evaluate(Goals::Explore & g) -{ - return 1; -} -float FuzzyHelper::evaluate(Goals::RecruitHero & g) -{ - return 1; -} -HeroMovementGoalEngineBase::HeroMovementGoalEngineBase() -{ - try - { - strengthRatio = new fl::InputVariable("strengthRatio"); //hero must be strong enough to defeat guards - heroStrength = new fl::InputVariable("heroStrength"); //we want to use weakest possible hero - turnDistance = new fl::InputVariable("turnDistance"); //we want to use hero who is near - missionImportance = new fl::InputVariable("lockedMissionImportance"); //we may want to preempt hero with low-priority mission - value = new fl::OutputVariable("Value"); - value->setMinimum(0); - value->setMaximum(5); - - std::vector helper = { strengthRatio, heroStrength, turnDistance, missionImportance }; - for(auto val : helper) - { - engine.addInputVariable(val); - } - engine.addOutputVariable(value); - - strengthRatio->addTerm(new fl::Ramp("LOW", SAFE_ATTACK_CONSTANT, 0)); - strengthRatio->addTerm(new fl::Ramp("HIGH", SAFE_ATTACK_CONSTANT, SAFE_ATTACK_CONSTANT * 3)); - strengthRatio->setRange(0, SAFE_ATTACK_CONSTANT * 3); - - //strength compared to our main hero - heroStrength->addTerm(new fl::Ramp("LOW", 0.2, 0)); - heroStrength->addTerm(new fl::Triangle("MEDIUM", 0.2, 0.8)); - heroStrength->addTerm(new fl::Ramp("HIGH", 0.5, 1)); - heroStrength->setRange(0.0, 1.0); - - turnDistance->addTerm(new fl::Ramp("SHORT", 0.5, 0)); - turnDistance->addTerm(new fl::Triangle("MEDIUM", 0.1, 0.8)); - turnDistance->addTerm(new fl::Ramp("LONG", 0.5, 3)); - turnDistance->setRange(0.0, 3.0); - - missionImportance->addTerm(new fl::Ramp("LOW", 2.5, 0)); - missionImportance->addTerm(new fl::Triangle("MEDIUM", 2, 3)); - missionImportance->addTerm(new fl::Ramp("HIGH", 2.5, 5)); - missionImportance->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 - value->addTerm(new fl::Ramp("LOW", 2.5, 0)); - value->addTerm(new fl::Triangle("MEDIUM", 2, 3)); //can't be center of mass :/ - value->addTerm(new fl::Ramp("HIGH", 2.5, 5)); - value->setRange(0.0, 5.0); - - //use unarmed scouts if possible - addRule("if strengthRatio is HIGH and heroStrength is LOW then Value is very HIGH"); - //we may want to use secondary hero(es) rather than main hero - addRule("if strengthRatio is HIGH and heroStrength is MEDIUM then Value is somewhat HIGH"); - addRule("if strengthRatio is HIGH and heroStrength is HIGH then Value is somewhat LOW"); - //don't assign targets to heroes who are too weak, but prefer targets of our main hero (in case we need to gather army) - addRule("if strengthRatio is LOW and heroStrength is LOW then Value is very LOW"); - //attempt to arm secondary heroes is not stupid - addRule("if strengthRatio is LOW and heroStrength is MEDIUM then Value is somewhat HIGH"); - addRule("if strengthRatio is LOW and heroStrength is HIGH then Value is LOW"); - - //do not cancel important goals - addRule("if lockedMissionImportance is HIGH then Value is very LOW"); - addRule("if lockedMissionImportance is MEDIUM then Value is somewhat LOW"); - addRule("if lockedMissionImportance is LOW then Value is HIGH"); - //pick nearby objects if it's easy, avoid long walks - addRule("if turnDistance is SHORT then Value is HIGH"); - addRule("if turnDistance is MEDIUM then Value is MEDIUM"); - addRule("if turnDistance is LONG then Value is LOW"); - } - catch(fl::Exception & fe) - { - logAi->error("HeroMovementGoalEngineBase: %s", fe.getWhat()); - } -} - -void HeroMovementGoalEngineBase::setSharedFuzzyVariables(Goals::AbstractGoal & goal) -{ - float turns = calculateTurnDistanceInputValue(goal.hero.h, goal.tile); - float missionImportanceData = 0; - if(vstd::contains(ai->lockedHeroes, goal.hero)) - missionImportanceData = ai->lockedHeroes[goal.hero]->priority; - - float strengthRatioData = 10.0f; //we are much stronger than enemy - ui64 danger = evaluateDanger(goal.tile, goal.hero.h); - if(danger) - strengthRatioData = (fl::scalar)goal.hero.h->getTotalStrength() / danger; - - try - { - strengthRatio->setValue(strengthRatioData); - heroStrength->setValue((fl::scalar)goal.hero->getTotalStrength() / ai->primaryHero()->getTotalStrength()); - turnDistance->setValue(turns); - missionImportance->setValue(missionImportanceData); - } - catch(fl::Exception & fe) - { - logAi->error("HeroMovementGoalEngineBase::setSharedFuzzyVariables: %s", fe.getWhat()); - } -} - -float FuzzyHelper::evaluate(Goals::VisitTile & g) -{ - return visitTileEngine.evaluate(g); -} -float FuzzyHelper::evaluate(Goals::VisitObj & g) -{ - return getObjEngine.evaluate(g); -} -float FuzzyHelper::evaluate(Goals::VisitHero & g) -{ - auto obj = cb->getObj(ObjectInstanceID(g.objid)); //we assume for now that these goals are similar - if(!obj) - return -100; //hero died in the meantime - //TODO: consider direct copy (constructor?) - g.setpriority(Goals::VisitTile(obj->visitablePos()).sethero(g.hero).setisAbstract(g.isAbstract).accept(this)); - return g.priority; -} -float FuzzyHelper::evaluate(Goals::GatherArmy & g) -{ - //the more army we need, the more important goal - //the more army we lack, the less important goal - float army = g.hero->getArmyStrength(); - float ratio = g.value / std::max(g.value - army, 2000.0f); //2000 is about the value of hero recruited from tavern - return 5 * (ratio / (ratio + 2)); //so 50% army gives 2.5, asymptotic 5 -} - -float FuzzyHelper::evaluate(Goals::ClearWayTo & g) -{ - if(!g.hero.h) - throw cannotFulfillGoalException("ClearWayTo called without hero!"); - - int3 t = ai->getCachedSectorMap(g.hero)->firstTileToGet(g.hero, g.tile); - - if(t.valid()) - { - if(isSafeToVisit(g.hero, t)) - { - g.setpriority(Goals::VisitTile(g.tile).sethero(g.hero).setisAbstract(g.isAbstract).accept(this)); - } - else - { - g.setpriority (Goals::GatherArmy(evaluateDanger(t, g.hero.h)*SAFE_ATTACK_CONSTANT). - sethero(g.hero).setisAbstract(true).accept(this)); - } - return g.priority; - } - else - return -1; - -} - -float FuzzyHelper::evaluate(Goals::BuildThis & g) -{ - return g.priority; //TODO -} -float FuzzyHelper::evaluate(Goals::DigAtTile & g) -{ - return 0; -} -float FuzzyHelper::evaluate(Goals::CollectRes & g) -{ - return g.priority; //handled by ResourceManager -} -float FuzzyHelper::evaluate(Goals::Build & g) -{ - return 0; -} -float FuzzyHelper::evaluate(Goals::BuyArmy & g) -{ - return g.priority; -} -float FuzzyHelper::evaluate(Goals::Invalid & g) -{ - return -1e10; -} -float FuzzyHelper::evaluate(Goals::AbstractGoal & g) -{ - logAi->warn("Cannot evaluate goal %s", g.name()); - return g.priority; -} -void FuzzyHelper::setPriority(Goals::TSubgoal & g) //calls evaluate - Visitor pattern -{ - g->setpriority(g->accept(this)); //this enforces returned value is set -} - -GetObjEngine::GetObjEngine() -{ - try - { - objectValue = new fl::InputVariable("objectValue"); //value of that object type known by AI - - engine.addInputVariable(objectValue); - - //objectValue ranges are based on checking RMG priorities of some objects and trying to guess sane value ranges - objectValue->addTerm(new fl::Ramp("LOW", 3000, 0)); //I have feeling that concave shape might work well instead of ramp for objectValue FL terms - objectValue->addTerm(new fl::Triangle("MEDIUM", 2500, 6000)); - objectValue->addTerm(new fl::Ramp("HIGH", 5000, 20000)); - objectValue->setRange(0, 20000); //relic artifact value is border value by design, even better things are scaled down. - - addRule("if objectValue is HIGH then value is HIGH"); - addRule("if objectValue is MEDIUM then value is MEDIUM"); - addRule("if objectValue is LOW then value is LOW"); - } - catch(fl::Exception & fe) - { - logAi->error("FindWanderTarget: %s", fe.getWhat()); - } - configure(); -} - -float GetObjEngine::evaluate(Goals::AbstractGoal & goal) -{ - auto g = dynamic_cast(goal); - - if(!g.hero) - return 0; - - auto obj = cb->getObj(ObjectInstanceID(g.objid)); - - - boost::optional objValueKnownByAI = MapObjectsEvaluator::getInstance().getObjectValue(obj->ID, obj->subID); - int objValue = 0; - - if(objValueKnownByAI != boost::none) //consider adding value manipulation based on object instances on map - { - objValue = std::min(std::max(objValueKnownByAI.get(), 0), 20000); - } - else - { - MapObjectsEvaluator::getInstance().addObjectData(obj->ID, obj->subID, 0); - logGlobal->warn("AI met object type it doesn't know - ID: " + std::to_string(obj->ID) + ", subID: " + std::to_string(obj->subID) + " - adding to database with value " + std::to_string(objValue)); - } - - setSharedFuzzyVariables(goal); - - float output = -1.0f; - try - { - objectValue->setValue(objValue); - engine.process(); - output = value->getValue(); - } - catch(fl::Exception & fe) - { - logAi->error("evaluate getWanderTargetObjectValue: %s", fe.getWhat()); - } - assert(output >= 0.0f); - return output; -} - -VisitTileEngine::VisitTileEngine() //so far no VisitTile-specific variables that are not shared with HeroMovementGoalEngineBase -{ - configure(); -} - -float VisitTileEngine::evaluate(Goals::AbstractGoal & goal) -{ - auto g = dynamic_cast(goal); - //we assume that hero is already set and we want to choose most suitable one for the mission - if(!g.hero) - return 0; - - //assert(cb->isInTheMap(g.tile)); - - setSharedFuzzyVariables(goal); - - try - { - engine.process(); - g.priority = value->getValue(); - } - catch(fl::Exception & fe) - { - logAi->error("evaluate VisitTile: %s", fe.getWhat()); - } - assert(g.priority >= 0); - return g.priority; -} +/* +* FuzzyEngines.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 "FuzzyEngines.h" + +#include "../../lib/mapObjects/MapObjects.h" +#include "VCAI.h" +#include "MapObjectsEvaluator.h" + +#define MIN_AI_STRENGTH (0.5f) //lower when combat AI gets smarter +#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us + +extern boost::thread_specific_ptr ai; + +engineBase::engineBase() +{ + engine.addRuleBlock(&rules); +} + +void engineBase::configure() +{ + engine.configure("Minimum", "Maximum", "Minimum", "AlgebraicSum", "Centroid", "General"); + logAi->info(engine.toString()); +} + +void engineBase::addRule(const std::string & txt) +{ + rules.addRule(fl::Rule::parse(txt, &engine)); +} + +struct armyStructure +{ + float walkers, shooters, flyers; + ui32 maxSpeed; +}; + +armyStructure evaluateArmyStructure(const CArmedInstance * army) +{ + ui64 totalStrenght = army->getArmyStrength(); + double walkersStrenght = 0; + double flyersStrenght = 0; + double shootersStrenght = 0; + ui32 maxSpeed = 0; + + for(auto s : army->Slots()) + { + bool walker = true; + if(s.second->type->hasBonusOfType(Bonus::SHOOTER)) + { + shootersStrenght += s.second->getPower(); + walker = false; + } + if(s.second->type->hasBonusOfType(Bonus::FLYING)) + { + flyersStrenght += s.second->getPower(); + walker = false; + } + if(walker) + walkersStrenght += s.second->getPower(); + + vstd::amax(maxSpeed, s.second->type->valOfBonuses(Bonus::STACKS_SPEED)); + } + armyStructure as; + as.walkers = walkersStrenght / totalStrenght; + as.shooters = shootersStrenght / totalStrenght; + as.flyers = flyersStrenght / totalStrenght; + as.maxSpeed = maxSpeed; + assert(as.walkers || as.flyers || as.shooters); + return as; +} + +float HeroMovementGoalEngineBase::calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const +{ + float turns = 0.0f; + float distance = CPathfinderHelper::getMovementCost(h, tile); + if(distance) + { + if(distance < h->movement) //we can move there within one turn + turns = (fl::scalar)distance / h->movement; + else + turns = 1 + (fl::scalar)(distance - h->movement) / h->maxMovePoints(true); //bool on land? + } + return turns; +} + +TacticalAdvantageEngine::TacticalAdvantageEngine() +{ + try + { + ourShooters = new fl::InputVariable("OurShooters"); + ourWalkers = new fl::InputVariable("OurWalkers"); + ourFlyers = new fl::InputVariable("OurFlyers"); + enemyShooters = new fl::InputVariable("EnemyShooters"); + enemyWalkers = new fl::InputVariable("EnemyWalkers"); + enemyFlyers = new fl::InputVariable("EnemyFlyers"); + + //Tactical advantage calculation + std::vector helper = + { + ourShooters, ourWalkers, ourFlyers, enemyShooters, enemyWalkers, enemyFlyers + }; + + for(auto val : helper) + { + engine.addInputVariable(val); + val->addTerm(new fl::Ramp("FEW", 0.6, 0.0)); + val->addTerm(new fl::Ramp("MANY", 0.4, 1)); + val->setRange(0.0, 1.0); + } + + ourSpeed = new fl::InputVariable("OurSpeed"); + enemySpeed = new fl::InputVariable("EnemySpeed"); + + helper = { ourSpeed, enemySpeed }; + + for(auto val : helper) + { + engine.addInputVariable(val); + val->addTerm(new fl::Ramp("LOW", 6.5, 3)); + val->addTerm(new fl::Triangle("MEDIUM", 5.5, 10.5)); + val->addTerm(new fl::Ramp("HIGH", 8.5, 16)); + val->setRange(0, 25); + } + + castleWalls = new fl::InputVariable("CastleWalls"); + engine.addInputVariable(castleWalls); + { + fl::Rectangle * none = new fl::Rectangle("NONE", CGTownInstance::NONE, CGTownInstance::NONE + (CGTownInstance::FORT - CGTownInstance::NONE) * 0.5f); + castleWalls->addTerm(none); + + fl::Trapezoid * medium = new fl::Trapezoid("MEDIUM", (CGTownInstance::FORT - CGTownInstance::NONE) * 0.5f, CGTownInstance::FORT, + CGTownInstance::CITADEL, CGTownInstance::CITADEL + (CGTownInstance::CASTLE - CGTownInstance::CITADEL) * 0.5f); + castleWalls->addTerm(medium); + + fl::Ramp * high = new fl::Ramp("HIGH", CGTownInstance::CITADEL - 0.1, CGTownInstance::CASTLE); + castleWalls->addTerm(high); + + castleWalls->setRange(CGTownInstance::NONE, CGTownInstance::CASTLE); + } + + + bankPresent = new fl::InputVariable("Bank"); + engine.addInputVariable(bankPresent); + { + fl::Rectangle * termFalse = new fl::Rectangle("FALSE", 0.0, 0.5f); + bankPresent->addTerm(termFalse); + fl::Rectangle * termTrue = new fl::Rectangle("TRUE", 0.5f, 1); + bankPresent->addTerm(termTrue); + bankPresent->setRange(0, 1); + } + + threat = new fl::OutputVariable("Threat"); + engine.addOutputVariable(threat); + threat->addTerm(new fl::Ramp("LOW", 1, MIN_AI_STRENGTH)); + threat->addTerm(new fl::Triangle("MEDIUM", 0.8, 1.2)); + threat->addTerm(new fl::Ramp("HIGH", 1, 1.5)); + threat->setRange(MIN_AI_STRENGTH, 1.5); + + addRule("if OurShooters is MANY and EnemySpeed is LOW then Threat is LOW"); + addRule("if OurShooters is MANY and EnemyShooters is FEW then Threat is LOW"); + addRule("if OurSpeed is LOW and EnemyShooters is MANY then Threat is HIGH"); + addRule("if OurSpeed is HIGH and EnemyShooters is MANY then Threat is LOW"); + + addRule("if OurWalkers is FEW and EnemyShooters is MANY then Threat is somewhat LOW"); + addRule("if OurShooters is MANY and EnemySpeed is HIGH then Threat is somewhat HIGH"); + //just to cover all cases + addRule("if OurShooters is FEW and EnemySpeed is HIGH then Threat is MEDIUM"); + addRule("if EnemySpeed is MEDIUM then Threat is MEDIUM"); + addRule("if EnemySpeed is LOW and OurShooters is FEW then Threat is MEDIUM"); + + addRule("if Bank is TRUE and OurShooters is MANY then Threat is somewhat HIGH"); + addRule("if Bank is TRUE and EnemyShooters is MANY then Threat is LOW"); + + addRule("if CastleWalls is HIGH and OurWalkers is MANY then Threat is very HIGH"); + addRule("if CastleWalls is HIGH and OurFlyers is MANY and OurShooters is MANY then Threat is MEDIUM"); + addRule("if CastleWalls is MEDIUM and OurShooters is MANY and EnemyWalkers is MANY then Threat is LOW"); + + } + catch(fl::Exception & pe) + { + logAi->error("initTacticalAdvantage: %s", pe.getWhat()); + } + configure(); +} + +float TacticalAdvantageEngine::getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy) +{ + float output = 1; + try + { + armyStructure ourStructure = evaluateArmyStructure(we); + armyStructure enemyStructure = evaluateArmyStructure(enemy); + + ourWalkers->setValue(ourStructure.walkers); + ourShooters->setValue(ourStructure.shooters); + ourFlyers->setValue(ourStructure.flyers); + ourSpeed->setValue(ourStructure.maxSpeed); + + enemyWalkers->setValue(enemyStructure.walkers); + enemyShooters->setValue(enemyStructure.shooters); + enemyFlyers->setValue(enemyStructure.flyers); + enemySpeed->setValue(enemyStructure.maxSpeed); + + bool bank = dynamic_cast(enemy); + if(bank) + bankPresent->setValue(1); + else + bankPresent->setValue(0); + + const CGTownInstance * fort = dynamic_cast(enemy); + if(fort) + castleWalls->setValue(fort->fortLevel()); + else + castleWalls->setValue(0); + + engine.process(); + output = threat->getValue(); + } + catch(fl::Exception & fe) + { + logAi->error("getTacticalAdvantage: %s ", fe.getWhat()); + } + + if(output < 0 || (output != output)) + { + fl::InputVariable * tab[] = { bankPresent, castleWalls, ourWalkers, ourShooters, ourFlyers, ourSpeed, enemyWalkers, enemyShooters, enemyFlyers, enemySpeed }; + std::string names[] = { "bankPresent", "castleWalls", "ourWalkers", "ourShooters", "ourFlyers", "ourSpeed", "enemyWalkers", "enemyShooters", "enemyFlyers", "enemySpeed" }; + std::stringstream log("Warning! Fuzzy engine doesn't cover this set of parameters: "); + + for(int i = 0; i < boost::size(tab); i++) + log << names[i] << ": " << tab[i]->getValue() << " "; + logAi->error(log.str()); + assert(false); + } + + return output; +} + +//std::shared_ptr chooseSolution (std::vector> & vec) + +HeroMovementGoalEngineBase::HeroMovementGoalEngineBase() +{ + try + { + strengthRatio = new fl::InputVariable("strengthRatio"); //hero must be strong enough to defeat guards + heroStrength = new fl::InputVariable("heroStrength"); //we want to use weakest possible hero + turnDistance = new fl::InputVariable("turnDistance"); //we want to use hero who is near + missionImportance = new fl::InputVariable("lockedMissionImportance"); //we may want to preempt hero with low-priority mission + value = new fl::OutputVariable("Value"); + value->setMinimum(0); + value->setMaximum(5); + + std::vector helper = { strengthRatio, heroStrength, turnDistance, missionImportance }; + for(auto val : helper) + { + engine.addInputVariable(val); + } + engine.addOutputVariable(value); + + strengthRatio->addTerm(new fl::Ramp("LOW", SAFE_ATTACK_CONSTANT, 0)); + strengthRatio->addTerm(new fl::Ramp("HIGH", SAFE_ATTACK_CONSTANT, SAFE_ATTACK_CONSTANT * 3)); + strengthRatio->setRange(0, SAFE_ATTACK_CONSTANT * 3); + + //strength compared to our main hero + heroStrength->addTerm(new fl::Ramp("LOW", 0.2, 0)); + heroStrength->addTerm(new fl::Triangle("MEDIUM", 0.2, 0.8)); + heroStrength->addTerm(new fl::Ramp("HIGH", 0.5, 1)); + heroStrength->setRange(0.0, 1.0); + + turnDistance->addTerm(new fl::Ramp("SHORT", 0.5, 0)); + turnDistance->addTerm(new fl::Triangle("MEDIUM", 0.1, 0.8)); + turnDistance->addTerm(new fl::Ramp("LONG", 0.5, 3)); + turnDistance->setRange(0.0, 3.0); + + missionImportance->addTerm(new fl::Ramp("LOW", 2.5, 0)); + missionImportance->addTerm(new fl::Triangle("MEDIUM", 2, 3)); + missionImportance->addTerm(new fl::Ramp("HIGH", 2.5, 5)); + missionImportance->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 + value->addTerm(new fl::Ramp("LOW", 2.5, 0)); + value->addTerm(new fl::Triangle("MEDIUM", 2, 3)); //can't be center of mass :/ + value->addTerm(new fl::Ramp("HIGH", 2.5, 5)); + value->setRange(0.0, 5.0); + + //use unarmed scouts if possible + addRule("if strengthRatio is HIGH and heroStrength is LOW then Value is very HIGH"); + //we may want to use secondary hero(es) rather than main hero + addRule("if strengthRatio is HIGH and heroStrength is MEDIUM then Value is somewhat HIGH"); + addRule("if strengthRatio is HIGH and heroStrength is HIGH then Value is somewhat LOW"); + //don't assign targets to heroes who are too weak, but prefer targets of our main hero (in case we need to gather army) + addRule("if strengthRatio is LOW and heroStrength is LOW then Value is very LOW"); + //attempt to arm secondary heroes is not stupid + addRule("if strengthRatio is LOW and heroStrength is MEDIUM then Value is somewhat HIGH"); + addRule("if strengthRatio is LOW and heroStrength is HIGH then Value is LOW"); + + //do not cancel important goals + addRule("if lockedMissionImportance is HIGH then Value is very LOW"); + addRule("if lockedMissionImportance is MEDIUM then Value is somewhat LOW"); + addRule("if lockedMissionImportance is LOW then Value is HIGH"); + //pick nearby objects if it's easy, avoid long walks + addRule("if turnDistance is SHORT then Value is HIGH"); + addRule("if turnDistance is MEDIUM then Value is MEDIUM"); + addRule("if turnDistance is LONG then Value is LOW"); + } + catch(fl::Exception & fe) + { + logAi->error("HeroMovementGoalEngineBase: %s", fe.getWhat()); + } +} + +void HeroMovementGoalEngineBase::setSharedFuzzyVariables(Goals::AbstractGoal & goal) +{ + float turns = calculateTurnDistanceInputValue(goal.hero.h, goal.tile); + float missionImportanceData = 0; + if(vstd::contains(ai->lockedHeroes, goal.hero)) + missionImportanceData = ai->lockedHeroes[goal.hero]->priority; + + float strengthRatioData = 10.0f; //we are much stronger than enemy + ui64 danger = evaluateDanger(goal.tile, goal.hero.h); + if(danger) + strengthRatioData = (fl::scalar)goal.hero.h->getTotalStrength() / danger; + + try + { + strengthRatio->setValue(strengthRatioData); + heroStrength->setValue((fl::scalar)goal.hero->getTotalStrength() / ai->primaryHero()->getTotalStrength()); + turnDistance->setValue(turns); + missionImportance->setValue(missionImportanceData); + } + catch(fl::Exception & fe) + { + logAi->error("HeroMovementGoalEngineBase::setSharedFuzzyVariables: %s", fe.getWhat()); + } +} + +GetObjEngine::GetObjEngine() +{ + try + { + objectValue = new fl::InputVariable("objectValue"); //value of that object type known by AI + + engine.addInputVariable(objectValue); + + //objectValue ranges are based on checking RMG priorities of some objects and trying to guess sane value ranges + objectValue->addTerm(new fl::Ramp("LOW", 3000, 0)); //I have feeling that concave shape might work well instead of ramp for objectValue FL terms + objectValue->addTerm(new fl::Triangle("MEDIUM", 2500, 6000)); + objectValue->addTerm(new fl::Ramp("HIGH", 5000, 20000)); + objectValue->setRange(0, 20000); //relic artifact value is border value by design, even better things are scaled down. + + addRule("if objectValue is HIGH then value is HIGH"); + addRule("if objectValue is MEDIUM then value is MEDIUM"); + addRule("if objectValue is LOW then value is LOW"); + } + catch(fl::Exception & fe) + { + logAi->error("FindWanderTarget: %s", fe.getWhat()); + } + configure(); +} + +float GetObjEngine::evaluate(Goals::AbstractGoal & goal) +{ + auto g = dynamic_cast(goal); + + if(!g.hero) + return 0; + + auto obj = ai->myCb->getObj(ObjectInstanceID(g.objid)); + + + boost::optional objValueKnownByAI = MapObjectsEvaluator::getInstance().getObjectValue(obj->ID, obj->subID); + int objValue = 0; + + if(objValueKnownByAI != boost::none) //consider adding value manipulation based on object instances on map + { + objValue = std::min(std::max(objValueKnownByAI.get(), 0), 20000); + } + else + { + MapObjectsEvaluator::getInstance().addObjectData(obj->ID, obj->subID, 0); + logGlobal->warn("AI met object type it doesn't know - ID: " + std::to_string(obj->ID) + ", subID: " + std::to_string(obj->subID) + " - adding to database with value " + std::to_string(objValue)); + } + + setSharedFuzzyVariables(goal); + + float output = -1.0f; + try + { + objectValue->setValue(objValue); + engine.process(); + output = value->getValue(); + } + catch(fl::Exception & fe) + { + logAi->error("evaluate getWanderTargetObjectValue: %s", fe.getWhat()); + } + assert(output >= 0.0f); + return output; +} + +VisitTileEngine::VisitTileEngine() //so far no VisitTile-specific variables that are not shared with HeroMovementGoalEngineBase +{ + configure(); +} + +float VisitTileEngine::evaluate(Goals::AbstractGoal & goal) +{ + auto g = dynamic_cast(goal); + //we assume that hero is already set and we want to choose most suitable one for the mission + if(!g.hero) + return 0; + + //assert(cb->isInTheMap(g.tile)); + + setSharedFuzzyVariables(goal); + + try + { + engine.process(); + g.priority = value->getValue(); + } + catch(fl::Exception & fe) + { + logAi->error("evaluate VisitTile: %s", fe.getWhat()); + } + assert(g.priority >= 0); + return g.priority; +} \ No newline at end of file diff --git a/AI/VCAI/Fuzzy.h b/AI/VCAI/FuzzyEngines.h similarity index 57% rename from AI/VCAI/Fuzzy.h rename to AI/VCAI/FuzzyEngines.h index bfe016af6..f175654f7 100644 --- a/AI/VCAI/Fuzzy.h +++ b/AI/VCAI/FuzzyEngines.h @@ -1,108 +1,74 @@ -/* - * Fuzzy.h, 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 - * -*/ -#pragma once -#include "fl/Headers.h" -#include "Goals.h" - -class VCAI; -class CArmedInstance; -class CBank; -struct SectorMap; - -class engineBase //subclasses create fuzzylite variables with "new" that are not freed - this is desired as fl::Engine wants to destroy these... -{ -protected: - fl::Engine engine; - fl::RuleBlock rules; - virtual void configure(); - void addRule(const std::string & txt); -public: - engineBase(); -}; - -class TacticalAdvantageEngine : public engineBase -{ -public: - TacticalAdvantageEngine(); - float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us -private: - fl::InputVariable * ourWalkers, *ourShooters, *ourFlyers, *enemyWalkers, *enemyShooters, *enemyFlyers; - fl::InputVariable * ourSpeed, *enemySpeed; - fl::InputVariable * bankPresent; - fl::InputVariable * castleWalls; - fl::OutputVariable * threat; -}; - -class HeroMovementGoalEngineBase : public engineBase //in future - maybe derive from some (GoalEngineBase : public engineBase) class for handling non-movement goals with common utility for goal engines -{ -public: - HeroMovementGoalEngineBase(); - - virtual float evaluate(Goals::AbstractGoal & goal) = 0; - -protected: - void setSharedFuzzyVariables(Goals::AbstractGoal & goal); - - fl::InputVariable * strengthRatio; - fl::InputVariable * heroStrength; - fl::InputVariable * turnDistance; - fl::InputVariable * missionImportance; - fl::OutputVariable * value; - -private: - float calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const; -}; - -class VisitTileEngine : public HeroMovementGoalEngineBase -{ -public: - VisitTileEngine(); - float evaluate(Goals::AbstractGoal & goal) override; -}; - -class GetObjEngine : public HeroMovementGoalEngineBase -{ -public: - GetObjEngine(); - float evaluate(Goals::AbstractGoal & goal) override; -protected: - fl::InputVariable * objectValue; -}; - -class FuzzyHelper -{ - friend class VCAI; - -public: - TacticalAdvantageEngine tacticalAdvantageEngine; - VisitTileEngine visitTileEngine; - GetObjEngine getObjEngine; - - float evaluate(Goals::Explore & g); - float evaluate(Goals::RecruitHero & g); - float evaluate(Goals::VisitTile & g); - float evaluate(Goals::VisitObj & g); - float evaluate(Goals::VisitHero & g); - float evaluate(Goals::BuildThis & g); - float evaluate(Goals::DigAtTile & g); - float evaluate(Goals::CollectRes & g); - float evaluate(Goals::Build & g); - float evaluate(Goals::BuyArmy & g); - float evaluate(Goals::GatherArmy & g); - float evaluate(Goals::ClearWayTo & g); - float evaluate(Goals::Invalid & g); - float evaluate(Goals::AbstractGoal & g); - void setPriority(Goals::TSubgoal & g); - - ui64 estimateBankDanger(const CBank * bank); //TODO: move to another class? - - Goals::TSubgoal chooseSolution(Goals::TGoalVec vec); - //std::shared_ptr chooseSolution (std::vector> & vec); -}; +/* +* FuzzyEngines.h, 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 +* +*/ +#pragma once +#include "fl/Headers.h" +#include "Goals.h" + +class CArmedInstance; + +class engineBase //subclasses create fuzzylite variables with "new" that are not freed - this is desired as fl::Engine wants to destroy these... +{ +protected: + fl::Engine engine; + fl::RuleBlock rules; + virtual void configure(); + void addRule(const std::string & txt); +public: + engineBase(); +}; + +class TacticalAdvantageEngine : public engineBase +{ +public: + TacticalAdvantageEngine(); + float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us +private: + fl::InputVariable * ourWalkers, *ourShooters, *ourFlyers, *enemyWalkers, *enemyShooters, *enemyFlyers; + fl::InputVariable * ourSpeed, *enemySpeed; + fl::InputVariable * bankPresent; + fl::InputVariable * castleWalls; + fl::OutputVariable * threat; +}; + +class HeroMovementGoalEngineBase : public engineBase //in future - maybe derive from some (GoalEngineBase : public engineBase) class for handling non-movement goals with common utility for goal engines +{ +public: + HeroMovementGoalEngineBase(); + + virtual float evaluate(Goals::AbstractGoal & goal) = 0; + +protected: + void setSharedFuzzyVariables(Goals::AbstractGoal & goal); + + fl::InputVariable * strengthRatio; + fl::InputVariable * heroStrength; + fl::InputVariable * turnDistance; + fl::InputVariable * missionImportance; + fl::OutputVariable * value; + +private: + float calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const; +}; + +class VisitTileEngine : public HeroMovementGoalEngineBase +{ +public: + VisitTileEngine(); + float evaluate(Goals::AbstractGoal & goal) override; +}; + +class GetObjEngine : public HeroMovementGoalEngineBase +{ +public: + GetObjEngine(); + float evaluate(Goals::AbstractGoal & goal) override; +protected: + fl::InputVariable * objectValue; +}; \ No newline at end of file diff --git a/AI/VCAI/FuzzyHelper.cpp b/AI/VCAI/FuzzyHelper.cpp new file mode 100644 index 000000000..094711fe1 --- /dev/null +++ b/AI/VCAI/FuzzyHelper.cpp @@ -0,0 +1,156 @@ +/* + * FuzzyHelper.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 "FuzzyHelper.h" + +#include "../../lib/mapObjects/CommonConstructors.h" +#include "VCAI.h" + +FuzzyHelper * fh; + +extern boost::thread_specific_ptr ai; + +Goals::TSubgoal FuzzyHelper::chooseSolution(Goals::TGoalVec vec) +{ + if(vec.empty()) //no possibilities found + return sptr(Goals::Invalid()); + + ai->cachedSectorMaps.clear(); + + //a trick to switch between heroes less often - calculatePaths is costly + auto sortByHeroes = [](const Goals::TSubgoal & lhs, const Goals::TSubgoal & rhs) -> bool + { + return lhs->hero.h < rhs->hero.h; + }; + boost::sort(vec, sortByHeroes); + + for(auto g : vec) + { + setPriority(g); + } + + auto compareGoals = [](const Goals::TSubgoal & lhs, const Goals::TSubgoal & rhs) -> bool + { + return lhs->priority < rhs->priority; + }; + return *boost::max_element(vec, compareGoals); +} + +ui64 FuzzyHelper::estimateBankDanger(const CBank * bank) +{ + //this one is not fuzzy anymore, just calculate weighted average + + auto objectInfo = VLC->objtypeh->getHandlerFor(bank->ID, bank->subID)->getObjectInfo(bank->appearance); + + CBankInfo * bankInfo = dynamic_cast(objectInfo.get()); + + ui64 totalStrength = 0; + ui8 totalChance = 0; + for(auto config : bankInfo->getPossibleGuards()) + { + totalStrength += config.second.totalStrength * config.first; + totalChance += config.first; + } + return totalStrength / std::max(totalChance, 1); //avoid division by zero + +} + +float FuzzyHelper::evaluate(Goals::VisitTile & g) +{ + return visitTileEngine.evaluate(g); +} +float FuzzyHelper::evaluate(Goals::VisitObj & g) +{ + return getObjEngine.evaluate(g); +} +float FuzzyHelper::evaluate(Goals::VisitHero & g) +{ + auto obj = ai->myCb->getObj(ObjectInstanceID(g.objid)); //we assume for now that these goals are similar + if(!obj) + return -100; //hero died in the meantime + //TODO: consider direct copy (constructor?) + g.setpriority(Goals::VisitTile(obj->visitablePos()).sethero(g.hero).setisAbstract(g.isAbstract).accept(this)); + return g.priority; +} +float FuzzyHelper::evaluate(Goals::GatherArmy & g) +{ + //the more army we need, the more important goal + //the more army we lack, the less important goal + float army = g.hero->getArmyStrength(); + float ratio = g.value / std::max(g.value - army, 2000.0f); //2000 is about the value of hero recruited from tavern + return 5 * (ratio / (ratio + 2)); //so 50% army gives 2.5, asymptotic 5 +} + +float FuzzyHelper::evaluate(Goals::ClearWayTo & g) +{ + if(!g.hero.h) + throw cannotFulfillGoalException("ClearWayTo called without hero!"); + + int3 t = ai->getCachedSectorMap(g.hero)->firstTileToGet(g.hero, g.tile); + + if(t.valid()) + { + if(isSafeToVisit(g.hero, t)) + { + g.setpriority(Goals::VisitTile(g.tile).sethero(g.hero).setisAbstract(g.isAbstract).accept(this)); + } + else + { + g.setpriority (Goals::GatherArmy(evaluateDanger(t, g.hero.h)*SAFE_ATTACK_CONSTANT). + sethero(g.hero).setisAbstract(true).accept(this)); + } + return g.priority; + } + else + return -1; + +} + +float FuzzyHelper::evaluate(Goals::BuildThis & g) +{ + return g.priority; //TODO +} +float FuzzyHelper::evaluate(Goals::DigAtTile & g) +{ + return 0; +} +float FuzzyHelper::evaluate(Goals::CollectRes & g) +{ + return g.priority; //handled by ResourceManager +} +float FuzzyHelper::evaluate(Goals::Build & g) +{ + return 0; +} +float FuzzyHelper::evaluate(Goals::BuyArmy & g) +{ + return g.priority; +} +float FuzzyHelper::evaluate(Goals::Explore & g) +{ + return 1; +} +float FuzzyHelper::evaluate(Goals::RecruitHero & g) +{ + return 1; +} +float FuzzyHelper::evaluate(Goals::Invalid & g) +{ + return -1e10; +} +float FuzzyHelper::evaluate(Goals::AbstractGoal & g) +{ + logAi->warn("Cannot evaluate goal %s", g.name()); + return g.priority; +} +void FuzzyHelper::setPriority(Goals::TSubgoal & g) //calls evaluate - Visitor pattern +{ + g->setpriority(g->accept(this)); //this enforces returned value is set +} diff --git a/AI/VCAI/FuzzyHelper.h b/AI/VCAI/FuzzyHelper.h new file mode 100644 index 000000000..619dbd11a --- /dev/null +++ b/AI/VCAI/FuzzyHelper.h @@ -0,0 +1,42 @@ +/* + * FuzzyHelper.h, 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 + * +*/ +#pragma once +#include "FuzzyEngines.h" + +class CBank; + +class FuzzyHelper +{ +public: + TacticalAdvantageEngine tacticalAdvantageEngine; + VisitTileEngine visitTileEngine; + GetObjEngine getObjEngine; + + float evaluate(Goals::Explore & g); + float evaluate(Goals::RecruitHero & g); + float evaluate(Goals::VisitTile & g); + float evaluate(Goals::VisitObj & g); + float evaluate(Goals::VisitHero & g); + float evaluate(Goals::BuildThis & g); + float evaluate(Goals::DigAtTile & g); + float evaluate(Goals::CollectRes & g); + float evaluate(Goals::Build & g); + float evaluate(Goals::BuyArmy & g); + float evaluate(Goals::GatherArmy & g); + float evaluate(Goals::ClearWayTo & g); + float evaluate(Goals::Invalid & g); + float evaluate(Goals::AbstractGoal & g); + void setPriority(Goals::TSubgoal & g); + + ui64 estimateBankDanger(const CBank * bank); //TODO: move to another class? + + Goals::TSubgoal chooseSolution(Goals::TGoalVec vec); + //std::shared_ptr chooseSolution (std::vector> & vec); +}; diff --git a/AI/VCAI/Goals.cpp b/AI/VCAI/Goals.cpp index 1b507ec6f..720c5633a 100644 --- a/AI/VCAI/Goals.cpp +++ b/AI/VCAI/Goals.cpp @@ -10,7 +10,7 @@ #include "StdInc.h" #include "Goals.h" #include "VCAI.h" -#include "Fuzzy.h" +#include "FuzzyHelper.h" #include "ResourceManager.h" #include "BuildingManager.h" #include "../../lib/mapping/CMap.h" //for victory conditions diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index c8845232d..fbb1f6733 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -9,7 +9,7 @@ */ #include "StdInc.h" #include "VCAI.h" -#include "Fuzzy.h" +#include "FuzzyHelper.h" #include "ResourceManager.h" #include "BuildingManager.h" From a7c2d03c87a0fdef517b9a563dc33e2326d14f5e Mon Sep 17 00:00:00 2001 From: Dydzio Date: Fri, 10 Aug 2018 20:36:42 +0200 Subject: [PATCH 26/33] Fixes --- AI/VCAI/FuzzyEngines.cpp | 21 +++++++++------------ AI/VCAI/FuzzyEngines.h | 10 ++++------ AI/VCAI/FuzzyHelper.cpp | 2 +- AI/VCAI/FuzzyHelper.h | 2 +- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/AI/VCAI/FuzzyEngines.cpp b/AI/VCAI/FuzzyEngines.cpp index 2adb5ec82..421a6900f 100644 --- a/AI/VCAI/FuzzyEngines.cpp +++ b/AI/VCAI/FuzzyEngines.cpp @@ -342,7 +342,7 @@ void HeroMovementGoalEngineBase::setSharedFuzzyVariables(Goals::AbstractGoal & g } } -GetObjEngine::GetObjEngine() +VisitObjEngine::VisitObjEngine() { try { @@ -367,14 +367,12 @@ GetObjEngine::GetObjEngine() configure(); } -float GetObjEngine::evaluate(Goals::AbstractGoal & goal) +float VisitObjEngine::evaluate(Goals::VisitObj & goal) { - auto g = dynamic_cast(goal); - - if(!g.hero) + if(!goal.hero) return 0; - auto obj = ai->myCb->getObj(ObjectInstanceID(g.objid)); + auto obj = ai->myCb->getObj(ObjectInstanceID(goal.objid)); boost::optional objValueKnownByAI = MapObjectsEvaluator::getInstance().getObjectValue(obj->ID, obj->subID); @@ -412,11 +410,10 @@ VisitTileEngine::VisitTileEngine() //so far no VisitTile-specific variables that configure(); } -float VisitTileEngine::evaluate(Goals::AbstractGoal & goal) +float VisitTileEngine::evaluate(Goals::VisitTile & goal) { - auto g = dynamic_cast(goal); //we assume that hero is already set and we want to choose most suitable one for the mission - if(!g.hero) + if(!goal.hero) return 0; //assert(cb->isInTheMap(g.tile)); @@ -426,12 +423,12 @@ float VisitTileEngine::evaluate(Goals::AbstractGoal & goal) try { engine.process(); - g.priority = value->getValue(); + goal.priority = value->getValue(); } catch(fl::Exception & fe) { logAi->error("evaluate VisitTile: %s", fe.getWhat()); } - assert(g.priority >= 0); - return g.priority; + assert(goal.priority >= 0); + return goal.priority; } \ No newline at end of file diff --git a/AI/VCAI/FuzzyEngines.h b/AI/VCAI/FuzzyEngines.h index f175654f7..e0b028b9b 100644 --- a/AI/VCAI/FuzzyEngines.h +++ b/AI/VCAI/FuzzyEngines.h @@ -42,8 +42,6 @@ class HeroMovementGoalEngineBase : public engineBase //in future - maybe derive public: HeroMovementGoalEngineBase(); - virtual float evaluate(Goals::AbstractGoal & goal) = 0; - protected: void setSharedFuzzyVariables(Goals::AbstractGoal & goal); @@ -61,14 +59,14 @@ class VisitTileEngine : public HeroMovementGoalEngineBase { public: VisitTileEngine(); - float evaluate(Goals::AbstractGoal & goal) override; + float evaluate(Goals::VisitTile & goal); }; -class GetObjEngine : public HeroMovementGoalEngineBase +class VisitObjEngine : public HeroMovementGoalEngineBase { public: - GetObjEngine(); - float evaluate(Goals::AbstractGoal & goal) override; + VisitObjEngine(); + float evaluate(Goals::VisitObj & goal); protected: fl::InputVariable * objectValue; }; \ No newline at end of file diff --git a/AI/VCAI/FuzzyHelper.cpp b/AI/VCAI/FuzzyHelper.cpp index 094711fe1..304cb45c8 100644 --- a/AI/VCAI/FuzzyHelper.cpp +++ b/AI/VCAI/FuzzyHelper.cpp @@ -68,7 +68,7 @@ float FuzzyHelper::evaluate(Goals::VisitTile & g) } float FuzzyHelper::evaluate(Goals::VisitObj & g) { - return getObjEngine.evaluate(g); + return visitObjEngine.evaluate(g); } float FuzzyHelper::evaluate(Goals::VisitHero & g) { diff --git a/AI/VCAI/FuzzyHelper.h b/AI/VCAI/FuzzyHelper.h index 619dbd11a..52715eb02 100644 --- a/AI/VCAI/FuzzyHelper.h +++ b/AI/VCAI/FuzzyHelper.h @@ -17,7 +17,7 @@ class FuzzyHelper public: TacticalAdvantageEngine tacticalAdvantageEngine; VisitTileEngine visitTileEngine; - GetObjEngine getObjEngine; + VisitObjEngine visitObjEngine; float evaluate(Goals::Explore & g); float evaluate(Goals::RecruitHero & g); From e197d22e6866360acd5210111f260471623cd0e3 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Thu, 16 Aug 2018 21:17:45 +0200 Subject: [PATCH 27/33] Improve dwelling value evaluation --- AI/VCAI/FuzzyEngines.cpp | 2 +- AI/VCAI/MapObjectsEvaluator.cpp | 40 +++++++++++++++++---------------- AI/VCAI/MapObjectsEvaluator.h | 1 + 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/AI/VCAI/FuzzyEngines.cpp b/AI/VCAI/FuzzyEngines.cpp index 421a6900f..2aae0cef8 100644 --- a/AI/VCAI/FuzzyEngines.cpp +++ b/AI/VCAI/FuzzyEngines.cpp @@ -375,7 +375,7 @@ float VisitObjEngine::evaluate(Goals::VisitObj & goal) auto obj = ai->myCb->getObj(ObjectInstanceID(goal.objid)); - boost::optional objValueKnownByAI = MapObjectsEvaluator::getInstance().getObjectValue(obj->ID, obj->subID); + boost::optional objValueKnownByAI = MapObjectsEvaluator::getInstance().getObjectValue(obj); int objValue = 0; if(objValueKnownByAI != boost::none) //consider adding value manipulation based on object instances on map diff --git a/AI/VCAI/MapObjectsEvaluator.cpp b/AI/VCAI/MapObjectsEvaluator.cpp index b6525e72e..7059e16d9 100644 --- a/AI/VCAI/MapObjectsEvaluator.cpp +++ b/AI/VCAI/MapObjectsEvaluator.cpp @@ -34,25 +34,7 @@ MapObjectsEvaluator::MapObjectsEvaluator() } else //some default handling when aiValue not found { - if(primaryID == Obj::CREATURE_GENERATOR1 || primaryID == Obj::CREATURE_GENERATOR4) - { - int aiValue = 0; - CGDwelling dwellingMock; - CRandomGenerator rngMock; - handler->configureObject(&dwellingMock, rngMock); - - for(auto & creLevel : dwellingMock.creatures) - { - for(auto & creatureID : creLevel.second) - { - auto creature = VLC->creh->creatures[creatureID]; - aiValue += (creature->AIValue * creature->growth); - } - } - objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = aiValue; - } - else - objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = 0; + objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = 0; } } } @@ -70,6 +52,26 @@ boost::optional MapObjectsEvaluator::getObjectValue(int primaryID, int seco return boost::optional(); } +boost::optional MapObjectsEvaluator::getObjectValue(const CGObjectInstance * obj) const +{ + if(obj->ID == Obj::CREATURE_GENERATOR1 || obj->ID == Obj::CREATURE_GENERATOR4) + { + auto dwelling = dynamic_cast(obj); + int aiValue = 0; + for(auto & creLevel : dwelling->creatures) + { + for(auto & creatureID : creLevel.second) + { + auto creature = VLC->creh->creatures[creatureID]; + aiValue += (creature->AIValue * creature->growth); + } + } + return aiValue; + } + else + return getObjectValue(obj->ID, obj->subID); +} + void MapObjectsEvaluator::addObjectData(int primaryID, int secondaryID, int value) //by current design it updates value if already in AI database { CompoundMapObjectID internalIdentifier = CompoundMapObjectID(primaryID, secondaryID); diff --git a/AI/VCAI/MapObjectsEvaluator.h b/AI/VCAI/MapObjectsEvaluator.h index bdee725b6..35a76d304 100644 --- a/AI/VCAI/MapObjectsEvaluator.h +++ b/AI/VCAI/MapObjectsEvaluator.h @@ -19,6 +19,7 @@ public: MapObjectsEvaluator(); static MapObjectsEvaluator & getInstance(); boost::optional getObjectValue(int primaryID, int secondaryID) const; + boost::optional getObjectValue(const CGObjectInstance * obj) const; void addObjectData(int primaryID, int secondaryID, int value); void removeObjectData(int primaryID, int secondaryID); }; From b1651d716f6f77ca8fd0abfa92addbda12179c8e Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sun, 19 Aug 2018 17:11:06 +0200 Subject: [PATCH 28/33] Stupid typo fix --- AI/VCAI/FuzzyEngines.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AI/VCAI/FuzzyEngines.cpp b/AI/VCAI/FuzzyEngines.cpp index 2aae0cef8..9ae6e8faa 100644 --- a/AI/VCAI/FuzzyEngines.cpp +++ b/AI/VCAI/FuzzyEngines.cpp @@ -356,9 +356,9 @@ VisitObjEngine::VisitObjEngine() objectValue->addTerm(new fl::Ramp("HIGH", 5000, 20000)); objectValue->setRange(0, 20000); //relic artifact value is border value by design, even better things are scaled down. - addRule("if objectValue is HIGH then value is HIGH"); - addRule("if objectValue is MEDIUM then value is MEDIUM"); - addRule("if objectValue is LOW then value is LOW"); + addRule("if objectValue is HIGH then Value is HIGH"); + addRule("if objectValue is MEDIUM then Value is MEDIUM"); + addRule("if objectValue is LOW then Value is LOW"); } catch(fl::Exception & fe) { From d41741251f69e6d172f6cae3ad4986ef83e88333 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sun, 19 Aug 2018 20:41:29 +0200 Subject: [PATCH 29/33] Fix VisitObj bugs / logic flaws --- AI/VCAI/Goals.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/AI/VCAI/Goals.cpp b/AI/VCAI/Goals.cpp index 720c5633a..ccd3ea11b 100644 --- a/AI/VCAI/Goals.cpp +++ b/AI/VCAI/Goals.cpp @@ -536,14 +536,14 @@ TGoalVec VisitObj::getAllPossibleSubgoals() } else { - for(auto h : cb->getHeroesInfo()) + for(auto potentialVisitor : cb->getHeroesInfo()) { - if(ai->isAccessibleForHero(pos, h)) + if(ai->isAccessibleForHero(pos, potentialVisitor)) { - if(isSafeToVisit(hero, pos)) - goalList.push_back(sptr(Goals::VisitObj(obj->id.getNum()).sethero(h))); + if(isSafeToVisit(potentialVisitor, pos)) + goalList.push_back(sptr(Goals::VisitObj(obj->id.getNum()).sethero(potentialVisitor))); else - goalList.push_back(sptr(Goals::GatherArmy(evaluateDanger(pos, h) * SAFE_ATTACK_CONSTANT).sethero(hero).setisAbstract(true))); + goalList.push_back(sptr(Goals::GatherArmy(evaluateDanger(pos, potentialVisitor) * SAFE_ATTACK_CONSTANT).sethero(potentialVisitor).setisAbstract(true))); } } if(!goalList.empty()) @@ -552,7 +552,7 @@ TGoalVec VisitObj::getAllPossibleSubgoals() } } - goalList.push_back(sptr(Goals::ClearWayTo(pos).sethero(hero))); + goalList.push_back(sptr(Goals::ClearWayTo(pos))); return goalList; } From 158b3c0b80b94f1b5c0a4ea7baf5e0a77795d981 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Mon, 20 Aug 2018 14:43:51 +0200 Subject: [PATCH 30/33] Initial implementation of invalid VisitObj removal --- AI/VCAI/VCAI.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index fbb1f6733..97a3e16ed 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -416,6 +416,38 @@ void VCAI::objectRemoved(const CGObjectInstance * obj) for(auto h : cb->getHeroesInfo()) unreserveObject(h, obj); + + vstd::erase_if(lockedHeroes, [&](const std::pair & x) -> bool + { + if((x.second->goalType == Goals::VISIT_OBJ) && (x.second->objid == obj->id.getNum())) + return true; + else + return false; + }); + + vstd::erase_if(ultimateGoalsFromBasic, [&](const std::pair & x) -> bool + { + if((x.first->goalType == Goals::VISIT_OBJ) && (x.first->objid == obj->id.getNum())) + return true; + else + return false; + }); + + auto goalErasePredicate = [&](const Goals::TSubgoal & x) ->bool + { + if((x->goalType == Goals::VISIT_OBJ) && (x->objid == obj->id.getNum())) + return true; + else + return false; + }; + + basicGoals.erase(std::remove_if(basicGoals.begin(), basicGoals.end(), goalErasePredicate)); + goalsToAdd.erase(std::remove_if(goalsToAdd.begin(), goalsToAdd.end(), goalErasePredicate)); + goalsToRemove.erase(std::remove_if(goalsToRemove.begin(), goalsToRemove.end(), goalErasePredicate)); + + for(auto goal : ultimateGoalsFromBasic) + goal.second.erase(std::remove_if(goal.second.begin(), goal.second.end(), goalErasePredicate)); + //TODO: Find better way to handle hero boat removal if(auto hero = dynamic_cast(obj)) { From 243ce7370a41dd7fdda1538084a1490b60c45396 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Mon, 20 Aug 2018 15:17:40 +0200 Subject: [PATCH 31/33] This fixes crash... idk why --- AI/VCAI/VCAI.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 97a3e16ed..4da39e808 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -441,12 +441,12 @@ void VCAI::objectRemoved(const CGObjectInstance * obj) return false; }; - basicGoals.erase(std::remove_if(basicGoals.begin(), basicGoals.end(), goalErasePredicate)); - goalsToAdd.erase(std::remove_if(goalsToAdd.begin(), goalsToAdd.end(), goalErasePredicate)); - goalsToRemove.erase(std::remove_if(goalsToRemove.begin(), goalsToRemove.end(), goalErasePredicate)); + vstd::erase_if(basicGoals, goalErasePredicate); + vstd::erase_if(goalsToAdd, goalErasePredicate); + vstd::erase_if(goalsToRemove, goalErasePredicate); for(auto goal : ultimateGoalsFromBasic) - goal.second.erase(std::remove_if(goal.second.begin(), goal.second.end(), goalErasePredicate)); + vstd::erase_if(goal.second, goalErasePredicate); //TODO: Find better way to handle hero boat removal if(auto hero = dynamic_cast(obj)) From 35d2af2e7dd00b6b67c82d5d63500ec5ee245115 Mon Sep 17 00:00:00 2001 From: DJWarmonger Date: Mon, 20 Aug 2018 21:31:58 +0200 Subject: [PATCH 32/33] Simplified CollectRes - rely on VisitObj now --- AI/VCAI/Goals.cpp | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/AI/VCAI/Goals.cpp b/AI/VCAI/Goals.cpp index ccd3ea11b..2e0476c85 100644 --- a/AI/VCAI/Goals.cpp +++ b/AI/VCAI/Goals.cpp @@ -1137,22 +1137,10 @@ TGoalVec Goals::CollectRes::getAllPossibleSubgoals() } 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 (ai->isAccessibleForHero(obj->visitablePos(), h)) { - if (ai->isTileNotReserved(h, t)) //no other hero wants to conquer that tile - { - if (isSafeToVisit(h, dest)) - { - if (dest != t) //there is something blocking our way - ret.push_back(sptr(Goals::ClearWayTo(dest, h).setisAbstract(true))); - else - ret.push_back(sptr(Goals::VisitObj(obj->id.getNum()).sethero(h).setisAbstract(true))); - } - else //we need to get army in order to pick that object - ret.push_back(sptr(Goals::GatherArmy(evaluateDanger(dest, h) * SAFE_ATTACK_CONSTANT).sethero(h).setisAbstract(true))); - } + //further decomposition and evaluation will be handled by VisitObj + ret.push_back(sptr(Goals::VisitObj(obj->id.getNum()).sethero(h).setisAbstract(true))); } } } From a44c792b7c61c9f7b7e46fb6c77dad2b5ce33f04 Mon Sep 17 00:00:00 2001 From: DJWarmonger Date: Tue, 21 Aug 2018 08:40:47 +0200 Subject: [PATCH 33/33] Important fix for ClearWayTo in new branch. --- AI/VCAI/FuzzyHelper.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AI/VCAI/FuzzyHelper.cpp b/AI/VCAI/FuzzyHelper.cpp index 304cb45c8..e657200d7 100644 --- a/AI/VCAI/FuzzyHelper.cpp +++ b/AI/VCAI/FuzzyHelper.cpp @@ -90,8 +90,8 @@ float FuzzyHelper::evaluate(Goals::GatherArmy & g) float FuzzyHelper::evaluate(Goals::ClearWayTo & g) { - if(!g.hero.h) - throw cannotFulfillGoalException("ClearWayTo called without hero!"); + if (!g.hero.h) + return 0; //lowest priority int3 t = ai->getCachedSectorMap(g.hero)->firstTileToGet(g.hero, g.tile);