1
0
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:
Michał W. Urbańczyk 2012-09-20 16:55:21 +00:00
parent 871f680ccc
commit 62e63d45b1
25 changed files with 793 additions and 257 deletions

View File

@ -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,74 +148,204 @@ static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const Battl
return shooters[0] < shooters[1];
}
template <typename Container, typename Pred>
auto sum(const Container & c, Pred p) -> decltype(p(*boost::begin(c)))
{
double ret = 0;
BOOST_FOREACH(const auto &element, c)
{
ret += p(element);
}
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)
{
sufferedDamage.fill(0);
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(enemyReachability.isReachable(i))
{
meleeAttackable[i] = true;
BOOST_FOREACH(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;
});
}
}
};
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);
}
}
}
AttackPossibility 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.damageDiff(); } );
}
};
BattleAction CBattleAI::activeStack( const CStack * stack )
{
//boost::this_thread::sleep(boost::posix_time::seconds(2));
try
{
print("activeStack called for " + stack->nodeName());
auto dists = cb->battleGetDistances(stack);
std::vector<EnemyInfo> enemiesShootable, enemiesReachable, enemiesUnreachable;
if(stack->type->idNumber == 145) //catapult
{
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 useCatapult(stack);
return attack;
}
if(cb->battleCanCastSpell())
attemptCastingSpell();
BOOST_FOREACH(const CStack *s, cb->battleGetStacks(CBattleCallback::ONLY_ENEMY))
ThreatMap threatsToUs(stack);
PotentialTargets targets(stack);
if(targets.possibleAttacks.size())
{
if(cb->battleCanShoot(stack, s->position))
{
enemiesShootable.push_back(s);
auto hlp = targets.bestAction();
if(hlp.attack.shooting)
return BattleAction::makeShotAttack(stack, hlp.enemy);
else
return BattleAction::makeMeleeAttack(stack, hlp.enemy, hlp.tile);
}
else
{
std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack, false);
BOOST_FOREACH(BattleHex hex, avHexes)
if(stack->waited())
{
if(CStack::isMeleeAttackPossible(stack, s, hex))
{
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);
}
}
if(!vstd::contains(enemiesReachable, s) && s->position.isValid())
enemiesUnreachable.push_back(s);
}
}
if(enemiesShootable.size())
{
const EnemyInfo &ei= *std::max_element(enemiesShootable.begin(), enemiesShootable.end(), isMoreProfitable);
return BattleAction::makeShotAttack(stack, ei.s);
}
else if(enemiesReachable.size())
{
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)));
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);
}

View File

@ -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();
};

View File

@ -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()
{

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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)
{

View File

@ -387,6 +387,9 @@ void CClient::newGame( CConnection *con, StartInfo *si )
{
auto cbc = make_shared<CBattleCallback>(gs, color, this);
battleCallbacks[color] = cbc;
if(!color)
battleints[color] = CDynLibHandler::getNewBattleAI(settings["server"]["neutralAI"].String());
else
battleints[color] = CDynLibHandler::getNewBattleAI("StupidAI");
battleints[color]->init(cbc.get());
}

View File

@ -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;

View File

@ -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

View File

@ -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";

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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);

View File

@ -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,6 +2791,11 @@ DuelParameters DuelParameters::fromJSON(const std::string &fname)
if(ss.heroId != -1)
{
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());
}

View File

@ -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));

View File

@ -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;

View File

@ -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) */

View File

@ -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);

View File

@ -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)
{

View File

@ -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,25 +4126,24 @@ 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;
const ui8 tier = affected->getCreature()->level;
if (bonus)
{
switch(bonus->additionalInfo)
@ -4165,7 +4164,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
}
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
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional premy to given effect
}
break;
case 1: //only Coronius as yet
@ -4173,7 +4172,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
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
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional attack to Slayer effect
}
break;
}
@ -4184,8 +4183,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
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));
}
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus));
}
}
@ -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