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 "../../lib/BattleState.h"
#include "../../CCallback.h" #include "../../CCallback.h"
#include "../../lib/CCreatureHandler.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) CBattleAI::CBattleAI(void)
: side(-1), cb(NULL) : side(-1), cb(NULL)
@ -19,10 +70,13 @@ CBattleAI::~CBattleAI(void)
print("destroyed"); print("destroyed");
} }
void CBattleAI::init( CPlayerBattleCallback * CB ) void CBattleAI::init( CBattleCallback * CB )
{ {
print("init called, saving ptr to IBattleCallback"); print("init called, saving ptr to IBattleCallback");
cbc = cb = CB; cbc = cb = CB;
playerID = CB->getPlayerID();; //TODO should be sth in callback
CB->waitTillRealize = true;
CB->unlockGsWhenWaiting = false;
} }
void CBattleAI::actionFinished( const BattleAction *action ) void CBattleAI::actionFinished( const BattleAction *action )
@ -94,73 +148,203 @@ static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const Battl
return shooters[0] < shooters[1]; 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)); double ret = 0;
print("activeStack called for " + stack->nodeName()); BOOST_FOREACH(const auto &element, c)
auto dists = cb->battleGetDistances(stack);
std::vector<EnemyInfo> enemiesShootable, enemiesReachable, enemiesUnreachable;
if(stack->type->idNumber == 145) //catapult
{ {
BattleAction attack; ret += p(element);
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;
} }
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)) sufferedDamage.fill(0);
{
enemiesShootable.push_back(s);
}
else
{
std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack, false);
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); meleeAttackable[i] = true;
if(i == enemiesReachable.end()) BOOST_FOREACH(auto n, BattleHex(i).neighbouringTiles())
{ meleeAttackable[n] = true;
enemiesReachable.push_back(s);
i = enemiesReachable.begin() + (enemiesReachable.size() - 1);
}
i->attackFrom.push_back(hex);
} }
} }
if(!vstd::contains(enemiesReachable, s) && s->position.isValid()) //Gather possible assaults
enemiesUnreachable.push_back(s); 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); if(possibleAttacks.empty())
return BattleAction::makeShotAttack(stack, ei.s); 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); print("activeStack called for " + stack->nodeName());
return BattleAction::makeMeleeAttack(stack, ei.s, *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), &willSecondHexBlockMoreEnemyShooters)); if(stack->type->idNumber == 145) //catapult
} return useCatapult(stack);
else
{ if(cb->battleCanCastSpell())
const EnemyInfo &ei= *std::min_element(enemiesUnreachable.begin(), enemiesUnreachable.end(), boost::bind(isCloser, _1, _2, boost::ref(dists))); attemptCastingSpell();
if(distToNearestNeighbour(ei.s->position, dists) < GameConstants::BFIELD_SIZE)
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); return BattleAction::makeDefend(stack);
@ -181,11 +365,6 @@ void CBattleAI::battleEnd(const BattleResult *br)
print("battleEnd called"); print("battleEnd called");
} }
// void CStupidAI::battleResultsApplied()
// {
// print("battleResultsApplied called");
// }
void CBattleAI::battleNewRoundFirst(int round) void CBattleAI::battleNewRoundFirst(int round)
{ {
print("battleNewRoundFirst called"); print("battleNewRoundFirst called");
@ -244,7 +423,7 @@ void CBattleAI::battleStacksRemoved(const BattleStacksRemoved & bsr)
void CBattleAI::print(const std::string &text) const 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) 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 class CBattleAI : public CBattleGameInterface
{ {
int side; int side;
CPlayerBattleCallback *cb; CBattleCallback *cb;
void print(const std::string &text) const; void print(const std::string &text) const;
public: public:
CBattleAI(void); CBattleAI(void);
~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 actionFinished(const BattleAction *action) OVERRIDE;//occurs AFTER every action taken by any stack or by the hero
void actionStarted(const BattleAction *action) OVERRIDE;//occurs BEFORE every action taken by any stack or by the hero void actionStarted(const BattleAction *action) OVERRIDE;//occurs BEFORE every action taken by any stack or by the hero
BattleAction activeStack(const CStack * stack) OVERRIDE; //called when it's turn of that stack BattleAction activeStack(const CStack * stack) OVERRIDE; //called when it's turn of that stack
@ -35,5 +35,8 @@ public:
void battleStacksRemoved(const BattleStacksRemoved & bsr) OVERRIDE; //called when certain stack is completely removed from battlefield void battleStacksRemoved(const BattleStacksRemoved & bsr) OVERRIDE; //called when certain stack is completely removed from battlefield
BattleAction goTowards(const CStack * stack, BattleHex hex ); BattleAction goTowards(const CStack * stack, BattleHex hex );
BattleAction useCatapult(const CStack * stack);
void attemptCastingSpell();
}; };

View File

@ -7,7 +7,7 @@
#define strcpy_s(a, b, c) strncpy(a, c, b) #define strcpy_s(a, b, c) strncpy(a, c, b)
#endif #endif
const char *g_cszAiName = "Stupid AI 0.1"; const char *g_cszAiName = "Battle AI";
extern "C" DLL_EXPORT int GetGlobalAiVersion() extern "C" DLL_EXPORT int GetGlobalAiVersion()
{ {

View File

@ -19,7 +19,7 @@ CStupidAI::~CStupidAI(void)
print("destroyed"); print("destroyed");
} }
void CStupidAI::init( CPlayerBattleCallback * CB ) void CStupidAI::init( CBattleCallback * CB )
{ {
print("init called, saving ptr to IBattleCallback"); print("init called, saving ptr to IBattleCallback");
cbc = cb = CB; cbc = cb = CB;

View File

@ -5,14 +5,14 @@
class CStupidAI : public CBattleGameInterface class CStupidAI : public CBattleGameInterface
{ {
int side; int side;
CPlayerBattleCallback *cb; CBattleCallback *cb;
void print(const std::string &text) const; void print(const std::string &text) const;
public: public:
CStupidAI(void); CStupidAI(void);
~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 actionFinished(const BattleAction *action) OVERRIDE;//occurs AFTER every action taken by any stack or by the hero
void actionStarted(const BattleAction *action) OVERRIDE;//occurs BEFORE every action taken by any stack or by the hero void actionStarted(const BattleAction *action) OVERRIDE;//occurs BEFORE every action taken by any stack or by the hero
BattleAction activeStack(const CStack * stack) OVERRIDE; //called when it's turn of that stack BattleAction activeStack(const CStack * stack) OVERRIDE; //called when it's turn of that stack

View File

@ -383,7 +383,7 @@ namespace vstd
return std::inserter(c, c.end()); 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> template<class ForwardRange, class ValueFunction>
auto minElementByFun(const ForwardRange& rng, ValueFunction vf) -> decltype(boost::begin(rng)) auto minElementByFun(const ForwardRange& rng, ValueFunction vf) -> decltype(boost::begin(rng))
{ {
@ -393,6 +393,17 @@ namespace vstd
return vf(lhs) < vf(rhs); 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; using std::shared_ptr;

View File

@ -1265,7 +1265,7 @@ void CBattleInterface::bSpellf()
else if(spellCastProblem == ESpellCastProblem::MAGIC_IS_BLOCKED) else if(spellCastProblem == ESpellCastProblem::MAGIC_IS_BLOCKED)
{ {
//Handle Orb of Inhibition-like effects -> we want to display dialog with info, why casting is impossible //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) if(!blockingBonus)
return;; return;;
@ -2002,7 +2002,7 @@ void CBattleInterface::activateStack()
queue->update(); queue->update();
redrawBackgroundWithHexes(activeStack); 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 //block cast spell button if hero doesn't have a spellbook
ESpellCastProblem::ESpellCastProblem spellcastingProblem; 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 //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)), const Bonus *spellcaster = s->getBonusLocalFirst(Selector::type(Bonus::SPELLCASTER)),
*randomSpellcaster = s->getBonus(Selector::type(Bonus::RANDOM_SPELLCASTER)); *randomSpellcaster = s->getBonusLocalFirst(Selector::type(Bonus::RANDOM_SPELLCASTER));
if (s->casts && (spellcaster || randomSpellcaster)) if (s->casts && (spellcaster || randomSpellcaster))
{ {
stackCanCastSpell = true; 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." spellText = CGI->generaltexth->allTexts[610]; //"%s, duration: %d rounds."
boost::replace_first (spellText, "%s", CGI->spellh->spells[effect]->name); 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)); boost::replace_first (spellText, "%d", boost::lexical_cast<std::string>(duration));
new CAnimImage("SpellInt", effect + 1, 0, 20 + 52 * printed, 184); 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: case ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED:
{ {
//Recanter's Cloak or similar effect. Try to retrieve bonus //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? //TODO what about other values and non-artifact sources?
if(b && b->val == 2 && b->source == Bonus::ARTIFACT) if(b && b->val == 2 && b->source == Bonus::ARTIFACT)
{ {

View File

@ -387,7 +387,10 @@ void CClient::newGame( CConnection *con, StartInfo *si )
{ {
auto cbc = make_shared<CBattleCallback>(gs, color, this); auto cbc = make_shared<CBattleCallback>(gs, color, this);
battleCallbacks[color] = cbc; 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()); battleints[color]->init(cbc.get());
} }
} }

View File

@ -1155,19 +1155,6 @@ si32 CStack::magicResistance() const
return magicResistance; 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) void CStack::stackEffectToFeature(std::vector<Bonus> & sf, const Bonus & sse)
{ {
si32 power = VLC->spellh->spells[sse.sid]->powers[sse.val]; 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 bool CStack::willMove(int turn /*= 0*/) const
{ {
return ( turn ? true : !vstd::contains(state, EBattleStackState::DEFENDING) ) return ( turn ? true : !vstd::contains(state, EBattleStackState::DEFENDING) )
@ -1374,6 +1350,14 @@ bool CStack::moved( int turn /*= 0*/ ) const
return false; return false;
} }
bool CStack::waited(int turn /*= 0*/) const
{
if(!turn)
return vstd::contains(state, EBattleStackState::WAITING);
else
return false;
}
bool CStack::doubleWide() const bool CStack::doubleWide() const
{ {
return getCreature()->doubleWide; return getCreature()->doubleWide;

View File

@ -167,11 +167,10 @@ public:
void init(); //set initial (invalid) values void init(); //set initial (invalid) values
void postInit(); //used to finish initialization when inheriting creature parameters is working void postInit(); //used to finish initialization when inheriting creature parameters is working
std::string getName() const; //plural or singular 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 willMove(int turn = 0) const; //if stack has remaining move this turn
bool ableToRetaliate() const; //if stack can retaliate after attacked bool ableToRetaliate() const; //if stack can retaliate after attacked
bool moved(int turn = 0) const; //if stack was already moved this turn 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 canMove(int turn = 0) const; //if stack can move
bool canBeHealed() const; //for first aid tent - only harmed stacks that are not war machines 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 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 int CArtifactInstance::getGivenSpellID() const
{ {
const Bonus * b = getBonus(Selector::type(Bonus::SPELL)); const Bonus * b = getBonusLocalFirst(Selector::type(Bonus::SPELL));
if(!b) if(!b)
{ {
tlog3 << "Warning: " << nodeName() << " doesn't bear any spell!\n"; tlog3 << "Warning: " << nodeName() << " doesn't bear any spell!\n";

View File

@ -10,6 +10,26 @@
namespace SiegeStuffThatShouldBeMovedToHandlers // <=== TODO 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 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}; 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; battle = B;
} }
int CCallbackBase::getPlayerID() const
{
return player;
}
ui8 CBattleInfoEssentials::battleTerrainType() const ui8 CBattleInfoEssentials::battleTerrainType() const
{ {
RETURN_IF_NOT_BATTLE(-1); RETURN_IF_NOT_BATTLE(-1);
@ -369,21 +394,26 @@ ui8 CBattleInfoEssentials::battleGetWallState(int partOfWall) const
} }
si8 CBattleInfoCallback::battleHasWallPenalty( const CStack * stack, BattleHex destHex ) 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); RETURN_IF_NOT_BATTLE(false);
if (!battleGetSiegeLevel() || stack->hasBonusOfType(Bonus::NO_WALL_PENALTY)) if (!battleGetSiegeLevel() || bonusBearer->hasBonusOfType(Bonus::NO_WALL_PENALTY))
return false; return false;
const int wallInStackLine = lineToWallHex(stack->position.getY()); const int wallInStackLine = lineToWallHex(shooterPosition.getY());
const int wallInDestLine = lineToWallHex(destHex.getY()); const int wallInDestLine = lineToWallHex(destHex.getY());
const bool stackLeft = stack->position < wallInStackLine; const bool stackLeft = shooterPosition < wallInStackLine;
const bool destRight = destHex > wallInDestLine; const bool destRight = destHex > wallInDestLine;
if (stackLeft && destRight) //shooting from outside to inside if (stackLeft && destRight) //shooting from outside to inside
{ {
int row = (stack->position + destHex) / (2 * GameConstants::BFIELD_WIDTH); int row = (shooterPosition + destHex) / (2 * GameConstants::BFIELD_WIDTH);
if (stack->position > destHex && ((destHex % GameConstants::BFIELD_WIDTH - stack->position % GameConstants::BFIELD_WIDTH) < 2)) //shooting up high if (shooterPosition > destHex && ((destHex % GameConstants::BFIELD_WIDTH - shooterPosition % GameConstants::BFIELD_WIDTH) < 2)) //shooting up high
row -= 2; row -= 2;
const int wallPos = lineToWallHex(row); const int wallPos = lineToWallHex(row);
if (battleHexToWallPart(wallPos) != -1) //wall still exists or is indestructible 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(); const CStack *active = battleActiveStack();
//active stack hasn't taken any action yet - must be placed at the beginning of queue, no matter what //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); out.push_back(active);
if(out.size() == howMany) 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? 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)) if(vstd::contains(s->state, EBattleStackState::HAD_MORALE))
p = 2; 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); return calculateDmgRange(attacker, defender, attacker->count, shooting, charge, lucky, deathBlow, ballistaDoubleDmg);
} }
TDmgRange CBattleInfoCallback::calculateDmgRange( const CStack* attacker, const CStack* defender, TQuantity attackerCount, TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo &info) const
bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg ) const
{ {
double additiveBonus = 1.0, multBonus = 1.0, double additiveBonus = 1.0, multBonus = 1.0,
minDmg = attacker->getMinDamage() * attackerCount, minDmg = info.attackerBonuses->getMinDamage() * info.attackerCount,
maxDmg = attacker->getMaxDamage() * 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) SiegeStuffThatShouldBeMovedToHandlers::retreiveTurretDamageRange(info.attacker, minDmg, maxDmg);
{
case -2: //keep
minDmg = 15;
maxDmg = 15;
break;
case -3: case -4: //turrets
minDmg = 7.5;
maxDmg = 7.5;
break;
}
} }
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 { //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 return b ? b->val : 0; //if there is no hero or no info on his primary skill, return 0
}; };
minDmg *= retreivePrimSkill(PrimarySkill::ATTACK) + 1; minDmg *= retreiveHeroPrimSkill(PrimarySkill::ATTACK) + 1;
maxDmg *= retreivePrimSkill(PrimarySkill::ATTACK) + 1; maxDmg *= retreiveHeroPrimSkill(PrimarySkill::ATTACK) + 1;
} }
int attackDefenceDifference = 0; 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; double multAttackReduction = info.attackerBonuses->valOfBonuses(Bonus::GENERAL_ATTACK_REDUCTION, -1024) / 100.0;
attackDefenceDifference = attacker->Attack() * multAttackReduction; attackDefenceDifference = info.attackerBonuses->Attack() * multAttackReduction;
} }
else 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; double multDefenceReduction = (100 - info.attackerBonuses->valOfBonuses(Bonus::ENEMY_DEFENCE_REDUCTION, -1024)) / 100.0;
attackDefenceDifference -= defender->Defense() * multDefenceReduction; attackDefenceDifference -= info.defenderBonuses->Defense() * multDefenceReduction;
} }
else else
{ {
attackDefenceDifference -= defender->Defense(); attackDefenceDifference -= info.defenderBonuses->Defense();
} }
//calculating total attack/defense skills modifier //calculating total attack/defense skills modifier
if(shooting) //precision handling (etc.) if(info.shooting) //precision handling (etc.)
attackDefenceDifference += attacker->getBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK), Selector::effectRange(Bonus::ONLY_DISTANCE_FIGHT))->totalValue(); attackDefenceDifference += info.attackerBonuses->getBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK), Selector::effectRange(Bonus::ONLY_DISTANCE_FIGHT))->totalValue();
else //bloodlust handling (etc.) 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; std::vector<int> affectedIds;
int spLevel = attacker->getEffect(55)->val; int spLevel = slayerEffect->val;
for(int g = 0; g < VLC->creh->creatures.size(); ++g) 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) 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; break;
} }
} }
@ -843,51 +865,51 @@ TDmgRange CBattleInfoCallback::calculateDmgRange( const CStack* attacker, const
//applying jousting bonus //applying jousting bonus
if( attacker->hasBonusOfType(Bonus::JOUSTING) && !defender->hasBonusOfType(Bonus::CHARGE_IMMUNITY) ) if( info.attackerBonuses->hasBonusOfType(Bonus::JOUSTING) && !info.defenderBonuses->hasBonusOfType(Bonus::CHARGE_IMMUNITY) )
additiveBonus += charge * 0.05; additiveBonus += info.chargedFields * 0.05;
//handling secondary abilities and artifacts giving premies to them //handling secondary abilities and artifacts giving premies to them
if(shooting) if(info.shooting)
additiveBonus += attacker->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::ARCHERY) / 100.0; additiveBonus += info.attackerBonuses->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::ARCHERY) / 100.0;
else 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) if(info.defenderBonuses)
multBonus *= (std::max(0, 100 - defender->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::ARMORER))) / 100.0; multBonus *= (std::max(0, 100 - info.defenderBonuses->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::ARMORER))) / 100.0;
//handling hate effect //handling hate effect
additiveBonus += attacker->valOfBonuses(Bonus::HATE, defender->getCreature()->idNumber) / 100.; additiveBonus += info.attackerBonuses->valOfBonuses(Bonus::HATE, defenderType->idNumber) / 100.;
//luck bonus //luck bonus
if (lucky) if (info.luckyHit)
{ {
additiveBonus += 1.0; additiveBonus += 1.0;
} }
//ballista double dmg //ballista double dmg
if(ballistaDoubleDmg) if(info.ballistaDoubleDamage)
{ {
additiveBonus += 1.0; additiveBonus += 1.0;
} }
if (deathBlow) //Dread Knight and many WoGified creatures if (info.deathBlow) //Dread Knight and many WoGified creatures
{ {
additiveBonus += 1.0; additiveBonus += 1.0;
} }
//handling spell effects //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 curseEffects = info.attackerBonuses->getBonuses(Selector::type(Bonus::ALWAYS_MINIMUM_DAMAGE)); //attacker->getEffect(42);
TBonusListPtr blessEffects = attacker->getBonuses(Selector::type(Bonus::ALWAYS_MAXIMUM_DAMAGE)); //attacker->getEffect(43); TBonusListPtr blessEffects = info.attackerBonuses->getBonuses(Selector::type(Bonus::ALWAYS_MAXIMUM_DAMAGE)); //attacker->getEffect(43);
int curseBlessAdditiveModifier = blessEffects->totalValue() - curseEffects->totalValue(); int curseBlessAdditiveModifier = blessEffects->totalValue() - curseEffects->totalValue();
double curseMultiplicativePenalty = curseEffects->size() ? (*std::max_element(curseEffects->begin(), curseEffects->end(), &Bonus::compareByAdditionalInfo))->additionalInfo : 0; 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 //wall / distance penalty + advanced air shield
const bool distPenalty = !attacker->hasBonusOfType(Bonus::NO_DISTANCE_PENALTY) && battleHasDistancePenalty(attacker, defender->position); const bool distPenalty = !info.attackerBonuses->hasBonusOfType(Bonus::NO_DISTANCE_PENALTY) && battleHasDistancePenalty(info.attackerBonuses, info.attackerPosition, info.defenderPosition);
const bool obstaclePenalty = battleHasWallPenalty(attacker, defender->position); 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; multBonus *= 0.5;
} }
@ -918,7 +940,7 @@ TDmgRange CBattleInfoCallback::calculateDmgRange( const CStack* attacker, const
multBonus *= 0.5; //cumulative 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; multBonus *= 0.5;
} }
@ -950,18 +972,38 @@ TDmgRange CBattleInfoCallback::calculateDmgRange( const CStack* attacker, const
return returnedVal; 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 TDmgRange CBattleInfoCallback::battleEstimateDamage(const CStack * attacker, const CStack * defender, TDmgRange * retaliationDmg) const
{ {
RETURN_IF_NOT_BATTLE(std::make_pair(0, 0)); RETURN_IF_NOT_BATTLE(std::make_pair(0, 0));
const bool shooting = battleCanShoot(attacker, defender->position); 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; //const ui8 mySide = !attacker->attackerOwned;
TDmgRange ret = calculateDmgRange(attacker, defender, shooting, 0, false, false, false); TDmgRange ret = calculateDmgRange(bai);
if(retaliationDmg) if(retaliationDmg)
{ {
if(shooting) if(bai.shooting)
{ {
retaliationDmg->first = retaliationDmg->second = 0; retaliationDmg->first = retaliationDmg->second = 0;
} }
@ -972,7 +1014,11 @@ TDmgRange CBattleInfoCallback::battleEstimateDamage(const CStack * attacker, con
{ {
BattleStackAttacked bsa; BattleStackAttacked bsa;
bsa.damageAmount = ret.*pairElems[i]; 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; return ret;
} }
shared_ptr<const CObstacleInstance> CBattleInfoCallback::battleGetObstacleOnPos(BattleHex tile, bool onlyBlocking /*= true*/) const shared_ptr<const CObstacleInstance> CBattleInfoCallback::battleGetObstacleOnPos(BattleHex tile, bool onlyBlocking /*= true*/) const
{ {
RETURN_IF_NOT_BATTLE(shared_ptr<const CObstacleInstance>()); RETURN_IF_NOT_BATTLE(shared_ptr<const CObstacleInstance>());
@ -1329,6 +1374,7 @@ ReachabilityInfo::TDistances CBattleInfoCallback::battleGetDistances(const CStac
RETURN_IF_NOT_BATTLE(ret); RETURN_IF_NOT_BATTLE(ret);
ReachabilityInfo::Parameters params(stack); ReachabilityInfo::Parameters params(stack);
params.perspective = battleGetMySide();
params.startPosition = hex.isValid() ? hex : stack->position; params.startPosition = hex.isValid() ? hex : stack->position;
auto reachability = getReachability(params); auto reachability = getReachability(params);
@ -1342,21 +1388,27 @@ ReachabilityInfo::TDistances CBattleInfoCallback::battleGetDistances(const CStac
} }
si8 CBattleInfoCallback::battleHasDistancePenalty(const CStack * stack, BattleHex destHex) const 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); RETURN_IF_NOT_BATTLE(false);
if(stack->hasBonusOfType(Bonus::NO_DISTANCE_PENALTY)) if(bonusBearer->hasBonusOfType(Bonus::NO_DISTANCE_PENALTY))
return false; return false;
if(BattleHex::getDistance(stack->position, destHex) <= 10) if(BattleHex::getDistance(shooterPosition, destHex) <= GameConstants::BATTLE_PENALTY_DISTANCE)
return false; return false;
const CStack * dstStack = battleGetStackByPos(destHex, false); if(const CStack * dstStack = battleGetStackByPos(destHex, false))
if(dstStack)
{ {
//If on dest hex stands stack that occupies a hex within our distance //If on dest hex stands stack that occupies a hex within our distance
BOOST_FOREACH(auto hex, dstStack->getHexes()) BOOST_FOREACH(auto hex, dstStack->getHexes())
if(BattleHex::getDistance(stack->position, hex) <= 10) if(BattleHex::getDistance(shooterPosition, hex) <= GameConstants::BATTLE_PENALTY_DISTANCE)
return false; return false;
//TODO what about two-hex shooters?
} }
return true; return true;
@ -1503,7 +1555,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C
{ {
if(spell->getTargetType() == CSpell::CREATURE if(spell->getTargetType() == CSpell::CREATURE
|| (spell->getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE || (spell->getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE
&& mode == ECastingMode::HERO_CASTING && mode == ECastingMode::HERO_CASTING //TODO why???
&& caster && caster
&& caster->getSpellSchoolLevel(spell) < SecSkillLevel::EXPERT)) && caster->getSpellSchoolLevel(spell) < SecSkillLevel::EXPERT))
{ {
@ -1641,6 +1693,51 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
return ESpellCastProblem::OK; 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 ui32 CBattleInfoCallback::battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const
{ {
RETURN_IF_NOT_BATTLE(-1); RETURN_IF_NOT_BATTLE(-1);
@ -1906,6 +2003,7 @@ si8 CBattleInfoCallback::battleMaxSpellLevel() const
return GameConstants::SPELL_LEVELS; return GameConstants::SPELL_LEVELS;
} }
bool AccessibilityInfo::accessible(BattleHex tile, const CStack *stack) const bool AccessibilityInfo::accessible(BattleHex tile, const CStack *stack) const
{ {
return accessible(tile, stack->doubleWide(), stack->attackerOwned); return accessible(tile, stack->doubleWide(), stack->attackerOwned);
@ -2011,3 +2109,38 @@ bool CPlayerBattleCallback::battleCanCastSpell(ESpellCastProblem::ESpellCastProb
return problem == ESpellCastProblem::OK; 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; class CSpell;
struct BattleInfo; struct BattleInfo;
struct CObstacleInstance; struct CObstacleInstance;
class IBonusBearer;
namespace boost namespace boost
{class shared_mutex;} {class shared_mutex;}
@ -47,6 +48,7 @@ protected:
public: public:
boost::shared_mutex &getGsMutex(); //just return a reference to mutex, does not lock nor anything boost::shared_mutex &getGsMutex(); //just return a reference to mutex, does not lock nor anything
int getPlayerID() const;
friend class CBattleInfoEssentials; 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 //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 class DLL_LINKAGE CBattleInfoCallback : public virtual CBattleInfoEssentials
{ {
public: public:
@ -202,13 +222,17 @@ public:
bool battleIsStackBlocked(const CStack * stack) const; //returns true if there is neighboring enemy stack bool battleIsStackBlocked(const CStack * stack) const; //returns true if there is neighboring enemy stack
std::set<const CStack*> batteAdjacentCreatures (const CStack * stack) const; 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, 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> 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 //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> 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 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 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 EWallParts::EWallParts battleHexToWallPart(BattleHex hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found
//*** MAGIC //*** 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 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 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 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; si32 battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode) const;
TSpell getRandomBeneficialSpell(const CStack * subject) 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) BOOST_FOREACH(CGHeroInstance * cgh, crossoverHeroes)
{ {
#define RESET_PRIM_SKILL(NAME, VALNAME) \ #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::subtype(PrimarySkill::NAME) && \
Selector::sourceType(Bonus::HERO_BASE_SKILL) )->val = cgh->type->heroClass->VALNAME; 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::BonusType ecf = static_cast<Bonus::BonusType>(typeNo);
Bonus *b = cre->getBonus(Selector::type(ecf)); Bonus *b = cre->getBonusLocalFirst(Selector::type(ecf));
cre->removeBonus(b); cre->removeBonus(b);
} }

View File

@ -16,7 +16,7 @@
using namespace boost::logic; using namespace boost::logic;
class CCallback; class CCallback;
class CPlayerBattleCallback; class CBattleCallback;
class ICallback; class ICallback;
class CGlobalAI; class CGlobalAI;
struct Component; struct Component;
@ -61,7 +61,7 @@ public:
std::string dllName; std::string dllName;
virtual ~CBattleGameInterface() {}; virtual ~CBattleGameInterface() {};
virtual void init(CPlayerBattleCallback * CB){}; virtual void init(CBattleCallback * CB){};
//battle call-ins //battle call-ins
virtual BattleAction activeStack(const CStack * stack)=0; //called when it's turn of that stack virtual BattleAction activeStack(const CStack * stack)=0; //called when it's turn of that stack
@ -115,7 +115,7 @@ public:
std::string battleAIName; std::string battleAIName;
CBattleGameInterface *battleAI; CBattleGameInterface *battleAI;
CPlayerBattleCallback *cbc; CBattleCallback *cbc;
//battle interface //battle interface
virtual BattleAction activeStack(const CStack * stack); virtual BattleAction activeStack(const CStack * stack);

View File

@ -1547,20 +1547,20 @@ void CGameState::initDuel()
{ {
CCreature *c = VLC->creh->creatures[cc.id]; CCreature *c = VLC->creh->creatures[cc.id];
if(cc.attack >= 0) 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) 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) 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) 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) if(cc.dmg >= 0)
{ {
c->getBonus(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 1))->val = cc.dmg; c->getBonusLocalFirst(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, 2))->val = cc.dmg;
} }
if(cc.shoots >= 0) 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; DuelParameters ret;
const JsonNode duelData(ResourceID(fname, EResType::TEXT)); const JsonNode duelData(ResourceID("DATA/" + fname, EResType::TEXT));
ret.terType = duelData["terType"].Float(); ret.terType = duelData["terType"].Float();
ret.bfieldType = duelData["bfieldType"].Float(); ret.bfieldType = duelData["bfieldType"].Float();
BOOST_FOREACH(const JsonNode &n, duelData["sides"].Vector()) BOOST_FOREACH(const JsonNode &n, duelData["sides"].Vector())
@ -2791,8 +2791,13 @@ DuelParameters DuelParameters::fromJSON(const std::string &fname)
if(ss.heroId != -1) if(ss.heroId != -1)
{ {
BOOST_FOREACH(const JsonNode &spell, n["spells"].Vector()) auto spells = n["spells"];
ss.spells.insert(spell.Float()); 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; bool luck = which == LUCK;
Bonus::BonusType type[] = {Bonus::MORALE, Bonus::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) if(!b)
{ {
b = new Bonus(Bonus::PERMANENT, type[luck], Bonus::SECONDARY_SKILL, +val, which, which, Bonus::BASE_NUMBER); 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 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; b->val = +val;
else else
addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::SURRENDER_DISCOUNT, Bonus::SECONDARY_SKILL, val * 20, which)); 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 AVAILABLE_HEROES_PER_PLAYER = 2;
const int SPELLBOOK_GOLD_COST = 500; 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 ui16 BACKPACK_START = 19;
const int ID_CATAPULT = 3, ID_LOCK = 145; 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()); 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); Bonus *ret = bonuses.getFirst(selector);
if(ret) if(ret)
@ -484,7 +529,7 @@ Bonus * CBonusSystemNode::getBonus(const CSelector &selector)
FOREACH_PARENT(pname) FOREACH_PARENT(pname)
{ {
ret = pname->getBonus(selector); ret = pname->getBonusLocalFirst(selector);
if (ret) if (ret)
return ret; return ret;
} }
@ -492,9 +537,9 @@ Bonus * CBonusSystemNode::getBonus(const CSelector &selector)
return NULL; 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) */ 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 class DLL_LINKAGE BonusList
{ {
private: private:
std::vector<Bonus*> bonuses; typedef std::vector<Bonus*> TInternalContainer;
TInternalContainer bonuses;
bool belongsToTree; bool belongsToTree;
public: public:
typedef TInternalContainer::const_reference const_reference;
typedef TInternalContainer::value_type value_type;
BonusList(bool BelongsToTree = false); BonusList(bool BelongsToTree = false);
BonusList(const BonusList &bonusList); BonusList(const BonusList &bonusList);
BonusList& operator=(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 CSelector &limit, const std::string &cachingStr = "") const;
const TBonusListPtr getBonuses(const CSelector &selector, 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 //legacy interface
int valOfBonuses(Bonus::BonusType type, const CSelector &selector) const; 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; 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 ui32 MaxHealth() const; //get max HP of stack with all modifiers
bool isLiving() const; //non-undead, non-non living or alive bool isLiving() const; //non-undead, non-non living or alive
virtual si32 magicResistance() const; 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) 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 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 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; 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), 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 //non-const interface
void getParents(TNodes &out); //retrieves list of parent nodes (nodes to inherit bonuses from) void getParents(TNodes &out); //retrieves list of parent nodes (nodes to inherit bonuses from)
@ -575,7 +586,7 @@ public:
void getRedAncestors(TNodes &out); void getRedAncestors(TNodes &out);
void getRedChildren(TNodes &out); void getRedChildren(TNodes &out);
void getRedDescendants(TNodes &out); void getRedDescendants(TNodes &out);
Bonus *getBonus(const CSelector &selector); Bonus *getBonusLocalFirst(const CSelector &selector);
void attachTo(CBonusSystemNode *parent); void attachTo(CBonusSystemNode *parent);
void detachFrom(CBonusSystemNode *parent); void detachFrom(CBonusSystemNode *parent);

View File

@ -54,7 +54,7 @@ DLL_LINKAGE void SetPrimSkill::applyGs( CGameState *gs )
if(which <4) 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); assert(skill);
if(abs) if(abs)
@ -1025,7 +1025,7 @@ DLL_LINKAGE void BattleTriggerEffect::applyGs( CGameState *gs )
} }
case Bonus::POISON: 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) if (b)
b->val = val; b->val = val;
break; break;
@ -1299,7 +1299,7 @@ DLL_LINKAGE void SetStackEffect::applyGs( CGameState *gs )
CStack *s = gs->curB->getStack(id); CStack *s = gs->curB->getStack(id);
if(s) 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) 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? if (bonus && (bat.shot())) //TODO: make it work in meele?
{ {
bat.bsa.front().flags |= BattleStackAttacked::EFFECT; bat.bsa.front().flags |= BattleStackAttacked::EFFECT;
@ -1342,7 +1342,7 @@ void CGameHandler::newTurn()
} }
if (t->hasBonusOfType (Bonus::DARKNESS)) 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 //unhiding what shouldn't be hidden? //that's handled in netpacks client
} }
@ -3642,7 +3642,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
BattleStackAdded bsa; BattleStackAdded bsa;
bsa.attacker = summoner->attackerOwned; 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); 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); 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; int spellID = ba.additionalInfo;
BattleHex destination(ba.destinationTile); BattleHex destination(ba.destinationTile);
const Bonus *randSpellcaster = stack->getBonus(Selector::type(Bonus::RANDOM_SPELLCASTER)); const Bonus *randSpellcaster = stack->getBonusLocalFirst(Selector::type(Bonus::RANDOM_SPELLCASTER));
const Bonus * spellcaster = stack->getBonus(Selector::typeSubtype(Bonus::SPELLCASTER, spellID)); const Bonus * spellcaster = stack->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPELLCASTER, spellID));
//TODO special bonus for genies ability //TODO special bonus for genies ability
if(randSpellcaster && battleGetRandomStackSpell(stack, CBattleInfoCallback::RANDOM_AIMED) < 0) 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.val = spellLvl;
pseudoBonus.turnsRemain = gs->curB->calculateSpellDuration(spell, caster, stackSpellPower ? stackSpellPower : usedSpellPower); pseudoBonus.turnsRemain = gs->curB->calculateSpellDuration(spell, caster, stackSpellPower ? stackSpellPower : usedSpellPower);
CStack::stackEffectToFeature(sse.effect, pseudoBonus); 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 sse.effect.back().additionalInfo = stack->ID; //we need to know who casted Bind
} }
const Bonus * bonus = NULL; const Bonus * bonus = NULL;
if (caster) 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; 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; continue;
sse.stacks.push_back((*it)->ID); sse.stacks.push_back(affected->ID);
//Apply hero specials - peculiar enchants //Apply hero specials - peculiar enchants
if ((*it)->base) // no war machines - TODO: make it work const ui8 tier = affected->getCreature()->level;
{ if (bonus)
ui8 tier = (*it)->base->type->level; {
if (bonus) switch(bonus->additionalInfo)
{
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
{ {
int damagePercent = caster->level * caster->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, spellID) / tier; case 0: //normal
Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, pseudoBonus.turnsRemain); {
specialBonus.valType = Bonus::PERCENT_TO_ALL; switch(tier)
specialBonus.sid = spellID; {
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> ((*it)->ID, specialBonus)); 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()) if(!sse.stacks.empty())
@ -4531,7 +4529,7 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
if(st->hasBonusOfType(Bonus::POISON)) 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?... if (b) //TODO: what if not?...
{ {
bte.val = std::max (b->val - 10, -(st->valOfBonuses(Bonus::POISON))); bte.val = std::max (b->val - 10, -(st->valOfBonuses(Bonus::POISON)));
@ -5984,7 +5982,7 @@ void CGameHandler::runBattle()
nextStackMorale = next->MoraleVal(); nextStackMorale = next->MoraleVal();
if(!vstd::contains(next->state,EBattleStackState::HAD_MORALE) //only one extra move per turn possible 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::DEFENDING)
&& !vstd::contains(next->state,EBattleStackState::WAITING) && !next->waited()
&& !vstd::contains(next->state, EBattleStackState::FEAR) && !vstd::contains(next->state, EBattleStackState::FEAR)
&& next->alive() && next->alive()
&& nextStackMorale > 0 && nextStackMorale > 0