mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-20 20:23:03 +02:00
Refactoring Battle AI.
Divide BattleAI on the smaller files.
This commit is contained in:
parent
c696746017
commit
18f8ca3cd0
66
AI/BattleAI/AttackPossibility.cpp
Normal file
66
AI/BattleAI/AttackPossibility.cpp
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* AttackPossibility.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 "AttackPossibility.h"
|
||||||
|
|
||||||
|
int AttackPossibility::damageDiff() const
|
||||||
|
{
|
||||||
|
if (!priorities)
|
||||||
|
priorities = new Priorities;
|
||||||
|
const auto dealtDmgValue = priorities->stackEvaluator(enemy) * damageDealt;
|
||||||
|
const auto receivedDmgValue = priorities->stackEvaluator(attack.attacker) * damageReceived;
|
||||||
|
return dealtDmgValue - receivedDmgValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int AttackPossibility::attackValue() const
|
||||||
|
{
|
||||||
|
return damageDiff() + tacticImpact;
|
||||||
|
}
|
||||||
|
|
||||||
|
AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex)
|
||||||
|
{
|
||||||
|
auto attacker = AttackInfo.attacker;
|
||||||
|
auto enemy = AttackInfo.defender;
|
||||||
|
|
||||||
|
const int remainingCounterAttacks = getValOr(state.counterAttacksLeft, enemy, enemy->counterAttacksRemaining());
|
||||||
|
const bool counterAttacksBlocked = attacker->hasBonusOfType(Bonus::BLOCKS_RETALIATION) || enemy->hasBonusOfType(Bonus::NO_RETALIATION);
|
||||||
|
const int totalAttacks = 1 + AttackInfo.attackerBonuses->getBonuses(Selector::type(Bonus::ADDITIONAL_ATTACK), (Selector::effectRange (Bonus::NO_LIMIT).Or(Selector::effectRange(Bonus::ONLY_MELEE_FIGHT))))->totalValue();
|
||||||
|
|
||||||
|
AttackPossibility ap = {enemy, hex, AttackInfo, 0, 0, 0};
|
||||||
|
|
||||||
|
auto curBai = AttackInfo; //we'll modify here the stack counts
|
||||||
|
for(int i = 0; i < totalAttacks; i++)
|
||||||
|
{
|
||||||
|
std::pair<ui32, ui32> retaliation(0,0);
|
||||||
|
auto attackDmg = getCbc()->battleEstimateDamage(CRandomGenerator::getDefault(), curBai, &retaliation);
|
||||||
|
ap.damageDealt = (attackDmg.first + attackDmg.second) / 2;
|
||||||
|
ap.damageReceived = (retaliation.first + retaliation.second) / 2;
|
||||||
|
|
||||||
|
if(remainingCounterAttacks <= i || counterAttacksBlocked)
|
||||||
|
ap.damageReceived = 0;
|
||||||
|
|
||||||
|
curBai.attackerCount = attacker->count - attacker->countKilledByAttack(ap.damageReceived).first;
|
||||||
|
curBai.defenderCount = enemy->count - enemy->countKilledByAttack(ap.damageDealt).first;
|
||||||
|
if(!curBai.attackerCount)
|
||||||
|
break;
|
||||||
|
//TODO what about defender? should we break? but in pessimistic scenario defender might be alive
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO other damage related to attack (eg. fire shield and other abilities)
|
||||||
|
|
||||||
|
//Limit damages by total stack health
|
||||||
|
vstd::amin(ap.damageDealt, enemy->count * enemy->MaxHealth() - (enemy->MaxHealth() - enemy->firstHPleft));
|
||||||
|
vstd::amin(ap.damageReceived, attacker->count * attacker->MaxHealth() - (attacker->MaxHealth() - attacker->firstHPleft));
|
||||||
|
|
||||||
|
return ap;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Priorities* AttackPossibility::priorities = nullptr;
|
50
AI/BattleAI/AttackPossibility.h
Normal file
50
AI/BattleAI/AttackPossibility.h
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* AttackPossibility.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 "../../lib/BattleState.h"
|
||||||
|
#include "CCallback.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
|
||||||
|
struct HypotheticChangesToBattleState
|
||||||
|
{
|
||||||
|
std::map<const CStack *, const IBonusBearer *> bonusesOfStacks;
|
||||||
|
std::map<const CStack *, int> counterAttacksLeft;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Priorities
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::vector<double> resourceTypeBaseValues;
|
||||||
|
std::function<double(const CStack *)> stackEvaluator;
|
||||||
|
Priorities()
|
||||||
|
{
|
||||||
|
// range::copy(VLC->objh->resVals, std::back_inserter(resourceTypeBaseValues));
|
||||||
|
stackEvaluator = [](const CStack*){ return 1.0; };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class AttackPossibility
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
const CStack *enemy; //redundant (to attack.defender) but looks nice
|
||||||
|
BattleHex tile; //tile from which we attack
|
||||||
|
BattleAttackInfo attack;
|
||||||
|
|
||||||
|
int damageDealt;
|
||||||
|
int damageReceived; //usually by counter-attack
|
||||||
|
int tacticImpact;
|
||||||
|
|
||||||
|
int damageDiff() const;
|
||||||
|
int attackValue() const;
|
||||||
|
|
||||||
|
static AttackPossibility evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex);
|
||||||
|
static Priorities * priorities;
|
||||||
|
};
|
@ -1,88 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* BattleAI.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 "StdInc.h"
|
||||||
#include "../../lib/AI_Base.h"
|
|
||||||
#include "BattleAI.h"
|
#include "BattleAI.h"
|
||||||
#include "../../lib/BattleState.h"
|
#include "StackWithBonuses.h"
|
||||||
#include "../../CCallback.h"
|
#include "EnemyInfo.h"
|
||||||
#include "../../lib/CCreatureHandler.h"
|
|
||||||
#include "../../lib/spells/CSpellHandler.h"
|
#include "../../lib/spells/CSpellHandler.h"
|
||||||
#include "../../lib/VCMI_Lib.h"
|
|
||||||
|
|
||||||
using boost::optional;
|
|
||||||
static std::shared_ptr<CBattleCallback> cbc;
|
|
||||||
|
/*
|
||||||
|
//
|
||||||
|
// //set has its own order, so remove_if won't work. TODO - reuse for map
|
||||||
|
// template<typename Elem, typename Predicate>
|
||||||
|
// void erase_if(std::set<Elem> &setContainer, Predicate pred)
|
||||||
|
// {
|
||||||
|
// auto itr = setContainer.begin();
|
||||||
|
// auto endItr = setContainer.end();
|
||||||
|
// while(itr != endItr)
|
||||||
|
// {
|
||||||
|
// auto tmpItr = itr++;
|
||||||
|
// if(pred(*tmpItr))
|
||||||
|
// setContainer.erase(tmpItr);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
*/
|
||||||
|
|
||||||
#define LOGL(text) print(text)
|
#define LOGL(text) print(text)
|
||||||
#define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl))
|
#define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl))
|
||||||
|
|
||||||
struct Priorities
|
|
||||||
{
|
|
||||||
double manaValue;
|
|
||||||
double generalResourceValueModifier;
|
|
||||||
std::vector<double> resourceTypeBaseValues;
|
|
||||||
std::function<double(const CStack *)> stackEvaluator;
|
|
||||||
|
|
||||||
|
|
||||||
Priorities()
|
|
||||||
{
|
|
||||||
manaValue = 0.;
|
|
||||||
generalResourceValueModifier = 1.;
|
|
||||||
range::copy(VLC->objh->resVals, std::back_inserter(resourceTypeBaseValues));
|
|
||||||
stackEvaluator = [](const CStack*){ return 1.0; };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Priorities *priorities = nullptr;
|
|
||||||
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
int distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances& dists, BattleHex *chosenHex = nullptr)
|
|
||||||
{
|
|
||||||
int ret = 1000000;
|
|
||||||
for(BattleHex n : hex.neighbouringTiles())
|
|
||||||
{
|
|
||||||
if(dists[n] >= 0 && dists[n] < ret)
|
|
||||||
{
|
|
||||||
ret = dists[n];
|
|
||||||
if(chosenHex)
|
|
||||||
*chosenHex = n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const ReachabilityInfo::TDistances & dists)
|
|
||||||
{
|
|
||||||
return distToNearestNeighbour(ei1.s->position, dists) < distToNearestNeighbour(ei2.s->position, dists);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Container, typename Pred>
|
|
||||||
auto sum(const Container & c, Pred p) -> decltype(p(*std::begin(c)))
|
|
||||||
{
|
|
||||||
double ret = 0;
|
|
||||||
for(const auto &element : c)
|
|
||||||
{
|
|
||||||
ret += p(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CBattleAI::CBattleAI(void)
|
CBattleAI::CBattleAI(void)
|
||||||
: side(-1)
|
: side(-1)
|
||||||
{
|
{
|
||||||
print("created");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
CBattleAI::~CBattleAI(void)
|
CBattleAI::~CBattleAI(void)
|
||||||
{
|
{
|
||||||
print("destroyed");
|
|
||||||
|
|
||||||
if(cb)
|
if(cb)
|
||||||
{
|
{
|
||||||
//Restore previous state of CB - it may be shared with the main AI (like VCAI)
|
//Restore previous state of CB - it may be shared with the main AI (like VCAI)
|
||||||
@ -93,10 +52,9 @@ CBattleAI::~CBattleAI(void)
|
|||||||
|
|
||||||
void CBattleAI::init(std::shared_ptr<CBattleCallback> CB)
|
void CBattleAI::init(std::shared_ptr<CBattleCallback> CB)
|
||||||
{
|
{
|
||||||
print("init called, saving ptr to IBattleCallback");
|
setCbc(CB);
|
||||||
cbc = cb = CB;
|
cb = CB;
|
||||||
playerID = *CB->getPlayerID();; //TODO should be sth in callback
|
playerID = *CB->getPlayerID();; //TODO should be sth in callback
|
||||||
|
|
||||||
wasWaitingForRealize = cb->waitTillRealize;
|
wasWaitingForRealize = cb->waitTillRealize;
|
||||||
wasUnlockingGs = CB->unlockGsWhenWaiting;
|
wasUnlockingGs = CB->unlockGsWhenWaiting;
|
||||||
CB->waitTillRealize = true;
|
CB->waitTillRealize = true;
|
||||||
@ -106,14 +64,11 @@ void CBattleAI::init(std::shared_ptr<CBattleCallback> CB)
|
|||||||
BattleAction CBattleAI::activeStack( const CStack * stack )
|
BattleAction CBattleAI::activeStack( const CStack * stack )
|
||||||
{
|
{
|
||||||
LOG_TRACE_PARAMS(logAi, "stack: %s", stack->nodeName()) ;
|
LOG_TRACE_PARAMS(logAi, "stack: %s", stack->nodeName()) ;
|
||||||
|
setCbc(cb); //TODO: make solid sure that AIs always use their callbacks (need to take care of event handlers too)
|
||||||
cbc = cb; //TODO: make solid sure that AIs always use their callbacks (need to take care of event handlers too)
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
print("activeStack called for " + stack->nodeName());
|
|
||||||
if(stack->type->idNumber == CreatureID::CATAPULT)
|
if(stack->type->idNumber == CreatureID::CATAPULT)
|
||||||
return useCatapult(stack);
|
return useCatapult(stack);
|
||||||
|
|
||||||
if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->hasBonusOfType(Bonus::HEALER))
|
if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->hasBonusOfType(Bonus::HEALER))
|
||||||
{
|
{
|
||||||
auto healingTargets = cb->battleGetStacks(CBattleInfoEssentials::ONLY_MINE);
|
auto healingTargets = cb->battleGetStacks(CBattleInfoEssentials::ONLY_MINE);
|
||||||
@ -121,7 +76,6 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
|
|||||||
for(auto stack : healingTargets)
|
for(auto stack : healingTargets)
|
||||||
if(auto woundHp = stack->MaxHealth() - stack->firstHPleft)
|
if(auto woundHp = stack->MaxHealth() - stack->firstHPleft)
|
||||||
woundHpToStack[woundHp] = stack;
|
woundHpToStack[woundHp] = stack;
|
||||||
|
|
||||||
if(woundHpToStack.empty())
|
if(woundHpToStack.empty())
|
||||||
return BattleAction::makeDefend(stack);
|
return BattleAction::makeDefend(stack);
|
||||||
else
|
else
|
||||||
@ -130,8 +84,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
|
|||||||
|
|
||||||
if(cb->battleCanCastSpell())
|
if(cb->battleCanCastSpell())
|
||||||
attemptCastingSpell();
|
attemptCastingSpell();
|
||||||
|
if(auto ret = getCbc()->battleIsFinished())
|
||||||
if(auto ret = cbc->battleIsFinished())
|
|
||||||
{
|
{
|
||||||
//spellcast may finish battle
|
//spellcast may finish battle
|
||||||
//send special preudo-action
|
//send special preudo-action
|
||||||
@ -142,9 +95,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
|
|||||||
|
|
||||||
if(auto action = considerFleeingOrSurrendering())
|
if(auto action = considerFleeingOrSurrendering())
|
||||||
return *action;
|
return *action;
|
||||||
|
|
||||||
PotentialTargets targets(stack);
|
PotentialTargets targets(stack);
|
||||||
|
|
||||||
if(targets.possibleAttacks.size())
|
if(targets.possibleAttacks.size())
|
||||||
{
|
{
|
||||||
auto hlp = targets.bestAction();
|
auto hlp = targets.bestAction();
|
||||||
@ -157,8 +108,8 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
|
|||||||
{
|
{
|
||||||
if(stack->waited())
|
if(stack->waited())
|
||||||
{
|
{
|
||||||
ThreatMap threatsToUs(stack);
|
//ThreatMap threatsToUs(stack); // These lines may be usefull but they are't used in the code.
|
||||||
auto dists = cbc->battleGetDistances(stack);
|
auto dists = getCbc()->battleGetDistances(stack);
|
||||||
const EnemyInfo &ei= *range::min_element(targets.unreachableEnemies, std::bind(isCloser, _1, _2, std::ref(dists)));
|
const EnemyInfo &ei= *range::min_element(targets.unreachableEnemies, std::bind(isCloser, _1, _2, std::ref(dists)));
|
||||||
if(distToNearestNeighbour(ei.s->position, dists) < GameConstants::BFIELD_SIZE)
|
if(distToNearestNeighbour(ei.s->position, dists) < GameConstants::BFIELD_SIZE)
|
||||||
{
|
{
|
||||||
@ -175,105 +126,16 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
|
|||||||
{
|
{
|
||||||
logAi->error("Exception occurred in %s %s",__FUNCTION__, e.what());
|
logAi->error("Exception occurred in %s %s",__FUNCTION__, e.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
return BattleAction::makeDefend(stack);
|
return BattleAction::makeDefend(stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CBattleAI::actionFinished(const BattleAction &action)
|
|
||||||
{
|
|
||||||
print("actionFinished called");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleAI::actionStarted(const BattleAction &action)
|
|
||||||
{
|
|
||||||
print("actionStarted called");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleAI::battleAttack(const BattleAttack *ba)
|
|
||||||
{
|
|
||||||
print("battleAttack called");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa)
|
|
||||||
{
|
|
||||||
print("battleStacksAttacked called");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleAI::battleEnd(const BattleResult *br)
|
|
||||||
{
|
|
||||||
print("battleEnd called");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleAI::battleNewRoundFirst(int round)
|
|
||||||
{
|
|
||||||
print("battleNewRoundFirst called");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleAI::battleNewRound(int round)
|
|
||||||
{
|
|
||||||
print("battleNewRound called");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleAI::battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance)
|
|
||||||
{
|
|
||||||
print("battleStackMoved called");;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleAI::battleSpellCast(const BattleSpellCast *sc)
|
|
||||||
{
|
|
||||||
print("battleSpellCast called");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleAI::battleStacksEffectsSet(const SetStackEffect & sse)
|
|
||||||
{
|
|
||||||
print("battleStacksEffectsSet called");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side)
|
|
||||||
{
|
|
||||||
print("battleStart called");
|
|
||||||
side = Side;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleAI::battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom)
|
|
||||||
{
|
|
||||||
print("battleStacksHealedRes called");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleAI::battleNewStackAppeared(const CStack * stack)
|
|
||||||
{
|
|
||||||
print("battleNewStackAppeared called");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleAI::battleObstaclesRemoved(const std::set<si32> & removedObstacles)
|
|
||||||
{
|
|
||||||
print("battleObstaclesRemoved called");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleAI::battleCatapultAttacked(const CatapultAttack & ca)
|
|
||||||
{
|
|
||||||
print("battleCatapultAttacked called");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleAI::battleStacksRemoved(const BattleStacksRemoved & bsr)
|
|
||||||
{
|
|
||||||
print("battleStacksRemoved called");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleAI::print(const std::string &text) const
|
|
||||||
{
|
|
||||||
logAi->trace("CBattleAI [%p]: %s", this, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination)
|
BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination)
|
||||||
{
|
{
|
||||||
assert(destination.isValid());
|
assert(destination.isValid());
|
||||||
auto avHexes = cb->battleGetAvailableHexes(stack, false);
|
auto avHexes = cb->battleGetAvailableHexes(stack, false);
|
||||||
auto reachability = cb->getReachability(stack);
|
auto reachability = cb->getReachability(stack);
|
||||||
|
|
||||||
if(vstd::contains(avHexes, destination))
|
if(vstd::contains(avHexes, destination))
|
||||||
return BattleAction::makeMove(stack, destination);
|
return BattleAction::makeMove(stack, destination);
|
||||||
|
|
||||||
auto destNeighbours = destination.neighbouringTiles();
|
auto destNeighbours = destination.neighbouringTiles();
|
||||||
if(vstd::contains_if(destNeighbours, [&](BattleHex n) { return stack->coversPos(destination); }))
|
if(vstd::contains_if(destNeighbours, [&](BattleHex n) { return stack->coversPos(destination); }))
|
||||||
{
|
{
|
||||||
@ -281,15 +143,11 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination)
|
|||||||
//We shouldn't even be here...
|
//We shouldn't even be here...
|
||||||
return BattleAction::makeDefend(stack);
|
return BattleAction::makeDefend(stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
vstd::erase_if(destNeighbours, [&](BattleHex hex){ return !reachability.accessibility.accessible(hex, stack); });
|
vstd::erase_if(destNeighbours, [&](BattleHex hex){ return !reachability.accessibility.accessible(hex, stack); });
|
||||||
|
|
||||||
if(!avHexes.size() || !destNeighbours.size()) //we are blocked or dest is blocked
|
if(!avHexes.size() || !destNeighbours.size()) //we are blocked or dest is blocked
|
||||||
{
|
{
|
||||||
print("goTowards: Stack cannot move! That's " + stack->nodeName());
|
|
||||||
return BattleAction::makeDefend(stack);
|
return BattleAction::makeDefend(stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(stack->hasBonusOfType(Bonus::FLYING))
|
if(stack->hasBonusOfType(Bonus::FLYING))
|
||||||
{
|
{
|
||||||
// Flying stack doesn't go hex by hex, so we can't backtrack using predecessors.
|
// Flying stack doesn't go hex by hex, so we can't backtrack using predecessors.
|
||||||
@ -297,13 +155,9 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination)
|
|||||||
auto distToDestNeighbour = [&](BattleHex hex) -> int
|
auto distToDestNeighbour = [&](BattleHex hex) -> int
|
||||||
{
|
{
|
||||||
auto nearestNeighbourToHex = vstd::minElementByFun(destNeighbours, [&](BattleHex a)
|
auto nearestNeighbourToHex = vstd::minElementByFun(destNeighbours, [&](BattleHex a)
|
||||||
{
|
{return BattleHex::getDistance(a, hex);});
|
||||||
return BattleHex::getDistance(a, hex);
|
|
||||||
});
|
|
||||||
|
|
||||||
return BattleHex::getDistance(*nearestNeighbourToHex, hex);
|
return BattleHex::getDistance(*nearestNeighbourToHex, hex);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto nearestAvailableHex = vstd::minElementByFun(avHexes, distToDestNeighbour);
|
auto nearestAvailableHex = vstd::minElementByFun(avHexes, distToDestNeighbour);
|
||||||
return BattleAction::makeMove(stack, *nearestAvailableHex);
|
return BattleAction::makeMove(stack, *nearestAvailableHex);
|
||||||
}
|
}
|
||||||
@ -312,17 +166,14 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination)
|
|||||||
BattleHex bestNeighbor = destination;
|
BattleHex bestNeighbor = destination;
|
||||||
if(distToNearestNeighbour(destination, reachability.distances, &bestNeighbor) > GameConstants::BFIELD_SIZE)
|
if(distToNearestNeighbour(destination, reachability.distances, &bestNeighbor) > GameConstants::BFIELD_SIZE)
|
||||||
{
|
{
|
||||||
print("goTowards: Cannot reach");
|
|
||||||
return BattleAction::makeDefend(stack);
|
return BattleAction::makeDefend(stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
BattleHex currentDest = bestNeighbor;
|
BattleHex currentDest = bestNeighbor;
|
||||||
while(1)
|
while(1)
|
||||||
{
|
{
|
||||||
assert(currentDest.isValid());
|
assert(currentDest.isValid());
|
||||||
if(vstd::contains(avHexes, currentDest))
|
if(vstd::contains(avHexes, currentDest))
|
||||||
return BattleAction::makeMove(stack, currentDest);
|
return BattleAction::makeMove(stack, currentDest);
|
||||||
|
|
||||||
currentDest = reachability.predecessors[currentDest];
|
currentDest = reachability.predecessors[currentDest];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -333,6 +184,7 @@ BattleAction CBattleAI::useCatapult(const CStack * stack)
|
|||||||
throw std::runtime_error("The method or operation is not implemented.");
|
throw std::runtime_error("The method or operation is not implemented.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
enum SpellTypes
|
enum SpellTypes
|
||||||
{
|
{
|
||||||
OFFENSIVE_SPELL, TIMED_EFFECT, OTHER
|
OFFENSIVE_SPELL, TIMED_EFFECT, OTHER
|
||||||
@ -345,74 +197,17 @@ SpellTypes spellType(const CSpell *spell)
|
|||||||
if (spell->hasEffects())
|
if (spell->hasEffects())
|
||||||
return TIMED_EFFECT;
|
return TIMED_EFFECT;
|
||||||
return OTHER;
|
return OTHER;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PossibleSpellcast
|
|
||||||
{
|
|
||||||
const CSpell *spell;
|
|
||||||
BattleHex dest;
|
|
||||||
si32 value;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CurrentOffensivePotential
|
|
||||||
{
|
|
||||||
std::map<const CStack *, PotentialTargets> ourAttacks;
|
|
||||||
std::map<const CStack *, PotentialTargets> enemyAttacks;
|
|
||||||
|
|
||||||
CurrentOffensivePotential(ui8 side)
|
|
||||||
{
|
|
||||||
for(auto stack : cbc->battleGetStacks())
|
|
||||||
{
|
|
||||||
if(stack->attackerOwned == !side)
|
|
||||||
ourAttacks[stack] = PotentialTargets(stack);
|
|
||||||
else
|
|
||||||
enemyAttacks[stack] = PotentialTargets(stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int potentialValue()
|
|
||||||
{
|
|
||||||
int ourPotential = 0, enemyPotential = 0;
|
|
||||||
for(auto &p : ourAttacks)
|
|
||||||
ourPotential += p.second.bestAction().attackValue();
|
|
||||||
|
|
||||||
for(auto &p : enemyAttacks)
|
|
||||||
enemyPotential += p.second.bestAction().attackValue();
|
|
||||||
|
|
||||||
return ourPotential - enemyPotential;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// //set has its own order, so remove_if won't work. TODO - reuse for map
|
|
||||||
// template<typename Elem, typename Predicate>
|
|
||||||
// void erase_if(std::set<Elem> &setContainer, Predicate pred)
|
|
||||||
// {
|
|
||||||
// auto itr = setContainer.begin();
|
|
||||||
// auto endItr = setContainer.end();
|
|
||||||
// while(itr != endItr)
|
|
||||||
// {
|
|
||||||
// auto tmpItr = itr++;
|
|
||||||
// if(pred(*tmpItr))
|
|
||||||
// setContainer.erase(tmpItr);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
void CBattleAI::attemptCastingSpell()
|
void CBattleAI::attemptCastingSpell()
|
||||||
{
|
{
|
||||||
LOGL("Casting spells sounds like fun. Let's see...");
|
LOGL("Casting spells sounds like fun. Let's see...");
|
||||||
|
auto hero = cb->battleGetMyHero(); //auto known = cb->battleGetFightingHero(side);
|
||||||
auto hero = cb->battleGetMyHero();
|
|
||||||
|
|
||||||
//auto known = cb->battleGetFightingHero(side);
|
|
||||||
|
|
||||||
//Get all spells we can cast
|
//Get all spells we can cast
|
||||||
std::vector<const CSpell*> possibleSpells;
|
std::vector<const CSpell*> possibleSpells;
|
||||||
vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [this] (const CSpell *s) -> bool
|
vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [this] (const CSpell *s) -> bool
|
||||||
{
|
{
|
||||||
auto problem = cbc->battleCanCastThisSpell(s);
|
auto problem = getCbc()->battleCanCastThisSpell(s);
|
||||||
return problem == ESpellCastProblem::OK;
|
return problem == ESpellCastProblem::OK;
|
||||||
});
|
});
|
||||||
LOGFL("I can cast %d spells.", possibleSpells.size());
|
LOGFL("I can cast %d spells.", possibleSpells.size());
|
||||||
@ -446,74 +241,59 @@ void CBattleAI::attemptCastingSpell()
|
|||||||
{
|
{
|
||||||
const int skillLevel = hero->getSpellSchoolLevel(ps.spell);
|
const int skillLevel = hero->getSpellSchoolLevel(ps.spell);
|
||||||
const int spellPower = hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
|
const int spellPower = hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
|
||||||
|
|
||||||
switch(spellType(ps.spell))
|
switch(spellType(ps.spell))
|
||||||
{
|
{
|
||||||
case OFFENSIVE_SPELL:
|
case OFFENSIVE_SPELL:
|
||||||
|
{
|
||||||
|
int damageDealt = 0, damageReceived = 0;
|
||||||
|
auto stacksSuffering = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest);
|
||||||
|
if(stacksSuffering.empty())
|
||||||
|
return -1;
|
||||||
|
for(auto stack : stacksSuffering)
|
||||||
{
|
{
|
||||||
int damageDealt = 0, damageReceived = 0;
|
const int dmg = ps.spell->calculateDamage(hero, stack, skillLevel, spellPower);
|
||||||
|
if(stack->owner == playerID)
|
||||||
auto stacksSuffering = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest);
|
damageReceived += dmg;
|
||||||
|
else
|
||||||
if(stacksSuffering.empty())
|
damageDealt += dmg;
|
||||||
return -1;
|
|
||||||
|
|
||||||
for(auto stack : stacksSuffering)
|
|
||||||
{
|
|
||||||
const int dmg = ps.spell->calculateDamage(hero, stack, skillLevel, spellPower);
|
|
||||||
if(stack->owner == playerID)
|
|
||||||
damageReceived += dmg;
|
|
||||||
else
|
|
||||||
damageDealt += dmg;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int damageDiff = damageDealt - damageReceived * 10;
|
|
||||||
|
|
||||||
LOGFL("Casting %s on hex %d would deal { %d %d } damage points among %d stacks.",
|
|
||||||
ps.spell->name % ps.dest % damageDealt % damageReceived % stacksSuffering.size());
|
|
||||||
//TODO tactic effect too
|
|
||||||
return damageDiff;
|
|
||||||
}
|
}
|
||||||
|
const int damageDiff = damageDealt - damageReceived * 10;
|
||||||
|
LOGFL("Casting %s on hex %d would deal { %d %d } damage points among %d stacks.",
|
||||||
|
ps.spell->name % ps.dest % damageDealt % damageReceived % stacksSuffering.size());
|
||||||
|
//TODO tactic effect too
|
||||||
|
return damageDiff;
|
||||||
|
}
|
||||||
case TIMED_EFFECT:
|
case TIMED_EFFECT:
|
||||||
|
{
|
||||||
|
auto stacksAffected = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest);
|
||||||
|
if(stacksAffected.empty())
|
||||||
|
return -1;
|
||||||
|
int totalGain = 0;
|
||||||
|
for(const CStack * sta : stacksAffected)
|
||||||
{
|
{
|
||||||
auto stacksAffected = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest);
|
StackWithBonuses swb;
|
||||||
|
swb.stack = sta;
|
||||||
if(stacksAffected.empty())
|
Bonus pseudoBonus;
|
||||||
return -1;
|
pseudoBonus.sid = ps.spell->id;
|
||||||
|
pseudoBonus.val = skillLevel;
|
||||||
int totalGain = 0;
|
pseudoBonus.turnsRemain = 1; //TODO
|
||||||
|
CStack::stackEffectToFeature(swb.bonusesToAdd, pseudoBonus);
|
||||||
for(const CStack * sta : stacksAffected)
|
HypotheticChangesToBattleState state;
|
||||||
{
|
state.bonusesOfStacks[swb.stack] = &swb;
|
||||||
StackWithBonuses swb;
|
PotentialTargets pt(swb.stack, state);
|
||||||
swb.stack = sta;
|
auto newValue = pt.bestActionValue();
|
||||||
|
auto oldValue = valueOfStack[swb.stack];
|
||||||
Bonus pseudoBonus;
|
auto gain = newValue - oldValue;
|
||||||
pseudoBonus.sid = ps.spell->id;
|
if(swb.stack->owner != playerID) //enemy
|
||||||
pseudoBonus.val = skillLevel;
|
gain = -gain;
|
||||||
pseudoBonus.turnsRemain = 1; //TODO
|
LOGFL("Casting %s on %s would improve the stack by %d points (from %d to %d)",
|
||||||
CStack::stackEffectToFeature(swb.bonusesToAdd, pseudoBonus);
|
ps.spell->name % sta->nodeName() % (gain) % (oldValue) % (newValue));
|
||||||
|
totalGain += gain;
|
||||||
HypotheticChangesToBattleState state;
|
|
||||||
state.bonusesOfStacks[swb.stack] = &swb;
|
|
||||||
|
|
||||||
PotentialTargets pt(swb.stack, state);
|
|
||||||
auto newValue = pt.bestActionValue();
|
|
||||||
auto oldValue = valueOfStack[swb.stack];
|
|
||||||
auto gain = newValue - oldValue;
|
|
||||||
if(swb.stack->owner != playerID) //enemy
|
|
||||||
gain = -gain;
|
|
||||||
|
|
||||||
LOGFL("Casting %s on %s would improve the stack by %d points (from %d to %d)",
|
|
||||||
ps.spell->name % sta->nodeName() % (gain) % (oldValue) % (newValue));
|
|
||||||
|
|
||||||
totalGain += gain;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGFL("Total gain of cast %s at hex %d is %d", ps.spell->name % (ps.dest.hex) % (totalGain));
|
|
||||||
|
|
||||||
return totalGain;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOGFL("Total gain of cast %s at hex %d is %d", ps.spell->name % (ps.dest.hex) % (totalGain));
|
||||||
|
return totalGain;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
assert(0);
|
assert(0);
|
||||||
return 0;
|
return 0;
|
||||||
@ -522,22 +302,18 @@ void CBattleAI::attemptCastingSpell()
|
|||||||
|
|
||||||
for(PossibleSpellcast & psc : possibleCasts)
|
for(PossibleSpellcast & psc : possibleCasts)
|
||||||
psc.value = evaluateSpellcast(psc);
|
psc.value = evaluateSpellcast(psc);
|
||||||
|
|
||||||
auto pscValue = [] (const PossibleSpellcast &ps) -> int
|
auto pscValue = [] (const PossibleSpellcast &ps) -> int
|
||||||
{
|
{
|
||||||
return ps.value;
|
return ps.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto castToPerform = *vstd::maxElementByFun(possibleCasts, pscValue);
|
auto castToPerform = *vstd::maxElementByFun(possibleCasts, pscValue);
|
||||||
LOGFL("Best spell is %s. Will cast.", castToPerform.spell->name);
|
LOGFL("Best spell is %s. Will cast.", castToPerform.spell->name);
|
||||||
|
|
||||||
BattleAction spellcast;
|
BattleAction spellcast;
|
||||||
spellcast.actionType = Battle::HERO_SPELL;
|
spellcast.actionType = Battle::HERO_SPELL;
|
||||||
spellcast.additionalInfo = castToPerform.spell->id;
|
spellcast.additionalInfo = castToPerform.spell->id;
|
||||||
spellcast.destinationTile = castToPerform.dest;
|
spellcast.destinationTile = castToPerform.dest;
|
||||||
spellcast.side = side;
|
spellcast.side = side;
|
||||||
spellcast.stackNumber = (!side) ? -1 : -2;
|
spellcast.stackNumber = (!side) ? -1 : -2;
|
||||||
|
|
||||||
cb->battleMakeAction(&spellcast);
|
cb->battleMakeAction(&spellcast);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -545,7 +321,6 @@ std::vector<BattleHex> CBattleAI::getTargetsToConsider(const CSpell * spell, con
|
|||||||
{
|
{
|
||||||
const CSpell::TargetInfo targetInfo(spell, caster->getSpellSchoolLevel(spell));
|
const CSpell::TargetInfo targetInfo(spell, caster->getSpellSchoolLevel(spell));
|
||||||
std::vector<BattleHex> ret;
|
std::vector<BattleHex> ret;
|
||||||
|
|
||||||
if(targetInfo.massive || targetInfo.type == CSpell::NO_TARGET)
|
if(targetInfo.massive || targetInfo.type == CSpell::NO_TARGET)
|
||||||
{
|
{
|
||||||
ret.push_back(BattleHex());
|
ret.push_back(BattleHex());
|
||||||
@ -555,240 +330,86 @@ std::vector<BattleHex> CBattleAI::getTargetsToConsider(const CSpell * spell, con
|
|||||||
switch(targetInfo.type)
|
switch(targetInfo.type)
|
||||||
{
|
{
|
||||||
case CSpell::CREATURE:
|
case CSpell::CREATURE:
|
||||||
|
{
|
||||||
|
for(const CStack * stack : getCbc()->battleAliveStacks())
|
||||||
{
|
{
|
||||||
for(const CStack * stack : cbc->battleAliveStacks())
|
bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack);
|
||||||
{
|
bool casterStack = stack->owner == caster->getOwner();
|
||||||
bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack);
|
|
||||||
bool casterStack = stack->owner == caster->getOwner();
|
|
||||||
|
|
||||||
if(!immune)
|
if(!immune)
|
||||||
switch (spell->positiveness)
|
switch (spell->positiveness)
|
||||||
{
|
{
|
||||||
case CSpell::POSITIVE:
|
case CSpell::POSITIVE:
|
||||||
if(casterStack || targetInfo.smart)
|
if(casterStack || targetInfo.smart)
|
||||||
ret.push_back(stack->position);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CSpell::NEUTRAL:
|
|
||||||
ret.push_back(stack->position);
|
ret.push_back(stack->position);
|
||||||
break;
|
break;
|
||||||
|
case CSpell::NEUTRAL:
|
||||||
case CSpell::NEGATIVE:
|
ret.push_back(stack->position);
|
||||||
if(!casterStack || targetInfo.smart)
|
break;
|
||||||
ret.push_back(stack->position);
|
case CSpell::NEGATIVE:
|
||||||
break;
|
if(!casterStack || targetInfo.smart)
|
||||||
}
|
ret.push_back(stack->position);
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case CSpell::LOCATION:
|
case CSpell::LOCATION:
|
||||||
{
|
{
|
||||||
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
|
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
|
||||||
if(BattleHex(i).isAvailable())
|
if(BattleHex(i).isAvailable())
|
||||||
ret.push_back(i);
|
ret.push_back(i);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int CBattleAI::distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances &dists, BattleHex *chosenHex)
|
||||||
|
{
|
||||||
|
int ret = 1000000;
|
||||||
|
for(BattleHex n : hex.neighbouringTiles())
|
||||||
|
{
|
||||||
|
if(dists[n] >= 0 && dists[n] < ret)
|
||||||
|
{
|
||||||
|
ret = dists[n];
|
||||||
|
if(chosenHex)
|
||||||
|
*chosenHex = n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBattleAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side)
|
||||||
|
{
|
||||||
|
print("battleStart called");
|
||||||
|
side = Side;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CBattleAI::isCloser(const EnemyInfo &ei1, const EnemyInfo &ei2, const ReachabilityInfo::TDistances &dists)
|
||||||
|
{
|
||||||
|
return distToNearestNeighbour(ei1.s->position, dists) < distToNearestNeighbour(ei2.s->position, dists);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBattleAI::print(const std::string &text) const
|
||||||
|
{
|
||||||
|
logAi->trace("CBattleAI [%p]: %s", this, text);
|
||||||
|
}
|
||||||
|
|
||||||
boost::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()
|
boost::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()
|
||||||
{
|
{
|
||||||
if(cb->battleCanSurrender(playerID))
|
if(cb->battleCanSurrender(playerID))
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
if(cb->battleCanFlee())
|
if(cb->battleCanFlee())
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
return boost::none;
|
return boost::none;
|
||||||
}
|
}
|
||||||
|
|
||||||
ThreatMap::ThreatMap(const CStack *Endangered) : endangered(Endangered)
|
|
||||||
{
|
|
||||||
sufferedDamage.fill(0);
|
|
||||||
|
|
||||||
for(const CStack *enemy : cbc->battleGetStacks())
|
|
||||||
{
|
|
||||||
//Consider only stacks of different owner
|
|
||||||
if(enemy->attackerOwned == endangered->attackerOwned)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
//Look-up which tiles can be melee-attacked
|
|
||||||
std::array<bool, GameConstants::BFIELD_SIZE> meleeAttackable;
|
|
||||||
meleeAttackable.fill(false);
|
|
||||||
auto enemyReachability = cbc->getReachability(enemy);
|
|
||||||
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
|
|
||||||
{
|
|
||||||
if(enemyReachability.isReachable(i))
|
|
||||||
{
|
|
||||||
meleeAttackable[i] = true;
|
|
||||||
for(auto n : BattleHex(i).neighbouringTiles())
|
|
||||||
meleeAttackable[n] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Gather possible assaults
|
|
||||||
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
|
|
||||||
{
|
|
||||||
if(cbc->battleCanShoot(enemy, i))
|
|
||||||
threatMap[i].push_back(BattleAttackInfo(enemy, endangered, true));
|
|
||||||
else if(meleeAttackable[i])
|
|
||||||
{
|
|
||||||
BattleAttackInfo bai(enemy, endangered, false);
|
|
||||||
bai.chargedFields = std::max(BattleHex::getDistance(enemy->position, i) - 1, 0); //TODO check real distance (BFS), not just metric
|
|
||||||
threatMap[i].push_back(BattleAttackInfo(bai));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
|
|
||||||
{
|
|
||||||
sufferedDamage[i] = sum(threatMap[i], [](const BattleAttackInfo &bai) -> int
|
|
||||||
{
|
|
||||||
auto dmg = cbc->calculateDmgRange(bai);
|
|
||||||
return (dmg.first + dmg.second)/2;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const TBonusListPtr StackWithBonuses::getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root /*= nullptr*/, const std::string &cachingStr /*= ""*/) const
|
|
||||||
{
|
|
||||||
TBonusListPtr ret = std::make_shared<BonusList>();
|
|
||||||
const TBonusListPtr originalList = stack->getAllBonuses(selector, limit, root, cachingStr);
|
|
||||||
range::copy(*originalList, std::back_inserter(*ret));
|
|
||||||
for(auto &bonus : bonusesToAdd)
|
|
||||||
{
|
|
||||||
auto b = std::make_shared<Bonus>(bonus);
|
|
||||||
if(selector(b.get()) && (!limit || !limit(b.get())))
|
|
||||||
ret->push_back(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO limiters?
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
int AttackPossibility::damageDiff() const
|
|
||||||
{
|
|
||||||
if (!priorities)
|
|
||||||
priorities = new Priorities;
|
|
||||||
const auto dealtDmgValue = priorities->stackEvaluator(enemy) * damageDealt;
|
|
||||||
const auto receivedDmgValue = priorities->stackEvaluator(attack.attacker) * damageReceived;
|
|
||||||
return dealtDmgValue - receivedDmgValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int AttackPossibility::attackValue() const
|
|
||||||
{
|
|
||||||
return damageDiff() + tacticImpact;
|
|
||||||
}
|
|
||||||
|
|
||||||
AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex)
|
|
||||||
{
|
|
||||||
auto attacker = AttackInfo.attacker;
|
|
||||||
auto enemy = AttackInfo.defender;
|
|
||||||
|
|
||||||
const int remainingCounterAttacks = getValOr(state.counterAttacksLeft, enemy, enemy->counterAttacksRemaining());
|
|
||||||
const bool counterAttacksBlocked = attacker->hasBonusOfType(Bonus::BLOCKS_RETALIATION) || enemy->hasBonusOfType(Bonus::NO_RETALIATION);
|
|
||||||
const int totalAttacks = 1 + AttackInfo.attackerBonuses->getBonuses(Selector::type(Bonus::ADDITIONAL_ATTACK), (Selector::effectRange (Bonus::NO_LIMIT).Or(Selector::effectRange(Bonus::ONLY_MELEE_FIGHT))))->totalValue();
|
|
||||||
|
|
||||||
AttackPossibility ap = {enemy, hex, AttackInfo, 0, 0, 0};
|
|
||||||
|
|
||||||
auto curBai = AttackInfo; //we'll modify here the stack counts
|
|
||||||
for(int i = 0; i < totalAttacks; i++)
|
|
||||||
{
|
|
||||||
std::pair<ui32, ui32> retaliation(0,0);
|
|
||||||
auto attackDmg = cbc->battleEstimateDamage(CRandomGenerator::getDefault(), curBai, &retaliation);
|
|
||||||
ap.damageDealt = (attackDmg.first + attackDmg.second) / 2;
|
|
||||||
ap.damageReceived = (retaliation.first + retaliation.second) / 2;
|
|
||||||
|
|
||||||
if(remainingCounterAttacks <= i || counterAttacksBlocked)
|
|
||||||
ap.damageReceived = 0;
|
|
||||||
|
|
||||||
curBai.attackerCount = attacker->count - attacker->countKilledByAttack(ap.damageReceived).first;
|
|
||||||
curBai.defenderCount = enemy->count - enemy->countKilledByAttack(ap.damageDealt).first;
|
|
||||||
if(!curBai.attackerCount)
|
|
||||||
break;
|
|
||||||
//TODO what about defender? should we break? but in pessimistic scenario defender might be alive
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO other damage related to attack (eg. fire shield and other abilities)
|
|
||||||
|
|
||||||
//Limit damages by total stack health
|
|
||||||
vstd::amin(ap.damageDealt, enemy->count * enemy->MaxHealth() - (enemy->MaxHealth() - enemy->firstHPleft));
|
|
||||||
vstd::amin(ap.damageReceived, attacker->count * attacker->MaxHealth() - (attacker->MaxHealth() - attacker->firstHPleft));
|
|
||||||
|
|
||||||
return ap;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
PotentialTargets::PotentialTargets(const CStack *attacker, const HypotheticChangesToBattleState &state /*= HypotheticChangesToBattleState()*/)
|
|
||||||
{
|
|
||||||
auto dists = cbc->battleGetDistances(attacker);
|
|
||||||
auto avHexes = cbc->battleGetAvailableHexes(attacker, false);
|
|
||||||
|
|
||||||
for(const CStack *enemy : cbc->battleGetStacks())
|
|
||||||
{
|
|
||||||
//Consider only stacks of different owner
|
|
||||||
if(enemy->attackerOwned == attacker->attackerOwned)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility
|
|
||||||
{
|
|
||||||
auto bai = BattleAttackInfo(attacker, enemy, shooting);
|
|
||||||
bai.attackerBonuses = getValOr(state.bonusesOfStacks, bai.attacker, bai.attacker);
|
|
||||||
bai.defenderBonuses = getValOr(state.bonusesOfStacks, bai.defender, bai.defender);
|
|
||||||
|
|
||||||
if(hex.isValid())
|
|
||||||
{
|
|
||||||
assert(dists[hex] <= attacker->Speed());
|
|
||||||
bai.chargedFields = dists[hex];
|
|
||||||
}
|
|
||||||
|
|
||||||
return AttackPossibility::evaluate(bai, state, hex);
|
|
||||||
};
|
|
||||||
|
|
||||||
if(cbc->battleCanShoot(attacker, enemy->position))
|
|
||||||
{
|
|
||||||
possibleAttacks.push_back(GenerateAttackInfo(true, BattleHex::INVALID));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for(BattleHex hex : avHexes)
|
|
||||||
if(CStack::isMeleeAttackPossible(attacker, enemy, hex))
|
|
||||||
possibleAttacks.push_back(GenerateAttackInfo(false, hex));
|
|
||||||
|
|
||||||
if(!vstd::contains_if(possibleAttacks, [=](const AttackPossibility &pa) { return pa.enemy == enemy; }))
|
|
||||||
unreachableEnemies.push_back(enemy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AttackPossibility PotentialTargets::bestAction() const
|
|
||||||
{
|
|
||||||
if(possibleAttacks.empty())
|
|
||||||
throw std::runtime_error("No best action, since we don't have any actions");
|
|
||||||
|
|
||||||
return *vstd::maxElementByFun(possibleAttacks, [](const AttackPossibility &ap) { return ap.attackValue(); } );
|
|
||||||
}
|
|
||||||
|
|
||||||
int PotentialTargets::bestActionValue() const
|
|
||||||
{
|
|
||||||
if(possibleAttacks.empty())
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return bestAction().attackValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
void EnemyInfo::calcDmg(const CStack * ourStack)
|
|
||||||
{
|
|
||||||
TDmgRange retal, dmg = cbc->battleEstimateDamage(CRandomGenerator::getDefault(), ourStack, s, &retal);
|
|
||||||
adi = (dmg.first + dmg.second) / 2;
|
|
||||||
adr = (retal.first + retal.second) / 2;
|
|
||||||
}
|
|
||||||
|
@ -1,110 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* BattleAI.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
|
#pragma once
|
||||||
|
#include "../../lib/AI_Base.h"
|
||||||
#include "../../lib/BattleHex.h"
|
#include "PotentialTargets.h"
|
||||||
#include "../../lib/HeroBonus.h"
|
|
||||||
#include "../../lib/CBattleCallback.h"
|
|
||||||
|
|
||||||
class CSpell;
|
class CSpell;
|
||||||
|
class EnemyInfo;
|
||||||
|
|
||||||
|
/*
|
||||||
class StackWithBonuses : public IBonusBearer
|
struct CurrentOffensivePotential
|
||||||
{
|
{
|
||||||
public:
|
std::map<const CStack *, PotentialTargets> ourAttacks;
|
||||||
const CStack *stack;
|
std::map<const CStack *, PotentialTargets> enemyAttacks;
|
||||||
mutable std::vector<Bonus> bonusesToAdd;
|
|
||||||
|
|
||||||
virtual const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override;
|
CurrentOffensivePotential(ui8 side)
|
||||||
};
|
|
||||||
|
|
||||||
struct EnemyInfo
|
|
||||||
{
|
|
||||||
const CStack * s;
|
|
||||||
int adi, adr;
|
|
||||||
std::vector<BattleHex> attackFrom; //for melee fight
|
|
||||||
EnemyInfo(const CStack * _s) : s(_s)
|
|
||||||
{}
|
|
||||||
void calcDmg(const CStack * ourStack);
|
|
||||||
|
|
||||||
bool operator==(const EnemyInfo& ei) const
|
|
||||||
{
|
{
|
||||||
return s == ei.s;
|
for(auto stack : cbc->battleGetStacks())
|
||||||
|
{
|
||||||
|
if(stack->attackerOwned == !side)
|
||||||
|
ourAttacks[stack] = PotentialTargets(stack);
|
||||||
|
else
|
||||||
|
enemyAttacks[stack] = PotentialTargets(stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int potentialValue()
|
||||||
|
{
|
||||||
|
int ourPotential = 0, enemyPotential = 0;
|
||||||
|
for(auto &p : ourAttacks)
|
||||||
|
ourPotential += p.second.bestAction().attackValue();
|
||||||
|
|
||||||
|
for(auto &p : enemyAttacks)
|
||||||
|
enemyPotential += p.second.bestAction().attackValue();
|
||||||
|
|
||||||
|
return ourPotential - enemyPotential;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
*/ // These lines may be usefull but they are't used in the code.
|
||||||
|
|
||||||
|
struct PossibleSpellcast
|
||||||
//FIXME: unused function
|
|
||||||
/*
|
|
||||||
static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const BattleHex &h2)
|
|
||||||
{
|
{
|
||||||
int shooters[2] = {0}; //count of shooters on hexes
|
const CSpell *spell;
|
||||||
|
BattleHex dest;
|
||||||
for(int i = 0; i < 2; i++)
|
si32 value;
|
||||||
BOOST_FOREACH(BattleHex neighbour, (i ? h2 : h1).neighbouringTiles())
|
|
||||||
if(const CStack *s = cbc->battleGetStackByPos(neighbour))
|
|
||||||
if(s->getCreature()->isShooting())
|
|
||||||
shooters[i]++;
|
|
||||||
|
|
||||||
return shooters[0] < shooters[1];
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct ThreatMap
|
|
||||||
{
|
|
||||||
std::array<std::vector<BattleAttackInfo>, GameConstants::BFIELD_SIZE> threatMap; // [hexNr] -> enemies able to strike
|
|
||||||
|
|
||||||
const CStack *endangered;
|
|
||||||
std::array<int, GameConstants::BFIELD_SIZE> sufferedDamage;
|
|
||||||
|
|
||||||
ThreatMap(const CStack *Endangered);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct HypotheticChangesToBattleState
|
|
||||||
{
|
|
||||||
std::map<const CStack *, const IBonusBearer *> bonusesOfStacks;
|
|
||||||
std::map<const CStack *, int> counterAttacksLeft;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AttackPossibility
|
|
||||||
{
|
|
||||||
const CStack *enemy; //redundant (to attack.defender) but looks nice
|
|
||||||
BattleHex tile; //tile from which we attack
|
|
||||||
BattleAttackInfo attack;
|
|
||||||
|
|
||||||
int damageDealt;
|
|
||||||
int damageReceived; //usually by counter-attack
|
|
||||||
int tacticImpact;
|
|
||||||
|
|
||||||
int damageDiff() const;
|
|
||||||
int attackValue() const;
|
|
||||||
|
|
||||||
static AttackPossibility evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex);
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename Key, typename Val, typename Val2>
|
|
||||||
const Val getValOr(const std::map<Key, Val> &Map, const Key &key, const Val2 defaultValue)
|
|
||||||
{
|
|
||||||
//returning references here won't work: defaultValue must be converted into Val, creating temporary
|
|
||||||
auto i = Map.find(key);
|
|
||||||
if(i != Map.end())
|
|
||||||
return i->second;
|
|
||||||
else
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PotentialTargets
|
|
||||||
{
|
|
||||||
std::vector<AttackPossibility> possibleAttacks;
|
|
||||||
std::vector<const CStack *> unreachableEnemies;
|
|
||||||
|
|
||||||
//std::function<AttackPossibility(bool,BattleHex)> GenerateAttackInfo; //args: shooting, destHex
|
|
||||||
|
|
||||||
PotentialTargets(){};
|
|
||||||
PotentialTargets(const CStack *attacker, const HypotheticChangesToBattleState &state = HypotheticChangesToBattleState());
|
|
||||||
|
|
||||||
AttackPossibility bestAction() const;
|
|
||||||
int bestActionValue() const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class CBattleAI : public CBattleGameInterface
|
class CBattleAI : public CBattleGameInterface
|
||||||
@ -115,39 +60,41 @@ class CBattleAI : public CBattleGameInterface
|
|||||||
//Previous setting of cb
|
//Previous setting of cb
|
||||||
bool wasWaitingForRealize, wasUnlockingGs;
|
bool wasWaitingForRealize, wasUnlockingGs;
|
||||||
|
|
||||||
void print(const std::string &text) const;
|
|
||||||
public:
|
public:
|
||||||
CBattleAI(void);
|
CBattleAI(void);
|
||||||
~CBattleAI(void);
|
~CBattleAI(void);
|
||||||
|
|
||||||
void init(std::shared_ptr<CBattleCallback> CB) override;
|
void init(std::shared_ptr<CBattleCallback> CB) override;
|
||||||
void actionFinished(const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero
|
void attemptCastingSpell();
|
||||||
void actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero
|
|
||||||
BattleAction activeStack(const CStack * stack) override; //called when it's turn of that stack
|
BattleAction activeStack(const CStack * stack) override; //called when it's turn of that stack
|
||||||
|
|
||||||
void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
|
|
||||||
void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override; //called when stack receives damage (after battleAttack())
|
|
||||||
void battleEnd(const BattleResult *br) override;
|
|
||||||
//void battleResultsApplied() override; //called when all effects of last battle are applied
|
|
||||||
void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;
|
|
||||||
void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
|
|
||||||
void battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance) override;
|
|
||||||
void battleSpellCast(const BattleSpellCast *sc) override;
|
|
||||||
void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks
|
|
||||||
//void battleTriggerEffect(const BattleTriggerEffect & bte) override;
|
|
||||||
void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right
|
|
||||||
void battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) override; //called when stacks are healed / resurrected first element of pair - stack id, second - healed hp
|
|
||||||
void battleNewStackAppeared(const CStack * stack) override; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned
|
|
||||||
void battleObstaclesRemoved(const std::set<si32> & removedObstacles) override; //called when a certain set of obstacles is removed from batlefield; IDs of them are given
|
|
||||||
void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
|
|
||||||
void battleStacksRemoved(const BattleStacksRemoved & bsr) override; //called when certain stack is completely removed from battlefield
|
|
||||||
|
|
||||||
BattleAction goTowards(const CStack * stack, BattleHex hex );
|
BattleAction goTowards(const CStack * stack, BattleHex hex );
|
||||||
BattleAction useCatapult(const CStack * stack);
|
|
||||||
|
|
||||||
boost::optional<BattleAction> considerFleeingOrSurrendering();
|
boost::optional<BattleAction> considerFleeingOrSurrendering();
|
||||||
|
|
||||||
void attemptCastingSpell();
|
|
||||||
std::vector<BattleHex> getTargetsToConsider(const CSpell *spell, const ISpellCaster * caster) const;
|
std::vector<BattleHex> getTargetsToConsider(const CSpell *spell, const ISpellCaster * caster) const;
|
||||||
};
|
static int distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances& dists, BattleHex *chosenHex = nullptr);
|
||||||
|
static bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const ReachabilityInfo::TDistances & dists);
|
||||||
|
|
||||||
|
void print(const std::string &text) const;
|
||||||
|
BattleAction useCatapult(const CStack *stack);
|
||||||
|
void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool Side);
|
||||||
|
//void actionFinished(const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero
|
||||||
|
//void actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero
|
||||||
|
//void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
|
||||||
|
//void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override; //called when stack receives damage (after battleAttack())
|
||||||
|
//void battleEnd(const BattleResult *br) override;
|
||||||
|
//void battleResultsApplied() override; //called when all effects of last battle are applied
|
||||||
|
//void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;
|
||||||
|
//void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
|
||||||
|
//void battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance) override;
|
||||||
|
//void battleSpellCast(const BattleSpellCast *sc) override;
|
||||||
|
//void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks
|
||||||
|
//void battleTriggerEffect(const BattleTriggerEffect & bte) override;
|
||||||
|
//void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right
|
||||||
|
//void battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) override; //called when stacks are healed / resurrected first element of pair - stack id, second - healed hp
|
||||||
|
//void battleNewStackAppeared(const CStack * stack) override; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned
|
||||||
|
//void battleObstaclesRemoved(const std::set<si32> & removedObstacles) override; //called when a certain set of obstacles is removed from batlefield; IDs of them are given
|
||||||
|
//void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
|
||||||
|
//void battleStacksRemoved(const BattleStacksRemoved & bsr) override; //called when certain stack is completely removed from battlefield
|
||||||
|
};
|
||||||
|
@ -6,7 +6,13 @@ include_directories(${Boost_INCLUDE_DIRS} ${CMAKE_HOME_DIRECTORY} ${CMAKE_HOME_D
|
|||||||
set(battleAI_SRCS
|
set(battleAI_SRCS
|
||||||
StdInc.cpp
|
StdInc.cpp
|
||||||
BattleAI.cpp
|
BattleAI.cpp
|
||||||
|
StackWithBonuses.cpp
|
||||||
|
EnemyInfo.cpp
|
||||||
|
AttackPossibility.cpp
|
||||||
|
PotentialTargets.cpp
|
||||||
main.cpp
|
main.cpp
|
||||||
|
common.cpp
|
||||||
|
ThreatMap.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(BattleAI SHARED ${battleAI_SRCS})
|
add_library(BattleAI SHARED ${battleAI_SRCS})
|
||||||
@ -16,6 +22,6 @@ set_target_properties(BattleAI PROPERTIES ${PCH_PROPERTIES})
|
|||||||
cotire(BattleAI)
|
cotire(BattleAI)
|
||||||
|
|
||||||
if (NOT APPLE) # Already inside vcmiclient bundle
|
if (NOT APPLE) # Already inside vcmiclient bundle
|
||||||
install(TARGETS BattleAI RUNTIME DESTINATION ${AI_LIB_DIR} LIBRARY DESTINATION ${AI_LIB_DIR})
|
install(TARGETS BattleAI RUNTIME DESTINATION ${AI_LIB_DIR} LIBRARY DESTINATION ${AI_LIB_DIR})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
21
AI/BattleAI/EnemyInfo.cpp
Normal file
21
AI/BattleAI/EnemyInfo.cpp
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* EnemyInfo.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 "EnemyInfo.h"
|
||||||
|
#include "CRandomGenerator.h"
|
||||||
|
#include "CCallback.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
void EnemyInfo::calcDmg(const CStack * ourStack)
|
||||||
|
{
|
||||||
|
TDmgRange retal, dmg = getCbc()->battleEstimateDamage(CRandomGenerator::getDefault(), ourStack, s, &retal);
|
||||||
|
adi = (dmg.first + dmg.second) / 2;
|
||||||
|
adr = (retal.first + retal.second) / 2;
|
||||||
|
}
|
28
AI/BattleAI/EnemyInfo.h
Normal file
28
AI/BattleAI/EnemyInfo.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* EnemyInfo.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 "BattleHex.h"
|
||||||
|
|
||||||
|
class CStack;
|
||||||
|
|
||||||
|
class EnemyInfo
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
const CStack * s;
|
||||||
|
int adi, adr;
|
||||||
|
std::vector<BattleHex> attackFrom; //for melee fight
|
||||||
|
EnemyInfo(const CStack * _s) : s(_s)
|
||||||
|
{}
|
||||||
|
void calcDmg(const CStack * ourStack);
|
||||||
|
bool operator==(const EnemyInfo& ei) const
|
||||||
|
{
|
||||||
|
return s == ei.s;
|
||||||
|
}
|
||||||
|
};
|
71
AI/BattleAI/PotentialTargets.cpp
Normal file
71
AI/BattleAI/PotentialTargets.cpp
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* PotentialTargets.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 "PotentialTargets.h"
|
||||||
|
|
||||||
|
PotentialTargets::PotentialTargets(const CStack *attacker, const HypotheticChangesToBattleState &state /*= HypotheticChangesToBattleState()*/)
|
||||||
|
{
|
||||||
|
auto dists = getCbc()->battleGetDistances(attacker);
|
||||||
|
auto avHexes = getCbc()->battleGetAvailableHexes(attacker, false);
|
||||||
|
|
||||||
|
for(const CStack *enemy : getCbc()->battleGetStacks())
|
||||||
|
{
|
||||||
|
//Consider only stacks of different owner
|
||||||
|
if(enemy->attackerOwned == attacker->attackerOwned)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility
|
||||||
|
{
|
||||||
|
auto bai = BattleAttackInfo(attacker, enemy, shooting);
|
||||||
|
bai.attackerBonuses = getValOr(state.bonusesOfStacks, bai.attacker, bai.attacker);
|
||||||
|
bai.defenderBonuses = getValOr(state.bonusesOfStacks, bai.defender, bai.defender);
|
||||||
|
|
||||||
|
if(hex.isValid())
|
||||||
|
{
|
||||||
|
assert(dists[hex] <= attacker->Speed());
|
||||||
|
bai.chargedFields = dists[hex];
|
||||||
|
}
|
||||||
|
|
||||||
|
return AttackPossibility::evaluate(bai, state, hex);
|
||||||
|
};
|
||||||
|
|
||||||
|
if(getCbc()->battleCanShoot(attacker, enemy->position))
|
||||||
|
{
|
||||||
|
possibleAttacks.push_back(GenerateAttackInfo(true, BattleHex::INVALID));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for(BattleHex hex : avHexes)
|
||||||
|
if(CStack::isMeleeAttackPossible(attacker, enemy, hex))
|
||||||
|
possibleAttacks.push_back(GenerateAttackInfo(false, hex));
|
||||||
|
|
||||||
|
if(!vstd::contains_if(possibleAttacks, [=](const AttackPossibility &pa) { return pa.enemy == enemy; }))
|
||||||
|
unreachableEnemies.push_back(enemy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int PotentialTargets::bestActionValue() const
|
||||||
|
{
|
||||||
|
if(possibleAttacks.empty())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return bestAction().attackValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
AttackPossibility PotentialTargets::bestAction() const
|
||||||
|
{
|
||||||
|
if(possibleAttacks.empty())
|
||||||
|
throw std::runtime_error("No best action, since we don't have any actions");
|
||||||
|
|
||||||
|
return *vstd::maxElementByFun(possibleAttacks, [](const AttackPossibility &ap) { return ap.attackValue(); } );
|
||||||
|
}
|
26
AI/BattleAI/PotentialTargets.h
Normal file
26
AI/BattleAI/PotentialTargets.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* PotentialTargets.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 "AttackPossibility.h"
|
||||||
|
|
||||||
|
class PotentialTargets
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::vector<AttackPossibility> possibleAttacks;
|
||||||
|
std::vector<const CStack *> unreachableEnemies;
|
||||||
|
|
||||||
|
//std::function<AttackPossibility(bool,BattleHex)> GenerateAttackInfo; //args: shooting, destHex
|
||||||
|
|
||||||
|
PotentialTargets(){};
|
||||||
|
PotentialTargets(const CStack *attacker, const HypotheticChangesToBattleState &state = HypotheticChangesToBattleState());
|
||||||
|
|
||||||
|
AttackPossibility bestAction() const;
|
||||||
|
int bestActionValue() const;
|
||||||
|
};
|
28
AI/BattleAI/StackWithBonuses.cpp
Normal file
28
AI/BattleAI/StackWithBonuses.cpp
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* StackWithBonuses.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 "StackWithBonuses.h"
|
||||||
|
#include "../../lib/BattleState.h"
|
||||||
|
|
||||||
|
const TBonusListPtr StackWithBonuses::getAllBonuses(const CSelector &selector, const CSelector &limit,
|
||||||
|
const CBonusSystemNode *root /*= nullptr*/, const std::string &cachingStr /*= ""*/) const
|
||||||
|
{
|
||||||
|
TBonusListPtr ret = std::make_shared<BonusList>();
|
||||||
|
const TBonusListPtr originalList = stack->getAllBonuses(selector, limit, root, cachingStr);
|
||||||
|
range::copy(*originalList, std::back_inserter(*ret));
|
||||||
|
for(auto &bonus : bonusesToAdd)
|
||||||
|
{
|
||||||
|
auto b = std::make_shared<Bonus>(bonus);
|
||||||
|
if(selector(b.get()) && (!limit || !limit(b.get())))
|
||||||
|
ret->push_back(b);
|
||||||
|
}
|
||||||
|
//TODO limiters?
|
||||||
|
return ret;
|
||||||
|
}
|
23
AI/BattleAI/StackWithBonuses.h
Normal file
23
AI/BattleAI/StackWithBonuses.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* StackWithBonuses.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 "../../lib/HeroBonus.h"
|
||||||
|
|
||||||
|
class CStack;
|
||||||
|
|
||||||
|
class StackWithBonuses : public IBonusBearer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
const CStack *stack;
|
||||||
|
mutable std::vector<Bonus> bonusesToAdd;
|
||||||
|
|
||||||
|
virtual const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit,
|
||||||
|
const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override;
|
||||||
|
};
|
@ -1,2 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* StdInc.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
|
||||||
|
*
|
||||||
|
*/
|
||||||
// Creates the precompiled header
|
// Creates the precompiled header
|
||||||
#include "StdInc.h"
|
#include "StdInc.h"
|
||||||
|
@ -1,5 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* StdInc.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
|
#pragma once
|
||||||
|
|
||||||
#include "../../Global.h"
|
#include "../../Global.h"
|
||||||
|
|
||||||
// This header should be treated as a pre compiled header file(PCH) in the compiler building settings.
|
// This header should be treated as a pre compiled header file(PCH) in the compiler building settings.
|
||||||
|
72
AI/BattleAI/ThreatMap.cpp
Normal file
72
AI/BattleAI/ThreatMap.cpp
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* ThreatMap.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 "ThreatMap.h"
|
||||||
|
#include "StdInc.h"
|
||||||
|
|
||||||
|
template <typename Container, typename Pred>
|
||||||
|
auto sum(const Container & c, Pred p) -> decltype(p(*std::begin(c)))
|
||||||
|
{
|
||||||
|
double ret = 0;
|
||||||
|
for(const auto &element : c)
|
||||||
|
{
|
||||||
|
ret += p(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
ThreatMap::ThreatMap(const CStack *Endangered) : endangered(Endangered)
|
||||||
|
{
|
||||||
|
sufferedDamage.fill(0);
|
||||||
|
|
||||||
|
for(const CStack *enemy : getCbc()->battleGetStacks())
|
||||||
|
{
|
||||||
|
//Consider only stacks of different owner
|
||||||
|
if(enemy->attackerOwned == endangered->attackerOwned)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
//Look-up which tiles can be melee-attacked
|
||||||
|
std::array<bool, GameConstants::BFIELD_SIZE> meleeAttackable;
|
||||||
|
meleeAttackable.fill(false);
|
||||||
|
auto enemyReachability = getCbc()->getReachability(enemy);
|
||||||
|
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
|
||||||
|
{
|
||||||
|
if(enemyReachability.isReachable(i))
|
||||||
|
{
|
||||||
|
meleeAttackable[i] = true;
|
||||||
|
for(auto n : BattleHex(i).neighbouringTiles())
|
||||||
|
meleeAttackable[n] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Gather possible assaults
|
||||||
|
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
|
||||||
|
{
|
||||||
|
if(getCbc()->battleCanShoot(enemy, i))
|
||||||
|
threatMap[i].push_back(BattleAttackInfo(enemy, endangered, true));
|
||||||
|
else if(meleeAttackable[i])
|
||||||
|
{
|
||||||
|
BattleAttackInfo bai(enemy, endangered, false);
|
||||||
|
bai.chargedFields = std::max(BattleHex::getDistance(enemy->position, i) - 1, 0); //TODO check real distance (BFS), not just metric
|
||||||
|
threatMap[i].push_back(BattleAttackInfo(bai));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
|
||||||
|
{
|
||||||
|
sufferedDamage[i] = sum(threatMap[i], [](const BattleAttackInfo &bai) -> int
|
||||||
|
{
|
||||||
|
auto dmg = getCbc()->calculateDmgRange(bai);
|
||||||
|
return (dmg.first + dmg.second)/2;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/ // These lines may be usefull but they are't used in the code.
|
26
AI/BattleAI/ThreatMap.h
Normal file
26
AI/BattleAI/ThreatMap.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* ThreatMap.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 "common.h"
|
||||||
|
#include "../../lib/BattleState.h"
|
||||||
|
#include "CCallback.h"
|
||||||
|
/*
|
||||||
|
class ThreatMap
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::array<std::vector<BattleAttackInfo>, GameConstants::BFIELD_SIZE> threatMap; // [hexNr] -> enemies able to strike
|
||||||
|
|
||||||
|
const CStack *endangered;
|
||||||
|
std::array<int, GameConstants::BFIELD_SIZE> sufferedDamage;
|
||||||
|
|
||||||
|
ThreatMap(const CStack *Endangered);
|
||||||
|
};*/ // These lines may be usefull but they are't used in the code.
|
23
AI/BattleAI/common.cpp
Normal file
23
AI/BattleAI/common.cpp
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* common.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 "common.h"
|
||||||
|
|
||||||
|
std::shared_ptr<CBattleCallback> cbc;
|
||||||
|
|
||||||
|
void setCbc(std::shared_ptr<CBattleCallback> cb)
|
||||||
|
{
|
||||||
|
cbc = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<CBattleCallback> getCbc()
|
||||||
|
{
|
||||||
|
return cbc;
|
||||||
|
}
|
26
AI/BattleAI/common.h
Normal file
26
AI/BattleAI/common.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* common.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
|
||||||
|
|
||||||
|
class CBattleCallback;
|
||||||
|
|
||||||
|
template<typename Key, typename Val, typename Val2>
|
||||||
|
const Val getValOr(const std::map<Key, Val> &Map, const Key &key, const Val2 defaultValue)
|
||||||
|
{
|
||||||
|
//returning references here won't work: defaultValue must be converted into Val, creating temporary
|
||||||
|
auto i = Map.find(key);
|
||||||
|
if(i != Map.end())
|
||||||
|
return i->second;
|
||||||
|
else
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCbc(std::shared_ptr<CBattleCallback> cb);
|
||||||
|
std::shared_ptr<CBattleCallback> getCbc();
|
@ -1,5 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* main.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 "StdInc.h"
|
||||||
|
|
||||||
#include "../../lib/AI_Base.h"
|
#include "../../lib/AI_Base.h"
|
||||||
#include "BattleAI.h"
|
#include "BattleAI.h"
|
||||||
|
|
||||||
|
3
AUTHORS
3
AUTHORS
@ -57,3 +57,6 @@ Arseniy Shestakov aka SXX, <me@arseniyshestakov.com>
|
|||||||
|
|
||||||
Vadim Markovtsev, <gmarkhor@gmail.com>
|
Vadim Markovtsev, <gmarkhor@gmail.com>
|
||||||
* resolving problems with macOS, bug fixes
|
* resolving problems with macOS, bug fixes
|
||||||
|
|
||||||
|
Michał Kalinowski, <feniks_fire@o2.pl>
|
||||||
|
* refactoring code
|
||||||
|
Loading…
Reference in New Issue
Block a user