vcmi-0.98/ 0000775 0000000 0000000 00000000000 12506717576 0012430 5 ustar 00root root 0000000 0000000 vcmi-0.98/.gitignore 0000664 0000000 0000000 00000000606 12506717576 0014422 0 ustar 00root root 0000000 0000000 /client/vcmiclient
/server/vcmiserver
/launcher/vcmilauncher
/launcher/vcmilauncher_automoc.cpp
*.dll
*.exe
*.depend
*.o
*.a
*.so
*.res
*.layout
*.pro.user
*.pro.user.*
/CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
Makefile
cmake_install.cmake
install_manifest.txt
*_cotire.cmake
cotire
moc_*.cpp
qrc_*.cpp
ui_*.h
/CPackConfig.cmake
/CPackSourceConfig.cmake
build-*
CMakeLists.txt.user.*
vcmi-0.98/.travis.yml 0000664 0000000 0000000 00000003641 12506717576 0014545 0 ustar 00root root 0000000 0000000 language: cpp
before_install:
#new boost
- sudo add-apt-repository --yes ppa:boost-latest/ppa
#new GCC
- sudo add-apt-repository --yes ppa:ubuntu-toolchain-r/test
#new Clang
- sudo add-apt-repository --yes ppa:h-rayflood/llvm
#new SDL2
- sudo add-apt-repository --yes ppa:zoogie/sdl2-snapshots
#new Qt
- sudo add-apt-repository --yes ppa:beineri/opt-qt532
#new FFmpeg
- sudo add-apt-repository --yes ppa:jon-severinsson/ffmpeg
#new CMake
- sudo add-apt-repository --yes ppa:andykimpe/cmake
- sudo apt-get update -qq
- sudo apt-get install -qq $SUPPORT
- sudo apt-get install -qq $PACKAGE
- sudo apt-get install -qq cmake libboost1.55-all-dev zlib1g-dev
- sudo apt-get install -qq libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev
- sudo apt-get install -qq libavformat-dev libswscale-dev
- sudo apt-get install -qq qt53declarative
#setup compiler
- source /opt/qt53/bin/qt53-env.sh
- export CC=${REAL_CC} CXX=${REAL_CXX}
before_script:
- mkdir build
- cd build
- cmake ..
script:
- make
env:
- ignore=this
matrix:
exclude:
- env: ignore=this
include:
- compiler: clang # fails all the time - missing packages?
env: REAL_CC=clang-3.2 REAL_CXX=clang++-3.2 PACKAGE=clang-3.2 SUPPORT=g++-4.8
- compiler: clang
env: REAL_CC=clang-3.3 REAL_CXX=clang++-3.3 PACKAGE=clang-3.3 SUPPORT=g++-4.8
- compiler: clang
env: REAL_CC=clang-3.4 REAL_CXX=clang++-3.4 PACKAGE=clang-3.4 SUPPORT=g++-4.8
#- compiler: gcc # fails due to running out of memory - vcmi need too much of it for successfull compilation
# env: REAL_CC=gcc-4.7 REAL_CXX=g++-4.7 PACKAGE=g++-4.7 SUPPORT=
#- compiler: gcc # same as 4.7
# env: REAL_CC=gcc-4.8 REAL_CXX=g++-4.8 PACKAGE=g++-4.8 SUPPORT=
notifications:
email:
recipients:
- vcmi.fail@mixaill.tk
- saven.ivan@gmail.com
on_success: change
on_failure: always
vcmi-0.98/AI/ 0000775 0000000 0000000 00000000000 12506717576 0012721 5 ustar 00root root 0000000 0000000 vcmi-0.98/AI/BattleAI/ 0000775 0000000 0000000 00000000000 12506717576 0014346 5 ustar 00root root 0000000 0000000 vcmi-0.98/AI/BattleAI/BattleAI.cbp 0000664 0000000 0000000 00000005636 12506717576 0016473 0 ustar 00root root 0000000 0000000
vcmi-0.98/AI/BattleAI/BattleAI.cpp 0000664 0000000 0000000 00000051553 12506717576 0016510 0 ustar 00root root 0000000 0000000 #include "StdInc.h"
#include "../../lib/AI_Base.h"
#include "BattleAI.h"
#include "../../lib/BattleState.h"
#include "../../CCallback.h"
#include "../../lib/CCreatureHandler.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/VCMI_Lib.h"
using boost::optional;
static shared_ptr cbc;
#define LOGL(text) print(text)
#define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl))
struct Priorities
{
double manaValue;
double generalResourceValueModifier;
std::vector resourceTypeBaseValues;
std::function 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
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)
: side(-1)
{
print("created");
}
CBattleAI::~CBattleAI(void)
{
print("destroyed");
if(cb)
{
//Restore previous state of CB - it may be shared with the main AI (like VCAI)
cb->waitTillRealize = wasWaitingForRealize;
cb->unlockGsWhenWaiting = wasUnlockingGs;
}
}
void CBattleAI::init(shared_ptr CB)
{
print("init called, saving ptr to IBattleCallback");
cbc = cb = CB;
playerID = *CB->getPlayerID();; //TODO should be sth in callback
wasWaitingForRealize = cb->waitTillRealize;
wasUnlockingGs = CB->unlockGsWhenWaiting;
CB->waitTillRealize = true;
CB->unlockGsWhenWaiting = false;
}
static bool thereRemainsEnemy()
{
return !cbc->battleIsFinished();
}
BattleAction CBattleAI::activeStack( const CStack * stack )
{
LOG_TRACE_PARAMS(logAi, "stack: %s", stack->nodeName()) ;
cbc = cb; //TODO: make solid sure that AIs always use their callbacks (need to take care of event handlers too)
try
{
print("activeStack called for " + stack->nodeName());
if(stack->type->idNumber == CreatureID::CATAPULT)
return useCatapult(stack);
if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->hasBonusOfType(Bonus::HEALER))
{
auto healingTargets = cb->battleGetStacks(CBattleInfoEssentials::ONLY_MINE);
std::map woundHpToStack;
for(auto stack : healingTargets)
if(auto woundHp = stack->MaxHealth() - stack->firstHPleft)
woundHpToStack[woundHp] = stack;
if(woundHpToStack.empty())
return BattleAction::makeDefend(stack);
else
return BattleAction::makeHeal(stack, woundHpToStack.rbegin()->second); //last element of the woundHpToStack is the most wounded stack
}
if(cb->battleCanCastSpell())
attemptCastingSpell();
if(!thereRemainsEnemy())
return BattleAction();
if(auto action = considerFleeingOrSurrendering())
return *action;
if(cb->battleGetStacks(CBattleInfoEssentials::ONLY_ENEMY).empty())
{
//We apparently won battle by casting spell, return defend... (accessing cb may cause trouble)
return BattleAction::makeDefend(stack);
}
PotentialTargets targets(stack);
if(targets.possibleAttacks.size())
{
auto hlp = targets.bestAction();
if(hlp.attack.shooting)
return BattleAction::makeShotAttack(stack, hlp.enemy);
else
return BattleAction::makeMeleeAttack(stack, hlp.enemy, hlp.tile);
}
else
{
if(stack->waited())
{
ThreatMap threatsToUs(stack);
auto dists = cbc->battleGetDistances(stack);
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)
{
return goTowards(stack, ei.s->position);
}
}
else
{
return BattleAction::makeWait(stack);
}
}
}
catch(std::exception &e)
{
logAi->errorStream() << "Exception occurred in " << __FUNCTION__ << " " << e.what();
}
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 & 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 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 > & 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 & 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->traceStream() << "CBattleAI [" << this <<"]: " << text;
}
BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination)
{
assert(destination.isValid());
auto avHexes = cb->battleGetAvailableHexes(stack, false);
auto reachability = cb->getReachability(stack);
if(vstd::contains(avHexes, destination))
return BattleAction::makeMove(stack, destination);
auto destNeighbours = destination.neighbouringTiles();
if(vstd::contains_if(destNeighbours, [&](BattleHex n) { return stack->coversPos(destination); }))
{
logAi->warnStream() << "Warning: already standing on neighbouring tile!";
//We shouldn't even be here...
return BattleAction::makeDefend(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
{
print("goTowards: Stack cannot move! That's " + stack->nodeName());
return BattleAction::makeDefend(stack);
}
if(stack->hasBonusOfType(Bonus::FLYING))
{
// Flying stack doesn't go hex by hex, so we can't backtrack using predecessors.
// We just check all available hexes and pick the one closest to the target.
auto distToDestNeighbour = [&](BattleHex hex) -> int
{
auto nearestNeighbourToHex = vstd::minElementByFun(destNeighbours, [&](BattleHex a)
{
return BattleHex::getDistance(a, hex);
});
return BattleHex::getDistance(*nearestNeighbourToHex, hex);
};
auto nearestAvailableHex = vstd::minElementByFun(avHexes, distToDestNeighbour);
return BattleAction::makeMove(stack, *nearestAvailableHex);
}
else
{
BattleHex bestNeighbor = destination;
if(distToNearestNeighbour(destination, reachability.distances, &bestNeighbor) > GameConstants::BFIELD_SIZE)
{
print("goTowards: Cannot reach");
return BattleAction::makeDefend(stack);
}
BattleHex currentDest = bestNeighbor;
while(1)
{
assert(currentDest.isValid());
if(vstd::contains(avHexes, currentDest))
return BattleAction::makeMove(stack, currentDest);
currentDest = reachability.predecessors[currentDest];
}
}
}
BattleAction CBattleAI::useCatapult(const CStack * stack)
{
throw std::runtime_error("The method or operation is not implemented.");
}
enum SpellTypes
{
OFFENSIVE_SPELL, TIMED_EFFECT, OTHER
};
SpellTypes spellType(const CSpell *spell)
{
if (spell->isOffensiveSpell())
return OFFENSIVE_SPELL;
if (spell->hasEffects())
return TIMED_EFFECT;
return OTHER;
}
struct PossibleSpellcast
{
const CSpell *spell;
BattleHex dest;
};
struct CurrentOffensivePotential
{
std::map ourAttacks;
std::map 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
// void erase_if(std::set &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()
{
LOGL("Casting spells sounds like fun. Let's see...");
auto hero = cb->battleGetMyHero();
//auto known = cb->battleGetFightingHero(side);
//Get all spells we can cast
std::vector possibleSpells;
vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [this] (const CSpell *s) -> bool
{
auto problem = cbc->battleCanCastThisSpell(s);
return problem == ESpellCastProblem::OK;
});
LOGFL("I can cast %d spells.", possibleSpells.size());
vstd::erase_if(possibleSpells, [](const CSpell *s)
{return spellType(s) == OTHER; });
LOGFL("I know about workings of %d of them.", possibleSpells.size());
//Get possible spell-target pairs
std::vector possibleCasts;
for(auto spell : possibleSpells)
{
for(auto hex : getTargetsToConsider(spell))
{
PossibleSpellcast ps = {spell, hex};
possibleCasts.push_back(ps);
}
}
LOGFL("Found %d spell-target combinations.", possibleCasts.size());
if(possibleCasts.empty())
return;
std::map valueOfStack;
for(auto stack : cb->battleGetStacks())
{
PotentialTargets pt(stack);
valueOfStack[stack] = pt.bestActionValue();
}
auto evaluateSpellcast = [&] (const PossibleSpellcast &ps) -> int
{
const int skillLevel = hero->getSpellSchoolLevel(ps.spell);
const int spellPower = hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
switch(spellType(ps.spell))
{
case OFFENSIVE_SPELL:
{
int damageDealt = 0, damageReceived = 0;
auto stacksSuffering = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, playerID, skillLevel, ps.dest, hero);
if(stacksSuffering.empty())
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;
LOGFL("Casting %s on hex %d would deal %d damage points among %d stacks.",
ps.spell->name % ps.dest % damageDiff % stacksSuffering.size());
//TODO tactic effect too
return damageDiff;
}
case TIMED_EFFECT:
{
StackWithBonuses swb;
swb.stack = cb->battleGetStackByPos(ps.dest);
if(!swb.stack)
return -1;
Bonus pseudoBonus;
pseudoBonus.sid = ps.spell->id;
pseudoBonus.val = skillLevel;
pseudoBonus.turnsRemain = 1; //TODO
CStack::stackEffectToFeature(swb.bonusesToAdd, pseudoBonus);
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 % swb.stack->nodeName() % gain % (oldValue) % (newValue));
return gain;
}
default:
assert(0);
return 0;
}
};
auto castToPerform = *vstd::maxElementByFun(possibleCasts, evaluateSpellcast);
LOGFL("Best spell is %s. Will cast.", castToPerform.spell->name);
BattleAction spellcast;
spellcast.actionType = Battle::HERO_SPELL;
spellcast.additionalInfo = castToPerform.spell->id;
spellcast.destinationTile = castToPerform.dest;
spellcast.side = side;
spellcast.stackNumber = (!side) ? -1 : -2;
cb->battleMakeAction(&spellcast);
}
std::vector CBattleAI::getTargetsToConsider( const CSpell *spell ) const
{
if(spell->getTargetType() == CSpell::NO_TARGET)
{
//Spell can be casted anywhere, all hexes are potentially considerable.
std::vector ret;
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
if(BattleHex(i).isAvailable())
ret.push_back(i);
return ret;
}
else
{
//TODO when massive effect -> doesn't matter where cast
return cbc->battleGetPossibleTargets(playerID, spell);
}
}
boost::optional CBattleAI::considerFleeingOrSurrendering()
{
if(cb->battleCanSurrender(playerID))
{
}
if(cb->battleCanFlee())
{
}
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 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 = make_shared();
const TBonusListPtr originalList = stack->getAllBonuses(selector, limit, root, cachingStr);
range::copy(*originalList, std::back_inserter(*ret));
for(auto &bonus : bonusesToAdd)
{
if(selector(&bonus) && (!limit || !limit(&bonus)))
ret->push_back(&bonus);
}
//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->counterAttacks);
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 retaliation(0,0);
auto attackDmg = cbc->battleEstimateDamage(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(ourStack, s, &retal);
adi = (dmg.first + dmg.second) / 2;
adr = (retal.first + retal.second) / 2;
}
vcmi-0.98/AI/BattleAI/BattleAI.h 0000664 0000000 0000000 00000013460 12506717576 0016150 0 ustar 00root root 0000000 0000000 #pragma once
#include "../../lib/BattleHex.h"
#include "../../lib/HeroBonus.h"
#include "../../lib/CBattleCallback.h"
class CSpell;
class StackWithBonuses : public IBonusBearer
{
public:
const CStack *stack;
mutable std::vector bonusesToAdd;
virtual const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override;
};
struct EnemyInfo
{
const CStack * s;
int adi, adr;
std::vector 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;
}
};
//FIXME: unused function
/*
static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const BattleHex &h2)
{
int shooters[2] = {0}; //count of shooters on hexes
for(int i = 0; i < 2; i++)
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, GameConstants::BFIELD_SIZE> threatMap; // [hexNr] -> enemies able to strike
const CStack *endangered;
std::array sufferedDamage;
ThreatMap(const CStack *Endangered);
};
struct HypotheticChangesToBattleState
{
std::map bonusesOfStacks;
std::map 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
const Val getValOr(const std::map &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 possibleAttacks;
std::vector unreachableEnemies;
//std::function GenerateAttackInfo; //args: shooting, destHex
PotentialTargets(){};
PotentialTargets(const CStack *attacker, const HypotheticChangesToBattleState &state = HypotheticChangesToBattleState());
AttackPossibility bestAction() const;
int bestActionValue() const;
};
class CBattleAI : public CBattleGameInterface
{
int side;
shared_ptr cb;
//Previous setting of cb
bool wasWaitingForRealize, wasUnlockingGs;
void print(const std::string &text) const;
public:
CBattleAI(void);
~CBattleAI(void);
void init(shared_ptr CB) override;
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
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 & 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 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 > & 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 & 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 useCatapult(const CStack * stack);
boost::optional considerFleeingOrSurrendering();
void attemptCastingSpell();
std::vector getTargetsToConsider(const CSpell *spell) const;
};
vcmi-0.98/AI/BattleAI/BattleAI.vcxproj 0000664 0000000 0000000 00000017425 12506717576 0017421 0 ustar 00root root 0000000 0000000
Debug
Win32
Debug
x64
RD
Win32
RD
x64
{C0300513-E845-43B4-9A4F-E8817EAEF57C}
BattleAI
DynamicLibrary
true
MultiByte
v120_xp
DynamicLibrary
true
MultiByte
v120_xp
DynamicLibrary
false
true
MultiByte
v120_xp
DynamicLibrary
false
true
MultiByte
v120_xp
$(VCMI_Out)\AI\
$(VCMI_Out)\AI\
..
$(VCMI_Out)\AI\
Use
StdInc.h
/Zm159 %(AdditionalOptions)
VCMI_lib.lib;%(AdditionalDependencies)
..\..\..\libs;..\..;..
Level3
Disabled
Use
StdInc.h
/Zm159 %(AdditionalOptions)
VCMI_lib.lib;%(AdditionalDependencies)
Use
StdInc.h
/Zm159 %(AdditionalOptions)
VCMI_lib.lib;%(AdditionalDependencies)
..\..\..\libs;..\..
Use
StdInc.h
/Zm159 %(AdditionalOptions)
VCMI_lib.lib;%(AdditionalDependencies)
%(PreprocessorDefinitions)
Create
StdInc.h
Create
Create
Create