1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Nullkiller - rewrite decomposition, decomposition cache, morale management

This commit is contained in:
Andrii Danylchenko 2021-05-16 14:56:42 +03:00 committed by Andrii Danylchenko
parent 35a1167f3d
commit e385c83a88
18 changed files with 470 additions and 154 deletions

View File

@ -31,6 +31,11 @@ public:
}
};
uint64_t ArmyManager::howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const
{
return howManyReinforcementsCanGet(hero, hero, source);
}
std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const
{
const CCreatureSet * armies[] = { target, source };
@ -75,15 +80,84 @@ std::vector<SlotInfo>::iterator ArmyManager::getWeakestCreature(std::vector<Slot
return weakest;
}
std::vector<SlotInfo> ArmyManager::getBestArmy(const CCreatureSet * target, const CCreatureSet * source) const
std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const
{
auto resultingArmy = getSortedSlots(target, source);
auto sortedSlots = getSortedSlots(target, source);
std::map<TFaction, uint64_t> alignmentMap;
if(resultingArmy.size() > GameConstants::ARMY_SIZE)
for(auto & slot : sortedSlots)
{
resultingArmy.resize(GameConstants::ARMY_SIZE);
alignmentMap[slot.creature->faction] += slot.power;
}
else if(source->needsLastStack())
std::set<TFaction> allowedFactions;
std::vector<SlotInfo> resultingArmy;
uint64_t armyValue = 0;
CArmedInstance newArmyInstance;
auto bonusModifiers = armyCarrier->getBonuses(Selector::type(Bonus::MORALE));
for(auto bonus : *bonusModifiers)
{
// army bonuses will change and object bonuses are temporary
if(bonus->source != Bonus::ARMY || bonus->source != Bonus::OBJECT)
{
newArmyInstance.addNewBonus(bonus);
}
}
while(allowedFactions.size() < alignmentMap.size())
{
auto strongestAlignment = vstd::maxElementByFun(alignmentMap, [&](std::pair<TFaction, uint64_t> pair) -> uint64_t
{
return vstd::contains(allowedFactions, pair.first) ? 0 : pair.second;
});
allowedFactions.insert(strongestAlignment->first);
std::vector<SlotInfo> newArmy;
uint64_t newValue = 0;
newArmyInstance.clear();
for(auto & slot : sortedSlots)
{
if(vstd::contains(allowedFactions, slot.creature->faction))
{
auto slotID = newArmyInstance.getSlotFor(slot.creature);
if(slotID.validSlot())
{
newArmyInstance.setCreature(slotID, slot.creature->idNumber, slot.count);
newArmy.push_back(slot);
}
}
}
for(auto & slot : newArmyInstance.Slots())
{
auto morale = slot.second->MoraleVal();
auto multiplier = 1.0f;
if(morale < 0)
{
multiplier += morale * 0.083f;
}
newValue += multiplier * slot.second->getPower();
}
if(armyValue >= newValue)
{
break;
}
resultingArmy = newArmy;
armyValue = newValue;
}
if(resultingArmy.size() <= GameConstants::ARMY_SIZE
&& allowedFactions.size() == alignmentMap.size()
&& source->needsLastStack())
{
auto weakest = getWeakestCreature(resultingArmy);
@ -101,19 +175,6 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const CCreatureSet * target, cons
return resultingArmy;
}
bool ArmyManager::canGetArmy(const CArmedInstance * target, const CArmedInstance * source) const
{
//TODO: merge with pickBestCreatures
//if (ai->primaryHero().h == source)
if(target->tempOwner != source->tempOwner)
{
logAi->error("Why are we even considering exchange between heroes from different players?");
return false;
}
return 0 < howManyReinforcementsCanGet(target, source);
}
ui64 ArmyManager::howManyReinforcementsCanBuy(const CCreatureSet * h, const CGDwelling * t) const
{
ui64 aivalue = 0;
@ -162,9 +223,9 @@ std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(const CCreatureSet * her
return creaturesInDwellings;
}
ui64 ArmyManager::howManyReinforcementsCanGet(const CCreatureSet * target, const CCreatureSet * source) const
ui64 ArmyManager::howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const
{
auto bestArmy = getBestArmy(target, source);
auto bestArmy = getBestArmy(armyCarrier, target, source);
uint64_t newArmy = 0;
uint64_t oldArmy = target->getArmyStrength();

View File

@ -42,10 +42,10 @@ class DLL_EXPORT IArmyManager //: public: IAbstractManager
{
public:
virtual void update() = 0;
virtual bool canGetArmy(const CArmedInstance * target, const CArmedInstance * source) const = 0;
virtual ui64 howManyReinforcementsCanBuy(const CCreatureSet * target, const CGDwelling * source) const = 0;
virtual ui64 howManyReinforcementsCanGet(const CCreatureSet * target, const CCreatureSet * source) const = 0;
virtual std::vector<SlotInfo> getBestArmy(const CCreatureSet * target, const CCreatureSet * source) const = 0;
virtual ui64 howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const = 0;
virtual ui64 howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const = 0;
virtual std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const = 0;
virtual std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const = 0;
virtual std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const = 0;
virtual std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const = 0;
@ -69,10 +69,10 @@ private:
public:
ArmyManager(CPlayerSpecificInfoCallback * CB, const Nullkiller * ai): cb(CB), ai(ai) {}
void update() override;
bool canGetArmy(const CArmedInstance * target, const CArmedInstance * source) const override;
ui64 howManyReinforcementsCanBuy(const CCreatureSet * target, const CGDwelling * source) const override;
ui64 howManyReinforcementsCanGet(const CCreatureSet * target, const CCreatureSet * source) const override;
std::vector<SlotInfo> getBestArmy(const CCreatureSet * target, const CCreatureSet * source) const override;
ui64 howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const override;
ui64 howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const override;
std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const override;
std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const override;
std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const override;
std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const override;

View File

@ -185,6 +185,13 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const
{
captureObjects(objectsToCapture);
}
else if(objectTypes.size())
{
captureObjects(
std::vector<const CGObjectInstance *>(
ai->nullkiller->memory->visitableObjs.begin(),
ai->nullkiller->memory->visitableObjs.end()));
}
else
{
captureObjects(ai->nullkiller->objectClusterizer->getNearbyObjects());

View File

@ -139,7 +139,7 @@ Goals::TGoalVec StartupBehavior::decompose() const
{
if(!startupTown->visitingHero)
{
if(ai->nullkiller->armyManager->howManyReinforcementsCanGet(startupTown->getUpperArmy(), closestHero) > 200)
if(ai->nullkiller->armyManager->howManyReinforcementsCanGet(startupTown->getUpperArmy(), startupTown->getUpperArmy(), closestHero) > 200)
{
auto paths = ai->nullkiller->pathfinder->getPathInfo(startupTown->visitablePos());

View File

@ -36,6 +36,7 @@ set(VCAI_SRCS
Markers/HeroExchange.cpp
Markers/UnlockCluster.cpp
Engine/Nullkiller.cpp
Engine/DeepDecomposer.cpp
Engine/PriorityEvaluator.cpp
Analyzers/DangerHitMapAnalyzer.cpp
Analyzers/BuildAnalyzer.cpp
@ -93,6 +94,7 @@ set(VCAI_HEADERS
Markers/HeroExchange.h
Markers/UnlockCluster.h
Engine/Nullkiller.h
Engine/DeepDecomposer.h
Engine/PriorityEvaluator.h
Analyzers/DangerHitMapAnalyzer.h
Analyzers/BuildAnalyzer.h

View File

@ -0,0 +1,222 @@
/*
* Nullkiller.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 "DeepDecomposer.h"
#include "../VCAI.h"
#include "../Behaviors/CaptureObjectsBehavior.h"
#include "../Behaviors/RecruitHeroBehavior.h"
#include "../Behaviors/BuyArmyBehavior.h"
#include "../Behaviors/StartupBehavior.h"
#include "../Behaviors/DefenceBehavior.h"
#include "../Behaviors/BuildingBehavior.h"
#include "../Behaviors/GatherArmyBehavior.h"
#include "../Behaviors/ClusterBehavior.h"
#include "../Goals/Invalid.h"
#include "../Goals/Composition.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
using namespace Goals;
void DeepDecomposer::reset()
{
decompositionCache.clear();
goals.clear();
}
Goals::TGoalVec DeepDecomposer::decompose(TSubgoal behavior, int depthLimit)
{
TGoalVec tasks;
goals.clear();
goals.resize(depthLimit);
decompositionCache.resize(depthLimit);
depth = 0;
goals[0] = {behavior};
while(goals[0].size())
{
bool fromCache;
TSubgoal current = goals[depth].back();
TGoalVec subgoals = decomposeCached(unwrapComposition(current), fromCache);
#if AI_TRACE_LEVEL >= 1
logAi->trace("Decomposition level %d returned %d goals", depth, subgoals.size());
#endif
if(depth < depthLimit - 1)
{
goals[depth + 1].clear();
}
for(TSubgoal subgoal : subgoals)
{
if(subgoal->invalid())
continue;
if(subgoal->isElementar())
{
// need to get rid of priority control in behaviors like Startup to avoid this check.
// 0 - goals directly from behavior
Goals::TSubgoal task = depth >= 1 ? aggregateGoals(0, subgoal) : subgoal;
#if AI_TRACE_LEVEL >= 1
logAi->trace("Found task %s", task->toString());
#endif
if(!isCompositionLoop(subgoal))
{
tasks.push_back(task);
if(!fromCache)
{
addToCache(subgoal);
}
}
}
else if(depth < depthLimit - 1)
{
#if AI_TRACE_LEVEL >= 1
logAi->trace("Found abstract goal %s", subgoal->toString());
#endif
if(!isCompositionLoop(subgoal))
{
// depth 0 gives reward, deepest goal - task to execute, all the rest is not important
// so avoid details to reduce decomposition complexity but they can give some negative effect so
auto goalToAdd = depth >= 1 ? unwrapComposition(subgoal) : subgoal;
if(!vstd::contains(goals[depth + 1], goalToAdd))
{
goals[depth + 1].push_back(subgoal);
}
}
}
}
if(depth < depthLimit - 1 && goals[depth + 1].size())
{
depth++;
}
else
{
goals[depth].pop_back();
while(depth > 0 && goals[depth].empty())
{
depth--;
goals[depth].pop_back();
}
}
}
return tasks;
}
Goals::TSubgoal DeepDecomposer::aggregateGoals(int startDepth, TSubgoal last)
{
Goals::Composition composition;
for(int i = 0; i <= depth; i++)
{
composition.addNext(goals[i].back());
}
composition.addNext(last);
return sptr(composition);
}
Goals::TSubgoal DeepDecomposer::unwrapComposition(Goals::TSubgoal goal)
{
return goal->goalType == Goals::COMPOSITION ? goal->decompose().back() : goal;
}
bool DeepDecomposer::isCompositionLoop(TSubgoal goal)
{
auto goalsToTest = goal->goalType == Goals::COMPOSITION ? goal->decompose() : TGoalVec{goal};
for(auto goalToTest : goalsToTest)
{
for(int i = depth; i >= 0; i--)
{
auto parent = unwrapComposition(goals[i].back());
if(parent == goalToTest)
{
return true;
}
}
}
return false;
}
TGoalVec DeepDecomposer::decomposeCached(TSubgoal goal, bool & fromCache)
{
#if AI_TRACE_LEVEL >= 1
logAi->trace("Decomposing %s, level %s", goal->toString(), depth);
#endif
if(goal->hasHash())
{
for(int i = 0; i <= depth; i++)
{
auto cached = decompositionCache[i].find(goal);
if(cached != decompositionCache[i].end())
{
#if AI_TRACE_LEVEL >= 1
logAi->trace("Use decomposition cache for %s, level: %d", goal->toString(), depth);
#endif
fromCache = true;
return cached->second;
}
}
decompositionCache[depth][goal] = {}; // if goal decomposition yields no goals we still need it in cache to not decompose again
}
#if AI_TRACE_LEVEL >= 2
logAi->trace("Calling decompose on %s, level %s", goal->toString(), depth);
#endif
fromCache = false;
return goal->decompose();
}
void DeepDecomposer::addToCache(TSubgoal goal)
{
bool trusted = true;
for(int parentDepth = 1; parentDepth <= depth; parentDepth++)
{
TSubgoal parent = unwrapComposition(goals[parentDepth].back());
if(parent->hasHash())
{
auto solution = parentDepth < depth ? aggregateGoals(parentDepth + 1, goal) : goal;
#if AI_TRACE_LEVEL >= 2
logAi->trace("Adding %s to decomosition cache of %s at level %d", solution->toString(), parent->toString(), parentDepth);
#endif
decompositionCache[parentDepth][parent].push_back(solution);
if(trusted && parentDepth != 0)
{
decompositionCache[0][parent].push_back(solution);
trusted = false;
}
}
}
}

View File

@ -0,0 +1,41 @@
/*
* DeepDecomposer.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 "../Goals/AbstractGoal.h"
struct GoalHash
{
uint64_t operator()(const Goals::TSubgoal & goal) const
{
return goal->getHash();
}
};
typedef std::unordered_map<Goals::TSubgoal, Goals::TGoalVec, GoalHash> TGoalHashSet;
class DeepDecomposer
{
private:
std::vector<Goals::TGoalVec> goals;
std::vector<TGoalHashSet> decompositionCache;
int depth;
public:
void reset();
Goals::TGoalVec decompose(Goals::TSubgoal behavior, int depthLimit);
private:
Goals::TSubgoal aggregateGoals(int startDepth, Goals::TSubgoal last);
Goals::TSubgoal unwrapComposition(Goals::TSubgoal goal);
bool isCompositionLoop(Goals::TSubgoal goal);
Goals::TGoalVec decomposeCached(Goals::TSubgoal goal, bool & fromCache);
void addToCache(Goals::TSubgoal goal);
};

View File

@ -19,6 +19,7 @@
#include "../Behaviors/GatherArmyBehavior.h"
#include "../Behaviors/ClusterBehavior.h"
#include "../Goals/Invalid.h"
#include "../Goals/Composition.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
@ -49,6 +50,7 @@ void Nullkiller::init(std::shared_ptr<CCallback> cb, PlayerColor playerID)
pathfinder.reset(new AIPathfinder(cb.get(), this));
armyManager.reset(new ArmyManager(cb.get(), this));
heroManager.reset(new HeroManager(cb.get(), this));
decomposer.reset(new DeepDecomposer());
}
Goals::TTask Nullkiller::choseBestTask(Goals::TTaskVec & tasks) const
@ -60,76 +62,23 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TTaskVec & tasks) const
return bestTask;
}
Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior) const
Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior, int decompositionMaxDepth) const
{
logAi->debug("Checking behavior %s", behavior->toString());
const int MAX_DEPTH = 10;
Goals::TGoalVec goals[MAX_DEPTH + 1];
Goals::TTaskVec tasks;
std::map<Goals::TSubgoal, Goals::TSubgoal> decompositionMap;
auto start = boost::chrono::high_resolution_clock::now();
goals[0] = {behavior};
int depth = 0;
while(goals[0].size())
Goals::TGoalVec elementarGoals = decomposer->decompose(behavior, decompositionMaxDepth);
Goals::TTaskVec tasks;
for(auto goal : elementarGoals)
{
TSubgoal current = goals[depth].back();
Goals::TTask task = Goals::taskptr(*goal);
#if AI_TRACE_LEVEL >= 1
logAi->trace("Decomposing %s, level: %d", current->toString(), depth);
#endif
if(task->priority <= 0)
task->priority = priorityEvaluator->evaluate(goal);
TGoalVec subgoals = current->decompose();
#if AI_TRACE_LEVEL >= 1
logAi->trace("Found %d goals", subgoals.size());
#endif
if(depth < MAX_DEPTH)
{
goals[depth + 1].clear();
}
for(auto subgoal : subgoals)
{
if(subgoal->isElementar())
{
auto task = taskptr(*subgoal);
#if AI_TRACE_LEVEL >= 1
logAi->trace("Found task %s", task->toString());
#endif
if(task->priority <= 0)
task->priority = priorityEvaluator->evaluate(subgoal);
tasks.push_back(task);
}
else if(depth < MAX_DEPTH)
{
#if AI_TRACE_LEVEL >= 1
logAi->trace("Found abstract goal %s", subgoal->toString());
#endif
goals[depth + 1].push_back(subgoal);
}
}
if(depth < MAX_DEPTH && goals[depth + 1].size())
{
depth++;
}
else
{
goals[depth].pop_back();
while(depth > 0 && goals[depth].empty())
{
depth--;
goals[depth].pop_back();
}
}
tasks.push_back(task);
}
if(tasks.empty())
@ -190,6 +139,7 @@ void Nullkiller::updateAiState(int pass)
objectClusterizer->clusterize();
buildAnalyzer->update();
decomposer->reset();
logAi->debug("AI state updated in %ld", timeElapsed(start));
}
@ -234,6 +184,8 @@ HeroLockedReason Nullkiller::getHeroLockedReason(const CGHeroInstance * hero) co
void Nullkiller::makeTurn()
{
const int MAX_DEPTH = 10;
resetAiState();
for(int i = 1; i <= MAXPASS; i++)
@ -241,18 +193,18 @@ void Nullkiller::makeTurn()
updateAiState(i);
Goals::TTaskVec bestTasks = {
choseBestTask(sptr(BuyArmyBehavior())),
choseBestTask(sptr(CaptureObjectsBehavior())),
choseBestTask(sptr(ClusterBehavior())),
choseBestTask(sptr(RecruitHeroBehavior())),
choseBestTask(sptr(DefenceBehavior())),
choseBestTask(sptr(BuildingBehavior())),
choseBestTask(sptr(GatherArmyBehavior()))
choseBestTask(sptr(BuyArmyBehavior()), 1),
choseBestTask(sptr(CaptureObjectsBehavior()), 1),
choseBestTask(sptr(ClusterBehavior()), MAX_DEPTH),
choseBestTask(sptr(RecruitHeroBehavior()), 1),
choseBestTask(sptr(DefenceBehavior()), MAX_DEPTH),
choseBestTask(sptr(BuildingBehavior()), 1),
choseBestTask(sptr(GatherArmyBehavior()), MAX_DEPTH)
};
if(cb->getDate(Date::DAY) == 1)
{
bestTasks.push_back(choseBestTask(sptr(StartupBehavior())));
bestTasks.push_back(choseBestTask(sptr(StartupBehavior()), 1));
}
Goals::TTask bestTask = choseBestTask(bestTasks);

View File

@ -12,12 +12,12 @@
#include "PriorityEvaluator.h"
#include "FuzzyHelper.h"
#include "AIMemory.h"
#include "DeepDecomposer.h"
#include "../Analyzers/DangerHitMapAnalyzer.h"
#include "../Analyzers/BuildAnalyzer.h"
#include "../Analyzers/ArmyManager.h"
#include "../Analyzers/HeroManager.h"
#include "../Analyzers/ObjectClusterizer.h"
#include "../Goals/AbstractGoal.h"
const float MAX_GOLD_PEASURE = 0.3f;
const float MIN_PRIORITY = 0.01f;
@ -50,6 +50,7 @@ public:
std::unique_ptr<ArmyManager> armyManager;
std::unique_ptr<AIMemory> memory;
std::unique_ptr<FuzzyHelper> dangerEvaluator;
std::unique_ptr<DeepDecomposer> decomposer;
PlayerColor playerID;
std::shared_ptr<CCallback> cb;
@ -69,6 +70,6 @@ public:
private:
void resetAiState();
void updateAiState(int pass);
Goals::TTask choseBestTask(Goals::TSubgoal behavior) const;
Goals::TTask choseBestTask(Goals::TSubgoal behavior, int decompositionMaxDepth) const;
Goals::TTask choseBestTask(Goals::TTaskVec & tasks) const;
};

View File

@ -143,6 +143,10 @@ namespace Goals
virtual bool operator==(const AbstractGoal & g) const;
virtual bool isElementar() const { return false; }
virtual bool hasHash() const { return false; }
virtual uint64_t getHash() const { return 0; }
bool operator!=(const AbstractGoal & g) const
{

View File

@ -13,12 +13,19 @@
#include "../VCAI.h"
#include "../../../lib/mapping/CMap.h" //for victory conditions
#include "../../../lib/CPathfinder.h"
#include "../../../lib/VCMI_Lib.h"
#include "../../../lib/CGeneralTextHandler.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
using namespace Goals;
bool isKeyMaster(const QuestInfo & q)
{
return q.obj && (q.obj->ID == Obj::BORDER_GATE || q.obj->ID == Obj::BORDERGUARD);
}
std::string CompleteQuest::toString() const
{
return "Complete quest " + questToString();
@ -26,7 +33,7 @@ std::string CompleteQuest::toString() const
TGoalVec CompleteQuest::decompose() const
{
if(q.obj && (q.obj->ID == Obj::BORDER_GATE || q.obj->ID == Obj::BORDERGUARD))
if(isKeyMaster(q))
{
return missionKeymaster();
}
@ -73,11 +80,35 @@ TGoalVec CompleteQuest::decompose() const
bool CompleteQuest::operator==(const CompleteQuest & other) const
{
if(isKeyMaster(q))
{
return isKeyMaster(other.q) && q.obj->subID == other.q.obj->subID;
}
else if(isKeyMaster(other.q))
{
return false;
}
return q.quest->qid == other.q.quest->qid;
}
uint64_t CompleteQuest::getHash() const
{
if(isKeyMaster(q))
{
return q.obj->subID;
}
return q.quest->qid;
}
std::string CompleteQuest::questToString() const
{
if(isKeyMaster(q))
{
return "find " + VLC->generaltexth->tentColors[q.obj->subID] + " keymaster tent";
}
if(q.quest->missionType == CQuest::MISSION_NONE)
return "inactive quest";

View File

@ -29,11 +29,12 @@ namespace Goals
virtual Goals::TGoalVec decompose() const override;
virtual std::string toString() const override;
virtual bool hasHash() const override { return true; }
virtual uint64_t getHash() const override;
virtual bool operator==(const CompleteQuest & other) const override;
private:
TGoalVec getQuestTasks() const;
TGoalVec tryCompleteQuest() const;
TGoalVec missionArt() const;
TGoalVec missionHero() const;

View File

@ -45,52 +45,7 @@ void Composition::accept(VCAI * ai)
TGoalVec Composition::decompose() const
{
if(isElementar())
return subtasks;
auto tasks = subtasks;
tasks.pop_back();
TSubgoal last = subtasks.back();
auto decomposed = last->decompose();
TGoalVec result;
for(TSubgoal goal : decomposed)
{
if(goal->invalid() || goal == last || vstd::contains(tasks, goal))
continue;
auto newComposition = Composition(tasks);
if(goal->goalType == COMPOSITION)
{
Composition & other = dynamic_cast<Composition &>(*goal);
bool cancel = false;
for(auto subgoal : other.subtasks)
{
if(subgoal == last || vstd::contains(tasks, subgoal))
{
cancel = true;
break;
}
newComposition.addNext(subgoal);
}
if(cancel)
continue;
}
else
{
newComposition.addNext(goal);
}
result.push_back(sptr(newComposition));
}
return result;
return subtasks;
}
Composition & Composition::addNext(const AbstractGoal & goal)
@ -100,7 +55,16 @@ Composition & Composition::addNext(const AbstractGoal & goal)
Composition & Composition::addNext(TSubgoal goal)
{
subtasks.push_back(goal);
if(goal->goalType == COMPOSITION)
{
Composition & other = dynamic_cast<Composition &>(*goal);
vstd::concatenate(subtasks, other.subtasks);
}
else
{
subtasks.push_back(goal);
}
return *this;
}

View File

@ -12,6 +12,7 @@
#include "../VCAI.h"
#include "../Engine/Nullkiller.h"
#include "../AIUtility.h"
#include "../Analyzers/ArmyManager.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;

View File

@ -11,7 +11,7 @@
#pragma once
#define PATHFINDER_TRACE_LEVEL 0
#define AI_TRACE_LEVEL 2
#define AI_TRACE_LEVEL 1
#define SCOUT_TURN_DISTANCE_LIMIT 3
#include "../../../lib/CPathfinder.h"

View File

@ -190,7 +190,7 @@ bool HeroExchangeMap::canExchange(const ChainActor * other)
uint64_t reinforcment = upgradeInfo.upgradeValue;
if(other->creatureSet->Slots().size())
reinforcment += ai->armyManager->howManyReinforcementsCanGet(actor->creatureSet, other->creatureSet);
reinforcment += ai->armyManager->howManyReinforcementsCanGet(actor->hero, actor->creatureSet, other->creatureSet);
#if PATHFINDER_TRACE_LEVEL >= 2
logAi->trace(
@ -291,7 +291,7 @@ CCreatureSet * HeroExchangeMap::tryUpgrade(const CCreatureSet * army, const CGOb
CCreatureSet * HeroExchangeMap::pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const
{
CCreatureSet * target = new HeroExchangeArmy();
auto bestArmy = ai->armyManager->getBestArmy(army1, army2);
auto bestArmy = ai->armyManager->getBestArmy(actor->hero, army1, army2);
for(auto & slotInfo : bestArmy)
{

View File

@ -90,6 +90,13 @@ namespace AIPathfinding
return bypassBattle(source, destination, pathfinderConfig, pathfinderHelper);
}
if(destination.nodeObject
&& (destination.nodeObject->ID == Obj::GARRISON || destination.nodeObject->ID == Obj::GARRISON2)
&& destination.objectRelations == PlayerRelations::ENEMIES)
{
return bypassBattle(source, destination, pathfinderConfig, pathfinderHelper);
}
return false;
}

View File

@ -803,11 +803,33 @@ void VCAI::pickBestCreatures(const CArmedInstance * destinationArmy, const CArme
{
const CArmedInstance * armies[] = {destinationArmy, source};
auto bestArmy = nullkiller->armyManager->getSortedSlots(destinationArmy, source);
auto bestArmy = nullkiller->armyManager->getBestArmy(destinationArmy, destinationArmy, source);
//foreach best type -> iterate over slots in both armies and if it's the appropriate type, send it to the slot where it belongs
for(SlotID i = SlotID(0); i.getNum() < bestArmy.size() && i.validSlot(); i.advance(1)) //i-th strongest creature type will go to i-th slot
for(SlotID i = SlotID(0); i.validSlot(); i.advance(1)) //i-th strongest creature type will go to i-th slot
{
if(i.getNum() >= bestArmy.size())
{
if(destinationArmy->hasStackAtSlot(i))
{
auto creature = destinationArmy->getCreature(i);
auto targetSlot = source->getSlotFor(creature);
if(targetSlot.validSlot())
{
// remove unwanted creatures
cb->mergeOrSwapStacks(destinationArmy, source, i, targetSlot);
}
else if(destinationArmy->getStack(i).getPower() < destinationArmy->getArmyStrength() / 100)
{
// dismiss creatures if the amount is small
cb->dismissCreature(destinationArmy, i);
}
}
continue;
}
const CCreature * targetCreature = bestArmy[i.getNum()].creature;
for(auto armyPtr : armies)