mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-24 22:14:36 +02:00
Work in progress on BattleAI. Related changes:
* battle AIs receive ptr to CBattleCallback (not sure why it was CPlayerBattleCallback, likely mistake) * reworked some battle callback methods to be more generic and able to handle some hypothetic scenarios * for testing purposes in duel mode the first AI will be taken fro mconfig and the second will remain stupid ai * minor changes
This commit is contained in:
parent
871f680ccc
commit
62e63d45b1
@ -4,8 +4,59 @@
|
||||
#include "../../lib/BattleState.h"
|
||||
#include "../../CCallback.h"
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
#include "../../lib/CSpellHandler.h"
|
||||
#include "../../lib/VCMI_Lib.h"
|
||||
|
||||
CPlayerBattleCallback * cbc;
|
||||
using boost::optional;
|
||||
CBattleCallback * cbc;
|
||||
|
||||
//#define LOGL(text) tlog6 << (text) << std::endl
|
||||
//#define LOGFL(text, formattingEl) tlog6 << boost::str(boost::format(text) % formattingEl) << std::endl
|
||||
#define LOGL(text) print(text)
|
||||
#define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl))
|
||||
|
||||
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 = NULL, const std::string &cachingStr = "") const OVERRIDE
|
||||
{
|
||||
TBonusListPtr ret = make_shared<BonusList>();
|
||||
const TBonusListPtr originalList = stack->getAllBonuses(selector, limit, root, cachingStr);
|
||||
boost::copy(*originalList, std::back_inserter(*ret));
|
||||
BOOST_FOREACH(auto &bonus, bonusesToAdd)
|
||||
{
|
||||
if(selector(&bonus) && (!limit || !limit(&bonus)))
|
||||
ret->push_back(&bonus);
|
||||
}
|
||||
|
||||
//TODO limiters?
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
struct Skirmish
|
||||
{
|
||||
const CStack *attacker, *defender;
|
||||
int retaliationDamage, dealtDamage;
|
||||
|
||||
Skirmish(const CStack *Attacker, const CStack *Defender)
|
||||
:attacker(Attacker), defender(Defender)
|
||||
{
|
||||
TDmgRange retal, dmg = cbc->battleEstimateDamage(attacker, defender, &retal);
|
||||
dealtDamage = (dmg.first + dmg.second) / 2;
|
||||
retaliationDamage = (retal.first + retal.second) / 2;
|
||||
|
||||
if(attacker->hasBonusOfType(Bonus::ADDITIONAL_ATTACK))
|
||||
dealtDamage *= 2;
|
||||
if(attacker->hasBonusOfType(Bonus::BLOCKS_RETALIATION) || defender->hasBonusOfType(Bonus::NO_RETALIATION))
|
||||
retaliationDamage = 0;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
CBattleAI::CBattleAI(void)
|
||||
: side(-1), cb(NULL)
|
||||
@ -19,10 +70,13 @@ CBattleAI::~CBattleAI(void)
|
||||
print("destroyed");
|
||||
}
|
||||
|
||||
void CBattleAI::init( CPlayerBattleCallback * CB )
|
||||
void CBattleAI::init( CBattleCallback * CB )
|
||||
{
|
||||
print("init called, saving ptr to IBattleCallback");
|
||||
cbc = cb = CB;
|
||||
playerID = CB->getPlayerID();; //TODO should be sth in callback
|
||||
CB->waitTillRealize = true;
|
||||
CB->unlockGsWhenWaiting = false;
|
||||
}
|
||||
|
||||
void CBattleAI::actionFinished( const BattleAction *action )
|
||||
@ -94,73 +148,203 @@ static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const Battl
|
||||
return shooters[0] < shooters[1];
|
||||
}
|
||||
|
||||
BattleAction CBattleAI::activeStack( const CStack * stack )
|
||||
template <typename Container, typename Pred>
|
||||
auto sum(const Container & c, Pred p) -> decltype(p(*boost::begin(c)))
|
||||
{
|
||||
//boost::this_thread::sleep(boost::posix_time::seconds(2));
|
||||
print("activeStack called for " + stack->nodeName());
|
||||
auto dists = cb->battleGetDistances(stack);
|
||||
std::vector<EnemyInfo> enemiesShootable, enemiesReachable, enemiesUnreachable;
|
||||
|
||||
if(stack->type->idNumber == 145) //catapult
|
||||
double ret = 0;
|
||||
BOOST_FOREACH(const auto &element, c)
|
||||
{
|
||||
BattleAction attack;
|
||||
static const int wallHexes[] = {50, 183, 182, 130, 62, 29, 12, 95};
|
||||
attack.destinationTile = wallHexes[ rand()%ARRAY_COUNT(wallHexes) ];
|
||||
attack.actionType = BattleAction::CATAPULT;
|
||||
attack.additionalInfo = 0;
|
||||
attack.side = side;
|
||||
attack.stackNumber = stack->ID;
|
||||
|
||||
return attack;
|
||||
ret += p(element);
|
||||
}
|
||||
|
||||
BOOST_FOREACH(const CStack *s, cb->battleGetStacks(CBattleCallback::ONLY_ENEMY))
|
||||
return ret;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
struct ThreatMap(const CStack *Endangered)
|
||||
: endangered(Endangered)
|
||||
{
|
||||
if(cb->battleCanShoot(stack, s->position))
|
||||
{
|
||||
enemiesShootable.push_back(s);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack, false);
|
||||
sufferedDamage.fill(0);
|
||||
|
||||
BOOST_FOREACH(BattleHex hex, avHexes)
|
||||
BOOST_FOREACH(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(CStack::isMeleeAttackPossible(stack, s, hex))
|
||||
if(enemyReachability.isReachable(i))
|
||||
{
|
||||
std::vector<EnemyInfo>::iterator i = std::find(enemiesReachable.begin(), enemiesReachable.end(), s);
|
||||
if(i == enemiesReachable.end())
|
||||
{
|
||||
enemiesReachable.push_back(s);
|
||||
i = enemiesReachable.begin() + (enemiesReachable.size() - 1);
|
||||
}
|
||||
|
||||
i->attackFrom.push_back(hex);
|
||||
meleeAttackable[i] = true;
|
||||
BOOST_FOREACH(auto n, BattleHex(i).neighbouringTiles())
|
||||
meleeAttackable[n] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!vstd::contains(enemiesReachable, s) && s->position.isValid())
|
||||
enemiesUnreachable.push_back(s);
|
||||
//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;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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 damageDiff() const
|
||||
{
|
||||
return damageDealt - damageReceived;
|
||||
}
|
||||
|
||||
int attackValue() const
|
||||
{
|
||||
//TODO consider tactical advantage
|
||||
return damageDiff();
|
||||
}
|
||||
};
|
||||
|
||||
struct PotentialTargets
|
||||
{
|
||||
std::vector<AttackPossibility> possibleAttacks;
|
||||
std::vector<const CStack *> unreachableEnemies;
|
||||
|
||||
std::function<AttackPossibility(bool,BattleHex)> GenerateAttackInfo; //args: shooting, destHex
|
||||
|
||||
PotentialTargets(const CStack *attacker, optional<IBonusBearer*> attackerBonuses = boost::none)
|
||||
{
|
||||
auto dists = cbc->battleGetDistances(attacker);
|
||||
std::vector<BattleHex> avHexes = cbc->battleGetAvailableHexes(attacker, false);
|
||||
|
||||
BOOST_FOREACH(const CStack *enemy, cbc->battleGetStacks())
|
||||
{
|
||||
//Consider only stacks of different owner
|
||||
if(enemy->attackerOwned == attacker->attackerOwned)
|
||||
continue;
|
||||
|
||||
GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility
|
||||
{
|
||||
auto bai = BattleAttackInfo(attacker, enemy, shooting);
|
||||
if(attackerBonuses)
|
||||
bai.attackerBonuses = *attackerBonuses;
|
||||
|
||||
AttackPossibility ap = {enemy, hex, bai, 0, 0};
|
||||
if(hex.isValid())
|
||||
{
|
||||
assert(dists[hex] <= attacker->Speed());
|
||||
ap.attack.chargedFields = dists[hex];
|
||||
}
|
||||
|
||||
std::pair<ui32, ui32> retaliation;
|
||||
auto attackDmg = cbc->battleEstimateDamage(ap.attack, &retaliation);
|
||||
ap.damageDealt = (attackDmg.first + attackDmg.second) / 2;
|
||||
ap.damageReceived = (retaliation.first + retaliation.second) / 2;
|
||||
//TODO other damage related to attack (eg. fire shield and other abilities)
|
||||
//TODO limit max damage by total stacks health (dealing 100000 dmg to single Pikineer is not that effective)
|
||||
|
||||
return ap;
|
||||
};
|
||||
|
||||
if(cbc->battleCanShoot(attacker, enemy->position))
|
||||
{
|
||||
possibleAttacks.push_back(GenerateAttackInfo(true, BattleHex::INVALID));
|
||||
}
|
||||
else
|
||||
{
|
||||
BOOST_FOREACH(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(enemiesShootable.size())
|
||||
AttackPossibility bestAction() const
|
||||
{
|
||||
const EnemyInfo &ei= *std::max_element(enemiesShootable.begin(), enemiesShootable.end(), isMoreProfitable);
|
||||
return BattleAction::makeShotAttack(stack, ei.s);
|
||||
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.damageDiff(); } );
|
||||
}
|
||||
else if(enemiesReachable.size())
|
||||
};
|
||||
|
||||
BattleAction CBattleAI::activeStack( const CStack * stack )
|
||||
{
|
||||
try
|
||||
{
|
||||
const EnemyInfo &ei= *std::max_element(enemiesReachable.begin(), enemiesReachable.end(), &isMoreProfitable);
|
||||
return BattleAction::makeMeleeAttack(stack, ei.s, *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), &willSecondHexBlockMoreEnemyShooters));
|
||||
}
|
||||
else
|
||||
{
|
||||
const EnemyInfo &ei= *std::min_element(enemiesUnreachable.begin(), enemiesUnreachable.end(), boost::bind(isCloser, _1, _2, boost::ref(dists)));
|
||||
if(distToNearestNeighbour(ei.s->position, dists) < GameConstants::BFIELD_SIZE)
|
||||
print("activeStack called for " + stack->nodeName());
|
||||
if(stack->type->idNumber == 145) //catapult
|
||||
return useCatapult(stack);
|
||||
|
||||
if(cb->battleCanCastSpell())
|
||||
attemptCastingSpell();
|
||||
|
||||
ThreatMap threatsToUs(stack);
|
||||
PotentialTargets targets(stack);
|
||||
|
||||
if(targets.possibleAttacks.size())
|
||||
{
|
||||
return goTowards(stack, ei.s->position);
|
||||
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())
|
||||
{
|
||||
auto dists = cbc->battleGetDistances(stack);
|
||||
const EnemyInfo &ei= *range::min_element(targets.unreachableEnemies, boost::bind(isCloser, _1, _2, boost::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)
|
||||
{
|
||||
tlog1 << "Exception occurred in " << __FUNCTION__ << " " << e.what() << std::endl;
|
||||
}
|
||||
|
||||
return BattleAction::makeDefend(stack);
|
||||
@ -181,11 +365,6 @@ void CBattleAI::battleEnd(const BattleResult *br)
|
||||
print("battleEnd called");
|
||||
}
|
||||
|
||||
// void CStupidAI::battleResultsApplied()
|
||||
// {
|
||||
// print("battleResultsApplied called");
|
||||
// }
|
||||
|
||||
void CBattleAI::battleNewRoundFirst(int round)
|
||||
{
|
||||
print("battleNewRoundFirst called");
|
||||
@ -244,7 +423,7 @@ void CBattleAI::battleStacksRemoved(const BattleStacksRemoved & bsr)
|
||||
|
||||
void CBattleAI::print(const std::string &text) const
|
||||
{
|
||||
tlog6 << "CStupidAI [" << this <<"]: " << text << std::endl;
|
||||
tlog6 << "CBattleAI [" << this <<"]: " << text << std::endl;
|
||||
}
|
||||
|
||||
BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination)
|
||||
@ -310,3 +489,141 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination)
|
||||
}
|
||||
}
|
||||
|
||||
BattleAction CBattleAI::useCatapult(const CStack * stack)
|
||||
{
|
||||
throw std::exception("The method or operation is not implemented.");
|
||||
}
|
||||
|
||||
bool isSupportedSpell(const CSpell *spell)
|
||||
{
|
||||
switch(spell->id)
|
||||
{
|
||||
// permanent effects
|
||||
case Spells::SHIELD:
|
||||
case Spells::AIR_SHIELD:
|
||||
case Spells::FIRE_SHIELD:
|
||||
case Spells::PROTECTION_FROM_AIR:
|
||||
case Spells::PROTECTION_FROM_FIRE:
|
||||
case Spells::PROTECTION_FROM_WATER:
|
||||
case Spells::PROTECTION_FROM_EARTH:
|
||||
case Spells::ANTI_MAGIC:
|
||||
case Spells::MAGIC_MIRROR:
|
||||
case Spells::BLESS:
|
||||
case Spells::CURSE:
|
||||
case Spells::BLOODLUST:
|
||||
case Spells::PRECISION:
|
||||
case Spells::WEAKNESS:
|
||||
case Spells::STONE_SKIN:
|
||||
case Spells::DISRUPTING_RAY:
|
||||
case Spells::PRAYER:
|
||||
case Spells::MIRTH:
|
||||
case Spells::SORROW:
|
||||
case Spells::FORTUNE:
|
||||
case Spells::MISFORTUNE:
|
||||
case Spells::HASTE:
|
||||
case Spells::SLOW:
|
||||
case Spells::SLAYER:
|
||||
case Spells::FRENZY:
|
||||
case Spells::COUNTERSTRIKE:
|
||||
case Spells::BERSERK:
|
||||
case Spells::HYPNOTIZE:
|
||||
case Spells::FORGETFULNESS:
|
||||
case Spells::BLIND:
|
||||
case Spells::STONE_GAZE:
|
||||
case Spells::POISON:
|
||||
case Spells::BIND:
|
||||
case Spells::DISEASE:
|
||||
case Spells::PARALYZE:
|
||||
case Spells::AGE:
|
||||
case Spells::ACID_BREATH_DEFENSE:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct PossibleSpellcast
|
||||
{
|
||||
const CSpell *spell;
|
||||
BattleHex dest;
|
||||
};
|
||||
|
||||
void CBattleAI::attemptCastingSpell()
|
||||
{
|
||||
LOGL("Casting spells sounds like fun. Let's see...");
|
||||
|
||||
auto known = cb->battleGetFightingHero(side);
|
||||
|
||||
//Get all spells we can cast
|
||||
std::vector<const CSpell*> possibleSpells;
|
||||
vstd::copy_if(VLC->spellh->spells, 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 !isSupportedSpell(s); });
|
||||
LOGFL("I know about workings of %d of them.", possibleSpells.size());
|
||||
|
||||
//Get possible spell-target pairs
|
||||
std::vector<PossibleSpellcast> possibleCasts;
|
||||
BOOST_FOREACH(auto spell, possibleSpells)
|
||||
{
|
||||
BOOST_FOREACH(auto hex, cbc->battleGetPossibleTargets(playerID, spell))
|
||||
{
|
||||
PossibleSpellcast ps = {spell, hex};
|
||||
possibleCasts.push_back(ps);
|
||||
}
|
||||
}
|
||||
LOGFL("Found %d spell-target combinations.", possibleCasts.size());
|
||||
if(possibleCasts.empty())
|
||||
return;
|
||||
|
||||
std::map<const CStack*, int> valueOfStack;
|
||||
BOOST_FOREACH(auto stack, cb->battleGetStacks())
|
||||
{
|
||||
PotentialTargets pt(stack);
|
||||
valueOfStack[stack] = pt.bestAction().attackValue();
|
||||
}
|
||||
|
||||
auto evaluateSpellcast = [&] (const PossibleSpellcast &ps) -> int
|
||||
{
|
||||
int skillLevel = 0;
|
||||
|
||||
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);
|
||||
|
||||
PotentialTargets pt(swb.stack, &swb);
|
||||
auto newValue = pt.bestAction().attackValue();
|
||||
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;
|
||||
};
|
||||
|
||||
auto castToPerform = *vstd::maxElementByFun(possibleCasts, evaluateSpellcast);
|
||||
BattleAction spellcast;
|
||||
spellcast.actionType = BattleAction::HERO_SPELL;
|
||||
spellcast.additionalInfo = castToPerform.spell->id;
|
||||
spellcast.destinationTile = castToPerform.dest;
|
||||
spellcast.side = side;
|
||||
spellcast.stackNumber = (!side) ? -1 : -2;
|
||||
|
||||
cb->battleMakeAction(&spellcast);
|
||||
}
|
@ -5,14 +5,14 @@
|
||||
class CBattleAI : public CBattleGameInterface
|
||||
{
|
||||
int side;
|
||||
CPlayerBattleCallback *cb;
|
||||
CBattleCallback *cb;
|
||||
|
||||
void print(const std::string &text) const;
|
||||
public:
|
||||
CBattleAI(void);
|
||||
~CBattleAI(void);
|
||||
|
||||
void init(CPlayerBattleCallback * CB) OVERRIDE;
|
||||
void init(CBattleCallback * 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
|
||||
@ -35,5 +35,8 @@ public:
|
||||
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);
|
||||
|
||||
void attemptCastingSpell();
|
||||
};
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
#define strcpy_s(a, b, c) strncpy(a, c, b)
|
||||
#endif
|
||||
|
||||
const char *g_cszAiName = "Stupid AI 0.1";
|
||||
const char *g_cszAiName = "Battle AI";
|
||||
|
||||
extern "C" DLL_EXPORT int GetGlobalAiVersion()
|
||||
{
|
||||
|
@ -19,7 +19,7 @@ CStupidAI::~CStupidAI(void)
|
||||
print("destroyed");
|
||||
}
|
||||
|
||||
void CStupidAI::init( CPlayerBattleCallback * CB )
|
||||
void CStupidAI::init( CBattleCallback * CB )
|
||||
{
|
||||
print("init called, saving ptr to IBattleCallback");
|
||||
cbc = cb = CB;
|
||||
|
@ -5,14 +5,14 @@
|
||||
class CStupidAI : public CBattleGameInterface
|
||||
{
|
||||
int side;
|
||||
CPlayerBattleCallback *cb;
|
||||
CBattleCallback *cb;
|
||||
|
||||
void print(const std::string &text) const;
|
||||
public:
|
||||
CStupidAI(void);
|
||||
~CStupidAI(void);
|
||||
|
||||
void init(CPlayerBattleCallback * CB) OVERRIDE;
|
||||
void init(CBattleCallback * 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
|
||||
|
13
Global.h
13
Global.h
@ -383,7 +383,7 @@ namespace vstd
|
||||
return std::inserter(c, c.end());
|
||||
}
|
||||
|
||||
//Retuns iterator to the element for which the value of ValueFunction is minimal
|
||||
//Returns iterator to the element for which the value of ValueFunction is minimal
|
||||
template<class ForwardRange, class ValueFunction>
|
||||
auto minElementByFun(const ForwardRange& rng, ValueFunction vf) -> decltype(boost::begin(rng))
|
||||
{
|
||||
@ -393,6 +393,17 @@ namespace vstd
|
||||
return vf(lhs) < vf(rhs);
|
||||
});
|
||||
}
|
||||
|
||||
//Returns iterator to the element for which the value of ValueFunction is maximal
|
||||
template<class ForwardRange, class ValueFunction>
|
||||
auto maxElementByFun(const ForwardRange& rng, ValueFunction vf) -> decltype(boost::begin(rng))
|
||||
{
|
||||
typedef decltype(*boost::begin(rng)) ElemType;
|
||||
return boost::max_element(rng, [&] (ElemType lhs, ElemType rhs) -> bool
|
||||
{
|
||||
return vf(lhs) < vf(rhs);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
using std::shared_ptr;
|
||||
|
@ -1265,7 +1265,7 @@ void CBattleInterface::bSpellf()
|
||||
else if(spellCastProblem == ESpellCastProblem::MAGIC_IS_BLOCKED)
|
||||
{
|
||||
//Handle Orb of Inhibition-like effects -> we want to display dialog with info, why casting is impossible
|
||||
auto blockingBonus = currentHero()->getBonus(Selector::type(Bonus::BLOCK_ALL_MAGIC));
|
||||
auto blockingBonus = currentHero()->getBonusLocalFirst(Selector::type(Bonus::BLOCK_ALL_MAGIC));
|
||||
if(!blockingBonus)
|
||||
return;;
|
||||
|
||||
@ -2002,7 +2002,7 @@ void CBattleInterface::activateStack()
|
||||
|
||||
queue->update();
|
||||
redrawBackgroundWithHexes(activeStack);
|
||||
bWait->block(vstd::contains(s->state, EBattleStackState::WAITING)); //block waiting button if stack has been already waiting
|
||||
bWait->block(s->waited()); //block waiting button if stack has been already waiting
|
||||
|
||||
//block cast spell button if hero doesn't have a spellbook
|
||||
ESpellCastProblem::ESpellCastProblem spellcastingProblem;
|
||||
@ -2014,8 +2014,8 @@ void CBattleInterface::activateStack()
|
||||
|
||||
|
||||
//set casting flag to true if creature can use it to not check it every time
|
||||
const Bonus *spellcaster = s->getBonus(Selector::type(Bonus::SPELLCASTER)),
|
||||
*randomSpellcaster = s->getBonus(Selector::type(Bonus::RANDOM_SPELLCASTER));
|
||||
const Bonus *spellcaster = s->getBonusLocalFirst(Selector::type(Bonus::SPELLCASTER)),
|
||||
*randomSpellcaster = s->getBonusLocalFirst(Selector::type(Bonus::RANDOM_SPELLCASTER));
|
||||
if (s->casts && (spellcaster || randomSpellcaster))
|
||||
{
|
||||
stackCanCastSpell = true;
|
||||
|
@ -422,7 +422,7 @@ void CCreatureWindow::init(const CStackInstance *Stack, const CBonusSystemNode *
|
||||
{
|
||||
spellText = CGI->generaltexth->allTexts[610]; //"%s, duration: %d rounds."
|
||||
boost::replace_first (spellText, "%s", CGI->spellh->spells[effect]->name);
|
||||
int duration = battleStack->getBonus(Selector::source(Bonus::SPELL_EFFECT,effect))->turnsRemain;
|
||||
int duration = battleStack->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT,effect))->turnsRemain;
|
||||
boost::replace_first (spellText, "%d", boost::lexical_cast<std::string>(duration));
|
||||
|
||||
new CAnimImage("SpellInt", effect + 1, 0, 20 + 52 * printed, 184);
|
||||
|
@ -655,7 +655,7 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
|
||||
case ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED:
|
||||
{
|
||||
//Recanter's Cloak or similar effect. Try to retrieve bonus
|
||||
const Bonus *b = owner->myHero->getBonus(Selector::type(Bonus::BLOCK_MAGIC_ABOVE));
|
||||
const Bonus *b = owner->myHero->getBonusLocalFirst(Selector::type(Bonus::BLOCK_MAGIC_ABOVE));
|
||||
//TODO what about other values and non-artifact sources?
|
||||
if(b && b->val == 2 && b->source == Bonus::ARTIFACT)
|
||||
{
|
||||
|
@ -387,7 +387,10 @@ void CClient::newGame( CConnection *con, StartInfo *si )
|
||||
{
|
||||
auto cbc = make_shared<CBattleCallback>(gs, color, this);
|
||||
battleCallbacks[color] = cbc;
|
||||
battleints[color] = CDynLibHandler::getNewBattleAI("StupidAI");
|
||||
if(!color)
|
||||
battleints[color] = CDynLibHandler::getNewBattleAI(settings["server"]["neutralAI"].String());
|
||||
else
|
||||
battleints[color] = CDynLibHandler::getNewBattleAI("StupidAI");
|
||||
battleints[color]->init(cbc.get());
|
||||
}
|
||||
}
|
||||
|
@ -1155,19 +1155,6 @@ si32 CStack::magicResistance() const
|
||||
return magicResistance;
|
||||
}
|
||||
|
||||
const Bonus * CStack::getEffect( ui16 id, int turn /*= 0*/ ) const
|
||||
{
|
||||
BOOST_FOREACH(Bonus *it, getBonusList())
|
||||
{
|
||||
if(it->source == Bonus::SPELL_EFFECT && it->sid == id)
|
||||
{
|
||||
if(!turn || it->turnsRemain > turn)
|
||||
return &(*it);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void CStack::stackEffectToFeature(std::vector<Bonus> & sf, const Bonus & sse)
|
||||
{
|
||||
si32 power = VLC->spellh->spells[sse.sid]->powers[sse.val];
|
||||
@ -1342,17 +1329,6 @@ void CStack::stackEffectToFeature(std::vector<Bonus> & sf, const Bonus & sse)
|
||||
}
|
||||
}
|
||||
|
||||
ui8 CStack::howManyEffectsSet(ui16 id) const
|
||||
{
|
||||
ui8 ret = 0;
|
||||
BOOST_FOREACH(const Bonus *it, getBonusList())
|
||||
if(it->source == Bonus::SPELL_EFFECT && it->sid == id) //effect found
|
||||
{
|
||||
++ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CStack::willMove(int turn /*= 0*/) const
|
||||
{
|
||||
return ( turn ? true : !vstd::contains(state, EBattleStackState::DEFENDING) )
|
||||
@ -1374,6 +1350,14 @@ bool CStack::moved( int turn /*= 0*/ ) const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CStack::waited(int turn /*= 0*/) const
|
||||
{
|
||||
if(!turn)
|
||||
return vstd::contains(state, EBattleStackState::WAITING);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CStack::doubleWide() const
|
||||
{
|
||||
return getCreature()->doubleWide;
|
||||
|
@ -167,11 +167,10 @@ public:
|
||||
void init(); //set initial (invalid) values
|
||||
void postInit(); //used to finish initialization when inheriting creature parameters is working
|
||||
std::string getName() const; //plural or singular
|
||||
const Bonus * getEffect(ui16 id, int turn = 0) const; //effect id (SP)
|
||||
ui8 howManyEffectsSet(ui16 id) const; //returns amount of effects with given id set for this stack
|
||||
bool willMove(int turn = 0) const; //if stack has remaining move this turn
|
||||
bool ableToRetaliate() const; //if stack can retaliate after attacked
|
||||
bool moved(int turn = 0) const; //if stack was already moved this turn
|
||||
bool waited(int turn = 0) const;
|
||||
bool canMove(int turn = 0) const; //if stack can move
|
||||
bool canBeHealed() const; //for first aid tent - only harmed stacks that are not war machines
|
||||
ui32 Speed(int turn = 0, bool useBind = false) const; //get speed of creature with all modificators
|
||||
|
@ -1214,7 +1214,7 @@ void CArtifactInstance::deserializationFix()
|
||||
|
||||
int CArtifactInstance::getGivenSpellID() const
|
||||
{
|
||||
const Bonus * b = getBonus(Selector::type(Bonus::SPELL));
|
||||
const Bonus * b = getBonusLocalFirst(Selector::type(Bonus::SPELL));
|
||||
if(!b)
|
||||
{
|
||||
tlog3 << "Warning: " << nodeName() << " doesn't bear any spell!\n";
|
||||
|
@ -10,6 +10,26 @@
|
||||
|
||||
namespace SiegeStuffThatShouldBeMovedToHandlers // <=== TODO
|
||||
{
|
||||
static void retreiveTurretDamageRange(const CStack *turret, double &outMinDmg, double &outMaxDmg)
|
||||
{
|
||||
assert(turret->getCreature()->idNumber == 149); //arrow turret
|
||||
|
||||
switch(turret->position)
|
||||
{
|
||||
case -2: //keep
|
||||
outMinDmg = 15;
|
||||
outMaxDmg = 15;
|
||||
break;
|
||||
case -3: case -4: //turrets
|
||||
outMinDmg = 7.5;
|
||||
outMaxDmg = 7.5;
|
||||
break;
|
||||
default:
|
||||
tlog1 << "Unknown turret type!" << std::endl;
|
||||
outMaxDmg = outMinDmg = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int lineToWallHex(int line) //returns hex with wall in given line (y coordinate)
|
||||
{
|
||||
static const int lineToHex[] = {12, 29, 45, 62, 78, 95, 112, 130, 147, 165, 182};
|
||||
@ -89,6 +109,11 @@ void CCallbackBase::setBattle(const BattleInfo *B)
|
||||
battle = B;
|
||||
}
|
||||
|
||||
int CCallbackBase::getPlayerID() const
|
||||
{
|
||||
return player;
|
||||
}
|
||||
|
||||
ui8 CBattleInfoEssentials::battleTerrainType() const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(-1);
|
||||
@ -369,21 +394,26 @@ ui8 CBattleInfoEssentials::battleGetWallState(int partOfWall) const
|
||||
}
|
||||
|
||||
si8 CBattleInfoCallback::battleHasWallPenalty( const CStack * stack, BattleHex destHex ) const
|
||||
{
|
||||
return battleHasWallPenalty(stack, stack->position, destHex);
|
||||
}
|
||||
|
||||
si8 CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer *bonusBearer, BattleHex shooterPosition, BattleHex destHex) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(false);
|
||||
if (!battleGetSiegeLevel() || stack->hasBonusOfType(Bonus::NO_WALL_PENALTY))
|
||||
if (!battleGetSiegeLevel() || bonusBearer->hasBonusOfType(Bonus::NO_WALL_PENALTY))
|
||||
return false;
|
||||
|
||||
const int wallInStackLine = lineToWallHex(stack->position.getY());
|
||||
const int wallInStackLine = lineToWallHex(shooterPosition.getY());
|
||||
const int wallInDestLine = lineToWallHex(destHex.getY());
|
||||
|
||||
const bool stackLeft = stack->position < wallInStackLine;
|
||||
const bool stackLeft = shooterPosition < wallInStackLine;
|
||||
const bool destRight = destHex > wallInDestLine;
|
||||
|
||||
if (stackLeft && destRight) //shooting from outside to inside
|
||||
{
|
||||
int row = (stack->position + destHex) / (2 * GameConstants::BFIELD_WIDTH);
|
||||
if (stack->position > destHex && ((destHex % GameConstants::BFIELD_WIDTH - stack->position % GameConstants::BFIELD_WIDTH) < 2)) //shooting up high
|
||||
int row = (shooterPosition + destHex) / (2 * GameConstants::BFIELD_WIDTH);
|
||||
if (shooterPosition > destHex && ((destHex % GameConstants::BFIELD_WIDTH - shooterPosition % GameConstants::BFIELD_WIDTH) < 2)) //shooting up high
|
||||
row -= 2;
|
||||
const int wallPos = lineToWallHex(row);
|
||||
if (battleHexToWallPart(wallPos) != -1) //wall still exists or is indestructible
|
||||
@ -524,7 +554,7 @@ void CBattleInfoCallback::battleGetStackQueue(std::vector<const CStack *> &out,
|
||||
const CStack *active = battleActiveStack();
|
||||
|
||||
//active stack hasn't taken any action yet - must be placed at the beginning of queue, no matter what
|
||||
if(!turn && active && active->willMove() && !vstd::contains(active->state, EBattleStackState::WAITING))
|
||||
if(!turn && active && active->willMove() && !active->waited())
|
||||
{
|
||||
out.push_back(active);
|
||||
if(out.size() == howMany)
|
||||
@ -549,7 +579,7 @@ void CBattleInfoCallback::battleGetStackQueue(std::vector<const CStack *> &out,
|
||||
}
|
||||
|
||||
int p = -1; //in which phase this tack will move?
|
||||
if(turn <= 0 && vstd::contains(s->state, EBattleStackState::WAITING)) //consider waiting state only for ongoing round
|
||||
if(turn <= 0 && s->waited()) //consider waiting state only for ongoing round
|
||||
{
|
||||
if(vstd::contains(s->state, EBattleStackState::HAD_MORALE))
|
||||
p = 2;
|
||||
@ -736,74 +766,66 @@ TDmgRange CBattleInfoCallback::calculateDmgRange(const CStack* attacker, const C
|
||||
return calculateDmgRange(attacker, defender, attacker->count, shooting, charge, lucky, deathBlow, ballistaDoubleDmg);
|
||||
}
|
||||
|
||||
TDmgRange CBattleInfoCallback::calculateDmgRange( const CStack* attacker, const CStack* defender, TQuantity attackerCount,
|
||||
bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg ) const
|
||||
TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo &info) const
|
||||
{
|
||||
double additiveBonus = 1.0, multBonus = 1.0,
|
||||
minDmg = attacker->getMinDamage() * attackerCount,
|
||||
maxDmg = attacker->getMaxDamage() * attackerCount;
|
||||
minDmg = info.attackerBonuses->getMinDamage() * info.attackerCount,
|
||||
maxDmg = info.attackerBonuses->getMaxDamage() * info.attackerCount;
|
||||
|
||||
if(attacker->getCreature()->idNumber == 149) //arrow turret
|
||||
const CCreature *attackerType = info.attacker->getCreature(),
|
||||
*defenderType = info.defender->getCreature();
|
||||
|
||||
if(attackerType->idNumber == 149) //arrow turret
|
||||
{
|
||||
switch(attacker->position)
|
||||
{
|
||||
case -2: //keep
|
||||
minDmg = 15;
|
||||
maxDmg = 15;
|
||||
break;
|
||||
case -3: case -4: //turrets
|
||||
minDmg = 7.5;
|
||||
maxDmg = 7.5;
|
||||
break;
|
||||
}
|
||||
SiegeStuffThatShouldBeMovedToHandlers::retreiveTurretDamageRange(info.attacker, minDmg, maxDmg);
|
||||
}
|
||||
|
||||
if(attacker->hasBonusOfType(Bonus::SIEGE_WEAPON) && attacker->getCreature()->idNumber != 149) //any siege weapon, but only ballista can attack (second condition - not arrow turret)
|
||||
if(info.attackerBonuses->hasBonusOfType(Bonus::SIEGE_WEAPON) && attackerType->idNumber != 149) //any siege weapon, but only ballista can attack (second condition - not arrow turret)
|
||||
{ //minDmg and maxDmg are multiplied by hero attack + 1
|
||||
auto retreivePrimSkill = [&](int skill) -> int
|
||||
auto retreiveHeroPrimSkill = [&](int skill) -> int
|
||||
{
|
||||
const Bonus *b = attacker->getBonus(Selector::sourceTypeSel(Bonus::HERO_BASE_SKILL) && Selector::typeSubtype(Bonus::PRIMARY_SKILL, skill));
|
||||
const Bonus *b = info.attackerBonuses->getBonus(Selector::sourceTypeSel(Bonus::HERO_BASE_SKILL) && Selector::typeSubtype(Bonus::PRIMARY_SKILL, skill));
|
||||
return b ? b->val : 0; //if there is no hero or no info on his primary skill, return 0
|
||||
};
|
||||
|
||||
|
||||
minDmg *= retreivePrimSkill(PrimarySkill::ATTACK) + 1;
|
||||
maxDmg *= retreivePrimSkill(PrimarySkill::ATTACK) + 1;
|
||||
minDmg *= retreiveHeroPrimSkill(PrimarySkill::ATTACK) + 1;
|
||||
maxDmg *= retreiveHeroPrimSkill(PrimarySkill::ATTACK) + 1;
|
||||
}
|
||||
|
||||
int attackDefenceDifference = 0;
|
||||
if(attacker->hasBonusOfType(Bonus::GENERAL_ATTACK_REDUCTION))
|
||||
if(info.attackerBonuses->hasBonusOfType(Bonus::GENERAL_ATTACK_REDUCTION))
|
||||
{
|
||||
double multAttackReduction = attacker->valOfBonuses(Bonus::GENERAL_ATTACK_REDUCTION, -1024) / 100.0;
|
||||
attackDefenceDifference = attacker->Attack() * multAttackReduction;
|
||||
double multAttackReduction = info.attackerBonuses->valOfBonuses(Bonus::GENERAL_ATTACK_REDUCTION, -1024) / 100.0;
|
||||
attackDefenceDifference = info.attackerBonuses->Attack() * multAttackReduction;
|
||||
}
|
||||
else
|
||||
{
|
||||
attackDefenceDifference = attacker->Attack();
|
||||
attackDefenceDifference = info.attackerBonuses->Attack();
|
||||
}
|
||||
|
||||
if(attacker->hasBonusOfType(Bonus::ENEMY_DEFENCE_REDUCTION))
|
||||
if(info.attackerBonuses->hasBonusOfType(Bonus::ENEMY_DEFENCE_REDUCTION))
|
||||
{
|
||||
double multDefenceReduction = (100 - attacker->valOfBonuses(Bonus::ENEMY_DEFENCE_REDUCTION, -1024)) / 100.0;
|
||||
attackDefenceDifference -= defender->Defense() * multDefenceReduction;
|
||||
double multDefenceReduction = (100 - info.attackerBonuses->valOfBonuses(Bonus::ENEMY_DEFENCE_REDUCTION, -1024)) / 100.0;
|
||||
attackDefenceDifference -= info.defenderBonuses->Defense() * multDefenceReduction;
|
||||
}
|
||||
else
|
||||
{
|
||||
attackDefenceDifference -= defender->Defense();
|
||||
attackDefenceDifference -= info.defenderBonuses->Defense();
|
||||
}
|
||||
|
||||
//calculating total attack/defense skills modifier
|
||||
|
||||
if(shooting) //precision handling (etc.)
|
||||
attackDefenceDifference += attacker->getBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK), Selector::effectRange(Bonus::ONLY_DISTANCE_FIGHT))->totalValue();
|
||||
if(info.shooting) //precision handling (etc.)
|
||||
attackDefenceDifference += info.attackerBonuses->getBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK), Selector::effectRange(Bonus::ONLY_DISTANCE_FIGHT))->totalValue();
|
||||
else //bloodlust handling (etc.)
|
||||
attackDefenceDifference += attacker->getBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK), Selector::effectRange(Bonus::ONLY_MELEE_FIGHT))->totalValue();
|
||||
attackDefenceDifference += info.attackerBonuses->getBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK), Selector::effectRange(Bonus::ONLY_MELEE_FIGHT))->totalValue();
|
||||
|
||||
|
||||
if(attacker->getEffect(55)) //slayer handling
|
||||
if(const Bonus *slayerEffect = info.attackerBonuses->getEffect(Spells::SLAYER)) //slayer handling
|
||||
{
|
||||
std::vector<int> affectedIds;
|
||||
int spLevel = attacker->getEffect(55)->val;
|
||||
int spLevel = slayerEffect->val;
|
||||
|
||||
for(int g = 0; g < VLC->creh->creatures.size(); ++g)
|
||||
{
|
||||
@ -821,9 +843,9 @@ TDmgRange CBattleInfoCallback::calculateDmgRange( const CStack* attacker, const
|
||||
|
||||
for(ui32 g=0; g<affectedIds.size(); ++g)
|
||||
{
|
||||
if(defender->getCreature()->idNumber == affectedIds[g])
|
||||
if(defenderType->idNumber == affectedIds[g])
|
||||
{
|
||||
attackDefenceDifference += VLC->spellh->spells[55]->powers[attacker->getEffect(55)->val];
|
||||
attackDefenceDifference += VLC->spellh->spells[Spells::SLAYER]->powers[spLevel];
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -843,51 +865,51 @@ TDmgRange CBattleInfoCallback::calculateDmgRange( const CStack* attacker, const
|
||||
|
||||
|
||||
//applying jousting bonus
|
||||
if( attacker->hasBonusOfType(Bonus::JOUSTING) && !defender->hasBonusOfType(Bonus::CHARGE_IMMUNITY) )
|
||||
additiveBonus += charge * 0.05;
|
||||
if( info.attackerBonuses->hasBonusOfType(Bonus::JOUSTING) && !info.defenderBonuses->hasBonusOfType(Bonus::CHARGE_IMMUNITY) )
|
||||
additiveBonus += info.chargedFields * 0.05;
|
||||
|
||||
|
||||
//handling secondary abilities and artifacts giving premies to them
|
||||
if(shooting)
|
||||
additiveBonus += attacker->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::ARCHERY) / 100.0;
|
||||
if(info.shooting)
|
||||
additiveBonus += info.attackerBonuses->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::ARCHERY) / 100.0;
|
||||
else
|
||||
additiveBonus += attacker->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::OFFENCE) / 100.0;
|
||||
additiveBonus += info.attackerBonuses->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::OFFENCE) / 100.0;
|
||||
|
||||
if(defender)
|
||||
multBonus *= (std::max(0, 100 - defender->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::ARMORER))) / 100.0;
|
||||
if(info.defenderBonuses)
|
||||
multBonus *= (std::max(0, 100 - info.defenderBonuses->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::ARMORER))) / 100.0;
|
||||
|
||||
//handling hate effect
|
||||
additiveBonus += attacker->valOfBonuses(Bonus::HATE, defender->getCreature()->idNumber) / 100.;
|
||||
additiveBonus += info.attackerBonuses->valOfBonuses(Bonus::HATE, defenderType->idNumber) / 100.;
|
||||
|
||||
//luck bonus
|
||||
if (lucky)
|
||||
if (info.luckyHit)
|
||||
{
|
||||
additiveBonus += 1.0;
|
||||
}
|
||||
|
||||
//ballista double dmg
|
||||
if(ballistaDoubleDmg)
|
||||
if(info.ballistaDoubleDamage)
|
||||
{
|
||||
additiveBonus += 1.0;
|
||||
}
|
||||
|
||||
if (deathBlow) //Dread Knight and many WoGified creatures
|
||||
if (info.deathBlow) //Dread Knight and many WoGified creatures
|
||||
{
|
||||
additiveBonus += 1.0;
|
||||
}
|
||||
|
||||
//handling spell effects
|
||||
if(!shooting && defender->hasBonusOfType(Bonus::GENERAL_DAMAGE_REDUCTION, 0)) //eg. shield
|
||||
if(!info.shooting && info.defenderBonuses->hasBonusOfType(Bonus::GENERAL_DAMAGE_REDUCTION, 0)) //eg. shield
|
||||
{
|
||||
multBonus *= defender->valOfBonuses(Bonus::GENERAL_DAMAGE_REDUCTION, 0) / 100.0;
|
||||
multBonus *= info.defenderBonuses->valOfBonuses(Bonus::GENERAL_DAMAGE_REDUCTION, 0) / 100.0;
|
||||
}
|
||||
else if(shooting && defender->hasBonusOfType(Bonus::GENERAL_DAMAGE_REDUCTION, 1)) //eg. air shield
|
||||
else if(info.shooting && info.defenderBonuses->hasBonusOfType(Bonus::GENERAL_DAMAGE_REDUCTION, 1)) //eg. air shield
|
||||
{
|
||||
multBonus *= defender->valOfBonuses(Bonus::GENERAL_DAMAGE_REDUCTION, 1) / 100.0;
|
||||
multBonus *= info.defenderBonuses->valOfBonuses(Bonus::GENERAL_DAMAGE_REDUCTION, 1) / 100.0;
|
||||
}
|
||||
|
||||
TBonusListPtr curseEffects = attacker->getBonuses(Selector::type(Bonus::ALWAYS_MINIMUM_DAMAGE)); //attacker->getEffect(42);
|
||||
TBonusListPtr blessEffects = attacker->getBonuses(Selector::type(Bonus::ALWAYS_MAXIMUM_DAMAGE)); //attacker->getEffect(43);
|
||||
TBonusListPtr curseEffects = info.attackerBonuses->getBonuses(Selector::type(Bonus::ALWAYS_MINIMUM_DAMAGE)); //attacker->getEffect(42);
|
||||
TBonusListPtr blessEffects = info.attackerBonuses->getBonuses(Selector::type(Bonus::ALWAYS_MAXIMUM_DAMAGE)); //attacker->getEffect(43);
|
||||
int curseBlessAdditiveModifier = blessEffects->totalValue() - curseEffects->totalValue();
|
||||
double curseMultiplicativePenalty = curseEffects->size() ? (*std::max_element(curseEffects->begin(), curseEffects->end(), &Bonus::compareByAdditionalInfo))->additionalInfo : 0;
|
||||
|
||||
@ -904,12 +926,12 @@ TDmgRange CBattleInfoCallback::calculateDmgRange( const CStack* attacker, const
|
||||
};
|
||||
|
||||
//wall / distance penalty + advanced air shield
|
||||
const bool distPenalty = !attacker->hasBonusOfType(Bonus::NO_DISTANCE_PENALTY) && battleHasDistancePenalty(attacker, defender->position);
|
||||
const bool obstaclePenalty = battleHasWallPenalty(attacker, defender->position);
|
||||
const bool distPenalty = !info.attackerBonuses->hasBonusOfType(Bonus::NO_DISTANCE_PENALTY) && battleHasDistancePenalty(info.attackerBonuses, info.attackerPosition, info.defenderPosition);
|
||||
const bool obstaclePenalty = battleHasWallPenalty(info.attackerBonuses, info.attackerPosition, info.defenderPosition);
|
||||
|
||||
if (shooting)
|
||||
if (info.shooting)
|
||||
{
|
||||
if (distPenalty || defender->hasBonus(isAdvancedAirShield))
|
||||
if (distPenalty || info.defenderBonuses->hasBonus(isAdvancedAirShield))
|
||||
{
|
||||
multBonus *= 0.5;
|
||||
}
|
||||
@ -918,7 +940,7 @@ TDmgRange CBattleInfoCallback::calculateDmgRange( const CStack* attacker, const
|
||||
multBonus *= 0.5; //cumulative
|
||||
}
|
||||
}
|
||||
if (!shooting && attacker->hasBonusOfType(Bonus::SHOOTER) && !attacker->hasBonusOfType(Bonus::NO_MELEE_PENALTY))
|
||||
if(!info.shooting && info.attackerBonuses->hasBonusOfType(Bonus::SHOOTER) && !info.attackerBonuses->hasBonusOfType(Bonus::NO_MELEE_PENALTY))
|
||||
{
|
||||
multBonus *= 0.5;
|
||||
}
|
||||
@ -950,18 +972,38 @@ TDmgRange CBattleInfoCallback::calculateDmgRange( const CStack* attacker, const
|
||||
return returnedVal;
|
||||
}
|
||||
|
||||
TDmgRange CBattleInfoCallback::calculateDmgRange( const CStack* attacker, const CStack* defender, TQuantity attackerCount,
|
||||
bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg ) const
|
||||
{
|
||||
BattleAttackInfo bai(attacker, defender, shooting);
|
||||
bai.attackerCount = attackerCount;
|
||||
bai.chargedFields = charge;
|
||||
bai.luckyHit = lucky;
|
||||
bai.deathBlow = deathBlow;
|
||||
bai.ballistaDoubleDamage = ballistaDoubleDmg;
|
||||
return calculateDmgRange(bai);
|
||||
}
|
||||
|
||||
TDmgRange CBattleInfoCallback::battleEstimateDamage(const CStack * attacker, const CStack * defender, TDmgRange * retaliationDmg) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(std::make_pair(0, 0));
|
||||
|
||||
const bool shooting = battleCanShoot(attacker, defender->position);
|
||||
const BattleAttackInfo bai(attacker, defender, shooting);
|
||||
return battleEstimateDamage(bai, retaliationDmg);
|
||||
}
|
||||
|
||||
std::pair<ui32, ui32> CBattleInfoCallback::battleEstimateDamage(const BattleAttackInfo &bai, std::pair<ui32, ui32> * retaliationDmg /*= NULL*/) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(std::make_pair(0, 0));
|
||||
|
||||
const bool shooting = battleCanShoot(bai.attacker, bai.defenderPosition); //TODO handle bonus bearer
|
||||
//const ui8 mySide = !attacker->attackerOwned;
|
||||
|
||||
TDmgRange ret = calculateDmgRange(attacker, defender, shooting, 0, false, false, false);
|
||||
TDmgRange ret = calculateDmgRange(bai);
|
||||
|
||||
if(retaliationDmg)
|
||||
{
|
||||
if(shooting)
|
||||
if(bai.shooting)
|
||||
{
|
||||
retaliationDmg->first = retaliationDmg->second = 0;
|
||||
}
|
||||
@ -972,7 +1014,11 @@ TDmgRange CBattleInfoCallback::battleEstimateDamage(const CStack * attacker, con
|
||||
{
|
||||
BattleStackAttacked bsa;
|
||||
bsa.damageAmount = ret.*pairElems[i];
|
||||
retaliationDmg->*pairElems[!i] = calculateDmgRange(defender, attacker, bsa.newAmount, false, 0, false, false, false).*pairElems[!i];
|
||||
bai.defender->prepareAttacked(bsa);
|
||||
|
||||
auto retaliationAttack = bai.reverse();
|
||||
retaliationAttack.attackerCount = bsa.newAmount;
|
||||
retaliationDmg->*pairElems[!i] = calculateDmgRange(retaliationAttack).*pairElems[!i];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -980,7 +1026,6 @@ TDmgRange CBattleInfoCallback::battleEstimateDamage(const CStack * attacker, con
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
shared_ptr<const CObstacleInstance> CBattleInfoCallback::battleGetObstacleOnPos(BattleHex tile, bool onlyBlocking /*= true*/) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(shared_ptr<const CObstacleInstance>());
|
||||
@ -1329,6 +1374,7 @@ ReachabilityInfo::TDistances CBattleInfoCallback::battleGetDistances(const CStac
|
||||
RETURN_IF_NOT_BATTLE(ret);
|
||||
|
||||
ReachabilityInfo::Parameters params(stack);
|
||||
params.perspective = battleGetMySide();
|
||||
params.startPosition = hex.isValid() ? hex : stack->position;
|
||||
auto reachability = getReachability(params);
|
||||
|
||||
@ -1342,21 +1388,27 @@ ReachabilityInfo::TDistances CBattleInfoCallback::battleGetDistances(const CStac
|
||||
}
|
||||
|
||||
si8 CBattleInfoCallback::battleHasDistancePenalty(const CStack * stack, BattleHex destHex) const
|
||||
{
|
||||
return battleHasDistancePenalty(stack, stack->position, destHex);
|
||||
}
|
||||
|
||||
si8 CBattleInfoCallback::battleHasDistancePenalty(const IBonusBearer *bonusBearer, BattleHex shooterPosition, BattleHex destHex) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(false);
|
||||
if(stack->hasBonusOfType(Bonus::NO_DISTANCE_PENALTY))
|
||||
if(bonusBearer->hasBonusOfType(Bonus::NO_DISTANCE_PENALTY))
|
||||
return false;
|
||||
|
||||
if(BattleHex::getDistance(stack->position, destHex) <= 10)
|
||||
if(BattleHex::getDistance(shooterPosition, destHex) <= GameConstants::BATTLE_PENALTY_DISTANCE)
|
||||
return false;
|
||||
|
||||
const CStack * dstStack = battleGetStackByPos(destHex, false);
|
||||
if(dstStack)
|
||||
if(const CStack * dstStack = battleGetStackByPos(destHex, false))
|
||||
{
|
||||
//If on dest hex stands stack that occupies a hex within our distance
|
||||
BOOST_FOREACH(auto hex, dstStack->getHexes())
|
||||
if(BattleHex::getDistance(stack->position, hex) <= 10)
|
||||
if(BattleHex::getDistance(shooterPosition, hex) <= GameConstants::BATTLE_PENALTY_DISTANCE)
|
||||
return false;
|
||||
|
||||
//TODO what about two-hex shooters?
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -1503,7 +1555,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C
|
||||
{
|
||||
if(spell->getTargetType() == CSpell::CREATURE
|
||||
|| (spell->getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE
|
||||
&& mode == ECastingMode::HERO_CASTING
|
||||
&& mode == ECastingMode::HERO_CASTING //TODO why???
|
||||
&& caster
|
||||
&& caster->getSpellSchoolLevel(spell) < SecSkillLevel::EXPERT))
|
||||
{
|
||||
@ -1641,6 +1693,51 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
|
||||
return ESpellCastProblem::OK;
|
||||
}
|
||||
|
||||
std::vector<BattleHex> CBattleInfoCallback::battleGetPossibleTargets(int player, const CSpell *spell) const
|
||||
{
|
||||
std::vector<BattleHex> ret;
|
||||
RETURN_IF_NOT_BATTLE(ret);
|
||||
|
||||
auto mode = ECastingMode::HERO_CASTING; //TODO get rid of this!
|
||||
|
||||
switch(spell->getTargetType())
|
||||
{
|
||||
case CSpell::CREATURE:
|
||||
case CSpell::CREATURE_EXPERT_MASSIVE:
|
||||
{
|
||||
const CGHeroInstance * caster = battleGetFightingHero(playerToSide(player)); //TODO
|
||||
|
||||
BOOST_FOREACH(const CStack * stack, battleAliveStacks())
|
||||
{
|
||||
switch (spell->positiveness)
|
||||
{
|
||||
case CSpell::POSITIVE:
|
||||
if(stack->owner == caster->getOwner())
|
||||
if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK)
|
||||
ret.push_back(stack->position);
|
||||
break;
|
||||
|
||||
case CSpell::NEUTRAL:
|
||||
if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK)
|
||||
ret.push_back(stack->position);
|
||||
break;
|
||||
|
||||
case CSpell::NEGATIVE:
|
||||
if(stack->owner != caster->getOwner())
|
||||
if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK)
|
||||
ret.push_back(stack->position);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
tlog1 << "FIXME " << __FUNCTION__ << " doesn't work with target type " << spell->getTargetType() << std::endl;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ui32 CBattleInfoCallback::battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(-1);
|
||||
@ -1906,6 +2003,7 @@ si8 CBattleInfoCallback::battleMaxSpellLevel() const
|
||||
|
||||
return GameConstants::SPELL_LEVELS;
|
||||
}
|
||||
|
||||
bool AccessibilityInfo::accessible(BattleHex tile, const CStack *stack) const
|
||||
{
|
||||
return accessible(tile, stack->doubleWide(), stack->attackerOwned);
|
||||
@ -2011,3 +2109,38 @@ bool CPlayerBattleCallback::battleCanCastSpell(ESpellCastProblem::ESpellCastProb
|
||||
|
||||
return problem == ESpellCastProblem::OK;
|
||||
}
|
||||
|
||||
BattleAttackInfo::BattleAttackInfo(const CStack *Attacker, const CStack *Defender, bool Shooting)
|
||||
{
|
||||
attacker = Attacker;
|
||||
defender = Defender;
|
||||
|
||||
attackerBonuses = Attacker;
|
||||
defenderBonuses = Defender;
|
||||
|
||||
attackerPosition = Attacker->position;
|
||||
defenderPosition = Defender->position;
|
||||
|
||||
attackerCount = Attacker->count;
|
||||
shooting = Shooting;
|
||||
chargedFields = 0;
|
||||
|
||||
luckyHit = false;
|
||||
deathBlow = false;
|
||||
ballistaDoubleDamage = false;
|
||||
}
|
||||
|
||||
BattleAttackInfo BattleAttackInfo::reverse() const
|
||||
{
|
||||
BattleAttackInfo ret = *this;
|
||||
std::swap(ret.attacker, ret.defender);
|
||||
std::swap(ret.attackerBonuses, ret.defenderBonuses);
|
||||
std::swap(ret.attackerPosition, ret.defenderPosition);
|
||||
|
||||
ret.attackerCount = ret.attacker->count;
|
||||
ret.shooting = false;
|
||||
ret.chargedFields = 0;
|
||||
ret.luckyHit = ret.ballistaDoubleDamage = ret.deathBlow = false;
|
||||
|
||||
return ret;
|
||||
}
|
@ -8,6 +8,7 @@ class CStack;
|
||||
class CSpell;
|
||||
struct BattleInfo;
|
||||
struct CObstacleInstance;
|
||||
class IBonusBearer;
|
||||
|
||||
namespace boost
|
||||
{class shared_mutex;}
|
||||
@ -47,6 +48,7 @@ protected:
|
||||
|
||||
public:
|
||||
boost::shared_mutex &getGsMutex(); //just return a reference to mutex, does not lock nor anything
|
||||
int getPlayerID() const;
|
||||
|
||||
friend class CBattleInfoEssentials;
|
||||
};
|
||||
@ -178,6 +180,24 @@ public:
|
||||
//ESpellCastProblem::ESpellCastProblem battleCanCastSpell(int player, ECastingMode::ECastingMode mode) const; //Checks if player is able to cast spells (at all) at the moment
|
||||
};
|
||||
|
||||
struct DLL_LINKAGE BattleAttackInfo
|
||||
{
|
||||
const IBonusBearer *attackerBonuses, *defenderBonuses;
|
||||
const CStack *attacker, *defender;
|
||||
BattleHex attackerPosition, defenderPosition;
|
||||
|
||||
int attackerCount;
|
||||
bool shooting;
|
||||
int chargedFields;
|
||||
|
||||
bool luckyHit;
|
||||
bool deathBlow;
|
||||
bool ballistaDoubleDamage;
|
||||
|
||||
BattleAttackInfo(const CStack *Attacker, const CStack *Defender, bool Shooting = false);
|
||||
BattleAttackInfo reverse() const;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE CBattleInfoCallback : public virtual CBattleInfoEssentials
|
||||
{
|
||||
public:
|
||||
@ -202,13 +222,17 @@ public:
|
||||
bool battleIsStackBlocked(const CStack * stack) const; //returns true if there is neighboring enemy stack
|
||||
std::set<const CStack*> batteAdjacentCreatures (const CStack * stack) const;
|
||||
|
||||
TDmgRange calculateDmgRange(const BattleAttackInfo &info) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
|
||||
TDmgRange calculateDmgRange(const CStack* attacker, const CStack* defender, TQuantity attackerCount, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
|
||||
TDmgRange calculateDmgRange(const CStack* attacker, const CStack* defender, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
|
||||
|
||||
//hextowallpart //int battleGetWallUnderHex(BattleHex hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found
|
||||
std::pair<ui32, ui32> battleEstimateDamage(const BattleAttackInfo &bai, std::pair<ui32, ui32> * retaliationDmg = NULL) const; //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair <min dmg, max dmg>
|
||||
std::pair<ui32, ui32> battleEstimateDamage(const CStack * attacker, const CStack * defender, std::pair<ui32, ui32> * retaliationDmg = NULL) const; //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair <min dmg, max dmg>
|
||||
si8 battleHasDistancePenalty( const CStack * stack, BattleHex destHex ) const;
|
||||
si8 battleHasDistancePenalty(const IBonusBearer *bonusBearer, BattleHex shooterPosition, BattleHex destHex ) const;
|
||||
si8 battleHasWallPenalty(const CStack * stack, BattleHex destHex) const; //checks if given stack has wall penalty
|
||||
si8 battleHasWallPenalty(const IBonusBearer *bonusBearer, BattleHex shooterPosition, BattleHex destHex) const; //checks if given stack has wall penalty
|
||||
EWallParts::EWallParts battleHexToWallPart(BattleHex hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found
|
||||
|
||||
//*** MAGIC
|
||||
@ -218,6 +242,7 @@ public:
|
||||
ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(int player, const CSpell * spell, ECastingMode::ECastingMode mode) const; //checks if given player can cast given spell
|
||||
ESpellCastProblem::ESpellCastProblem battleCanCastThisSpellHere(int player, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; //checks if given player can cast given spell at given tile in given mode
|
||||
ESpellCastProblem::ESpellCastProblem battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination) const; //determines if creature can cast a spell here
|
||||
std::vector<BattleHex> battleGetPossibleTargets(int player, const CSpell *spell) const;
|
||||
|
||||
si32 battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode) const;
|
||||
TSpell getRandomBeneficialSpell(const CStack * subject) const;
|
||||
|
@ -334,7 +334,7 @@ void CCampaignScenario::prepareCrossoverHeroes( std::vector<CGHeroInstance *> he
|
||||
BOOST_FOREACH(CGHeroInstance * cgh, crossoverHeroes)
|
||||
{
|
||||
#define RESET_PRIM_SKILL(NAME, VALNAME) \
|
||||
cgh->getBonus(Selector::type(Bonus::PRIMARY_SKILL) && \
|
||||
cgh->getBonusLocalFirst(Selector::type(Bonus::PRIMARY_SKILL) && \
|
||||
Selector::subtype(PrimarySkill::NAME) && \
|
||||
Selector::sourceType(Bonus::HERO_BASE_SKILL) )->val = cgh->type->heroClass->VALNAME;
|
||||
|
||||
|
@ -257,7 +257,7 @@ static void RemoveAbility(CCreature *cre, const JsonNode &ability)
|
||||
|
||||
Bonus::BonusType ecf = static_cast<Bonus::BonusType>(typeNo);
|
||||
|
||||
Bonus *b = cre->getBonus(Selector::type(ecf));
|
||||
Bonus *b = cre->getBonusLocalFirst(Selector::type(ecf));
|
||||
cre->removeBonus(b);
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
using namespace boost::logic;
|
||||
class CCallback;
|
||||
class CPlayerBattleCallback;
|
||||
class CBattleCallback;
|
||||
class ICallback;
|
||||
class CGlobalAI;
|
||||
struct Component;
|
||||
@ -61,7 +61,7 @@ public:
|
||||
std::string dllName;
|
||||
|
||||
virtual ~CBattleGameInterface() {};
|
||||
virtual void init(CPlayerBattleCallback * CB){};
|
||||
virtual void init(CBattleCallback * CB){};
|
||||
|
||||
//battle call-ins
|
||||
virtual BattleAction activeStack(const CStack * stack)=0; //called when it's turn of that stack
|
||||
@ -115,7 +115,7 @@ public:
|
||||
|
||||
std::string battleAIName;
|
||||
CBattleGameInterface *battleAI;
|
||||
CPlayerBattleCallback *cbc;
|
||||
CBattleCallback *cbc;
|
||||
|
||||
//battle interface
|
||||
virtual BattleAction activeStack(const CStack * stack);
|
||||
|
@ -1547,20 +1547,20 @@ void CGameState::initDuel()
|
||||
{
|
||||
CCreature *c = VLC->creh->creatures[cc.id];
|
||||
if(cc.attack >= 0)
|
||||
c->getBonus(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK))->val = cc.attack;
|
||||
c->getBonusLocalFirst(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK))->val = cc.attack;
|
||||
if(cc.defense >= 0)
|
||||
c->getBonus(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE))->val = cc.defense;
|
||||
c->getBonusLocalFirst(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE))->val = cc.defense;
|
||||
if(cc.speed >= 0)
|
||||
c->getBonus(Selector::type(Bonus::STACKS_SPEED))->val = cc.speed;
|
||||
c->getBonusLocalFirst(Selector::type(Bonus::STACKS_SPEED))->val = cc.speed;
|
||||
if(cc.HP >= 0)
|
||||
c->getBonus(Selector::type(Bonus::STACK_HEALTH))->val = cc.HP;
|
||||
c->getBonusLocalFirst(Selector::type(Bonus::STACK_HEALTH))->val = cc.HP;
|
||||
if(cc.dmg >= 0)
|
||||
{
|
||||
c->getBonus(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 1))->val = cc.dmg;
|
||||
c->getBonus(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 2))->val = cc.dmg;
|
||||
c->getBonusLocalFirst(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 1))->val = cc.dmg;
|
||||
c->getBonusLocalFirst(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 2))->val = cc.dmg;
|
||||
}
|
||||
if(cc.shoots >= 0)
|
||||
c->getBonus(Selector::type(Bonus::SHOTS))->val = cc.shoots;
|
||||
c->getBonusLocalFirst(Selector::type(Bonus::SHOTS))->val = cc.shoots;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2757,7 +2757,7 @@ DuelParameters DuelParameters::fromJSON(const std::string &fname)
|
||||
{
|
||||
DuelParameters ret;
|
||||
|
||||
const JsonNode duelData(ResourceID(fname, EResType::TEXT));
|
||||
const JsonNode duelData(ResourceID("DATA/" + fname, EResType::TEXT));
|
||||
ret.terType = duelData["terType"].Float();
|
||||
ret.bfieldType = duelData["bfieldType"].Float();
|
||||
BOOST_FOREACH(const JsonNode &n, duelData["sides"].Vector())
|
||||
@ -2791,8 +2791,13 @@ DuelParameters DuelParameters::fromJSON(const std::string &fname)
|
||||
|
||||
if(ss.heroId != -1)
|
||||
{
|
||||
BOOST_FOREACH(const JsonNode &spell, n["spells"].Vector())
|
||||
ss.spells.insert(spell.Float());
|
||||
auto spells = n["spells"];
|
||||
if(spells.getType() == JsonNode::DATA_STRING && spells.String() == "all")
|
||||
BOOST_FOREACH(auto spell, VLC->spellh->spells)
|
||||
ss.spells.insert(spell->id);
|
||||
else
|
||||
BOOST_FOREACH(const JsonNode &spell, n["spells"].Vector())
|
||||
ss.spells.insert(spell.Float());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1148,7 +1148,7 @@ void CGHeroInstance::updateSkill(int which, int val)
|
||||
bool luck = which == LUCK;
|
||||
Bonus::BonusType type[] = {Bonus::MORALE, Bonus::LUCK};
|
||||
|
||||
Bonus *b = getBonus(Selector::type(type[luck]) && Selector::sourceType(Bonus::SECONDARY_SKILL));
|
||||
Bonus *b = getBonusLocalFirst(Selector::type(type[luck]) && Selector::sourceType(Bonus::SECONDARY_SKILL));
|
||||
if(!b)
|
||||
{
|
||||
b = new Bonus(Bonus::PERMANENT, type[luck], Bonus::SECONDARY_SKILL, +val, which, which, Bonus::BASE_NUMBER);
|
||||
@ -1160,7 +1160,7 @@ void CGHeroInstance::updateSkill(int which, int val)
|
||||
else if(which == DIPLOMACY) //surrender discount: 20% per level
|
||||
{
|
||||
|
||||
if(Bonus *b = getBonus(Selector::type(Bonus::SURRENDER_DISCOUNT) && Selector::sourceType(Bonus::SECONDARY_SKILL)))
|
||||
if(Bonus *b = getBonusLocalFirst(Selector::type(Bonus::SURRENDER_DISCOUNT) && Selector::sourceType(Bonus::SECONDARY_SKILL)))
|
||||
b->val = +val;
|
||||
else
|
||||
addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::SURRENDER_DISCOUNT, Bonus::SECONDARY_SKILL, val * 20, which));
|
||||
|
@ -80,6 +80,8 @@ namespace GameConstants
|
||||
const int AVAILABLE_HEROES_PER_PLAYER = 2;
|
||||
const int SPELLBOOK_GOLD_COST = 500;
|
||||
|
||||
const int BATTLE_PENALTY_DISTANCE = 10; //if the distance is > than this, then shooting stack has distance penalty
|
||||
|
||||
const ui16 BACKPACK_START = 19;
|
||||
const int ID_CATAPULT = 3, ID_LOCK = 145;
|
||||
|
||||
|
@ -476,7 +476,52 @@ const TBonusListPtr IBonusBearer::getSpellBonuses() const
|
||||
return getBonuses(Selector::sourceType(Bonus::SPELL_EFFECT), cachingStr.str());
|
||||
}
|
||||
|
||||
Bonus * CBonusSystemNode::getBonus(const CSelector &selector)
|
||||
const Bonus * IBonusBearer::getEffect(ui16 id, int turn /*= 0*/) const
|
||||
{
|
||||
//TODO should check only local bonuses?
|
||||
auto bonuses = getAllBonuses();
|
||||
BOOST_FOREACH(const Bonus *it, *bonuses)
|
||||
{
|
||||
if(it->source == Bonus::SPELL_EFFECT && it->sid == id)
|
||||
{
|
||||
if(!turn || it->turnsRemain > turn)
|
||||
return &(*it);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ui8 IBonusBearer::howManyEffectsSet(ui16 id) const
|
||||
{
|
||||
//TODO should check only local bonuses?
|
||||
ui8 ret = 0;
|
||||
|
||||
auto bonuses = getAllBonuses();
|
||||
BOOST_FOREACH(const Bonus *it, *bonuses)
|
||||
{
|
||||
if(it->source == Bonus::SPELL_EFFECT && it->sid == id) //effect found
|
||||
{
|
||||
++ret;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
const TBonusListPtr IBonusBearer::getAllBonuses() const
|
||||
{
|
||||
auto matchAll= [] (const Bonus *) { return true; };
|
||||
auto matchNone= [] (const Bonus *) { return true; };
|
||||
return getAllBonuses(matchAll, matchNone);
|
||||
}
|
||||
|
||||
const Bonus * IBonusBearer::getBonus(const CSelector &selector) const
|
||||
{
|
||||
auto bonuses = getAllBonuses();
|
||||
return bonuses->getFirst(selector);
|
||||
}
|
||||
|
||||
Bonus * CBonusSystemNode::getBonusLocalFirst(const CSelector &selector)
|
||||
{
|
||||
Bonus *ret = bonuses.getFirst(selector);
|
||||
if(ret)
|
||||
@ -484,7 +529,7 @@ Bonus * CBonusSystemNode::getBonus(const CSelector &selector)
|
||||
|
||||
FOREACH_PARENT(pname)
|
||||
{
|
||||
ret = pname->getBonus(selector);
|
||||
ret = pname->getBonusLocalFirst(selector);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@ -492,9 +537,9 @@ Bonus * CBonusSystemNode::getBonus(const CSelector &selector)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const Bonus * CBonusSystemNode::getBonus( const CSelector &selector ) const
|
||||
const Bonus * CBonusSystemNode::getBonusLocalFirst( const CSelector &selector ) const
|
||||
{
|
||||
return (const_cast<CBonusSystemNode*>(this))->getBonus(selector);
|
||||
return (const_cast<CBonusSystemNode*>(this))->getBonusLocalFirst(selector);
|
||||
}
|
||||
|
||||
void CBonusSystemNode::getParents(TCNodes &out) const /*retreives list of parent nodes (nodes to inherit bonuses from) */
|
||||
|
@ -351,10 +351,16 @@ DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const Bonus &bonus);
|
||||
class DLL_LINKAGE BonusList
|
||||
{
|
||||
private:
|
||||
std::vector<Bonus*> bonuses;
|
||||
typedef std::vector<Bonus*> TInternalContainer;
|
||||
|
||||
TInternalContainer bonuses;
|
||||
bool belongsToTree;
|
||||
|
||||
|
||||
public:
|
||||
typedef TInternalContainer::const_reference const_reference;
|
||||
typedef TInternalContainer::value_type value_type;
|
||||
|
||||
BonusList(bool BelongsToTree = false);
|
||||
BonusList(const BonusList &bonusList);
|
||||
BonusList& operator=(const BonusList &bonusList);
|
||||
@ -508,6 +514,9 @@ public:
|
||||
const TBonusListPtr getBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const;
|
||||
const TBonusListPtr getBonuses(const CSelector &selector, const std::string &cachingStr = "") const;
|
||||
|
||||
const TBonusListPtr getAllBonuses() const;
|
||||
const Bonus *getBonus(const CSelector &selector) const; //returns any bonus visible on node that matches (or nullptr if none matches)
|
||||
|
||||
//legacy interface
|
||||
int valOfBonuses(Bonus::BonusType type, const CSelector &selector) const;
|
||||
int valOfBonuses(Bonus::BonusType type, int subtype = -1) const; //subtype -> subtype of bonus, if -1 then anyt;
|
||||
@ -526,6 +535,8 @@ public:
|
||||
ui32 MaxHealth() const; //get max HP of stack with all modifiers
|
||||
bool isLiving() const; //non-undead, non-non living or alive
|
||||
virtual si32 magicResistance() const;
|
||||
const Bonus * getEffect(ui16 id, int turn = 0) const; //effect id (SP)
|
||||
ui8 howManyEffectsSet(ui16 id) const; //returns amount of effects with given id set for this stack
|
||||
|
||||
si32 manaLimit() const; //maximum mana value for this hero (basically 10*knowledge)
|
||||
int getPrimSkillLevel(int id) const; //0-attack, 1-defence, 2-spell power, 3-knowledge
|
||||
@ -567,7 +578,7 @@ public:
|
||||
TBonusListPtr limitBonuses(const BonusList &allBonuses) const; //same as above, returns out by val for convienence
|
||||
const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = NULL, const std::string &cachingStr = "") const;
|
||||
void getParents(TCNodes &out) const; //retrieves list of parent nodes (nodes to inherit bonuses from),
|
||||
const Bonus *getBonus(const CSelector &selector) const;
|
||||
const Bonus *getBonusLocalFirst(const CSelector &selector) const;
|
||||
|
||||
//non-const interface
|
||||
void getParents(TNodes &out); //retrieves list of parent nodes (nodes to inherit bonuses from)
|
||||
@ -575,7 +586,7 @@ public:
|
||||
void getRedAncestors(TNodes &out);
|
||||
void getRedChildren(TNodes &out);
|
||||
void getRedDescendants(TNodes &out);
|
||||
Bonus *getBonus(const CSelector &selector);
|
||||
Bonus *getBonusLocalFirst(const CSelector &selector);
|
||||
|
||||
void attachTo(CBonusSystemNode *parent);
|
||||
void detachFrom(CBonusSystemNode *parent);
|
||||
|
@ -54,7 +54,7 @@ DLL_LINKAGE void SetPrimSkill::applyGs( CGameState *gs )
|
||||
|
||||
if(which <4)
|
||||
{
|
||||
Bonus *skill = hero->getBonus(Selector::type(Bonus::PRIMARY_SKILL) && Selector::subtype(which) && Selector::sourceType(Bonus::HERO_BASE_SKILL));
|
||||
Bonus *skill = hero->getBonusLocalFirst(Selector::type(Bonus::PRIMARY_SKILL) && Selector::subtype(which) && Selector::sourceType(Bonus::HERO_BASE_SKILL));
|
||||
assert(skill);
|
||||
|
||||
if(abs)
|
||||
@ -1025,7 +1025,7 @@ DLL_LINKAGE void BattleTriggerEffect::applyGs( CGameState *gs )
|
||||
}
|
||||
case Bonus::POISON:
|
||||
{
|
||||
Bonus * b = st->getBonus(Selector::source(Bonus::SPELL_EFFECT, 71) && Selector::type(Bonus::STACK_HEALTH));
|
||||
Bonus * b = st->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT, 71) && Selector::type(Bonus::STACK_HEALTH));
|
||||
if (b)
|
||||
b->val = val;
|
||||
break;
|
||||
@ -1299,7 +1299,7 @@ DLL_LINKAGE void SetStackEffect::applyGs( CGameState *gs )
|
||||
CStack *s = gs->curB->getStack(id);
|
||||
if(s)
|
||||
{
|
||||
if(spellid == 47 || spellid == 80 || !s->hasBonus(Selector::source(Bonus::SPELL_EFFECT, spellid)))//disrupting ray or acid breath or not on the list - just add
|
||||
if(spellid == Spells::DISRUPTING_RAY || spellid == Spells::ACID_BREATH_DEFENSE || !s->hasBonus(Selector::source(Bonus::SPELL_EFFECT, spellid)))//disrupting ray or acid breath or not on the list - just add
|
||||
{
|
||||
BOOST_FOREACH(Bonus &fromEffect, effect)
|
||||
{
|
||||
|
@ -787,7 +787,7 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
|
||||
}
|
||||
}
|
||||
|
||||
const Bonus * bonus = att->getBonus(Selector::type(Bonus::SPELL_LIKE_ATTACK));
|
||||
const Bonus * bonus = att->getBonusLocalFirst(Selector::type(Bonus::SPELL_LIKE_ATTACK));
|
||||
if (bonus && (bat.shot())) //TODO: make it work in meele?
|
||||
{
|
||||
bat.bsa.front().flags |= BattleStackAttacked::EFFECT;
|
||||
@ -1342,7 +1342,7 @@ void CGameHandler::newTurn()
|
||||
}
|
||||
if (t->hasBonusOfType (Bonus::DARKNESS))
|
||||
{
|
||||
t->hideTiles(t->getOwner(), t->getBonus(Selector::type(Bonus::DARKNESS))->val);
|
||||
t->hideTiles(t->getOwner(), t->getBonusLocalFirst(Selector::type(Bonus::DARKNESS))->val);
|
||||
}
|
||||
//unhiding what shouldn't be hidden? //that's handled in netpacks client
|
||||
}
|
||||
@ -3642,7 +3642,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
|
||||
BattleStackAdded bsa;
|
||||
bsa.attacker = summoner->attackerOwned;
|
||||
|
||||
bsa.creID = summoner->getBonus(Selector::type(Bonus::DAEMON_SUMMONING))->subtype; //in case summoner can summon more than one type of monsters... scream!
|
||||
bsa.creID = summoner->getBonusLocalFirst(Selector::type(Bonus::DAEMON_SUMMONING))->subtype; //in case summoner can summon more than one type of monsters... scream!
|
||||
ui64 risedHp = summoner->count * summoner->valOfBonuses(Bonus::DAEMON_SUMMONING, bsa.creID);
|
||||
bsa.amount = std::min ((ui32)(risedHp / VLC->creh->creatures[bsa.creID]->MaxHealth()), destStack->baseAmount);
|
||||
|
||||
@ -3676,8 +3676,8 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
|
||||
int spellID = ba.additionalInfo;
|
||||
BattleHex destination(ba.destinationTile);
|
||||
|
||||
const Bonus *randSpellcaster = stack->getBonus(Selector::type(Bonus::RANDOM_SPELLCASTER));
|
||||
const Bonus * spellcaster = stack->getBonus(Selector::typeSubtype(Bonus::SPELLCASTER, spellID));
|
||||
const Bonus *randSpellcaster = stack->getBonusLocalFirst(Selector::type(Bonus::RANDOM_SPELLCASTER));
|
||||
const Bonus * spellcaster = stack->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPELLCASTER, spellID));
|
||||
|
||||
//TODO special bonus for genies ability
|
||||
if(randSpellcaster && battleGetRandomStackSpell(stack, CBattleInfoCallback::RANDOM_AIMED) < 0)
|
||||
@ -4126,67 +4126,65 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
|
||||
pseudoBonus.val = spellLvl;
|
||||
pseudoBonus.turnsRemain = gs->curB->calculateSpellDuration(spell, caster, stackSpellPower ? stackSpellPower : usedSpellPower);
|
||||
CStack::stackEffectToFeature(sse.effect, pseudoBonus);
|
||||
if (spellID == 72 && stack)//bind
|
||||
if (spellID == Spells::BIND && stack)//bind
|
||||
{
|
||||
sse.effect.back().additionalInfo = stack->ID; //we need to know who casted Bind
|
||||
}
|
||||
const Bonus * bonus = NULL;
|
||||
if (caster)
|
||||
bonus = caster->getBonus(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, spellID));
|
||||
bonus = caster->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, spellID));
|
||||
//TODO does hero speciality should affects his stack casting spells?
|
||||
|
||||
si32 power = 0;
|
||||
for(auto it = attackedCres.begin(); it != attackedCres.end(); ++it)
|
||||
BOOST_FOREACH(const CStack *affected, attackedCres)
|
||||
{
|
||||
if(vstd::contains(sc.resisted, (*it)->ID)) //this creature resisted the spell
|
||||
if(vstd::contains(sc.resisted, affected->ID)) //this creature resisted the spell
|
||||
continue;
|
||||
sse.stacks.push_back((*it)->ID);
|
||||
sse.stacks.push_back(affected->ID);
|
||||
|
||||
//Apply hero specials - peculiar enchants
|
||||
if ((*it)->base) // no war machines - TODO: make it work
|
||||
{
|
||||
ui8 tier = (*it)->base->type->level;
|
||||
if (bonus)
|
||||
{
|
||||
switch(bonus->additionalInfo)
|
||||
{
|
||||
case 0: //normal
|
||||
{
|
||||
switch(tier)
|
||||
{
|
||||
case 1: case 2:
|
||||
power = 3;
|
||||
break;
|
||||
case 3: case 4:
|
||||
power = 2;
|
||||
break;
|
||||
case 5: case 6:
|
||||
power = 1;
|
||||
break;
|
||||
}
|
||||
Bonus specialBonus(sse.effect.back());
|
||||
specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely
|
||||
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> ((*it)->ID, specialBonus)); //additional premy to given effect
|
||||
}
|
||||
break;
|
||||
case 1: //only Coronius as yet
|
||||
{
|
||||
power = std::max(5 - tier, 0);
|
||||
Bonus specialBonus = CStack::featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, pseudoBonus.turnsRemain);
|
||||
specialBonus.sid = spellID;
|
||||
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> ((*it)->ID, specialBonus)); //additional attack to Slayer effect
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (caster && caster->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, spellID)) //TODO: better handling of bonus percentages
|
||||
const ui8 tier = affected->getCreature()->level;
|
||||
if (bonus)
|
||||
{
|
||||
switch(bonus->additionalInfo)
|
||||
{
|
||||
int damagePercent = caster->level * caster->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, spellID) / tier;
|
||||
Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, pseudoBonus.turnsRemain);
|
||||
specialBonus.valType = Bonus::PERCENT_TO_ALL;
|
||||
specialBonus.sid = spellID;
|
||||
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> ((*it)->ID, specialBonus));
|
||||
case 0: //normal
|
||||
{
|
||||
switch(tier)
|
||||
{
|
||||
case 1: case 2:
|
||||
power = 3;
|
||||
break;
|
||||
case 3: case 4:
|
||||
power = 2;
|
||||
break;
|
||||
case 5: case 6:
|
||||
power = 1;
|
||||
break;
|
||||
}
|
||||
Bonus specialBonus(sse.effect.back());
|
||||
specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely
|
||||
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional premy to given effect
|
||||
}
|
||||
break;
|
||||
case 1: //only Coronius as yet
|
||||
{
|
||||
power = std::max(5 - tier, 0);
|
||||
Bonus specialBonus = CStack::featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, pseudoBonus.turnsRemain);
|
||||
specialBonus.sid = spellID;
|
||||
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional attack to Slayer effect
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (caster && caster->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, spellID)) //TODO: better handling of bonus percentages
|
||||
{
|
||||
int damagePercent = caster->level * caster->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, spellID) / tier;
|
||||
Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, pseudoBonus.turnsRemain);
|
||||
specialBonus.valType = Bonus::PERCENT_TO_ALL;
|
||||
specialBonus.sid = spellID;
|
||||
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus));
|
||||
}
|
||||
}
|
||||
|
||||
if(!sse.stacks.empty())
|
||||
@ -4531,7 +4529,7 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
|
||||
|
||||
if(st->hasBonusOfType(Bonus::POISON))
|
||||
{
|
||||
const Bonus * b = st->getBonus(Selector::source(Bonus::SPELL_EFFECT, 71) && Selector::type(Bonus::STACK_HEALTH));
|
||||
const Bonus * b = st->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT, 71) && Selector::type(Bonus::STACK_HEALTH));
|
||||
if (b) //TODO: what if not?...
|
||||
{
|
||||
bte.val = std::max (b->val - 10, -(st->valOfBonuses(Bonus::POISON)));
|
||||
@ -5984,7 +5982,7 @@ void CGameHandler::runBattle()
|
||||
nextStackMorale = next->MoraleVal();
|
||||
if(!vstd::contains(next->state,EBattleStackState::HAD_MORALE) //only one extra move per turn possible
|
||||
&& !vstd::contains(next->state,EBattleStackState::DEFENDING)
|
||||
&& !vstd::contains(next->state,EBattleStackState::WAITING)
|
||||
&& !next->waited()
|
||||
&& !vstd::contains(next->state, EBattleStackState::FEAR)
|
||||
&& next->alive()
|
||||
&& nextStackMorale > 0
|
||||
|
Loading…
Reference in New Issue
Block a user