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:
parent
35a1167f3d
commit
e385c83a88
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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
|
||||
|
222
AI/Nullkiller/Engine/DeepDecomposer.cpp
Normal file
222
AI/Nullkiller/Engine/DeepDecomposer.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
41
AI/Nullkiller/Engine/DeepDecomposer.h
Normal file
41
AI/Nullkiller/Engine/DeepDecomposer.h
Normal 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);
|
||||
};
|
@ -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,77 +62,24 @@ 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};
|
||||
Goals::TGoalVec elementarGoals = decomposer->decompose(behavior, decompositionMaxDepth);
|
||||
Goals::TTaskVec tasks;
|
||||
|
||||
int depth = 0;
|
||||
while(goals[0].size())
|
||||
for(auto goal : elementarGoals)
|
||||
{
|
||||
TSubgoal current = goals[depth].back();
|
||||
|
||||
#if AI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Decomposing %s, level: %d", current->toString(), depth);
|
||||
#endif
|
||||
|
||||
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
|
||||
Goals::TTask task = Goals::taskptr(*goal);
|
||||
|
||||
if(task->priority <= 0)
|
||||
task->priority = priorityEvaluator->evaluate(subgoal);
|
||||
task->priority = priorityEvaluator->evaluate(goal);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -144,6 +144,10 @@ namespace Goals
|
||||
|
||||
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
|
||||
{
|
||||
return !(*this == g);
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
Composition & Composition::addNext(const AbstractGoal & goal)
|
||||
@ -99,8 +54,17 @@ Composition & Composition::addNext(const AbstractGoal & goal)
|
||||
}
|
||||
|
||||
Composition & Composition::addNext(TSubgoal goal)
|
||||
{
|
||||
if(goal->goalType == COMPOSITION)
|
||||
{
|
||||
Composition & other = dynamic_cast<Composition &>(*goal);
|
||||
|
||||
vstd::concatenate(subtasks, other.subtasks);
|
||||
}
|
||||
else
|
||||
{
|
||||
subtasks.push_back(goal);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user