diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index 65822feb8..1d56768de 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -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 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(); + const TBonusListPtr originalList = stack->getAllBonuses(selector, limit, root, cachingStr); + boost::copy(*originalList, std::back_inserter(*ret)); + BOOST_FOREACH(auto &bonus, bonusesToAdd) + { + if(selector(&bonus) && (!limit || !limit(&bonus))) + ret->push_back(&bonus); + } + + //TODO limiters? + + return ret; + } +}; + +struct Skirmish +{ + const CStack *attacker, *defender; + int retaliationDamage, dealtDamage; + + Skirmish(const CStack *Attacker, const CStack *Defender) + :attacker(Attacker), defender(Defender) + { + TDmgRange retal, dmg = cbc->battleEstimateDamage(attacker, defender, &retal); + dealtDamage = (dmg.first + dmg.second) / 2; + retaliationDamage = (retal.first + retal.second) / 2; + + if(attacker->hasBonusOfType(Bonus::ADDITIONAL_ATTACK)) + dealtDamage *= 2; + if(attacker->hasBonusOfType(Bonus::BLOCKS_RETALIATION) || defender->hasBonusOfType(Bonus::NO_RETALIATION)) + retaliationDamage = 0; + + } +}; CBattleAI::CBattleAI(void) : side(-1), cb(NULL) @@ -19,10 +70,13 @@ CBattleAI::~CBattleAI(void) print("destroyed"); } -void CBattleAI::init( CPlayerBattleCallback * CB ) +void CBattleAI::init( CBattleCallback * CB ) { print("init called, saving ptr to IBattleCallback"); cbc = cb = CB; + playerID = CB->getPlayerID();; //TODO should be sth in callback + CB->waitTillRealize = true; + CB->unlockGsWhenWaiting = false; } void CBattleAI::actionFinished( const BattleAction *action ) @@ -94,73 +148,203 @@ static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const Battl return shooters[0] < shooters[1]; } -BattleAction CBattleAI::activeStack( const CStack * stack ) +template +auto sum(const Container & c, Pred p) -> decltype(p(*boost::begin(c))) { - //boost::this_thread::sleep(boost::posix_time::seconds(2)); - print("activeStack called for " + stack->nodeName()); - auto dists = cb->battleGetDistances(stack); - std::vector enemiesShootable, enemiesReachable, enemiesUnreachable; - - if(stack->type->idNumber == 145) //catapult + double ret = 0; + BOOST_FOREACH(const auto &element, c) { - BattleAction attack; - static const int wallHexes[] = {50, 183, 182, 130, 62, 29, 12, 95}; - attack.destinationTile = wallHexes[ rand()%ARRAY_COUNT(wallHexes) ]; - attack.actionType = BattleAction::CATAPULT; - attack.additionalInfo = 0; - attack.side = side; - attack.stackNumber = stack->ID; - - return attack; + ret += p(element); } - BOOST_FOREACH(const CStack *s, cb->battleGetStacks(CBattleCallback::ONLY_ENEMY)) + return ret; +} + +struct ThreatMap +{ + std::array, GameConstants::BFIELD_SIZE> threatMap; // [hexNr] -> enemies able to strike + + const CStack *endangered; + std::array sufferedDamage; + + struct ThreatMap(const CStack *Endangered) + : endangered(Endangered) { - if(cb->battleCanShoot(stack, s->position)) - { - enemiesShootable.push_back(s); - } - else - { - std::vector avHexes = cb->battleGetAvailableHexes(stack, false); + sufferedDamage.fill(0); - BOOST_FOREACH(BattleHex hex, avHexes) + BOOST_FOREACH(const CStack *enemy, cbc->battleGetStacks()) + { + //Consider only stacks of different owner + if(enemy->attackerOwned == endangered->attackerOwned) + continue; + + //Look-up which tiles can be melee-attacked + std::array 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::iterator i = std::find(enemiesReachable.begin(), enemiesReachable.end(), s); - if(i == enemiesReachable.end()) - { - enemiesReachable.push_back(s); - i = enemiesReachable.begin() + (enemiesReachable.size() - 1); - } - - i->attackFrom.push_back(hex); + meleeAttackable[i] = true; + BOOST_FOREACH(auto n, BattleHex(i).neighbouringTiles()) + meleeAttackable[n] = true; } } - if(!vstd::contains(enemiesReachable, s) && s->position.isValid()) - enemiesUnreachable.push_back(s); + //Gather possible assaults + for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) + { + if(cbc->battleCanShoot(enemy, i)) + threatMap[i].push_back(BattleAttackInfo(enemy, endangered, true)); + else if(meleeAttackable[i]) + { + BattleAttackInfo bai(enemy, endangered, false); + bai.chargedFields = std::max(BattleHex::getDistance(enemy->position, i) - 1, 0); //TODO check real distance (BFS), not just metric + threatMap[i].push_back(BattleAttackInfo(bai)); + } + } + } + + for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) + { + sufferedDamage[i] = sum(threatMap[i], [](const BattleAttackInfo &bai) -> int + { + auto dmg = cbc->calculateDmgRange(bai); + return (dmg.first + dmg.second)/2; + }); + } + } +}; + +struct AttackPossibility +{ + const CStack *enemy; //redundant (to attack.defender) but looks nice + BattleHex tile; //tile from which we attack + BattleAttackInfo attack; + + int damageDealt; + int damageReceived; //usually by counter-attack + int damageDiff() const + { + return damageDealt - damageReceived; + } + + int attackValue() const + { + //TODO consider tactical advantage + return damageDiff(); + } +}; + +struct PotentialTargets +{ + std::vector possibleAttacks; + std::vector unreachableEnemies; + + std::function GenerateAttackInfo; //args: shooting, destHex + + PotentialTargets(const CStack *attacker, optional attackerBonuses = boost::none) + { + auto dists = cbc->battleGetDistances(attacker); + std::vector 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 retaliation; + auto attackDmg = cbc->battleEstimateDamage(ap.attack, &retaliation); + ap.damageDealt = (attackDmg.first + attackDmg.second) / 2; + ap.damageReceived = (retaliation.first + retaliation.second) / 2; + //TODO other damage related to attack (eg. fire shield and other abilities) + //TODO limit max damage by total stacks health (dealing 100000 dmg to single Pikineer is not that effective) + + return ap; + }; + + if(cbc->battleCanShoot(attacker, enemy->position)) + { + possibleAttacks.push_back(GenerateAttackInfo(true, BattleHex::INVALID)); + } + else + { + BOOST_FOREACH(BattleHex hex, avHexes) + if(CStack::isMeleeAttackPossible(attacker, enemy, hex)) + possibleAttacks.push_back(GenerateAttackInfo(false, hex)); + + if(!vstd::contains_if(possibleAttacks, [=](const AttackPossibility &pa) { return pa.enemy == enemy; })) + unreachableEnemies.push_back(enemy); + } } } - if(enemiesShootable.size()) + AttackPossibility bestAction() const { - const EnemyInfo &ei= *std::max_element(enemiesShootable.begin(), enemiesShootable.end(), isMoreProfitable); - return BattleAction::makeShotAttack(stack, ei.s); + if(possibleAttacks.empty()) + throw std::runtime_error("No best action, since we don't have any actions"); + + return *vstd::maxElementByFun(possibleAttacks, [](const AttackPossibility &ap) { return ap.damageDiff(); } ); } - else if(enemiesReachable.size()) +}; + +BattleAction CBattleAI::activeStack( const CStack * stack ) +{ + try { - const EnemyInfo &ei= *std::max_element(enemiesReachable.begin(), enemiesReachable.end(), &isMoreProfitable); - return BattleAction::makeMeleeAttack(stack, ei.s, *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), &willSecondHexBlockMoreEnemyShooters)); - } - else - { - const EnemyInfo &ei= *std::min_element(enemiesUnreachable.begin(), enemiesUnreachable.end(), boost::bind(isCloser, _1, _2, boost::ref(dists))); - if(distToNearestNeighbour(ei.s->position, dists) < GameConstants::BFIELD_SIZE) + print("activeStack called for " + stack->nodeName()); + if(stack->type->idNumber == 145) //catapult + return useCatapult(stack); + + if(cb->battleCanCastSpell()) + attemptCastingSpell(); + + ThreatMap threatsToUs(stack); + PotentialTargets targets(stack); + + if(targets.possibleAttacks.size()) { - return goTowards(stack, ei.s->position); + auto hlp = targets.bestAction(); + if(hlp.attack.shooting) + return BattleAction::makeShotAttack(stack, hlp.enemy); + else + return BattleAction::makeMeleeAttack(stack, hlp.enemy, hlp.tile); } + else + { + if(stack->waited()) + { + auto dists = cbc->battleGetDistances(stack); + const EnemyInfo &ei= *range::min_element(targets.unreachableEnemies, boost::bind(isCloser, _1, _2, boost::ref(dists))); + if(distToNearestNeighbour(ei.s->position, dists) < GameConstants::BFIELD_SIZE) + { + return goTowards(stack, ei.s->position); + } + } + else + { + return BattleAction::makeWait(stack); + } + } + } + catch(std::exception &e) + { + tlog1 << "Exception occurred in " << __FUNCTION__ << " " << e.what() << std::endl; } return BattleAction::makeDefend(stack); @@ -181,11 +365,6 @@ void CBattleAI::battleEnd(const BattleResult *br) print("battleEnd called"); } -// void CStupidAI::battleResultsApplied() -// { -// print("battleResultsApplied called"); -// } - void CBattleAI::battleNewRoundFirst(int round) { print("battleNewRoundFirst called"); @@ -244,7 +423,7 @@ void CBattleAI::battleStacksRemoved(const BattleStacksRemoved & bsr) void CBattleAI::print(const std::string &text) const { - tlog6 << "CStupidAI [" << this <<"]: " << text << std::endl; + tlog6 << "CBattleAI [" << this <<"]: " << text << std::endl; } BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination) @@ -310,3 +489,141 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination) } } +BattleAction CBattleAI::useCatapult(const CStack * stack) +{ + throw std::exception("The method or operation is not implemented."); +} + +bool isSupportedSpell(const CSpell *spell) +{ + switch(spell->id) + { + // permanent effects + case Spells::SHIELD: + case Spells::AIR_SHIELD: + case Spells::FIRE_SHIELD: + case Spells::PROTECTION_FROM_AIR: + case Spells::PROTECTION_FROM_FIRE: + case Spells::PROTECTION_FROM_WATER: + case Spells::PROTECTION_FROM_EARTH: + case Spells::ANTI_MAGIC: + case Spells::MAGIC_MIRROR: + case Spells::BLESS: + case Spells::CURSE: + case Spells::BLOODLUST: + case Spells::PRECISION: + case Spells::WEAKNESS: + case Spells::STONE_SKIN: + case Spells::DISRUPTING_RAY: + case Spells::PRAYER: + case Spells::MIRTH: + case Spells::SORROW: + case Spells::FORTUNE: + case Spells::MISFORTUNE: + case Spells::HASTE: + case Spells::SLOW: + case Spells::SLAYER: + case Spells::FRENZY: + case Spells::COUNTERSTRIKE: + case Spells::BERSERK: + case Spells::HYPNOTIZE: + case Spells::FORGETFULNESS: + case Spells::BLIND: + case Spells::STONE_GAZE: + case Spells::POISON: + case Spells::BIND: + case Spells::DISEASE: + case Spells::PARALYZE: + case Spells::AGE: + case Spells::ACID_BREATH_DEFENSE: + return true; + + default: + return false; + } +}; + +struct PossibleSpellcast +{ + const CSpell *spell; + BattleHex dest; +}; + +void CBattleAI::attemptCastingSpell() +{ + LOGL("Casting spells sounds like fun. Let's see..."); + + auto known = cb->battleGetFightingHero(side); + + //Get all spells we can cast + std::vector 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 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 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); +} \ No newline at end of file diff --git a/AI/BattleAI/BattleAI.h b/AI/BattleAI/BattleAI.h index 308fd8796..8d4ec54f0 100644 --- a/AI/BattleAI/BattleAI.h +++ b/AI/BattleAI/BattleAI.h @@ -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(); }; diff --git a/AI/BattleAI/main.cpp b/AI/BattleAI/main.cpp index f7492d5f6..33790d5ee 100644 --- a/AI/BattleAI/main.cpp +++ b/AI/BattleAI/main.cpp @@ -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() { diff --git a/AI/StupidAI/StupidAI.cpp b/AI/StupidAI/StupidAI.cpp index 66025828c..d16f6f94a 100644 --- a/AI/StupidAI/StupidAI.cpp +++ b/AI/StupidAI/StupidAI.cpp @@ -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; diff --git a/AI/StupidAI/StupidAI.h b/AI/StupidAI/StupidAI.h index a5501153e..cfef50a86 100644 --- a/AI/StupidAI/StupidAI.h +++ b/AI/StupidAI/StupidAI.h @@ -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 diff --git a/Global.h b/Global.h index 2892607fe..7a72444bb 100644 --- a/Global.h +++ b/Global.h @@ -383,7 +383,7 @@ namespace vstd return std::inserter(c, c.end()); } - //Retuns iterator to the element for which the value of ValueFunction is minimal + //Returns iterator to the element for which the value of ValueFunction is minimal template 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 + 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; diff --git a/client/BattleInterface/CBattleInterface.cpp b/client/BattleInterface/CBattleInterface.cpp index 1a81d1bd6..3cc937adb 100644 --- a/client/BattleInterface/CBattleInterface.cpp +++ b/client/BattleInterface/CBattleInterface.cpp @@ -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; diff --git a/client/CCreatureWindow.cpp b/client/CCreatureWindow.cpp index 05f224ff9..1db31830d 100644 --- a/client/CCreatureWindow.cpp +++ b/client/CCreatureWindow.cpp @@ -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(duration)); new CAnimImage("SpellInt", effect + 1, 0, 20 + 52 * printed, 184); diff --git a/client/CSpellWindow.cpp b/client/CSpellWindow.cpp index abfb5625a..cbb93e7ac 100644 --- a/client/CSpellWindow.cpp +++ b/client/CSpellWindow.cpp @@ -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) { diff --git a/client/Client.cpp b/client/Client.cpp index 6cbf7bc53..778b8aee4 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -387,7 +387,10 @@ void CClient::newGame( CConnection *con, StartInfo *si ) { auto cbc = make_shared(gs, color, this); battleCallbacks[color] = cbc; - battleints[color] = CDynLibHandler::getNewBattleAI("StupidAI"); + if(!color) + battleints[color] = CDynLibHandler::getNewBattleAI(settings["server"]["neutralAI"].String()); + else + battleints[color] = CDynLibHandler::getNewBattleAI("StupidAI"); battleints[color]->init(cbc.get()); } } diff --git a/lib/BattleState.cpp b/lib/BattleState.cpp index 5e2661d59..33e518543 100644 --- a/lib/BattleState.cpp +++ b/lib/BattleState.cpp @@ -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 & sf, const Bonus & sse) { si32 power = VLC->spellh->spells[sse.sid]->powers[sse.val]; @@ -1342,17 +1329,6 @@ void CStack::stackEffectToFeature(std::vector & 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; diff --git a/lib/BattleState.h b/lib/BattleState.h index ee889b3a5..6ff93a61c 100644 --- a/lib/BattleState.h +++ b/lib/BattleState.h @@ -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 diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index fd9e45307..a4340cb0c 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -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"; diff --git a/lib/CBattleCallback.cpp b/lib/CBattleCallback.cpp index 333bda3fd..78226d25a 100644 --- a/lib/CBattleCallback.cpp +++ b/lib/CBattleCallback.cpp @@ -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 &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 &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 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; ggetCreature()->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 CBattleInfoCallback::battleEstimateDamage(const BattleAttackInfo &bai, std::pair * 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 CBattleInfoCallback::battleGetObstacleOnPos(BattleHex tile, bool onlyBlocking /*= true*/) const { RETURN_IF_NOT_BATTLE(shared_ptr()); @@ -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)) { @@ -1590,7 +1642,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell switch(spell->getTargetType()) { case CSpell::CREATURE: - case CSpell::CREATURE_EXPERT_MASSIVE: + case CSpell::CREATURE_EXPERT_MASSIVE: if(mode == ECastingMode::HERO_CASTING) { const CGHeroInstance * caster = battleGetFightingHero(side); @@ -1641,6 +1693,51 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell return ESpellCastProblem::OK; } +std::vector CBattleInfoCallback::battleGetPossibleTargets(int player, const CSpell *spell) const +{ + std::vector 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; +} \ No newline at end of file diff --git a/lib/CBattleCallback.h b/lib/CBattleCallback.h index 72e8b4bd2..69b0eb7fa 100644 --- a/lib/CBattleCallback.h +++ b/lib/CBattleCallback.h @@ -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: @@ -201,14 +221,18 @@ public: bool battleCanShoot(const CStack * stack, BattleHex dest) const; //determines if stack with given ID shoot at the selected destination bool battleIsStackBlocked(const CStack * stack) const; //returns true if there is neighboring enemy stack std::set batteAdjacentCreatures (const CStack * stack) const; - + + TDmgRange calculateDmgRange(const BattleAttackInfo &info) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair 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 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 //hextowallpart //int battleGetWallUnderHex(BattleHex hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found + std::pair battleEstimateDamage(const BattleAttackInfo &bai, std::pair * retaliationDmg = NULL) const; //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair std::pair battleEstimateDamage(const CStack * attacker, const CStack * defender, std::pair * retaliationDmg = NULL) const; //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair 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 battleGetPossibleTargets(int player, const CSpell *spell) const; si32 battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode) const; TSpell getRandomBeneficialSpell(const CStack * subject) const; diff --git a/lib/CCampaignHandler.cpp b/lib/CCampaignHandler.cpp index 203d17020..701e1b322 100644 --- a/lib/CCampaignHandler.cpp +++ b/lib/CCampaignHandler.cpp @@ -334,7 +334,7 @@ void CCampaignScenario::prepareCrossoverHeroes( std::vector 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; diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 69bb41a99..6611c6f91 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -257,7 +257,7 @@ static void RemoveAbility(CCreature *cre, const JsonNode &ability) Bonus::BonusType ecf = static_cast(typeNo); - Bonus *b = cre->getBonus(Selector::type(ecf)); + Bonus *b = cre->getBonusLocalFirst(Selector::type(ecf)); cre->removeBonus(b); } diff --git a/lib/CGameInterface.h b/lib/CGameInterface.h index 229a09675..021374193 100644 --- a/lib/CGameInterface.h +++ b/lib/CGameInterface.h @@ -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); diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 3dc9f9f23..a76434d96 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -1547,20 +1547,20 @@ void CGameState::initDuel() { CCreature *c = VLC->creh->creatures[cc.id]; if(cc.attack >= 0) - c->getBonus(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK))->val = cc.attack; + c->getBonusLocalFirst(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK))->val = cc.attack; if(cc.defense >= 0) - c->getBonus(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE))->val = cc.defense; + c->getBonusLocalFirst(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE))->val = cc.defense; if(cc.speed >= 0) - c->getBonus(Selector::type(Bonus::STACKS_SPEED))->val = cc.speed; + c->getBonusLocalFirst(Selector::type(Bonus::STACKS_SPEED))->val = cc.speed; if(cc.HP >= 0) - c->getBonus(Selector::type(Bonus::STACK_HEALTH))->val = cc.HP; + c->getBonusLocalFirst(Selector::type(Bonus::STACK_HEALTH))->val = cc.HP; if(cc.dmg >= 0) { - c->getBonus(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 1))->val = cc.dmg; - c->getBonus(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 2))->val = cc.dmg; + c->getBonusLocalFirst(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 1))->val = cc.dmg; + c->getBonusLocalFirst(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 2))->val = cc.dmg; } if(cc.shoots >= 0) - c->getBonus(Selector::type(Bonus::SHOTS))->val = cc.shoots; + c->getBonusLocalFirst(Selector::type(Bonus::SHOTS))->val = cc.shoots; } } @@ -2757,7 +2757,7 @@ DuelParameters DuelParameters::fromJSON(const std::string &fname) { DuelParameters ret; - const JsonNode duelData(ResourceID(fname, EResType::TEXT)); + const JsonNode duelData(ResourceID("DATA/" + fname, EResType::TEXT)); ret.terType = duelData["terType"].Float(); ret.bfieldType = duelData["bfieldType"].Float(); BOOST_FOREACH(const JsonNode &n, duelData["sides"].Vector()) @@ -2791,8 +2791,13 @@ DuelParameters DuelParameters::fromJSON(const std::string &fname) if(ss.heroId != -1) { - BOOST_FOREACH(const JsonNode &spell, n["spells"].Vector()) - ss.spells.insert(spell.Float()); + auto spells = n["spells"]; + if(spells.getType() == JsonNode::DATA_STRING && spells.String() == "all") + BOOST_FOREACH(auto spell, VLC->spellh->spells) + ss.spells.insert(spell->id); + else + BOOST_FOREACH(const JsonNode &spell, n["spells"].Vector()) + ss.spells.insert(spell.Float()); } } diff --git a/lib/CObjectHandler.cpp b/lib/CObjectHandler.cpp index b9fb18f15..924eb0b68 100644 --- a/lib/CObjectHandler.cpp +++ b/lib/CObjectHandler.cpp @@ -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)); diff --git a/lib/GameConstants.h b/lib/GameConstants.h index fcc9a7285..07cba33f8 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -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; diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index e7353aadc..1cb901af1 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -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(this))->getBonus(selector); + return (const_cast(this))->getBonusLocalFirst(selector); } void CBonusSystemNode::getParents(TCNodes &out) const /*retreives list of parent nodes (nodes to inherit bonuses from) */ diff --git a/lib/HeroBonus.h b/lib/HeroBonus.h index 1ce19a16b..6bb163d95 100644 --- a/lib/HeroBonus.h +++ b/lib/HeroBonus.h @@ -351,10 +351,16 @@ DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const Bonus &bonus); class DLL_LINKAGE BonusList { private: - std::vector bonuses; + typedef std::vector 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); diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index bfd4d871a..c9daba087 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -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) { diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 1cc70c7e3..c1c49ebb1 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -787,7 +787,7 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt } } - const Bonus * bonus = att->getBonus(Selector::type(Bonus::SPELL_LIKE_ATTACK)); + const Bonus * bonus = att->getBonusLocalFirst(Selector::type(Bonus::SPELL_LIKE_ATTACK)); if (bonus && (bat.shot())) //TODO: make it work in meele? { bat.bsa.front().flags |= BattleStackAttacked::EFFECT; @@ -1342,7 +1342,7 @@ void CGameHandler::newTurn() } if (t->hasBonusOfType (Bonus::DARKNESS)) { - t->hideTiles(t->getOwner(), t->getBonus(Selector::type(Bonus::DARKNESS))->val); + t->hideTiles(t->getOwner(), t->getBonusLocalFirst(Selector::type(Bonus::DARKNESS))->val); } //unhiding what shouldn't be hidden? //that's handled in netpacks client } @@ -3642,7 +3642,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) BattleStackAdded bsa; bsa.attacker = summoner->attackerOwned; - bsa.creID = summoner->getBonus(Selector::type(Bonus::DAEMON_SUMMONING))->subtype; //in case summoner can summon more than one type of monsters... scream! + bsa.creID = summoner->getBonusLocalFirst(Selector::type(Bonus::DAEMON_SUMMONING))->subtype; //in case summoner can summon more than one type of monsters... scream! ui64 risedHp = summoner->count * summoner->valOfBonuses(Bonus::DAEMON_SUMMONING, bsa.creID); bsa.amount = std::min ((ui32)(risedHp / VLC->creh->creatures[bsa.creID]->MaxHealth()), destStack->baseAmount); @@ -3676,8 +3676,8 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) int spellID = ba.additionalInfo; BattleHex destination(ba.destinationTile); - const Bonus *randSpellcaster = stack->getBonus(Selector::type(Bonus::RANDOM_SPELLCASTER)); - const Bonus * spellcaster = stack->getBonus(Selector::typeSubtype(Bonus::SPELLCASTER, spellID)); + const Bonus *randSpellcaster = stack->getBonusLocalFirst(Selector::type(Bonus::RANDOM_SPELLCASTER)); + const Bonus * spellcaster = stack->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPELLCASTER, spellID)); //TODO special bonus for genies ability if(randSpellcaster && battleGetRandomStackSpell(stack, CBattleInfoCallback::RANDOM_AIMED) < 0) @@ -4126,67 +4126,65 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest pseudoBonus.val = spellLvl; pseudoBonus.turnsRemain = gs->curB->calculateSpellDuration(spell, caster, stackSpellPower ? stackSpellPower : usedSpellPower); CStack::stackEffectToFeature(sse.effect, pseudoBonus); - if (spellID == 72 && stack)//bind + if (spellID == Spells::BIND && stack)//bind { sse.effect.back().additionalInfo = stack->ID; //we need to know who casted Bind } const Bonus * bonus = NULL; if (caster) - bonus = caster->getBonus(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, spellID)); + bonus = caster->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, spellID)); + //TODO does hero speciality should affects his stack casting spells? si32 power = 0; - for(auto it = attackedCres.begin(); it != attackedCres.end(); ++it) + BOOST_FOREACH(const CStack *affected, attackedCres) { - if(vstd::contains(sc.resisted, (*it)->ID)) //this creature resisted the spell + if(vstd::contains(sc.resisted, affected->ID)) //this creature resisted the spell continue; - sse.stacks.push_back((*it)->ID); + sse.stacks.push_back(affected->ID); //Apply hero specials - peculiar enchants - if ((*it)->base) // no war machines - TODO: make it work - { - ui8 tier = (*it)->base->type->level; - if (bonus) - { - switch(bonus->additionalInfo) - { - case 0: //normal - { - switch(tier) - { - case 1: case 2: - power = 3; - break; - case 3: case 4: - power = 2; - break; - case 5: case 6: - power = 1; - break; - } - Bonus specialBonus(sse.effect.back()); - specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely - sse.uniqueBonuses.push_back (std::pair ((*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 ((*it)->ID, specialBonus)); //additional attack to Slayer effect - } - break; - } - } - if (caster && caster->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, spellID)) //TODO: better handling of bonus percentages + const ui8 tier = affected->getCreature()->level; + if (bonus) + { + switch(bonus->additionalInfo) { - int damagePercent = caster->level * caster->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, spellID) / tier; - Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, pseudoBonus.turnsRemain); - specialBonus.valType = Bonus::PERCENT_TO_ALL; - specialBonus.sid = spellID; - sse.uniqueBonuses.push_back (std::pair ((*it)->ID, specialBonus)); + case 0: //normal + { + switch(tier) + { + case 1: case 2: + power = 3; + break; + case 3: case 4: + power = 2; + break; + case 5: case 6: + power = 1; + break; + } + Bonus specialBonus(sse.effect.back()); + specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely + sse.uniqueBonuses.push_back (std::pair (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 (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 (affected->ID, specialBonus)); + } } if(!sse.stacks.empty()) @@ -4531,7 +4529,7 @@ void CGameHandler::stackTurnTrigger(const CStack * st) if(st->hasBonusOfType(Bonus::POISON)) { - const Bonus * b = st->getBonus(Selector::source(Bonus::SPELL_EFFECT, 71) && Selector::type(Bonus::STACK_HEALTH)); + const Bonus * b = st->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT, 71) && Selector::type(Bonus::STACK_HEALTH)); if (b) //TODO: what if not?... { bte.val = std::max (b->val - 10, -(st->valOfBonuses(Bonus::POISON))); @@ -5984,7 +5982,7 @@ void CGameHandler::runBattle() nextStackMorale = next->MoraleVal(); if(!vstd::contains(next->state,EBattleStackState::HAD_MORALE) //only one extra move per turn possible && !vstd::contains(next->state,EBattleStackState::DEFENDING) - && !vstd::contains(next->state,EBattleStackState::WAITING) + && !next->waited() && !vstd::contains(next->state, EBattleStackState::FEAR) && next->alive() && nextStackMorale > 0