From d390113c23425c4ed7d88aa1f472f72b6d230188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=2E=20Urba=C5=84czyk?= Date: Sun, 26 Aug 2012 09:07:48 +0000 Subject: [PATCH] * New files for lib: CBattleCallback.cpp and CBattleCallback.h * Updated MSVC project files * Filesystem: My version of .SND archive does not have \0 after WAV extension. Fixed (though hardcoded 3-char extension length). * New bonus types: BLOCK_MAGIC_ABOVE for blocking casting spells above given level and BLOCK_ALL_MAGIC for blocking all magic. * Heavy rewrite of battle callbacks. Fixed some minor bugs. Code reusage between lib/client/server (removed proxy calls). Better access control and support for various perspectives. * Fixed #1031 * Fixed Orb of Inhibition and Recanter's Cloak (they were incorrectly implemented). Fixed #97. * Fleeing hero won't lose artifacts. Spellbook won't be captured. Fixed #980. * Fixed crash when attacking stack dies before counterattack (ie. because of Fire Shield) * Server does some basic checks if action requests during battle are valid * Minor stuff. --- AI/StupidAI/StupidAI.cpp | 73 +- AI/StupidAI/StupidAI.h | 4 +- AI/VCAI/VCAI.cpp | 30 +- CCallback.h | 6 +- Global.h | 58 +- client/BattleInterface/CBattleAnimations.cpp | 2 +- client/BattleInterface/CBattleInterface.cpp | 60 +- client/BattleInterface/CBattleInterface.h | 4 + .../CBattleInterfaceClasses.cpp | 2 +- client/CPlayerInterface.cpp | 2 +- client/CSpellWindow.cpp | 21 +- client/Client.cpp | 33 +- client/Client.h | 2 + client/NetPacksClient.cpp | 5 +- client/VCMI_client.vcxproj | 4 - client/VCMI_client.vcxproj.filters | 7 +- lib/BattleHex.cpp | 7 +- lib/BattleHex.h | 2 +- lib/BattleState.cpp | 1850 +++------------ lib/BattleState.h | 89 +- lib/CArtHandler.cpp | 109 +- lib/CArtHandler.h | 13 +- lib/CBattleCallback.cpp | 2021 +++++++++++++++++ lib/CBattleCallback.h | 269 +++ lib/CGameInterface.h | 6 +- lib/CSpellHandler.cpp | 4 + lib/CSpellHandler.h | 3 +- lib/Filesystem/CLodArchiveLoader.cpp | 6 +- lib/GameConstants.h | 37 +- lib/HeroBonus.cpp | 20 +- lib/HeroBonus.h | 5 +- lib/IGameCallback.cpp | 415 +--- lib/IGameCallback.h | 81 +- lib/NetPacksLib.cpp | 16 +- lib/ResourceSet.h | 3 - lib/VCMI_lib.vcxproj | 13 +- server/CGameHandler.cpp | 166 +- server/CGameHandler.h | 2 +- server/NetPacksServer.cpp | 5 +- 39 files changed, 3069 insertions(+), 2386 deletions(-) create mode 100644 lib/CBattleCallback.cpp create mode 100644 lib/CBattleCallback.h diff --git a/AI/StupidAI/StupidAI.cpp b/AI/StupidAI/StupidAI.cpp index ec93098d0..e0733d2b4 100644 --- a/AI/StupidAI/StupidAI.cpp +++ b/AI/StupidAI/StupidAI.cpp @@ -5,7 +5,7 @@ #include "../../CCallback.h" #include "../../lib/CCreatureHandler.h" -CBattleCallback * cbc; +CPlayerBattleCallback * cbc; CStupidAI::CStupidAI(void) : side(-1), cb(NULL) @@ -19,7 +19,7 @@ CStupidAI::~CStupidAI(void) print("destroyed"); } -void CStupidAI::init( CBattleCallback * CB ) +void CStupidAI::init( CPlayerBattleCallback * CB ) { print("init called, saving ptr to IBattleCallback"); cbc = cb = CB; @@ -60,7 +60,7 @@ bool isMoreProfitable(const EnemyInfo &ei1, const EnemyInfo& ei2) return (ei1.adi-ei1.adr) < (ei2.adi - ei2.adr); } -int distToNearestNeighbour(BattleHex hex, const std::vector & dists, BattleHex *chosenHex = NULL) +int distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances& dists, BattleHex *chosenHex = NULL) { int ret = 1000000; BOOST_FOREACH(BattleHex n, hex.neighbouringTiles()) @@ -76,7 +76,7 @@ int distToNearestNeighbour(BattleHex hex, const std::vector & dists, Battle return ret; } -bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const std::vector & dists) +bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const ReachabilityInfo::TDistances & dists) { return distToNearestNeighbour(ei1.s->position, dists) < distToNearestNeighbour(ei2.s->position, dists); } @@ -97,9 +97,9 @@ static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const Battl BattleAction CStupidAI::activeStack( const CStack * stack ) { //boost::this_thread::sleep(boost::posix_time::seconds(2)); - print("activeStack called"); + print("activeStack called for " + stack->nodeName()); std::vector avHexes = cb->battleGetAvailableHexes(stack, false); - std::vector dists = cb->battleGetDistances(stack); + auto dists = cb->battleGetDistances(stack); std::vector enemiesShootable, enemiesReachable, enemiesUnreachable; if(stack->type->idNumber == 145) //catapult @@ -246,33 +246,66 @@ void CStupidAI::print(const std::string &text) const tlog6 << "CStupidAI [" << this <<"]: " << text << std::endl; } -BattleAction CStupidAI::goTowards(const CStack * stack, BattleHex hex) +BattleAction CStupidAI::goTowards(const CStack * stack, BattleHex destination) { - BattleHex realDest = hex; - BattleHex predecessors[GameConstants::BFIELD_SIZE]; - std::vector dists = cb->battleGetDistances(stack, hex); - if(distToNearestNeighbour(hex, dists, &realDest) > GameConstants::BFIELD_SIZE) + assert(destination.isValid()); + auto avHexes = cb->battleGetAvailableHexes(stack, false); + auto reachability = cb->getReachability(stack); + + if(vstd::contains(avHexes, destination)) + return BattleAction::makeMove(stack, destination); + + auto destNeighbours = destination.neighbouringTiles(); + if(vstd::contains_if(destNeighbours, [&](BattleHex n) { return stack->coversPos(destination); })) { - print("goTowards: Cannot reach"); + tlog3 << "Warning: already standing on neighbouring tile!" << std::endl; + //We shouldn't even be here... return BattleAction::makeDefend(stack); } - dists = cb->battleGetDistances(stack, realDest, predecessors); - std::vector avHexes = cb->battleGetAvailableHexes(stack, false); + vstd::erase_if(destNeighbours, [&](BattleHex hex){ return !reachability.accessibility.accessible(hex, stack); }); - if(!avHexes.size()) + if(!avHexes.size() || !destNeighbours.size()) //we are blocked or dest is blocked { print("goTowards: Stack cannot move! That's " + stack->nodeName()); return BattleAction::makeDefend(stack); } - while(1) + if(stack->hasBonusOfType(Bonus::FLYING)) { - assert(realDest.isValid()); - if(vstd::contains(avHexes, hex)) - return BattleAction::makeMove(stack, hex); + // Flying stack doesn't go hex by hex, so we can't backtrack using predecessors. + // We just check all available hexes and pick the one closest to the target. + auto distToDestNeighbour = [&](BattleHex hex) -> int + { + auto nearestNeighbourToHex = vstd::minElementByFun(destNeighbours, [&](BattleHex a) + { + return BattleHex::getDistance(a, hex); + }); - hex = predecessors[hex]; + return BattleHex::getDistance(*nearestNeighbourToHex, hex); + }; + + auto nearestAvailableHex = vstd::minElementByFun(avHexes, distToDestNeighbour); + return BattleAction::makeMove(stack, *nearestAvailableHex); + } + else + { + BattleHex bestNeighbor = destination; + if(distToNearestNeighbour(destination, reachability.distances, &bestNeighbor) > GameConstants::BFIELD_SIZE) + { + print("goTowards: Cannot reach"); + return BattleAction::makeDefend(stack); + } + + BattleHex currentDest = bestNeighbor; + while(1) + { + assert(currentDest.isValid()); + if(vstd::contains(avHexes, currentDest)) + return BattleAction::makeMove(stack, currentDest); + + currentDest = reachability.predecessors[currentDest]; + } } } diff --git a/AI/StupidAI/StupidAI.h b/AI/StupidAI/StupidAI.h index cfef50a86..a5501153e 100644 --- a/AI/StupidAI/StupidAI.h +++ b/AI/StupidAI/StupidAI.h @@ -5,14 +5,14 @@ class CStupidAI : public CBattleGameInterface { int side; - CBattleCallback *cb; + CPlayerBattleCallback *cb; void print(const std::string &text) const; public: CStupidAI(void); ~CStupidAI(void); - void init(CBattleCallback * CB) OVERRIDE; + void init(CPlayerBattleCallback * 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/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index bf2809f7a..86124ea8e 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -174,13 +174,6 @@ void removeDuplicates(std::vector &vec) vec.erase(std::unique(vec.begin(), vec.end()), vec.end()); } - -template -void erase_if(Range &vec, Predicate pred) -{ - vec.erase(boost::remove_if(vec, pred),vec.end()); -} - struct AtScopeExit { boost::function foo; @@ -1711,7 +1704,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) cb->moveHero(*h, CGHeroInstance::convertPosition(endpos, true)); waitTillFree(); //movement may cause battle or blocking dialog boost::this_thread::interruption_point(); - if(h->tempOwner != playerID) //we lost hero - remove all tasks assigned to him/her + if(!h) //we lost hero - remove all tasks assigned to him/her { lostHero(h); //we need to throw, otherwise hero will be assigned to sth again @@ -1727,7 +1720,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) performObjectInteraction (visitedObject, h); } - if(h->tempOwner == playerID) //we could have lost hero after last move + if(h) //we could have lost hero after last move { cb->recalculatePaths(); if (startHpos == h->visitablePos() && !ret) //we didn't move and didn't reach the target @@ -3616,7 +3609,7 @@ HeroPtr::HeroPtr(const CGHeroInstance *H) h = H; name = h->name; - hid = H->subID; + hid = H->id; // infosCount[ai->playerID][hid]++; } @@ -3641,12 +3634,23 @@ const CGHeroInstance * HeroPtr::get(bool doWeExpectNull /*= false*/) const { //TODO? check if these all assertions every time we get info about hero affect efficiency // - //behave terribly when attempting unauthorised access to hero that is not ours (or was lost) + //behave terribly when attempting unauthorized access to hero that is not ours (or was lost) assert(doWeExpectNull || h); + if(h) { - assert(cb->getObj(h->id)); - assert(h->tempOwner == ai->playerID); + auto obj = cb->getObj(hid); + const bool owned = obj && obj->tempOwner == ai->playerID; + + if(doWeExpectNull && !owned) + { + return nullptr; + } + else + { + assert(obj); + assert(owned); + } } return h; diff --git a/CCallback.h b/CCallback.h index 662154bcd..ae529f9d9 100644 --- a/CCallback.h +++ b/CCallback.h @@ -78,17 +78,15 @@ public: struct CPack; -class CBattleCallback : public IBattleCallback, public CBattleInfoCallback +class CBattleCallback : public IBattleCallback, public CPlayerBattleCallback { -private: - CBattleCallback(CGameState *GS, int Player, CClient *C); - protected: int sendRequest(const CPack *request); //returns requestID (that'll be matched to requestID in PackageApplied) CClient *cl; //virtual bool hasAccess(int playerId) const; public: + CBattleCallback(CGameState *GS, int Player, CClient *C); int battleMakeAction(BattleAction* action) OVERRIDE;//for casting spells by hero - DO NOT use it for moving active stack bool battleMakeTacticAction(BattleAction * action) OVERRIDE; // performs tactic phase actions diff --git a/Global.h b/Global.h index 2e131539d..48cf2cb81 100644 --- a/Global.h +++ b/Global.h @@ -44,17 +44,10 @@ #include #include #include +#include +//#include #include -#include - -#include -#include -#include -#include - -#include -#include -#include +#include //The only available version is 3, as of Boost 1.50 #define BOOST_FILESYSTEM_VERSION 3 @@ -72,8 +65,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -172,7 +167,14 @@ namespace vstd template bool contains(const Container & c, const Item &i) { - return std::find(c.begin(),c.end(),i) != c.end(); + return std::find(boost::begin(c), boost::end(c),i) != boost::end(c); + } + + //returns true if container c contains item i + template + bool contains_if(const Container & c, Pred p) + { + return std::find_if(boost::begin(c), boost::end(c), p) != boost::end(c); } //returns true if map c contains item i @@ -201,7 +203,7 @@ namespace vstd int find_pos(const Container & c, const T2 &s) { size_t i=0; - for (auto iter = c.begin(); iter != c.end(); iter++, i++) + for (auto iter = boost::begin(c); iter != boost::end(c); iter++, i++) if(*iter == s) return i; return -1; @@ -343,11 +345,39 @@ namespace vstd { assert(r.size()); index %= r.size(); - // auto itr = std::begin(r); //not available in gcc-4.5 - auto itr = r.begin(); + auto itr = boost::begin(r); std::advance(itr, index); return *itr; } + + template + void erase_if(Range &vec, Predicate pred) + { + vec.erase(boost::remove_if(vec, pred),vec.end()); + } + + template + OutputIterator copy_if(const InputRange &input, OutputIterator result, Predicate pred) + { + return std::copy_if(boost::const_begin(input), boost::end(input), result, pred); + } + + template + std::insert_iterator set_inserter(Container &c) + { + return std::inserter(c, c.end()); + } + + //Retuns iterator to the element for which the value of ValueFunction is minimal + template + auto minElementByFun(const ForwardRange& rng, ValueFunction vf) -> decltype(boost::begin(rng)) + { + typedef decltype(*boost::begin(rng)) ElemType; + return boost::min_element(rng, [&] (const ElemType &lhs, const ElemType &rhs) -> bool + { + return vf(lhs) < vf(rhs); + }); + } } using std::shared_ptr; diff --git a/client/BattleInterface/CBattleAnimations.cpp b/client/BattleInterface/CBattleAnimations.cpp index 9b023ee09..13d6f533e 100644 --- a/client/BattleInterface/CBattleAnimations.cpp +++ b/client/BattleInterface/CBattleAnimations.cpp @@ -141,7 +141,7 @@ CAttackAnimation::CAttackAnimation(CBattleInterface *_owner, const CStack *attac assert(attackingStack && "attackingStack is NULL in CBattleAttack::CBattleAttack !\n"); bool isCatapultAttack = attackingStack->hasBonusOfType(Bonus::CATAPULT) - && owner->curInt->cb->battleGetWallUnderHex(_dest) >= 0; + && owner->curInt->cb->battleHexToWallPart(_dest) >= 0; assert(attackedStack || isCatapultAttack); attackingStackPosBeforeReturn = attackingStack->position; diff --git a/client/BattleInterface/CBattleInterface.cpp b/client/BattleInterface/CBattleInterface.cpp index fc7aa6091..11928b427 100644 --- a/client/BattleInterface/CBattleInterface.cpp +++ b/client/BattleInterface/CBattleInterface.cpp @@ -1252,16 +1252,35 @@ void CBattleInterface::bSpellf() CCS->curh->changeGraphic(0,0); - if ( myTurn && curInt->cb->battleCanCastSpell()) + if(!myTurn) + return; + + auto myHero = currentHero(); + ESpellCastProblem::ESpellCastProblem spellCastProblem; + if (curInt->cb->battleCanCastSpell(&spellCastProblem)) { - const CGHeroInstance * chi = NULL; - if(attackingHeroInstance->tempOwner == curInt->playerID) - chi = attackingHeroInstance; - else - chi = defendingHeroInstance; - CSpellWindow * spellWindow = new CSpellWindow(genRect(595, 620, (screen->w - 620)/2, (screen->h - 595)/2), chi, curInt); + CSpellWindow * spellWindow = new CSpellWindow(genRect(595, 620, (screen->w - 620)/2, (screen->h - 595)/2), myHero, curInt); GH.pushInt(spellWindow); } + 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)); + if(!blockingBonus) + return;; + + if(blockingBonus->source == Bonus::ARTIFACT) + { + const int artID = blockingBonus->sid; + //If we have artifact, put name of our hero. Otherwise assume it's the enemy. + //TODO check who *really* is source of bonus + std::string heroName = myHero->hasArt(artID) ? myHero->name : enemyHero().name; + + //%s wields the %s, an ancient artifact which creates a p dead to all magic. + LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[683]) + % heroName % CGI->arth->artifacts[artID]->Name())); + } + } } void CBattleInterface::bWaitf() @@ -1495,11 +1514,11 @@ bool CBattleInterface::isCatapultAttackable(BattleHex hex) const if(!siegeH || tacticsMode) return false; - int wallUnder = curInt->cb->battleGetWallUnderHex(hex); + int wallUnder = curInt->cb->battleHexToWallPart(hex); if(wallUnder == -1) return false; - return curInt->cb->battleGetWallState(wallUnder) < 3; + return curInt->cb->battleGetWallState(wallUnder) < EWallState::DESTROYED; } const CGHeroInstance * CBattleInterface::getActiveHero() @@ -1985,7 +2004,9 @@ void CBattleInterface::activateStack() bWait->block(vstd::contains(s->state, EBattleStackState::WAITING)); //block waiting button if stack has been already waiting //block cast spell button if hero doesn't have a spellbook - bSpell->block(!curInt->cb->battleCanCastSpell()); + ESpellCastProblem::ESpellCastProblem spellcastingProblem; + bool canCastSpells = curInt->cb->battleCanCastSpell(&spellcastingProblem); + bSpell->block(!canCastSpells && spellcastingProblem != ESpellCastProblem::MAGIC_IS_BLOCKED); //if magic is blocked, we leave button active, so the message can be displayed (cf bug #97) bSurrender->block((curInt == attackerInt ? defendingHeroInstance : attackingHeroInstance) == NULL); bFlee->block(!curInt->cb->battleCanFlee()); bSurrender->block(curInt->cb->battleGetSurrenderCost() < 0); @@ -3484,6 +3505,25 @@ Point CBattleInterface::whereToBlitObstacleImage(SDL_Surface *image, const CObst return r.topLeft(); } +const CGHeroInstance * CBattleInterface::currentHero() const +{ + if(attackingHeroInstance->tempOwner == curInt->playerID) + return attackingHeroInstance; + else + return defendingHeroInstance; +} + +InfoAboutHero CBattleInterface::enemyHero() const +{ + InfoAboutHero ret; + if(attackingHeroInstance->tempOwner == curInt->playerID) + curInt->cb->getHeroInfo(defendingHeroInstance, ret); + else + curInt->cb->getHeroInfo(attackingHeroInstance, ret); + + return ret; +} + std::string CBattleInterface::SiegeHelper::townTypeInfixes[GameConstants::F_NUMBER] = {"CS", "RM", "TW", "IN", "NC", "DN", "ST", "FR", "EL"}; CBattleInterface::SiegeHelper::SiegeHelper(const CGTownInstance *siegeTown, const CBattleInterface * _owner) diff --git a/client/BattleInterface/CBattleInterface.h b/client/BattleInterface/CBattleInterface.h index 5ee4e9a89..ec4d4fb4e 100644 --- a/client/BattleInterface/CBattleInterface.h +++ b/client/BattleInterface/CBattleInterface.h @@ -45,6 +45,7 @@ class CCreatureAnimation; struct ProjectileInfo; class CClickableHex; struct BattleHex; +struct InfoAboutHero; /// Class which manages the locked hex fields that are blocked e.g. by obstacles class CBattleObstacle @@ -284,6 +285,9 @@ public: BattleHex fromWhichHexAttack(BattleHex myNumber); void obstaclePlaced(const CObstacleInstance & oi); + const CGHeroInstance * currentHero() const; + InfoAboutHero enemyHero() const; + friend class CPlayerInterface; friend class CAdventureMapButton; friend class CInGameConsole; diff --git a/client/BattleInterface/CBattleInterfaceClasses.cpp b/client/BattleInterface/CBattleInterfaceClasses.cpp index a9c2bc684..9cbc8fbfc 100644 --- a/client/BattleInterface/CBattleInterfaceClasses.cpp +++ b/client/BattleInterface/CBattleInterfaceClasses.cpp @@ -616,7 +616,7 @@ void CClickableHex::clickRight(tribool down, bool previousState) void CStackQueue::update() { stacksSorted.clear(); - owner->curInt->cb->getStackQueue(stacksSorted, QUEUE_SIZE); + owner->curInt->cb->battleGetStackQueue(stacksSorted, QUEUE_SIZE); for (int i = 0; i < QUEUE_SIZE ; i++) { stackBoxes[i]->setStack(stacksSorted[i]); diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 77d6d95f5..0e9b9bf02 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -892,7 +892,7 @@ void CPlayerInterface::battleAttack(const BattleAttack *ba) if (!i->isSecondary()) //display projectile only for primary target { const CStack * attacked = cb->battleGetStackByID(i->stackAttacked); - battleInt->stackAttacking(attacker, cb->battleGetPos(i->stackAttacked), attacked, true); + battleInt->stackAttacking(attacker, attacked->position, attacked, true); } } } diff --git a/client/CSpellWindow.cpp b/client/CSpellWindow.cpp index 21237c3d6..abfb5625a 100644 --- a/client/CSpellWindow.cpp +++ b/client/CSpellWindow.cpp @@ -654,9 +654,24 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState) break; case ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED: { - std::string text = CGI->generaltexth->allTexts[541], caster = owner->myHero->name; - text = boost::str(boost::format(text) % caster); - owner->myInt->showInfoDialog(text); + //Recanter's Cloak or similar effect. Try to retrieve bonus + const Bonus *b = owner->myHero->getBonus(Selector::type(Bonus::BLOCK_MAGIC_ABOVE)); + //TODO what about other values and non-artifact sources? + if(b && b->val == 2 && b->source == Bonus::ARTIFACT) + { + std::string artName = CGI->arth->artifacts[b->sid]->Name(); + //The %s prevents %s from casting 3rd level or higher spells. + owner->myInt->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[536]) + % artName % owner->myHero->name)); + } + else + { + // General message: + // %s recites the incantations but they seem to have no effect. + std::string text = CGI->generaltexth->allTexts[541], caster = owner->myHero->name; + text = boost::str(boost::format(text) % caster); + owner->myInt->showInfoDialog(text); + } } break; case ESpellCastProblem::NO_APPROPRIATE_TARGET: diff --git a/client/Client.cpp b/client/Client.cpp index 8ddddf93a..6027a717a 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -117,7 +117,7 @@ void CClient::waitForMoveAndSend(int color) try { assert(vstd::contains(battleints, color)); - BattleAction ba = battleints[color]->activeStack(gs->curB->getStack(gs->curB->activeStack, false)); + BattleAction ba = battleints[color]->activeStack(gs->curB->battleGetStackByID(gs->curB->activeStack, false)); MakeAction temp_action(ba); sendRequest(&temp_action, color); return; @@ -381,13 +381,14 @@ void CClient::newGame( CConnection *con, StartInfo *si ) battleints[color] = playerint[color]; playerint[color]->init(cb.get()); - callbacks[color] = cb; + battleCallbacks[color] = callbacks[color] = cb; } else { - CBattleCallback * cbc = new CBattleCallback(gs, color, this); + auto cbc = make_shared(gs, color, this); + battleCallbacks[color] = cbc; battleints[color] = CDynLibHandler::getNewBattleAI("StupidAI"); - battleints[color]->init(cbc); + battleints[color]->init(cbc.get()); } } @@ -399,7 +400,9 @@ void CClient::newGame( CConnection *con, StartInfo *si ) battleints[254] = playerint[254] = p; privilagedBattleEventReceivers.push_back(p); GH.curInt = p; - p->init(new CCallback(gs, -1, this)); + auto cb = make_shared(gs, -1, this); + battleCallbacks[-1] = callbacks[-1] = cb; + p->init(cb.get()); battleStarted(gs->curB); } else @@ -554,6 +557,15 @@ void CClient::stopConnection() void CClient::battleStarted(const BattleInfo * info) { + BOOST_FOREACH(auto &battleCb, battleCallbacks) + { + if(vstd::contains(info->sides, battleCb.first) || battleCb.first >= GameConstants::PLAYER_LIMIT) + battleCb.second->setBattle(info); + } +// BOOST_FOREACH(ui8 side, info->sides) +// if(battleCallbacks.count(side)) +// battleCallbacks[side]->setBattle(info); + CPlayerInterface * att, * def; if(vstd::contains(playerint, info->sides[0]) && playerint[info->sides[0]]->human) att = static_cast( playerint[info->sides[0]] ); @@ -586,10 +598,19 @@ void CClient::battleStarted(const BattleInfo * info) } } +void CClient::battleFinished() +{ + BOOST_FOREACH(ui8 side, gs->curB->sides) + if(battleCallbacks.count(side)) + battleCallbacks[side]->setBattle(nullptr); +} + void CClient::loadNeutralBattleAI() { battleints[255] = CDynLibHandler::getNewBattleAI(settings["server"]["neutralAI"].String()); - battleints[255]->init(new CBattleCallback(gs, 255, this)); + auto cbc = make_shared(gs, 255, this); + battleCallbacks[255] = cbc; + battleints[255]->init(cbc.get()); } void CClient::commitPackage( CPackForClient *pack ) diff --git a/client/Client.h b/client/Client.h index 2ceba1b9a..c2d6a23a9 100644 --- a/client/Client.h +++ b/client/Client.h @@ -114,6 +114,7 @@ class CClient : public IGameCallback public: CCallback *cb; std::map > callbacks; //callbacks given to player interfaces + std::map > battleCallbacks; //callbacks given to player interfaces std::vector privilagedGameEventReceivers; //scripting modules, spectator interfaces std::vector privilagedBattleEventReceivers; //scripting modules, spectator interfaces std::map playerint; @@ -225,4 +226,5 @@ public: ////////////////////////////////////////////////////////////////////////// template void serialize(Handler &h, const int version); + void battleFinished(); }; diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 4b279b984..57d3531f5 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -589,7 +589,7 @@ void BattleNextRound::applyCl( CClient *cl ) void BattleSetActiveStack::applyCl( CClient *cl ) { - CStack * activated = GS(cl)->curB->getStack(stack); + const CStack * activated = GS(cl)->curB->battleGetStackByID(stack); int playerToCall = -1; //player that will move activated stack if( activated->hasBonusOfType(Bonus::HYPNOTIZED) ) { @@ -616,11 +616,12 @@ void BattleObstaclePlaced::applyCl(CClient * cl) void BattleResult::applyFirstCl( CClient *cl ) { BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleEnd,this); + cl->battleFinished(); } void BattleStackMoved::applyFirstCl( CClient *cl ) { - const CStack * movedStack = GS(cl)->curB->getStack(stack); + const CStack * movedStack = GS(cl)->curB->battleGetStackByID(stack); BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleStackMoved,movedStack,tilesToMove,distance); } diff --git a/client/VCMI_client.vcxproj b/client/VCMI_client.vcxproj index 91d29497d..257eab966 100644 --- a/client/VCMI_client.vcxproj +++ b/client/VCMI_client.vcxproj @@ -248,7 +248,6 @@ - @@ -289,12 +288,10 @@ - - @@ -315,7 +312,6 @@ - diff --git a/client/VCMI_client.vcxproj.filters b/client/VCMI_client.vcxproj.filters index 0a7e0fd89..c28e3c9be 100644 --- a/client/VCMI_client.vcxproj.filters +++ b/client/VCMI_client.vcxproj.filters @@ -46,28 +46,23 @@ - - - - - @@ -86,6 +81,7 @@ Source Files + @@ -94,6 +90,7 @@ + \ No newline at end of file diff --git a/lib/BattleHex.cpp b/lib/BattleHex.cpp index 65eca726d..02baceb8f 100644 --- a/lib/BattleHex.cpp +++ b/lib/BattleHex.cpp @@ -84,11 +84,10 @@ char BattleHex::getDistance(BattleHex hex1, BattleHex hex2) return std::max(xDst, yDst) + std::min(xDst, yDst) - (yDst + (yDst + xDst < 2 ? 0 : 1))/2; } -void BattleHex::checkAndPush(int tile, std::vector & ret) +void BattleHex::checkAndPush(BattleHex tile, std::vector & ret) { - if( tile>=0 && tile & ret); + static void checkAndPush(BattleHex tile, std::vector & ret); bool isAvailable() const; //valid position not in first or last column }; \ No newline at end of file diff --git a/lib/BattleState.cpp b/lib/BattleState.cpp index 535b7b37a..8fa0ec581 100644 --- a/lib/BattleState.cpp +++ b/lib/BattleState.cpp @@ -28,7 +28,7 @@ extern boost::rand48 ran; const CStack * BattleInfo::getNextStack() const { std::vector hlp; - getStackQueue(hlp, 1, -1); + battleGetStackQueue(hlp, 1, -1); if(hlp.size()) return hlp[0]; @@ -36,350 +36,156 @@ const CStack * BattleInfo::getNextStack() const return NULL; } -static const CStack *takeStack(std::vector &st, int &curside, int turn) -{ - const CStack *ret = NULL; - unsigned i, //fastest stack - j=0; //fastest stack of the other side - for(i = 0; i < st.size(); i++) - if(st[i]) - break; - //no stacks left - if(i == st.size()) - return NULL; +// const CStack * BattleInfo::getStackT(BattleHex tileID, bool onlyAlive) const +// { +// return const_cast(this)->getStackT(tileID, onlyAlive); +// } - const CStack *fastest = st[i], *other = NULL; - int bestSpeed = fastest->Speed(turn); +// void BattleInfo::getAccessibilityMap(bool *accessibility, bool twoHex, bool attackerOwned, bool addOccupiable, std::set & occupyable, bool flying, const CStack * stackToOmmit) const +// { +// memset(accessibility, 1, GameConstants::BFIELD_SIZE); //initialize array with trues +// +// //removing accessibility for side columns of hexes +// for(int v = 0; v < GameConstants::BFIELD_SIZE; ++v) +// { +// if( v % GameConstants::BFIELD_WIDTH == 0 || v % GameConstants::BFIELD_WIDTH == (GameConstants::BFIELD_WIDTH - 1) ) +// accessibility[v] = false; +// } +// +// for(ui32 g=0; galive() || (stackToOmmit && stacks[g]->ID==stackToOmmit->ID) || stacks[g]->position < 0) //we don't want to lock position of this stack (eg. if it's a turret) +// continue; +// +// accessibility[stacks[g]->position] = false; +// if(stacks[g]->doubleWide()) //if it's a double hex creature +// { +// if(stacks[g]->attackerOwned) +// accessibility[stacks[g]->position-1] = false; +// else +// accessibility[stacks[g]->position+1] = false; +// } +// } +// //obstacles +// BOOST_FOREACH(const auto &obstacle, obstacles) +// { +// BOOST_FOREACH(BattleHex hex, obstacle->getBlockedTiles()) +// { +// assert(hex.isValid()); +// accessibility[hex] = false; +// } +// } +// +// //walls +// if(siege > 0) +// { +// static const int permanentlyLocked[] = {12, 45, 78, 112, 147, 165}; +// for(int b=0; b lockedIfNotDestroyed[] = //(which part of wall, which hex is blocked if this part of wall is not destroyed +// {std::make_pair(2, BattleHex(182)), std::make_pair(3, BattleHex(130)), +// std::make_pair(4, BattleHex(62)), std::make_pair(5, BattleHex(29))}; +// for(int b=0; b rem; //tiles to unlock +// for(int h=0; h::const_iterator it = rem.begin(); it != rem.end(); ++it) +// { +// accessibility[*it] = true; +// }*/ +// } +// } - if(fastest->attackerOwned != curside) - { - ret = fastest; - } - else - { - for(j = i + 1; j < st.size(); j++) - { - if(!st[j]) continue; - if(st[j]->attackerOwned != curside || st[j]->Speed(turn) != bestSpeed) - break; - } +// bool BattleInfo::isAccessible(BattleHex hex, bool * accessibility, bool twoHex, bool attackerOwned, bool flying, bool lastPos) +// { +// if(flying && !lastPos) +// return true; +// +// if(twoHex) +// { +// //if given hex is accessible and appropriate adjacent one is free too +// return accessibility[hex] && accessibility[hex + (attackerOwned ? -1 : 1 )]; +// } +// else +// { +// return accessibility[hex]; +// } +// } - if(j >= st.size()) - { - ret = fastest; - } - else - { - other = st[j]; - if(other->Speed(turn) != bestSpeed) - ret = fastest; - else - ret = other; - } - } - - assert(ret); - if(ret == fastest) - st[i] = NULL; - else - st[j] = NULL; - - curside = ret->attackerOwned; - return ret; -} - -CStack * BattleInfo::getStack(int stackID, bool onlyAlive) -{ - for(ui32 g=0; gID == stackID && (!onlyAlive || stacks[g]->alive())) - return stacks[g]; - } - return NULL; -} - -const CStack * BattleInfo::getStack(int stackID, bool onlyAlive) const -{ - return const_cast(this)->getStack(stackID, onlyAlive); -} - -CStack * BattleInfo::getStackT(BattleHex tileID, bool onlyAlive) -{ - for(ui32 g=0; gposition == tileID - || (stacks[g]->doubleWide() && stacks[g]->attackerOwned && stacks[g]->position-1 == tileID) - || (stacks[g]->doubleWide() && !stacks[g]->attackerOwned && stacks[g]->position+1 == tileID)) - { - if(!onlyAlive || stacks[g]->alive()) - { - return stacks[g]; - } - } - } - return NULL; -} - -const CStack * BattleInfo::getStackT(BattleHex tileID, bool onlyAlive) const -{ - return const_cast(this)->getStackT(tileID, onlyAlive); -} - -void BattleInfo::getAccessibilityMap(bool *accessibility, bool twoHex, bool attackerOwned, bool addOccupiable, std::set & occupyable, bool flying, const CStack * stackToOmmit) const -{ - memset(accessibility, 1, GameConstants::BFIELD_SIZE); //initialize array with trues - - //removing accessibility for side columns of hexes - for(int v = 0; v < GameConstants::BFIELD_SIZE; ++v) - { - if( v % GameConstants::BFIELD_WIDTH == 0 || v % GameConstants::BFIELD_WIDTH == (GameConstants::BFIELD_WIDTH - 1) ) - accessibility[v] = false; - } - - for(ui32 g=0; galive() || (stackToOmmit && stacks[g]->ID==stackToOmmit->ID) || stacks[g]->position < 0) //we don't want to lock position of this stack (eg. if it's a turret) - continue; - - accessibility[stacks[g]->position] = false; - if(stacks[g]->doubleWide()) //if it's a double hex creature - { - if(stacks[g]->attackerOwned) - accessibility[stacks[g]->position-1] = false; - else - accessibility[stacks[g]->position+1] = false; - } - } - //obstacles - BOOST_FOREACH(const auto &obstacle, obstacles) - { - BOOST_FOREACH(BattleHex hex, obstacle->getBlockedTiles()) - { - assert(hex.isValid()); - accessibility[hex] = false; - } - } - - //walls - if(siege > 0) - { - static const int permanentlyLocked[] = {12, 45, 78, 112, 147, 165}; - for(int b=0; b lockedIfNotDestroyed[] = //(which part of wall, which hex is blocked if this part of wall is not destroyed - {std::make_pair(2, BattleHex(182)), std::make_pair(3, BattleHex(130)), - std::make_pair(4, BattleHex(62)), std::make_pair(5, BattleHex(29))}; - for(int b=0; b rem; //tiles to unlock - for(int h=0; h::const_iterator it = rem.begin(); it != rem.end(); ++it) - { - accessibility[*it] = true; - }*/ - } -} - -bool BattleInfo::isAccessible(BattleHex hex, bool * accessibility, bool twoHex, bool attackerOwned, bool flying, bool lastPos) -{ - if(flying && !lastPos) - return true; - - if(twoHex) - { - //if given hex is accessible and appropriate adjacent one is free too - return accessibility[hex] && accessibility[hex + (attackerOwned ? -1 : 1 )]; - } - else - { - return accessibility[hex]; - } -} - -void BattleInfo::makeBFS(BattleHex start, bool *accessibility, BattleHex *predecessor, int *dists, bool twoHex, bool attackerOwned, bool flying, bool fillPredecessors) const //both pointers must point to the at least 187-elements int arrays -{ - std::set quicksands = getStoppers(!attackerOwned); - - //inits - for(int b=0; b > hexq; //bfs queue (second filed used only if fillPredecessors is true) - hexq.push(std::make_pair(start, true)); - dists[hexq.front().first] = 0; - int curNext = -1; //for bfs loop only (helper var) - while(!hexq.empty()) //bfs loop - { - std::pair curHex = hexq.front(); - std::vector neighbours = curHex.first.neighbouringTiles(); - hexq.pop(); - if(curHex.first != start && !flying && vstd::contains(quicksands, curHex.first)) //walking stack can't step past the quicksands - continue; - - for(ui32 nr=0; nr=dists[curNext]) - bool accessible = isAccessible(curNext, accessibility, twoHex, attackerOwned, flying, dists[curHex.first]+1 == dists[curNext]); - if( dists[curHex.first]+1 >= dists[curNext] ) - continue; - if(accessible && curHex.second) - { - hexq.push(std::make_pair(curNext, true)); - dists[curNext] = dists[curHex.first] + 1; - } - else if(fillPredecessors && !(accessible && !curHex.second)) - { - hexq.push(std::make_pair(curNext, false)); - dists[curNext] = dists[curHex.first] + 1; - } - predecessor[curNext] = curHex.first; - } - } -}; - -std::vector BattleInfo::getAccessibility( const CStack * stack, bool addOccupiable, std::vector * attackable /*= NULL*/, bool forPassingBy /*= false*/ ) const -{ - std::vector ret; - bool ac[GameConstants::BFIELD_SIZE]; - - if(stack->position < 0) //turrets - return std::vector(); - - std::set occupyable; - - getAccessibilityMap(ac, stack->doubleWide(), stack->attackerOwned, addOccupiable, occupyable, stack->hasBonusOfType(Bonus::FLYING), stack); - - BattleHex pr[GameConstants::BFIELD_SIZE]; - int dist[GameConstants::BFIELD_SIZE]; - makeBFS(stack->position, ac, pr, dist, stack->doubleWide(), stack->attackerOwned, stack->hasBonusOfType(Bonus::FLYING), false); - - if(stack->doubleWide()) - { - if(!addOccupiable) - { - std::vector rem; - for(int b=0; battackerOwned ? ac[b-1] : ac[b+1]) ) - { - rem.push_back(b); - } - } - - for(ui32 g=0; gattackerOwned ? (v%GameConstants::BFIELD_WIDTH)==1 : (v%GameConstants::BFIELD_WIDTH)==(GameConstants::BFIELD_WIDTH - 2)) - ac[v] = false; - } - } - - for (int i = 0; i < GameConstants::BFIELD_SIZE; ++i) - { - bool rangeFits; - if (tacticDistance) - { - rangeFits = pr[i] >= 0; //reachable in terms of obstacles - if(!forPassingBy) //only if we're passing through, we may step out of the tactic range -> otherwise check range - rangeFits = rangeFits && isInTacticRange(i); - } - else - rangeFits = dist[i] <= stack->Speed(0, true); //we can reach the stack - - if( ( !addOccupiable && rangeFits && ac[i] ) - || ( addOccupiable && rangeFits && isAccessible(i, ac, stack->doubleWide(), stack->attackerOwned, stack->hasBonusOfType(Bonus::FLYING), true) )//we can reach it - || (vstd::contains(occupyable, i) && (!tacticDistance && dist[ i + (stack->attackerOwned ? 1 : -1 ) ] <= stack->Speed(0, true) ) && ac[i + (stack->attackerOwned ? 1 : -1 )] ) //it's occupyable and we can reach adjacent hex - ) - { - ret.push_back(i); - } - } - - if(attackable) - { - struct HLP - { - static bool meleeAttackable(BattleHex hex, const std::vector & baseRng) - { - BOOST_FOREACH(BattleHex h, baseRng) - { - if(BattleHex::mutualPosition(h, hex) > 0) - return true; - } - - return false; - } - }; - BOOST_FOREACH(const CStack * otherSt, stacks) - { - if(otherSt->owner == stack->owner || !otherSt->isValidTarget(false)) - continue; - - std::vector occupiedBySecond; - occupiedBySecond.push_back(otherSt->position); - if(otherSt->doubleWide()) - occupiedBySecond.push_back(otherSt->occupiedHex()); - - if(battleCanShoot(stack, otherSt->position)) - { - attackable->insert(attackable->end(), occupiedBySecond.begin(), occupiedBySecond.end()); - - continue; - } - - - BOOST_FOREACH(BattleHex he, occupiedBySecond) - { - if(HLP::meleeAttackable(he, ret)) - attackable->push_back(he); - } - - } - } - - return ret; -} +// void BattleInfo::makeBFS(BattleHex start, bool *accessibility, BattleHex *predecessor, int *dists, bool twoHex, bool attackerOwned, bool flying, bool fillPredecessors) const //both pointers must point to the at least 187-elements int arrays +// { +// std::set quicksands = getStoppers(!attackerOwned); +// +// //inits +// for(int b=0; b > hexq; //bfs queue (second filed used only if fillPredecessors is true) +// hexq.push(std::make_pair(start, true)); +// dists[hexq.front().first] = 0; +// int curNext = -1; //for bfs loop only (helper var) +// while(!hexq.empty()) //bfs loop +// { +// std::pair curHex = hexq.front(); +// std::vector neighbours = curHex.first.neighbouringTiles(); +// hexq.pop(); +// if(curHex.first != start && !flying && vstd::contains(quicksands, curHex.first)) //walking stack can't step past the quicksands +// continue; +// +// for(ui32 nr=0; nr=dists[curNext]) +// bool accessible = isAccessible(curNext, accessibility, twoHex, attackerOwned, flying, dists[curHex.first]+1 == dists[curNext]); +// if( dists[curHex.first]+1 >= dists[curNext] ) +// continue; +// if(accessible && curHex.second) +// { +// hexq.push(std::make_pair(curNext, true)); +// dists[curNext] = dists[curHex.first] + 1; +// } +// else if(fillPredecessors && !(accessible && !curHex.second)) +// { +// hexq.push(std::make_pair(curNext, false)); +// dists[curNext] = dists[curHex.first] + 1; +// } +// predecessor[curNext] = curHex.first; +// } +// } +// }; +// BattleHex BattleInfo::getClosestTile (bool attackerOwned, int initialPos, std::set & possibilities) const { @@ -417,8 +223,6 @@ BattleHex BattleInfo::getClosestTile (bool attackerOwned, int initialPos, std::s int BattleInfo::getAvaliableHex(TCreature creID, bool attackerOwned, int initialPos) const { - bool ac[GameConstants::BFIELD_SIZE]; - bool twoHex = VLC->creh->creatures[creID]->isDoubleWide(); bool flying = VLC->creh->creatures[creID]->isFlying(); @@ -433,41 +237,26 @@ int BattleInfo::getAvaliableHex(TCreature creID, bool attackerOwned, int initial pos = GameConstants::BFIELD_WIDTH - 1; //top right } + auto accessibility = getAccesibility(); + std::set occupyable; - getAccessibilityMap (ac, twoHex, attackerOwned, true, occupyable, flying); + for(int i = 0; i < accessibility.size(); i++) + if(accessibility.accessible(i, twoHex, attackerOwned)) + occupyable.insert(i); - for (int i = 0; i < GameConstants::BFIELD_SIZE; ++i) - { - if (ac[i]) - occupyable.insert (i); - } if (!occupyable.size()) - return -1; //all tiles are covered - - return getClosestTile (attackerOwned, pos, occupyable); -} - -bool BattleInfo::isStackBlocked(const CStack * stack) const -{ - if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON)) //siege weapons cannot be blocked - return false; - - BOOST_FOREACH(CStack * s, getAdjacentCreatures(stack)) { - if (s->owner != stack->owner) //blocked by enemy stack - return true; + return BattleHex::INVALID; //all tiles are covered } - return false; + + return getClosestTile(attackerOwned, pos, occupyable); } -std::pair< std::vector, int > BattleInfo::getPath(BattleHex start, BattleHex dest, bool*accessibility, bool flyingCreature, bool twoHex, bool attackerOwned) +std::pair< std::vector, int > BattleInfo::getPath(BattleHex start, BattleHex dest, const CStack *stack) { - BattleHex predecessor[GameConstants::BFIELD_SIZE]; //for getting the Path - int dist[GameConstants::BFIELD_SIZE]; //calculated distances - - makeBFS(start, accessibility, predecessor, dist, twoHex, attackerOwned, flyingCreature, false); + auto reachability = getReachability(stack); - if(predecessor[dest] == -1) //cannot reach destination + if(reachability.predecessors[dest] == -1) //cannot reach destination { return std::make_pair(std::vector(), 0); } @@ -478,262 +267,16 @@ std::pair< std::vector, int > BattleInfo::getPath(BattleHex start, Ba while(curElem != start) { path.push_back(curElem); - curElem = predecessor[curElem]; + curElem = reachability.predecessors[curElem]; } - return std::make_pair(path, dist[dest]); -} - -TDmgRange BattleInfo::calculateDmgRange( const CStack* attacker, const CStack* defender, TQuantity attackerCount, TQuantity defenderCount, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, - bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg ) const -{ - double additiveBonus = 1.0, multBonus = 1.0, - minDmg = attacker->getMinDamage() * attackerCount, - maxDmg = attacker->getMaxDamage() * attackerCount; - - if(attacker->getCreature()->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; - } - } - - if(attacker->hasBonusOfType(Bonus::SIEGE_WEAPON) && attacker->getCreature()->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 *= attackerHero->getPrimSkillLevel(0) + 1; - maxDmg *= attackerHero->getPrimSkillLevel(0) + 1; - } - - int attackDefenceDifference = 0; - if(attacker->hasBonusOfType(Bonus::GENERAL_ATTACK_REDUCTION)) - { - double multAttackReduction = attacker->valOfBonuses(Bonus::GENERAL_ATTACK_REDUCTION, -1024) / 100.0; - attackDefenceDifference = attacker->Attack() * multAttackReduction; - } - else - { - attackDefenceDifference = attacker->Attack(); - } - - if(attacker->hasBonusOfType(Bonus::ENEMY_DEFENCE_REDUCTION)) - { - double multDefenceReduction = (100 - attacker->valOfBonuses(Bonus::ENEMY_DEFENCE_REDUCTION, -1024)) / 100.0; - attackDefenceDifference -= defender->Defense() * multDefenceReduction; - } - else - { - attackDefenceDifference -= defender->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(); - else //bloodlust handling (etc.) - attackDefenceDifference += attacker->getBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK), Selector::effectRange(Bonus::ONLY_MELEE_FIGHT))->totalValue(); - - - if(attacker->getEffect(55)) //slayer handling - { - std::vector affectedIds; - int spLevel = attacker->getEffect(55)->val; - - for(int g = 0; g < VLC->creh->creatures.size(); ++g) - { - BOOST_FOREACH(const Bonus *b, VLC->creh->creatures[g]->getBonusList()) - { - if ( (b->type == Bonus::KING3 && spLevel >= 3) || //expert - (b->type == Bonus::KING2 && spLevel >= 2) || //adv + - (b->type == Bonus::KING1 && spLevel >= 0) ) //none or basic + - { - affectedIds.push_back(g); - break; - } - } - } - - for(ui32 g=0; ggetCreature()->idNumber == affectedIds[g]) - { - attackDefenceDifference += VLC->spellh->spells[55]->powers[attacker->getEffect(55)->val]; - break; - } - } - } - - //bonus from attack/defense skills - if(attackDefenceDifference < 0) //decreasing dmg - { - double dec = 0.025 * (-attackDefenceDifference); - if(dec > 0.7) - { - multBonus *= 0.3; //1.0 - 0.7 - } - else - { - multBonus *= 1.0 - dec; - } - } - else //increasing dmg - { - double inc = 0.05 * attackDefenceDifference; - if(inc > 4.0) - { - additiveBonus += 4.0; - } - else - { - additiveBonus += inc; - } - } - - - //applying jousting bonus - if( attacker->hasBonusOfType(Bonus::JOUSTING) && !defender->hasBonusOfType(Bonus::CHARGE_IMMUNITY) ) - additiveBonus += charge * 0.05; - - - //handling secondary abilities and artifacts giving premies to them - if(attackerHero) - { - if(shooting) - { - additiveBonus += attackerHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::ARCHERY) / 100.0; - } - else - { - additiveBonus += attackerHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::OFFENCE) / 100.0; - } - } - - if(defendingHero) - { - multBonus *= (std::max(0, 100-defendingHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::ARMORER))) / 100.0; - } - - //handling hate effect - additiveBonus += attacker->valOfBonuses(Bonus::HATE, defender->getCreature()->idNumber) / 100.; - - //luck bonus - if (lucky) - { - additiveBonus += 1.0; - } - - //ballista double dmg - if(ballistaDoubleDmg) - { - additiveBonus += 1.0; - } - - if (deathBlow) //Dread Knight and many WoGified creatures - { - additiveBonus += 1.0; - } - - //handling spell effects - if(!shooting && defender->hasBonusOfType(Bonus::GENERAL_DAMAGE_REDUCTION, 0)) //eg. shield - { - multBonus *= defender->valOfBonuses(Bonus::GENERAL_DAMAGE_REDUCTION, 0) / 100.0; - } - else if(shooting && defender->hasBonusOfType(Bonus::GENERAL_DAMAGE_REDUCTION, 1)) //eg. air shield - { - multBonus *= defender->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); - int curseBlessAdditiveModifier = blessEffects->totalValue() - curseEffects->totalValue(); - double curseMultiplicativePenalty = curseEffects->size() ? (*std::max_element(curseEffects->begin(), curseEffects->end(), &Bonus::compareByAdditionalInfo))->additionalInfo : 0; - - if(curseMultiplicativePenalty) //curse handling (partial, the rest is below) - { - multBonus *= 1.0 - curseMultiplicativePenalty/100; - } - - class HLP - { - public: - static bool hasAdvancedAirShield(const CStack * stack) - { - - BOOST_FOREACH(const Bonus *it, stack->getBonusList()) - { - if (it->source == Bonus::SPELL_EFFECT && it->sid == 28 && it->val >= 2) - { - return true; - } - } - return false; - } - }; - - //wall / distance penalty + advanced air shield - bool distPenalty = !NBonus::hasOfType(attackerHero, Bonus::NO_DISTANCE_PENALTY) && - hasDistancePenalty(attacker, defender->position); - bool obstaclePenalty = hasWallPenalty(attacker, defender->position); - if (shooting) - { - if (distPenalty || HLP::hasAdvancedAirShield(defender)) - { - multBonus *= 0.5; - } - if (obstaclePenalty) - { - multBonus *= 0.5; //cumulative - } - } - if (!shooting && attacker->hasBonusOfType(Bonus::SHOOTER) && !attacker->hasBonusOfType(Bonus::NO_MELEE_PENALTY)) - { - multBonus *= 0.5; - } - - minDmg *= additiveBonus * multBonus; - maxDmg *= additiveBonus * multBonus; - - TDmgRange returnedVal; - - if(curseEffects->size()) //curse handling (rest) - { - minDmg += curseBlessAdditiveModifier; - returnedVal = std::make_pair(int(minDmg), int(minDmg)); - } - else if(blessEffects->size()) //bless handling - { - maxDmg += curseBlessAdditiveModifier; - returnedVal = std::make_pair(int(maxDmg), int(maxDmg)); - } - else - { - returnedVal = std::make_pair(int(minDmg), int(maxDmg)); - } - - //damage cannot be less than 1 - vstd::amax(returnedVal.first, 1); - vstd::amax(returnedVal.second, 1); - - return returnedVal; -} - -TDmgRange BattleInfo::calculateDmgRange(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, - bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg) const -{ - return calculateDmgRange(attacker, defender, attacker->count, defender->count, attackerHero, defendingHero, shooting, charge, lucky, deathBlow, ballistaDoubleDmg); + return std::make_pair(path, reachability.distances[dest]); } ui32 BattleInfo::calculateDmg( const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg ) { - TDmgRange range = calculateDmgRange(attacker, defender, attackerHero, defendingHero, shooting, charge, lucky, deathBlow, ballistaDoubleDmg); + TDmgRange range = calculateDmgRange(attacker, defender, shooting, charge, lucky, deathBlow, ballistaDoubleDmg); if(range.first != range.second) { @@ -762,9 +305,9 @@ void BattleInfo::calculateCasualties( std::map *casualties ) const } } -std::set BattleInfo::getAttackedCreatures(const CSpell * s, int skillLevel, ui8 attackerOwner, BattleHex destinationTile ) +std::set BattleInfo::getAttackedCreatures(const CSpell * s, int skillLevel, ui8 attackerOwner, BattleHex destinationTile) { - std::set attackedCres; /*std::set to exclude multiple occurrences of two hex creatures*/ + std::set attackedCres; /*std::set to exclude multiple occurrences of two hex creatures*/ const ui8 attackerSide = sides[1] == attackerOwner; const auto attackedHexes = s->rangeInHexes(destinationTile, skillLevel, attackerSide); @@ -800,7 +343,7 @@ std::set BattleInfo::getAttackedCreatures(const CSpell * s, int skillLe BattleHex lightningHex = destinationTile; for (int i = 0; i < 5; ++i) //TODO: depends on spell school level { - auto stack = getStackT (lightningHex, true); + auto stack = battleGetStackByPos (lightningHex, true); if (!stack) break; attackedCres.insert (stack); @@ -815,8 +358,7 @@ std::set BattleInfo::getAttackedCreatures(const CSpell * s, int skillLe { BOOST_FOREACH(BattleHex hex, attackedHexes) { - CStack * st = getStackT(hex, onlyAlive); - if(st) + if(const CStack * st = battleGetStackByPos(hex, onlyAlive)) { if (s->id == 76) //Death Cloud //TODO: fireball and fire immunity { @@ -834,7 +376,7 @@ std::set BattleInfo::getAttackedCreatures(const CSpell * s, int skillLe { if(skillLevel < 3) /*not expert */ { - CStack * st = getStackT(destinationTile, onlyAlive); + const CStack * st = battleGetStackByPos(destinationTile, onlyAlive); if(st) attackedCres.insert(st); } @@ -855,141 +397,19 @@ std::set BattleInfo::getAttackedCreatures(const CSpell * s, int skillLe } else if(s->getTargetType() == CSpell::CREATURE) { - CStack * st = getStackT(destinationTile, onlyAlive); - if(st) + if(const CStack * st = battleGetStackByPos(destinationTile, onlyAlive)) attackedCres.insert(st); } else //custom range from attackedHexes { BOOST_FOREACH(BattleHex hex, attackedHexes) { - CStack * st = getStackT(hex, onlyAlive); - if(st) + if(const CStack * st = battleGetStackByPos(hex, onlyAlive)) attackedCres.insert(st); } } return attackedCres; } -void BattleInfo::getPotentiallyAttackableHexes(AttackableTiles &at, const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos) -{ - const int WN = GameConstants::BFIELD_WIDTH; - ui16 hex = (attackerPos != BattleHex::INVALID) ? attackerPos.hex : attacker->position.hex; //real or hypothetical (cursor) position - if (attacker->hasBonusOfType(Bonus::ATTACKS_ALL_ADJACENT)) - { - std::vector hexes = attacker->getSurroundingHexes(attackerPos); - BOOST_FOREACH (BattleHex tile, hexes) - { - at.hostileCreaturePositions.insert(tile); - } - } - if (attacker->hasBonusOfType(Bonus::THREE_HEADED_ATTACK)) - { - std::vector hexes = attacker->getSurroundingHexes(attackerPos); - BOOST_FOREACH (BattleHex tile, hexes) - { - if ((BattleHex::mutualPosition(tile, destinationTile) > -1 && BattleHex::mutualPosition(tile, hex) > -1) //adjacent both to attacker's head and attacked tile - || tile == destinationTile) //or simply attacked directly - { - CStack * st = getStackT(tile, true); - if(st && st->owner != attacker->owner) //only hostile stacks - does it work well with Berserk? - { - at.hostileCreaturePositions.insert(tile); - } - } - } - } - if (attacker->hasBonusOfType(Bonus::TWO_HEX_ATTACK_BREATH)) - { - std::vector hexes; //only one, in fact - int pseudoVector = destinationTile.hex - hex; - switch (pseudoVector) - { - case 1: - case -1: - BattleHex::checkAndPush(destinationTile.hex + pseudoVector, hexes); - break; - case WN: //17 - case WN + 1: //18 - case -WN: //-17 - case -WN + 1: //-16 - BattleHex::checkAndPush(destinationTile.hex + pseudoVector + ((hex/WN)%2 ? 1 : -1 ), hexes); - break; - case WN-1: //16 - case -WN-1: //-18 - BattleHex::checkAndPush(destinationTile.hex + pseudoVector + ((hex/WN)%2 ? 1 : 0), hexes); - break; - } - BOOST_FOREACH (BattleHex tile, hexes) - { - CStack * st = getStackT(tile, true); - if(st) //friendly stacks can also be damaged by Dragon Breath - { - at.friendlyCreaturePositions.insert(tile); - } - } - } -} -std::set BattleInfo::getAttackedCreatures(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos) -{ //TODO: caching? - AttackableTiles at; - getPotentiallyAttackableHexes(at, attacker, destinationTile, attackerPos); - std::set attackedCres; - BOOST_FOREACH (BattleHex tile, at.hostileCreaturePositions) //all around & three-headed attack - { - CStack * st = getStackT(tile, true); - if(st && st->owner != attacker->owner) //only hostile stacks - does it work well with Berserk? - { - attackedCres.insert(st); - } - } - BOOST_FOREACH (BattleHex tile, at.friendlyCreaturePositions) - { - CStack * st = getStackT(tile, true); - if(st) //friendly stacks can also be damaged by Dragon Breath - { - attackedCres.insert(st); - } - } - return attackedCres; -} - -std::set BattleInfo::getAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos) -{ - AttackableTiles at; - getPotentiallyAttackableHexes(at, attacker, destinationTile, attackerPos); - std::set attackedHexes; - BOOST_FOREACH (BattleHex tile, at.hostileCreaturePositions) - { - CStack * st = getStackT(tile, true); - if(st && st->owner != attacker->owner) //only hostile stacks - does it work well with Berserk? - { - attackedHexes.insert(tile); - } - } - BOOST_FOREACH (BattleHex tile, at.friendlyCreaturePositions) - { - CStack * st = getStackT(tile, true); - if(st) //friendly stacks can also be damaged by Dragon Breath - { - attackedHexes.insert(tile); - } - } - return attackedHexes; -} - -std::set BattleInfo::getAdjacentCreatures (const CStack * stack) const -{ - std::set stacks; - - CStack * localStack; - BOOST_FOREACH (BattleHex hex, stack->getSurroundingHexes()) - { - localStack = const_cast(getStackT(hex, true)); //only alive? - if (localStack) - stacks.insert(localStack); - } - return stacks; -} int BattleInfo::calculateSpellDuration( const CSpell * spell, const CGHeroInstance * caster, int usedSpellPower) { @@ -1029,105 +449,56 @@ CStack * BattleInfo::generateNewStack(const CStackBasicDescriptor &base, bool at return ret; } -ui32 BattleInfo::getSpellCost(const CSpell * sp, const CGHeroInstance * caster) const -{ - ui32 ret = caster->getSpellCost(sp); - - //checking for friendly stacks reducing cost of the spell and - //enemy stacks increasing it - si32 manaReduction = 0; - si32 manaIncrease = 0; - for(int g=0; gowner == caster->tempOwner && stacks[g]->hasBonusOfType(Bonus::CHANGES_SPELL_COST_FOR_ALLY) ) - { - vstd::amax(manaReduction, stacks[g]->valOfBonuses(Bonus::CHANGES_SPELL_COST_FOR_ALLY)); - } - if( stacks[g]->owner != caster->tempOwner && stacks[g]->hasBonusOfType(Bonus::CHANGES_SPELL_COST_FOR_ENEMY) ) - { - vstd::amax(manaIncrease, stacks[g]->valOfBonuses(Bonus::CHANGES_SPELL_COST_FOR_ENEMY)); - } - } - - return ret - manaReduction + manaIncrease; -} - -int BattleInfo::hexToWallPart(BattleHex hex) const -{ - if(siege == 0) //there is no battle! - return -1; - - static const std::pair attackable[] = //potentially attackable parts of wall - {std::make_pair(50, 0), std::make_pair(183, 1), std::make_pair(182, 2), std::make_pair(130, 3), - std::make_pair(62, 4), std::make_pair(29, 5), std::make_pair(12, 6), std::make_pair(95, 7), std::make_pair(96, 7), - std::make_pair(45, -2), std::make_pair(78, -2), std::make_pair(112, -2), std::make_pair(147, -2)}; // -2 - indestructible walls - - for(int g = 0; g < ARRAY_COUNT(attackable); ++g) - { - if(attackable[g].first == hex) - return attackable[g].second; - } - - return -1; //not found! -} - -int BattleInfo::lineToWallHex( int line ) const -{ - static const int lineToHex[] = {12, 29, 45, 62, 78, 95, 112, 130, 147, 165, 182}; - - return lineToHex[line]; -} - -std::pair BattleInfo::getNearestStack(const CStack * closest, boost::logic::tribool attackerOwned) const -{ - bool ac[GameConstants::BFIELD_SIZE]; - std::set occupyable; - - getAccessibilityMap(ac, closest->doubleWide(), closest->attackerOwned, false, occupyable, closest->hasBonusOfType(Bonus::FLYING), closest); - - BattleHex predecessor[GameConstants::BFIELD_SIZE]; - int dist[GameConstants::BFIELD_SIZE]; - makeBFS(closest->position, ac, predecessor, dist, closest->doubleWide(), closest->attackerOwned, closest->hasBonusOfType(Bonus::FLYING), true); - - std::vector< std::pair< std::pair, const CStack *> > stackPairs; //pairs <, stack> - for(int g=0; gID == closest->ID) //if there is not stack or we are the closest one - continue; - if(boost::logic::indeterminate(attackerOwned) || atG->attackerOwned == attackerOwned) - { - if(predecessor[g] == -1) //TODO: is it really the best solution? - continue; - stackPairs.push_back( std::make_pair( std::make_pair(dist[predecessor[g]], g), atG) ); - } - } - - if(stackPairs.size() > 0) - { - std::vector< std::pair< std::pair, const CStack *> > minimalPairs; - minimalPairs.push_back(stackPairs[0]); - - for(int b=1; b, const CStack *> minPair = minimalPairs[minimalPairs.size()/2]; - - return std::make_pair(minPair.second, predecessor[minPair.first.second]); - } - - return std::make_pair(NULL, BattleHex::INVALID); -} +// std::pair BattleInfo::getNearestStack(const CStack * closest, boost::logic::tribool attackerOwned) const +// { +// bool ac[GameConstants::BFIELD_SIZE]; +// std::set occupyable; +// +// getAccessibilityMap(ac, closest->doubleWide(), closest->attackerOwned, false, occupyable, closest->hasBonusOfType(Bonus::FLYING), closest); +// +// BattleHex predecessor[GameConstants::BFIELD_SIZE]; +// int dist[GameConstants::BFIELD_SIZE]; +// makeBFS(closest->position, ac, predecessor, dist, closest->doubleWide(), closest->attackerOwned, closest->hasBonusOfType(Bonus::FLYING), true); +// +// std::vector< std::pair< std::pair, const CStack *> > stackPairs; //pairs <, stack> +// for(int g=0; gID == closest->ID) //if there is not stack or we are the closest one +// continue; +// if(boost::logic::indeterminate(attackerOwned) || atG->attackerOwned == attackerOwned) +// { +// if(predecessor[g] == -1) //TODO: is it really the best solution? +// continue; +// stackPairs.push_back( std::make_pair( std::make_pair(dist[predecessor[g]], g), atG) ); +// } +// } +// +// if(stackPairs.size() > 0) +// { +// std::vector< std::pair< std::pair, const CStack *> > minimalPairs; +// minimalPairs.push_back(stackPairs[0]); +// +// for(int b=1; b, const CStack *> minPair = minimalPairs[minimalPairs.size()/2]; +// +// return std::make_pair(minPair.second, predecessor[minPair.first.second]); +// } +// +// return std::make_pair(NULL, BattleHex::INVALID); +// } ui32 BattleInfo::calculateSpellBonus(ui32 baseDamage, const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature) const { ui32 ret = baseDamage; @@ -1234,225 +605,6 @@ bool BattleInfo::resurrects(TSpell spellid) const return vstd::contains(VLC->spellh->risingSpells, spellid); } -void BattleInfo::getStackQueue( std::vector &out, int howMany, int turn /*= 0*/, int lastMoved /*= -1*/ ) const -{ - //we'll split creatures with remaining movement to 4 parts - std::vector phase[4]; //0 - turrets/catapult, 1 - normal (unmoved) creatures, other war machines, 2 - waited cres that had morale, 3 - rest of waited cres - int toMove = 0; //how many stacks still has move - const CStack *active = getStack(activeStack); - - //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)) - { - out.push_back(active); - if(out.size() == howMany) - return; - } - - - for(ui32 i=0; iwillMove()) //we are considering current round and stack won't move - || (turn > 0 && !s->canMove(turn)) //stack won't be able to move in later rounds - || (turn <= 0 && s == active && out.size() && s == out.front())) //it's active stack already added at the beginning of queue - { - continue; - } - - 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(vstd::contains(s->state, EBattleStackState::HAD_MORALE)) - p = 2; - else - p = 3; - } - else if(s->getCreature()->idNumber == 145 || s->getCreature()->idNumber == 149) //catapult and turrets are first - { - p = 0; - } - else - { - p = 1; - } - - phase[p].push_back(s); - toMove++; - } - - for(int i = 0; i < 4; i++) - std::sort(phase[i].begin(), phase[i].end(), CMP_stack(i, turn > 0 ? turn : 0)); - - for(size_t i = 0; i < phase[0].size() && i < howMany; i++) - out.push_back(phase[0][i]); - - if(out.size() == howMany) - return; - - if(lastMoved == -1) - { - if(active) - { - if(out.size() && out.front() == active) - lastMoved = active->attackerOwned; - else - lastMoved = active->attackerOwned; - } - else - { - lastMoved = 0; - } - } - - int pi = 1; - while(out.size() < howMany) - { - const CStack *hlp = takeStack(phase[pi], lastMoved, turn); - if(!hlp) - { - pi++; - if(pi > 3) - { - //if(turn != 2) - getStackQueue(out, howMany, turn + 1, lastMoved); - return; - } - } - else - { - out.push_back(hlp); - } - } -} - -si8 BattleInfo::hasDistancePenalty( const CStack * stack, BattleHex destHex ) const -{ - struct HLP - { - static bool lowerAnalyze(const CStack * stack, BattleHex hex) - { - int distance = BattleHex::getDistance(hex, stack->position); - - //I hope it's approximately correct - return distance > 10 && !stack->hasBonusOfType(Bonus::NO_DISTANCE_PENALTY); - } - }; - const CStack * dstStack = getStackT(destHex, false); - - if (dstStack->doubleWide()) - return HLP::lowerAnalyze(stack, destHex) && HLP::lowerAnalyze(stack, dstStack->occupiedHex()); - else - return HLP::lowerAnalyze(stack, destHex); - -} - -si8 BattleInfo::sameSideOfWall(int pos1, int pos2) const -{ - int wallInStackLine = lineToWallHex(pos1/GameConstants::BFIELD_WIDTH); - int wallInDestLine = lineToWallHex(pos2/GameConstants::BFIELD_WIDTH); - - bool stackLeft = pos1 < wallInStackLine; - bool destLeft = pos2 < wallInDestLine; - - return stackLeft != destLeft; -} - -si8 BattleInfo::hasWallPenalty( const CStack* stack, BattleHex destHex ) const -{ - if (!siege || stack->hasBonusOfType(Bonus::NO_WALL_PENALTY)) - { - return false; - } - - int wallInStackLine = lineToWallHex(stack->position/GameConstants::BFIELD_WIDTH); - int wallInDestLine = lineToWallHex(destHex/GameConstants::BFIELD_WIDTH); - - bool stackLeft = stack->position < wallInStackLine; - 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 - row -= 2; - int wallPos = lineToWallHex(row); - if (hexToWallPart(wallPos) != -1) //wall still exists or is indestructible - return true; - } - return false; -} - -si8 BattleInfo::canTeleportTo(const CStack * stack, BattleHex destHex, int telportLevel) const -{ - bool ac[GameConstants::BFIELD_SIZE]; - - std::set occupyable; - - getAccessibilityMap(ac, stack->doubleWide(), stack->attackerOwned, false, occupyable, stack->hasBonusOfType(Bonus::FLYING), stack); - - if (siege && telportLevel < 2) //check for wall - { - return ac[destHex] && sameSideOfWall(stack->position, destHex); - } - else - { - return ac[destHex]; - } - -} - -bool BattleInfo::battleCanShoot(const CStack * stack, BattleHex dest) const -{ - if(tacticDistance) //no shooting during tactics - return false; - - const CStack *dst = getStackT(dest); - - if(!stack || !dst) return false; - - const CGHeroInstance * stackHero = battleGetOwner(stack); - - if(stack->hasBonusOfType(Bonus::FORGETFULL)) //forgetfulness - return false; - - if(stack->getCreature()->idNumber == 145 && dst) //catapult cannot attack creatures - return false; - - if(stack->hasBonusOfType(Bonus::SHOOTER)//it's shooter - && stack->owner != dst->owner - && dst->alive() - && (!isStackBlocked(stack) || NBonus::hasOfType(stackHero, Bonus::FREE_SHOOTING)) - && stack->shots - ) - return true; - return false; -} - -bool BattleInfo::battleCanFlee(int player) const -{ - if (player == sides[0]) - { - if (!heroes[0]) - return false;//current player have no hero - } - else - { - if (!heroes[1]) - return false; - } - - if( ( heroes[0] && heroes[0]->hasBonusOfType(Bonus::ENEMY_CANT_ESCAPE) ) //eg. one of heroes is wearing shakles of war - || ( heroes[1] && heroes[1]->hasBonusOfType(Bonus::ENEMY_CANT_ESCAPE))) - return false; - - if (player == sides[1] && siege //defender in siege - && !(town->subID == 6 && vstd::contains(town->builtBuildings, 17)))//without escape tunnel - return false; - - return true; -} - const CStack * BattleInfo::battleGetStack(BattleHex pos, bool onlyAlive) { CStack * stack = NULL; @@ -1478,23 +630,6 @@ const CGHeroInstance * BattleInfo::battleGetOwner(const CStack * stack) const return heroes[!stack->attackerOwned]; } -si8 BattleInfo::battleMinSpellLevel() const -{ - si8 levelLimit = 0; - - if(const CGHeroInstance *h1 = heroes[0]) - { - vstd::amax(levelLimit, h1->valOfBonuses(Bonus::LEVEL_SPELL_IMMUNITY)); - } - - if(const CGHeroInstance *h2 = heroes[1]) - { - vstd::amax(levelLimit, h2->valOfBonuses(Bonus::LEVEL_SPELL_IMMUNITY)); - } - - return levelLimit; -} - void BattleInfo::localInit() { belligerents[0]->battle = belligerents[1]->battle = this; @@ -2031,309 +1166,6 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, int terrain, int battlefieldTyp return curB; } -bool BattleInfo::isInTacticRange( BattleHex dest ) const -{ - - return ((!tacticsSide && dest.getX() > 0 && dest.getX() <= tacticDistance) - || (tacticsSide && dest.getX() < GameConstants::BFIELD_WIDTH - 1 && dest.getX() >= GameConstants::BFIELD_WIDTH - tacticDistance - 1)); -} - -ESpellCastProblem::ESpellCastProblem BattleInfo::battleCanCastSpell(int player, ECastingMode::ECastingMode mode) const -{ - int side = sides[0] == player ? 0 : 1; - - switch (mode) - { - case ECastingMode::HERO_CASTING: - { - if(tacticDistance) - return ESpellCastProblem::ONGOING_TACTIC_PHASE; - if(castSpells[side] > 0) - return ESpellCastProblem::ALREADY_CASTED_THIS_TURN; - if(!heroes[side]) - return ESpellCastProblem::NO_HERO_TO_CAST_SPELL; - if(!heroes[side]->getArt(17)) - return ESpellCastProblem::NO_SPELLBOOK; - } - break; - } - - return ESpellCastProblem::OK; -} - -ESpellCastProblem::ESpellCastProblem BattleInfo::battleCanCastThisSpell( int player, const CSpell * spell, ECastingMode::ECastingMode mode ) const -{ - ESpellCastProblem::ESpellCastProblem genProblem = battleCanCastSpell(player, mode); - if(genProblem != ESpellCastProblem::OK) - return genProblem; - - int cside = sides[0] == player ? 0 : 1; //caster's side - switch(mode) - { - case ECastingMode::HERO_CASTING: - { - const CGHeroInstance * caster = heroes[cside]; - if(!caster->canCastThisSpell(spell)) - return ESpellCastProblem::HERO_DOESNT_KNOW_SPELL; - - if(caster->mana < getSpellCost(spell, caster)) //not enough mana - return ESpellCastProblem::NOT_ENOUGH_MANA; - } - break; - } - - - if(spell->id < 10) //it's adventure spell (not combat)) - return ESpellCastProblem::ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL; - - if(NBonus::hasOfType(heroes[1-cside], Bonus::SPELL_IMMUNITY, spell->id)) //non - casting hero provides immunity for this spell - return ESpellCastProblem::SECOND_HEROS_SPELL_IMMUNITY; - - if(battleMinSpellLevel() > spell->level) //non - casting hero stops caster from casting this spell - return ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED; - - int spellIDs[] = {66, 67, 68, 69}; //IDs of summon elemental spells (fire, earth, water, air) - int creIDs[] = {114, 113, 115, 112}; //(fire, earth, water, air) - - int * idp = std::find(spellIDs, spellIDs + ARRAY_COUNT(spellIDs), spell->id); - int arpos = idp - spellIDs; - if(arpos < ARRAY_COUNT(spellIDs)) - { - //check if there are summoned elementals of other type - BOOST_FOREACH ( const CStack * st, stacks) - { - if (vstd::contains(st->state, EBattleStackState::SUMMONED) && st->getCreature()->idNumber != creIDs[arpos]) - { - return ESpellCastProblem::ANOTHER_ELEMENTAL_SUMMONED; - } - } - } - - //checking if there exists an appropriate target - switch(spell->getTargetType()) - { - case CSpell::CREATURE: - case CSpell::CREATURE_EXPERT_MASSIVE: - if(mode == ECastingMode::HERO_CASTING) - { - const CGHeroInstance * caster = getHero(player); - bool targetExists = false; - BOOST_FOREACH(const CStack * stack, stacks) - { - switch (spell->positiveness) - { - case CSpell::POSITIVE: - if(stack->owner == caster->getOwner()) - { - if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK) - { - targetExists = true; - break; - } - } - break; - case CSpell::NEUTRAL: - if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK) - { - targetExists = true; - break; - } - break; - case CSpell::NEGATIVE: - if(stack->owner != caster->getOwner()) - { - if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK) - { - targetExists = true; - break; - } - } - break; - } - } - if(!targetExists) - { - return ESpellCastProblem::NO_APPROPRIATE_TARGET; - } - } - break; - case CSpell::OBSTACLE: - break; - } - - return ESpellCastProblem::OK; -} - -ESpellCastProblem::ESpellCastProblem BattleInfo::battleCanCastThisSpellHere( int player, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest ) const -{ - ESpellCastProblem::ESpellCastProblem moreGeneralProblem = battleCanCastThisSpell(player, spell, mode); - if(moreGeneralProblem != ESpellCastProblem::OK) - return moreGeneralProblem; - - if(spell->getTargetType() == CSpell::OBSTACLE) - { - //isObstacleOnTile(dest) - // - // - //TODO - //assert that it's remove obstacle - //rules whether we can remove spell-created obstacle - return ESpellCastProblem::NO_APPROPRIATE_TARGET; - } - - - //get dead stack if we cast resurrection or animate dead - const CStack *deadStack = getStackIf([dest](const CStack *s) { return !s->alive() && s->position == dest; }); - const CStack *aliveStack = getStackIf([dest](const CStack *s) { return s->alive() && s->position == dest;}); - - - if(spell->isRisingSpell()) - { - if(!deadStack && !aliveStack) - return ESpellCastProblem::NO_APPROPRIATE_TARGET; - if(spell->id == Spells::ANIMATE_DEAD && deadStack && !deadStack->hasBonusOfType(Bonus::UNDEAD)) - return ESpellCastProblem::NO_APPROPRIATE_TARGET; - if(deadStack && deadStack->owner != player) //you can resurrect only your own stacks //FIXME: it includes alive stacks as well - return ESpellCastProblem::NO_APPROPRIATE_TARGET; - } - else if(spell->getTargetType() == CSpell::CREATURE || spell->getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE) - { - if(!aliveStack) - return ESpellCastProblem::NO_APPROPRIATE_TARGET; - if(spell->isNegative() && aliveStack->owner == player) - return ESpellCastProblem::NO_APPROPRIATE_TARGET; - if(spell->isPositive() && aliveStack->owner != player) - return ESpellCastProblem::NO_APPROPRIATE_TARGET; - } - - - if (mode != ECastingMode::CREATURE_ACTIVE_CASTING && mode != ECastingMode::ENCHANTER_CASTING) - return battleIsImmune(getHero(player), spell, mode, dest); - else - return battleIsImmune(NULL, spell, mode, dest); -} - -TSpell BattleInfo::getRandomBeneficialSpell(const CStack * subject) const -{ - std::vector possibleSpells; - CSpell * spell; - for (int i = 0; i < GameConstants::SPELLS_QUANTITY; ++i) //should not use future spells added by mods - { - spell = VLC->spellh->spells[i]; - if (spell->isPositive()) //only positive - { - if (subject->hasBonusFrom(Bonus::SPELL_EFFECT, i) || - battleCanCastThisSpellHere(subject->owner, spell, ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK) - continue; - switch (i) - { - case Spells::SHIELD: - case Spells::FIRE_SHIELD: // not if all enemy units are shooters - { - bool walkerPresent = false; - BOOST_FOREACH (CStack * stack, stacks) - { - if ((stack->owner != subject->owner) && !stack->shots) - { - walkerPresent = true; - break; - } - } - if (!walkerPresent) - continue; - } - break; - case Spells::AIR_SHIELD: //only against active shooters - { - bool shooterPresent = false; - BOOST_FOREACH (CStack * stack, stacks) - { - if ((stack->owner != subject->owner) && stack->hasBonusOfType(Bonus::SHOOTER) && stack->shots) - { - shooterPresent = true; - break; - } - } - if (!shooterPresent) - continue; - break; - } - case Spells::ANTI_MAGIC: - case Spells::MAGIC_MIRROR: - { - if (!heroes[whatSide(theOtherPlayer(subject->owner))]) //only if there is enemy hero - continue; - } - break; - case Spells::CURE: //only damaged units - what about affected by curse? - { - if (subject->firstHPleft >= subject->MaxHealth()) - continue; - } - break; - case Spells::BLOODLUST: - { - if (subject->shots) //if can shoot - only if enemy uits are adjacent - continue; - } - break; - case Spells::PRECISION: - { - if (!(subject->hasBonusOfType(Bonus::SHOOTER) && subject->shots)) - continue; - } - break; - case Spells::SLAYER://only if monsters are present - { - bool monsterPresent = false; - BOOST_FOREACH (CStack * stack, stacks) - { - if ((stack->owner != subject->owner) && - (stack->hasBonus(Selector::type(Bonus::KING1) || Selector::type(Bonus::KING2) || Selector::type(Bonus::KING3)))) - { - monsterPresent = true; - break; - } - } - if (!monsterPresent) - continue; - } - break; - case Spells::CLONE: //not allowed - continue; - break; - } - possibleSpells.push_back(i); - } - } - if (possibleSpells.size()) - return possibleSpells[ran() % possibleSpells.size()]; - else - return -1; -} -TSpell BattleInfo::getRandomCastedSpell(const CStack * caster) const -{ - TBonusListPtr bl = caster->getBonuses(Selector::type(Bonus::SPELLCASTER)); - if (!bl->size()) - return -1; - int totalWeight = 0; - BOOST_FOREACH(Bonus * b, *bl) - { - totalWeight += std::max(b->additionalInfo, 1); //minimal chance to cast is 1 - } - int randomPos = ran() % totalWeight; - BOOST_FOREACH(Bonus * b, *bl) - { - randomPos -= std::max(b->additionalInfo, 1); - if(randomPos < 0) - { - return b->subtype; - } - } - - return -1; -} - const CGHeroInstance * BattleInfo::getHero( int player ) const { assert(sides[0] == player || sides[1] == player); @@ -2343,160 +1175,10 @@ const CGHeroInstance * BattleInfo::getHero( int player ) const return heroes[1]; } -bool NegateRemover(const Bonus* b) -{ - return b->source == Bonus::CREATURE_ABILITY; -} - -bool BattleInfo::battleTestElementalImmunity(const CStack * subject, const CSpell * spell, Bonus::BonusType element, bool damageSpell) const //helper for battleisImmune -{ - if (!spell->isPositive()) //negative or indifferent - { - if ((damageSpell && subject->hasBonusOfType(element, 2)) || subject->hasBonusOfType(element, 1)) - return true; - } - else if (spell->isPositive()) //positive - { - if (subject->hasBonusOfType(element, 0)) //must be immune to all spells - return true; - } - return false; -} - -ESpellCastProblem::ESpellCastProblem BattleInfo::battleIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const -{ - const CStack * subject; - bool risingSpell = vstd::contains(VLC->spellh->risingSpells, spell->id); - if (risingSpell) - subject = getStackT(dest, false); //including dead stacks - else - subject = getStackT(dest, true); //only alive - - if(subject) - { - if (spell->isPositive() && subject->hasBonusOfType(Bonus::RECEPTIVE)) //accept all positive spells - return ESpellCastProblem::OK; - - switch (spell->id) //TODO: more general logic for new spells? - { - case Spells::DESTROY_UNDEAD: - if (!subject->hasBonusOfType(Bonus::UNDEAD)) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - break; - case Spells::DEATH_RIPPLE: - if (subject->hasBonusOfType(Bonus::SIEGE_WEAPON)) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; //don't break here - undeads and war machines are immune, non-living are not - case Spells::BLESS: - case Spells::CURSE: //undeads are immune to bless & curse - if (subject->hasBonusOfType(Bonus::UNDEAD)) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - break; - case Spells::HASTE: - case Spells::SLOW: - case Spells::TELEPORT: - case Spells::CLONE: - if (subject->hasBonusOfType(Bonus::SIEGE_WEAPON)) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; //war machines are immune to some spells than involve movement - if (spell->id == Spells::CLONE && caster) //TODO: how about stacks casting Clone? - { - if (vstd::contains(subject->state, EBattleStackState::CLONED)) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; //can't clone already cloned creature - int maxLevel = (std::max(caster->getSpellSchoolLevel(spell), (ui8)1) + 4); - int creLevel = subject->getCreature()->level; - if (maxLevel < creLevel) //tier 1-5 for basic, 1-6 for advanced, 1-7 for expert - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - } - break; - case Spells::FORGETFULNESS: - if (!subject->hasBonusOfType(Bonus::SHOOTER)) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - break; - case Spells::DISPEL_HELPFUL_SPELLS: - { - TBonusListPtr spellBon = subject->getSpellBonuses(); - bool hasPositiveSpell = false; - BOOST_FOREACH(const Bonus * b, *spellBon) - { - if(VLC->spellh->spells[b->sid]->isPositive()) - { - hasPositiveSpell = true; - break; - } - } - if(!hasPositiveSpell) - { - return ESpellCastProblem::NO_SPELLS_TO_DISPEL; - } - } - break; - } - - bool damageSpell = (vstd::contains(VLC->spellh->damageSpells, spell->id)); - - if (damageSpell && subject->hasBonusOfType(Bonus::DIRECT_DAMAGE_IMMUNITY)) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - - if (spell->fire) - { - if (battleTestElementalImmunity(subject, spell, Bonus::FIRE_IMMUNITY, damageSpell)) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - } - if (spell->water) - { - if (battleTestElementalImmunity(subject, spell, Bonus::WATER_IMMUNITY, damageSpell)) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - } - if (spell->earth) - { - if (battleTestElementalImmunity(subject, spell, Bonus::EARTH_IMMUNITY, damageSpell)) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - } - if (spell->air) - { - if (battleTestElementalImmunity(subject, spell, Bonus::AIR_IMMUNITY, damageSpell)) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - } - if (vstd::contains(VLC->spellh->mindSpells, spell->id)) - { - if (subject->hasBonusOfType(Bonus::MIND_IMMUNITY)) - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - } - - if (risingSpell) - { - if (subject->count >= subject->baseAmount) //TODO: calculate potential hp raised - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - } - - TBonusListPtr immunities = subject->getBonuses(Selector::type(Bonus::LEVEL_SPELL_IMMUNITY)); - if(subject->hasBonusOfType(Bonus::NEGATE_ALL_NATURAL_IMMUNITIES)) - { - //std::remove_if(immunities->begin(), immunities->end(), NegateRemover); - immunities->remove_if(NegateRemover); - } - - if(subject->hasBonusOfType(Bonus::SPELL_IMMUNITY, spell->id) || - ( immunities->size() > 0 && immunities->totalValue() >= spell->level && spell->level)) - { - return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; - } - } - else //no target stack on this tile - { - if(spell->getTargetType() == CSpell::CREATURE || - (spell->getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE && mode == ECastingMode::HERO_CASTING && caster && caster->getSpellSchoolLevel(spell) < 3)) - { - return ESpellCastProblem::WRONG_SPELL_TARGET; - } - } - - return ESpellCastProblem::OK; -} - -std::vector BattleInfo::calculateResistedStacks(const CSpell * sp, const CGHeroInstance * caster, const CGHeroInstance * hero2, const std::set affectedCreatures, int casterSideOwner, ECastingMode::ECastingMode mode, int usedSpellPower, int spellLevel) const +std::vector BattleInfo::calculateResistedStacks(const CSpell * sp, const CGHeroInstance * caster, const CGHeroInstance * hero2, const std::set affectedCreatures, int casterSideOwner, ECastingMode::ECastingMode mode, int usedSpellPower, int spellLevel) const { std::vector ret; - for(std::set::const_iterator it = affectedCreatures.begin(); it != affectedCreatures.end(); ++it) + for(auto it = affectedCreatures.begin(); it != affectedCreatures.end(); ++it) { if(battleIsImmune(caster, sp, mode, (*it)->position) != ESpellCastProblem::OK) { @@ -2526,7 +1208,7 @@ std::vector BattleInfo::calculateResistedStacks(const CSpell * sp, const C if(sp->id == 60) //hypnotize { - for(std::set::const_iterator it = affectedCreatures.begin(); it != affectedCreatures.end(); ++it) + for(auto it = affectedCreatures.begin(); it != affectedCreatures.end(); ++it) { if( (*it)->hasBonusOfType(Bonus::SPELL_IMMUNITY, sp->id) //100% sure spell immunity || ( (*it)->count - 1 ) * (*it)->MaxHealth() + (*it)->firstHPleft @@ -2542,27 +1224,6 @@ std::vector BattleInfo::calculateResistedStacks(const CSpell * sp, const C return ret; } -int BattleInfo::getSurrenderingCost(int player) const -{ - if(!battleCanFlee(player)) //to surrender, conditions of fleeing must be fulfilled - return -1; - if(!getHero(theOtherPlayer(player))) //additionally, there must be an enemy hero - return -2; - - int ret = 0; - double discount = 0; - BOOST_FOREACH(const CStack *s, stacks) - if(s->owner == player && s->base) //we pay for our stack that comes from our army (the last condition eliminates summoned cres and war machines) - ret += s->getCreature()->cost[Res::GOLD] * s->count; - - if(const CGHeroInstance *h = getHero(player)) - discount += h->valOfBonuses(Bonus::SURRENDER_DISCOUNT); - - ret *= (100.0 - discount) / 100.0; - vstd::amax(ret, 0); //no negative costs for >100% discounts (impossible in original H3 mechanics, but some day...) - return ret; -} - int BattleInfo::theOtherPlayer(int player) const { return sides[!whatSide(player)]; @@ -2601,14 +1262,6 @@ shared_ptr BattleInfo::getObstacleOnTile(BattleHex tile) cons return shared_ptr(); } -const CStack * BattleInfo::getStackIf(boost::function pred) const -{ - auto stackItr = range::find_if(stacks, pred); - return stackItr == stacks.end() - ? NULL - : *stackItr; -} - int BattleInfo::battlefieldTypeToBI(int bfieldType) { static const std::map theMap = boost::assign::map_list_of(19, BattlefieldBI::CLOVER_FIELD) @@ -2624,33 +1277,20 @@ int BattleInfo::battlefieldTypeToBI(int bfieldType) return BattlefieldBI::NONE; } -std::set BattleInfo::getStoppers(bool whichSidePerspective) const +CStack * BattleInfo::getStack(int stackID, bool onlyAlive /*= true*/) { - std::set ret; - BOOST_FOREACH(auto &oi, obstacles) - { - if(isObstacleVisibleForSide(*oi, whichSidePerspective)) - { - range::copy(oi->getStoppingTile(), std::inserter(ret, ret.begin())); - } - } - return ret; + return const_cast(battleGetStackByID(stackID, onlyAlive)); } -bool BattleInfo::hasNativeStack(ui8 side) const +CStack * BattleInfo::getStackT(BattleHex tileID, bool onlyAlive /*= true*/) { - BOOST_FOREACH(const CStack *s, stacks) - { - if(s->attackerOwned == !side && s->getCreature()->isItNativeTerrain(terrainType)) - return true; - } - - return false; + return const_cast(battleGetStackByPos(tileID, onlyAlive)); } -bool BattleInfo::isObstacleVisibleForSide(const CObstacleInstance & coi, ui8 side) const +BattleInfo::BattleInfo() { - return coi.visibleForSide(side, hasNativeStack(side)); + setBattle(this); + setNodeType(BATTLE); } CStack::CStack(const CStackInstance *Base, int O, int I, bool AO, int S) @@ -2735,7 +1375,7 @@ si32 CStack::magicResistance() const { magicResistance = base->magicResistance(); int auraBonus = 0; - BOOST_FOREACH (CStack * stack, base->armyObj->battle->getAdjacentCreatures(this)) + BOOST_FOREACH (const CStack * stack, base->armyObj->battle-> batteAdjacentCreatures(this)) { if (stack->owner == owner) { @@ -2975,13 +1615,18 @@ bool CStack::doubleWide() const } BattleHex CStack::occupiedHex() const +{ + return occupiedHex(position); +} + +BattleHex CStack::occupiedHex(BattleHex assumedPos) const { if (doubleWide()) { if (attackerOwned) - return position - 1; + return assumedPos - 1; else - return position + 1; + return assumedPos + 1; } else { @@ -2990,12 +1635,27 @@ BattleHex CStack::occupiedHex() const } std::vector CStack::getHexes() const +{ + return getHexes(position); +} + +std::vector CStack::getHexes(BattleHex assumedPos) const +{ + return getHexes(assumedPos, doubleWide(), attackerOwned); +} + +std::vector CStack::getHexes(BattleHex assumedPos, bool twoHex, bool AttackerOwned) { std::vector hexes; - hexes.push_back(BattleHex(position)); - BattleHex occupied = occupiedHex(); - if(occupied.isValid()) - hexes.push_back(occupied); + hexes.push_back(assumedPos); + + if (twoHex) + { + if (AttackerOwned) + hexes.push_back(assumedPos - 1); + else + hexes.push_back(assumedPos + 1); + } return hexes; } diff --git a/lib/BattleState.h b/lib/BattleState.h index dd36c2f06..b8e1c82f9 100644 --- a/lib/BattleState.h +++ b/lib/BattleState.h @@ -9,6 +9,7 @@ #include "CObstacleInstance.h" #include "ConstTransitivePtr.h" #include "GameConstants.h" +#include "CBattleCallback.h" /* * BattleState.h, part of VCMI engine @@ -28,10 +29,11 @@ class CStackInstance; struct BattleStackAttacked; + //only for use in BattleInfo struct DLL_LINKAGE SiegeInfo { - ui8 wallState[8]; //[0] - keep, [1] - bottom tower, [2] - bottom wall, [3] - below gate, [4] - over gate, [5] - upper wall, [6] - uppert tower, [7] - gate; 1 - intact, 2 - damaged, 3 - destroyed + ui8 wallState[EWallParts::PARTS_COUNT]; template void serialize(Handler &h, const int version) { @@ -39,17 +41,9 @@ struct DLL_LINKAGE SiegeInfo } }; -struct DLL_LINKAGE AttackableTiles -{ - std::set hostileCreaturePositions; - std::set friendlyCreaturePositions; //for Dragon Breath - template void serialize(Handler &h, const int version) - { - h & hostileCreaturePositions & friendlyCreaturePositions; - } -}; -struct DLL_LINKAGE BattleInfo : public CBonusSystemNode + +struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallback { ui8 sides[2]; //sides[0] - attacker, sides[1] - defender si32 round, activeStack, selectedStack; @@ -81,82 +75,62 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode h & static_cast(*this); } + ////////////////////////////////////////////////////////////////////////// + BattleInfo(); + ~BattleInfo(){}; + ////////////////////////////////////////////////////////////////////////// //void getBonuses(BonusList &out, const CSelector &selector, const CBonusSystemNode *root = NULL) const; ////////////////////////////////////////////////////////////////////////// - - const CStack *getStackIf(boost::function pred) const; - const CStack * getNextStack() const; //which stack will have turn after current one - void getStackQueue(std::vector &out, int howMany, int turn = 0, int lastMoved = -1) const; //returns stack in order of their movement action - CStack * getStack(int stackID, bool onlyAlive = true); - const CStack * getStack(int stackID, bool onlyAlive = true) const; CStack * getStackT(BattleHex tileID, bool onlyAlive = true); - const CStack * getStackT(BattleHex tileID, bool onlyAlive = true) const; - void getAccessibilityMap(bool *accessibility, bool twoHex, bool attackerOwned, bool addOccupiable, std::set & occupyable, bool flying, const CStack* stackToOmmit = NULL) const; //send pointer to at least 187 allocated bytes - static bool isAccessible(BattleHex hex, bool * accessibility, bool twoHex, bool attackerOwned, bool flying, bool lastPos); //helper for makeBFS + CStack * getStack(int stackID, bool onlyAlive = true); + + const CStack * getNextStack() const; //which stack will have turn after current one + //void getStackQueue(std::vector &out, int howMany, int turn = 0, int lastMoved = -1) const; //returns stack in order of their movement action + + //void getAccessibilityMap(bool *accessibility, bool twoHex, bool attackerOwned, bool addOccupiable, std::set & occupyable, bool flying, const CStack* stackToOmmit = NULL) const; //send pointer to at least 187 allocated bytes + //static bool isAccessible(BattleHex hex, bool * accessibility, bool twoHex, bool attackerOwned, bool flying, bool lastPos); //helper for makeBFS BattleHex getClosestTile (bool attackerOwned, int initialPos, std::set & possibilities) const; //TODO: vector or set? copying one to another is bad int getAvaliableHex(TCreature creID, bool attackerOwned, int initialPos = -1) const; //find place for summon / clone effects - void makeBFS(BattleHex start, bool*accessibility, BattleHex *predecessor, int *dists, bool twoHex, bool attackerOwned, bool flying, bool fillPredecessors) const; //*accessibility must be prepared bool[187] array; last two pointers must point to the at least 187-elements int arrays - there is written result - std::pair< std::vector, int > getPath(BattleHex start, BattleHex dest, bool*accessibility, bool flyingCreature, bool twoHex, bool attackerOwned); //returned value: pair; length may be different than number of elements in path since flying vreatures jump between distant hexes - std::vector getAccessibility(const CStack * stack, bool addOccupiable, std::vector * attackable = NULL, bool forPassingBy = false) const; //returns vector of accessible tiles (taking into account the creature range) + //void makeBFS(BattleHex start, bool*accessibility, BattleHex *predecessor, int *dists, bool twoHex, bool attackerOwned, bool flying, bool fillPredecessors) const; //*accessibility must be prepared bool[187] array; last two pointers must point to the at least 187-elements int arrays - there is written result + std::pair< std::vector, int > getPath(BattleHex start, BattleHex dest, const CStack *stack); //returned value: pair; length may be different than number of elements in path since flying vreatures jump between distant hexes + //std::vector getAccessibility(const CStack * stack, bool addOccupiable, std::vector * attackable = NULL, bool forPassingBy = false) const; //returns vector of accessible tiles (taking into account the creature range) - bool isObstacleVisibleForSide(const CObstacleInstance &obstacle, ui8 side) const; + //bool isObstacleVisibleForSide(const CObstacleInstance &obstacle, ui8 side) const; shared_ptr getObstacleOnTile(BattleHex tile) const; - bool isStackBlocked(const CStack * stack) const; //returns true if there is neighboring enemy stack std::set getStoppers(bool whichSidePerspective) const; ui32 calculateDmg(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg); //charge - number of hexes travelled before attack (for champion's jousting) - TDmgRange calculateDmgRange(const CStack* attacker, const CStack* defender, TQuantity attackerCount, TQuantity defenderCount, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, 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, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair void calculateCasualties(std::map *casualties) const; //casualties are array of maps size 2 (attacker, defeneder), maps are (crid => amount) - std::set getAttackedCreatures(const CSpell * s, int skillLevel, ui8 attackerOwner, BattleHex destinationTile); //calculates stack affected by given spell - void getPotentiallyAttackableHexes(AttackableTiles &at, const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos); //hexes around target that could be attacked in melee - std::set getAttackedCreatures(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID); //calculates range of multi-hex attacks - std::set getAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID); //calculates range of multi-hex attacks - std::set getAdjacentCreatures (const CStack * stack) const; + using CBattleInfoCallback::getAttackedCreatures; + std::set getAttackedCreatures(const CSpell * s, int skillLevel, ui8 attackerOwner, BattleHex destinationTile); //calculates stack affected by given spell + //void getPotentiallyAttackableHexes(AttackableTiles &at, const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos); //hexes around target that could be attacked in melee + //std::set getAttackedCreatures(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID); //calculates range of multi-hex attacks + //std::set getAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID); //calculates range of multi-hex attacks static int calculateSpellDuration(const CSpell * spell, const CGHeroInstance * caster, int usedSpellPower); CStack * generateNewStack(const CStackInstance &base, bool attackerOwned, int slot, BattleHex position) const; //helper for CGameHandler::setupBattle and spells addign new stacks to the battlefield CStack * generateNewStack(const CStackBasicDescriptor &base, bool attackerOwned, int slot, BattleHex position) const; //helper for CGameHandler::setupBattle and spells addign new stacks to the battlefield int getIdForNewStack() const; //suggest a currently unused ID that'd suitable for generating a new stack - ui32 getSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //returns cost of given spell - int hexToWallPart(BattleHex hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found - int lineToWallHex(int line) const; //returns hex with wall in given line - std::pair getNearestStack(const CStack * closest, boost::logic::tribool attackerOwned) const; //if attackerOwned is indetermnate, returened stack is of any owner; hex is the number of hex we should be looking from; returns (nerarest creature, predecessorHex) + //std::pair getNearestStack(const CStack * closest, boost::logic::tribool attackerOwned) const; //if attackerOwned is indetermnate, returened stack is of any owner; hex is the number of hex we should be looking from; returns (nerarest creature, predecessorHex) ui32 calculateSpellBonus(ui32 baseDamage, const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature) const; ui32 calculateSpellDmg(const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const; //calculates damage inflicted by spell ui32 calculateHealedHP(const CGHeroInstance * caster, const CSpell * spell, const CStack * stack, const CStack * sacrificedStack = NULL) const; ui32 calculateHealedHP(int healedHealth, const CSpell * spell, const CStack * stack) const; //for Archangel ui32 calculateHealedHP(const CSpell * spell, int usedSpellPower, int spellSchoolLevel, const CStack * stack) const; //unused bool resurrects(TSpell spellid) const; //TODO: move it to spellHandler? - si8 hasDistancePenalty(const CStack * stackID, BattleHex destHex) const; //determines if given stack has distance penalty shooting given pos - si8 sameSideOfWall(int pos1, int pos2) const; //determines if given positions are on the same side of wall - si8 hasWallPenalty(const CStack * stack, BattleHex destHex) const; //determines if given stack has wall penalty shooting given pos - si8 canTeleportTo(const CStack * stack, BattleHex destHex, int telportLevel) const; //determines if given stack can teleport to given place - bool battleCanShoot(const CStack * stack, BattleHex dest) const; //determines if stack with given ID shoot at the selected destination + const CGHeroInstance * getHero(int player) const; //returns fighting hero that belongs to given player - ESpellCastProblem::ESpellCastProblem battleCanCastSpell(int player, ECastingMode::ECastingMode mode) const; //returns true if there are no general issues preventing from casting a spell - ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(int player, const CSpell * spell, ECastingMode::ECastingMode mode) const; //checks if given player can cast given spell - ESpellCastProblem::ESpellCastProblem battleIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; //checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into acount general problems such as not having spellbook or mana points etc. - 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 - bool battleTestElementalImmunity(const CStack * subject, const CSpell * spell, Bonus::BonusType element, bool damageSpell) const; - TSpell getRandomBeneficialSpell(const CStack * subject) const; - TSpell getRandomCastedSpell(const CStack * caster) const; //called at the beginning of turn for Faerie Dragon - std::vector calculateResistedStacks(const CSpell * sp, const CGHeroInstance * caster, const CGHeroInstance * hero2, const std::set affectedCreatures, int casterSideOwner, ECastingMode::ECastingMode mode, int usedSpellPower, int spellLevel) const; + std::vector calculateResistedStacks(const CSpell * sp, const CGHeroInstance * caster, const CGHeroInstance * hero2, const std::set affectedCreatures, int casterSideOwner, ECastingMode::ECastingMode mode, int usedSpellPower, int spellLevel) const; - - bool battleCanFlee(int player) const; //returns true if player can flee from the battle const CStack * battleGetStack(BattleHex pos, bool onlyAlive); //returns stack at given tile const CGHeroInstance * battleGetOwner(const CStack * stack) const; //returns hero that owns given stack; NULL if none - si8 battleMinSpellLevel() const; //calculates minimum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned void localInit(); void localInitStack(CStack * s); static BattleInfo * setupBattle( int3 tile, int terrain, int battlefieldType, const CArmedInstance *armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance *town ); - bool isInTacticRange( BattleHex dest ) const; - int getSurrenderingCost(int player) const; - bool hasNativeStack(ui8 side) const; + //bool hasNativeStack(ui8 side) const; int theOtherPlayer(int player) const; ui8 whatSide(int player) const; @@ -168,7 +142,7 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode class DLL_LINKAGE CStack : public CBonusSystemNode, public CStackBasicDescriptor { public: - const CStackInstance *base; + const CStackInstance *base; //garrison slot from which stack originates (NULL for war machines, summoned cres, etc) ui32 ID; //unique ID of stack ui32 baseAmount; @@ -226,7 +200,10 @@ public: bool doubleWide() const; BattleHex occupiedHex() const; //returns number of occupied hex (not the position) if stack is double wide; otherwise -1 + BattleHex occupiedHex(BattleHex assumedPos) const; //returns number of occupied hex (not the position) if stack is double wide and would stand on assumedPos; otherwise -1 std::vector getHexes() const; //up to two occupied hexes, starting from front + std::vector getHexes(BattleHex assumedPos) const; //up to two occupied hexes, starting from front + static std::vector getHexes(BattleHex assumedPos, bool twoHex, bool AttackerOwned); //up to two occupied hexes, starting from front bool coversPos(BattleHex position) const; //checks also if unit is double-wide std::vector getSurroundingHexes(BattleHex attackerPos = BattleHex::INVALID) const; // get six or 8 surrounding hexes depending on creature size diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index a10f7e46d..17278a7b6 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -526,31 +526,46 @@ void CArtHandler::getAllowedArts(std::vector > &ou out.push_back(art); } } -void CArtHandler::giveArtBonus( int aid, Bonus::BonusType type, int val, int subtype, int valType, ILimiter * limiter, int additionalInfo) + +Bonus *createBonus(Bonus::BonusType type, int val, int subtype, int valType, shared_ptr limiter = nullptr, int additionalInfo = 0) { - Bonus *added = new Bonus(Bonus::PERMANENT,type,Bonus::ARTIFACT,val,aid,subtype); + Bonus *added = new Bonus(Bonus::PERMANENT,type,Bonus::ARTIFACT,val,-1,subtype); added->additionalInfo = additionalInfo; added->valType = valType; - added->limiter.reset(limiter); - if(type == Bonus::MORALE || type == Bonus::LUCK) - added->description = artifacts[aid]->Name() + (val > 0 ? " +" : " ") + boost::lexical_cast(val); - else - added->description = artifacts[aid]->Name(); - artifacts[aid]->addNewBonus(added); + added->limiter = limiter; + return added; } -void CArtHandler::giveArtBonus(int aid, Bonus::BonusType type, int val, int subtype, IPropagator* propagator /*= NULL*/, int additionalInfo) +Bonus *createBonus(Bonus::BonusType type, int val, int subtype, shared_ptr propagator = nullptr, int additionalInfo = 0) { - Bonus *added = new Bonus(Bonus::PERMANENT,type,Bonus::ARTIFACT,val,aid,subtype); + Bonus *added = new Bonus(Bonus::PERMANENT,type,Bonus::ARTIFACT,val,-1,subtype); added->additionalInfo = additionalInfo; added->valType = Bonus::BASE_NUMBER; - added->propagator.reset(propagator); - if(type == Bonus::MORALE || type == Bonus::LUCK) - added->description = artifacts[aid]->Name() + (val > 0 ? " +" : " ") + boost::lexical_cast(val); - else - added->description = artifacts[aid]->Name(); - artifacts[aid]->addNewBonus(added); + added->propagator = propagator; + return added; } + +void CArtHandler::giveArtBonus( int aid, Bonus::BonusType type, int val, int subtype, int valType, shared_ptr limiter, int additionalInfo) +{ + giveArtBonus(aid, createBonus(type, val, subtype, valType, limiter, additionalInfo)); +} + +void CArtHandler::giveArtBonus(int aid, Bonus::BonusType type, int val, int subtype, shared_ptr propagator /*= NULL*/, int additionalInfo) +{ + giveArtBonus(aid, createBonus(type, val, subtype, propagator, additionalInfo)); +} + +void CArtHandler::giveArtBonus(int aid, Bonus *bonus) +{ + bonus->sid = aid; + if(bonus->subtype == Bonus::MORALE || bonus->type == Bonus::LUCK) + bonus->description = artifacts[aid]->Name() + (bonus->val > 0 ? " +" : " ") + boost::lexical_cast(bonus->val); + else + bonus->description = artifacts[aid]->Name(); + + artifacts[aid]->addNewBonus(bonus); +} + void CArtHandler::makeItCreatureArt (int aid, bool onlyCreature /*=true*/) { CArtifact *a = artifacts[aid]; @@ -584,6 +599,13 @@ void CArtHandler::addBonuses() #define ART_ATTACK_AND_DEFENSE(ID, val) ART_PRIM_SKILL(ID,0,val); ART_PRIM_SKILL(ID,1,val) #define ART_POWER_AND_KNOWLEDGE(ID, val) ART_PRIM_SKILL(ID,2,val); ART_PRIM_SKILL(ID,3,val) + //Propagators/limiters used more than once + auto battleWidePropagator = make_shared(CBonusSystemNode::BATTLE); + auto visitedTownPropagator = make_shared(CBonusSystemNode::TOWN_AND_VISITOR); + + auto shooterOnlyLimiter = make_shared(Bonus::SHOOTER); + auto dragonNatureLimiter = make_shared(Bonus::DRAGON_NATURE); + //Attack bonus artifacts (Weapons) ART_PRIM_SKILL(7,0,+2); //Centaur Axe ART_PRIM_SKILL(8,0,+3); //Blackshard of the Dead Knight @@ -697,7 +719,7 @@ void CArtHandler::addBonuses() giveArtBonus(81,Bonus::FIRE_SPELL_DMG_PREMY,+50);//Orb of Tempestuous Fire giveArtBonus(82,Bonus::WATER_SPELL_DMG_PREMY,+50);//Orb of Driving Rain - giveArtBonus(83,Bonus::LEVEL_SPELL_IMMUNITY,3,-1,Bonus::INDEPENDENT_MAX);//Recanter's Cloak + giveArtBonus(83,createBonus(Bonus::BLOCK_MAGIC_ABOVE, 2, -1, Bonus::INDEPENDENT_MIN)->addPropagator(battleWidePropagator));//Recanter's Cloak giveArtBonus(84,Bonus::BLOCK_MORALE,0);//Spirit of Oppression giveArtBonus(85,Bonus::BLOCK_LUCK,0);//Hourglass of the Evil Hour @@ -707,8 +729,8 @@ void CArtHandler::addBonuses() giveArtBonus(89,Bonus::EARTH_SPELLS,0);//Tome of Earth Magic giveArtBonus(90,Bonus::WATER_WALKING, 0, 1);//Boots of Levitation - giveArtBonus(91,Bonus::NO_DISTANCE_PENALTY,0, 0, 0, new HasAnotherBonusLimiter(Bonus::SHOOTER));//Golden Bow - giveArtBonus(91,Bonus::NO_WALL_PENALTY, 0, 0, 0, new HasAnotherBonusLimiter(Bonus::SHOOTER)); + giveArtBonus(91,Bonus::NO_DISTANCE_PENALTY,0, 0, 0, shooterOnlyLimiter);//Golden Bow + giveArtBonus(91,Bonus::NO_WALL_PENALTY, 0, 0, 0, shooterOnlyLimiter); giveArtBonus(92,Bonus::SPELL_IMMUNITY,0,35);//Sphere of Permanence giveArtBonus(93,Bonus::NEGATE_ALL_NATURAL_IMMUNITIES,0);//Orb of Vulnerability @@ -720,14 +742,15 @@ void CArtHandler::addBonuses() giveArtBonus(98,Bonus::LAND_MOVEMENT,+600);//Boots of Speed giveArtBonus(99,Bonus::STACKS_SPEED,+2);//Cape of Velocity - giveArtBonus(100,Bonus::SPELL_IMMUNITY,0,59);//Pendant of Dispassion - giveArtBonus(101,Bonus::SPELL_IMMUNITY,0,62);//Pendant of Second Sight - giveArtBonus(102,Bonus::SPELL_IMMUNITY,0,42);//Pendant of Holiness - giveArtBonus(103,Bonus::SPELL_IMMUNITY,0,24);//Pendant of Life - giveArtBonus(104,Bonus::SPELL_IMMUNITY,0,25, 1, new HasAnotherBonusLimiter(Bonus::UNDEAD));//Pendant of Death does not display info for living stacks - giveArtBonus(105,Bonus::SPELL_IMMUNITY,0,60);//Pendant of Free Will - giveArtBonus(106,Bonus::SPELL_IMMUNITY,0,17);//Pendant of Negativity - giveArtBonus(107,Bonus::SPELL_IMMUNITY,0,61);//Pendant of Total Recall + giveArtBonus(100,Bonus::SPELL_IMMUNITY,0,Spells::BERSERK);//Pendant of Dispassion + giveArtBonus(101,Bonus::SPELL_IMMUNITY,0,Spells::BLIND);//Pendant of Second Sight + giveArtBonus(102,Bonus::SPELL_IMMUNITY,0,Spells::CURSE);//Pendant of Holiness + giveArtBonus(103,Bonus::SPELL_IMMUNITY,0,Spells::DEATH_RIPPLE);//Pendant of Life + giveArtBonus(104,Bonus::SPELL_IMMUNITY,0,Spells::DESTROY_UNDEAD, 1, make_shared(Bonus::UNDEAD));//Pendant of Death does not display info for living stacks + giveArtBonus(105,Bonus::SPELL_IMMUNITY,0,Spells::HYPNOTIZE);//Pendant of Free Will + giveArtBonus(106,Bonus::SPELL_IMMUNITY,0,Spells::LIGHTNING_BOLT);//Pendant of Negativity + giveArtBonus(106,Bonus::SPELL_IMMUNITY,0,Spells::CHAIN_LIGHTNING);//Pendant of Negativity + giveArtBonus(107,Bonus::SPELL_IMMUNITY,0,Spells::FORGETFULNESS);//Pendant of Total Recall giveArtBonus(108,Bonus::MORALE,+3);//Pendant of Courage giveArtBonus(108,Bonus::LUCK,+3);//Pendant of Courage @@ -741,11 +764,13 @@ void CArtHandler::addBonuses() giveArtBonus(116,Bonus::GENERATE_RESOURCE,+750, Res::GOLD); //Endless Bag of Gold giveArtBonus(117,Bonus::GENERATE_RESOURCE,+500, Res::GOLD); //Endless Purse of Gold - giveArtBonus(118,Bonus::CREATURE_GROWTH,+5,1, new CPropagatorNodeType(CBonusSystemNode::TOWN_AND_VISITOR)); //Legs of Legion - giveArtBonus(119,Bonus::CREATURE_GROWTH,+4,2, new CPropagatorNodeType(CBonusSystemNode::TOWN_AND_VISITOR)); //Loins of Legion - giveArtBonus(120,Bonus::CREATURE_GROWTH,+3,3, new CPropagatorNodeType(CBonusSystemNode::TOWN_AND_VISITOR)); //Torso of Legion - giveArtBonus(121,Bonus::CREATURE_GROWTH,+2,4, new CPropagatorNodeType(CBonusSystemNode::TOWN_AND_VISITOR)); //Arms of Legion - giveArtBonus(122,Bonus::CREATURE_GROWTH,+1,5, new CPropagatorNodeType(CBonusSystemNode::TOWN_AND_VISITOR)); //Head of Legion + + //Town will receive bonus if hero is visiting town or stays in its garrison. + giveArtBonus(118,Bonus::CREATURE_GROWTH,+5,1, visitedTownPropagator); //Legs of Legion + giveArtBonus(119,Bonus::CREATURE_GROWTH,+4,2, visitedTownPropagator); //Loins of Legion + giveArtBonus(120,Bonus::CREATURE_GROWTH,+3,3, visitedTownPropagator); //Torso of Legion + giveArtBonus(121,Bonus::CREATURE_GROWTH,+2,4, visitedTownPropagator); //Arms of Legion + giveArtBonus(122,Bonus::CREATURE_GROWTH,+1,5, visitedTownPropagator); //Head of Legion //Sea Captain's Hat giveArtBonus(123,Bonus::WHIRLPOOL_PROTECTION,0); @@ -753,13 +778,13 @@ void CArtHandler::addBonuses() giveArtBonus(123,Bonus::SPELL,3,0, Bonus::INDEPENDENT_MAX); giveArtBonus(123,Bonus::SPELL,3,1, Bonus::INDEPENDENT_MAX); - giveArtBonus(124,Bonus::SPELLS_OF_LEVEL,3,1); //Spellbinder's Hat - giveArtBonus(125,Bonus::ENEMY_CANT_ESCAPE,0); //Shackles of War - giveArtBonus(126,Bonus::LEVEL_SPELL_IMMUNITY,GameConstants::SPELL_LEVELS,-1,Bonus::INDEPENDENT_MAX);//Orb of Inhibition + giveArtBonus(124, Bonus::SPELLS_OF_LEVEL,3,1); //Spellbinder's Hat + giveArtBonus(125, Bonus::ENEMY_CANT_ESCAPE,0); //Shackles of War + giveArtBonus(126, Bonus::BLOCK_ALL_MAGIC, 0, -1, battleWidePropagator);//Orb of Inhibition //vial of dragon blood - giveArtBonus(127, Bonus::PRIMARY_SKILL, +5, PrimarySkill::ATTACK, Bonus::BASE_NUMBER, new HasAnotherBonusLimiter(Bonus::DRAGON_NATURE)); - giveArtBonus(127, Bonus::PRIMARY_SKILL, +5, PrimarySkill::DEFENSE, Bonus::BASE_NUMBER, new HasAnotherBonusLimiter(Bonus::DRAGON_NATURE)); + giveArtBonus(127, Bonus::PRIMARY_SKILL, +5, PrimarySkill::ATTACK, Bonus::BASE_NUMBER, dragonNatureLimiter); + giveArtBonus(127, Bonus::PRIMARY_SKILL, +5, PrimarySkill::DEFENSE, Bonus::BASE_NUMBER, dragonNatureLimiter); //Armageddon's Blade giveArtBonus(128, Bonus::SPELL, 3, 26, Bonus::INDEPENDENT_MAX); @@ -786,7 +811,7 @@ void CArtHandler::addBonuses() giveArtBonus(132, Bonus::OPENING_BATTLE_SPELL, 50, 52); // Misfortune // Statue of Legion - gives only 50% growth - giveArtBonus(133, Bonus::CREATURE_GROWTH_PERCENT, 50, -1, new CPropagatorNodeType(CBonusSystemNode::PLAYER)); + giveArtBonus(133, Bonus::CREATURE_GROWTH_PERCENT, 50, -1, make_shared(CBonusSystemNode::PLAYER)); //Power of the Dragon Father giveArtBonus(134, Bonus::LEVEL_SPELL_IMMUNITY, 4, -1, Bonus::INDEPENDENT_MAX); @@ -798,9 +823,9 @@ void CArtHandler::addBonuses() giveArtBonus(136, Bonus::FREE_SHIP_BOARDING, 0); //Bow of the Sharpshooter - giveArtBonus(137, Bonus::NO_DISTANCE_PENALTY, 0, 0, 0, new HasAnotherBonusLimiter(Bonus::SHOOTER)); - giveArtBonus(137, Bonus::NO_WALL_PENALTY, 0, 0, 0, new HasAnotherBonusLimiter(Bonus::SHOOTER)); - giveArtBonus(137, Bonus::FREE_SHOOTING, 0, 0, 0, new HasAnotherBonusLimiter(Bonus::SHOOTER)); + giveArtBonus(137, Bonus::NO_DISTANCE_PENALTY, 0, 0, 0, shooterOnlyLimiter); + giveArtBonus(137, Bonus::NO_WALL_PENALTY, 0, 0, 0, shooterOnlyLimiter); + giveArtBonus(137, Bonus::FREE_SHOOTING, 0, 0, 0, shooterOnlyLimiter); //Wizard's Well giveArtBonus(138, Bonus::FULL_MANA_REGENERATION, 0); @@ -844,7 +869,7 @@ void CArtHandler::addBonuses() giveArtBonus(142, Bonus::SPELL_AFTER_ATTACK, 50, Spells::BERSERK, NULL, 1); giveArtBonus(142, Bonus::SPELL_AFTER_ATTACK, 50, Spells::POISON, NULL, 1); giveArtBonus(142, Bonus::SPELL_AFTER_ATTACK, 50, Spells::DISRUPTING_RAY, NULL, 1); - artifacts[142].get()->setDescription ("Tripple shots, tripple attack, casts various spells during attack, attacks have range of Inferno, no distance penalty, catapult"); + artifacts[142].get()->setDescription ("Triple shots, triple attack, casts various spells during attack, attacks have range of Inferno, no distance penalty, catapult"); //Monster's Power giveArtBonus(143, Bonus::STACK_HEALTH, +100, -1, Bonus::PERCENT_TO_BASE); giveArtBonus(143, Bonus::CREATURE_DAMAGE, +100, 2, Bonus::PERCENT_TO_ALL); diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index aad1a5477..ff38aa2d6 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -34,6 +34,14 @@ namespace ArtifactPosition }; } +namespace ArtifactId +{ + enum ArtifactId + { + SPELLBOOK = 17 + }; +} + namespace ArtBearer { enum @@ -181,8 +189,9 @@ public: class DLL_LINKAGE CArtHandler //handles artifacts { - void giveArtBonus(int aid, Bonus::BonusType type, int val, int subtype = -1, int valType = Bonus::BASE_NUMBER, ILimiter * limiter = NULL, int additionalinfo = 0); - void giveArtBonus(int aid, Bonus::BonusType type, int val, int subtype, IPropagator* propagator, int additionalinfo = 0); + void giveArtBonus(int aid, Bonus::BonusType type, int val, int subtype = -1, int valType = Bonus::BASE_NUMBER, shared_ptr limiter = NULL, int additionalinfo = 0); + void giveArtBonus(int aid, Bonus::BonusType type, int val, int subtype, shared_ptr propagator, int additionalinfo = 0); + void giveArtBonus(int aid, Bonus *bonus); public: std::vector treasures, minors, majors, relics; std::vector< ConstTransitivePtr > artifacts; diff --git a/lib/CBattleCallback.cpp b/lib/CBattleCallback.cpp new file mode 100644 index 000000000..30e276524 --- /dev/null +++ b/lib/CBattleCallback.cpp @@ -0,0 +1,2021 @@ +#include "StdInc.h" +#include "CBattleCallback.h" +#include "BattleState.h" +#include "CGameState.h" +#include "NetPacks.h" +#include "CSpellHandler.h" +#include "VCMI_Lib.h" + +#define RETURN_IF_NOT_BATTLE(X) if(!duringBattle()) {tlog1 << __FUNCTION__ << " called when no battle!\n"; return X; } + +namespace SiegeStuffThatShouldBeMovedToHandlers // <=== TODO +{ + 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}; + + return lineToHex[line]; + } + + static bool sameSideOfWall(BattleHex pos1, BattleHex pos2) + { + const int wallInStackLine = lineToWallHex(pos1.getY()); + const int wallInDestLine = lineToWallHex(pos2.getY()); + + const bool stackLeft = pos1 < wallInStackLine; + const bool destLeft = pos2 < wallInDestLine; + + return stackLeft != destLeft; + } + + static int getMoatDamage(int townType) + { + //TODO move to config file + static const int dmgs[] = {70, 70, -1, + 90, 70, 90, + 70, 90, 70}; + + if(townType >= 0 && townType < ARRAY_COUNT(dmgs)) + return dmgs[townType]; + + tlog1 << "No moat info for town " << townType << std::endl; + return 0; + } + static EWallParts::EWallParts hexToWallPart(BattleHex hex) + { + //potentially attackable parts of wall + // -2 - indestructible walls + static const std::pair attackable[] = + { + std::make_pair(50, EWallParts::KEEP), + std::make_pair(183, EWallParts::BOTTOM_TOWER), + std::make_pair(182, EWallParts::BOTTOM_WALL), + std::make_pair(130, EWallParts::BELOW_GATE), + std::make_pair(62, EWallParts::OVER_GATE), + std::make_pair(29, EWallParts::UPPER_WAL), + std::make_pair(12, EWallParts::UPPER_TOWER), + std::make_pair(95, EWallParts::GATE), + std::make_pair(96, EWallParts::GATE), + std::make_pair(45, EWallParts::INDESTRUCTIBLE_PART), + std::make_pair(78, EWallParts::INDESTRUCTIBLE_PART), + std::make_pair(112, EWallParts::INDESTRUCTIBLE_PART), + std::make_pair(147, EWallParts::INDESTRUCTIBLE_PART) + }; + + for(int g = 0; g < ARRAY_COUNT(attackable); ++g) + { + if(attackable[g].first == hex) + return attackable[g].second; + } + + return EWallParts::INVALID; //not found! + } +} + +using namespace SiegeStuffThatShouldBeMovedToHandlers; + +boost::shared_mutex& CCallbackBase::getGsMutex() +{ + return *gs->mx; +} + +bool CCallbackBase::duringBattle() const +{ + return getBattle() != nullptr; +} + +void CCallbackBase::setBattle(const BattleInfo *B) +{ + battle = B; +} + +ui8 CBattleInfoEssentials::battleTerrainType() const +{ + RETURN_IF_NOT_BATTLE(-1); + return getBattle()->terrainType; +} + +int CBattleInfoEssentials::battleGetBattlefieldType() const +{ + RETURN_IF_NOT_BATTLE(-1); + return getBattle()->battlefieldType; +} + +std::vector > CBattleInfoEssentials::battleGetAllObstacles(boost::optional perspective /*= boost::none*/) const +{ + std::vector > ret; + RETURN_IF_NOT_BATTLE(ret); + + if(!perspective) + { + //if no particular perspective request, use default one + perspective = battleGetMySide(); + } + else + { + if(player >= 0 && *perspective != battleGetMySide()) + { + tlog1 << "Unauthorized access attempt!\n"; + assert(0); //I want to notice if that happens + //perspective = battleGetMySide(); + } + } + + BOOST_FOREACH(auto oi, getBattle()->obstacles) + { + if(getBattle()->battleIsObstacleVisibleForSide(*oi, *perspective)) + ret.push_back(oi); + } + + return ret; +} + +bool CBattleInfoEssentials::battleIsObstacleVisibleForSide(const CObstacleInstance & coi, BattlePerspective::BattlePerspective side) const +{ + RETURN_IF_NOT_BATTLE(false); + return side == BattlePerspective::ALL_KNOWING || coi.visibleForSide(side, battleHasNativeStack(side)); +} + +bool CBattleInfoEssentials::battleHasNativeStack(ui8 side) const +{ + RETURN_IF_NOT_BATTLE(false); + + BOOST_FOREACH(const CStack *s, battleGetAllStacks()) + { + if(s->attackerOwned == !side && s->getCreature()->isItNativeTerrain(getBattle()->terrainType)) + return true; + } + + return false; +} + +TStacks CBattleInfoEssentials::battleGetAllStacks() const /*returns all stacks, alive or dead or undead or mechanical :) */ +{ + TStacks ret; + RETURN_IF_NOT_BATTLE(ret); + boost::copy(getBattle()->stacks, std::back_inserter(ret)); + return ret; +} + +TStacks CBattleInfoEssentials::battleAliveStacks() const +{ + TStacks ret; + vstd::copy_if(battleGetAllStacks(), std::back_inserter(ret), [](const CStack *s){ return s->alive(); }); + return ret; +} + +TStacks CBattleInfoEssentials::battleAliveStacks(ui8 side) const +{ + TStacks ret; + vstd::copy_if(battleGetAllStacks(), std::back_inserter(ret), [=](const CStack *s){ return s->alive() && s->attackerOwned == !side; }); + return ret; +} + +int CBattleInfoEssentials::battleGetMoatDmg() const +{ + RETURN_IF_NOT_BATTLE(0); + + auto town = getBattle()->town; + if(!town) + return 0; + + return getMoatDamage(town->subID); +} + +const CGTownInstance * CBattleInfoEssentials::battleGetDefendedTown() const +{ + RETURN_IF_NOT_BATTLE(nullptr); + + + if(!getBattle() || getBattle()->town == NULL) + return NULL; + + return getBattle()->town; +} + +BattlePerspective::BattlePerspective CBattleInfoEssentials::battleGetMySide() const +{ + RETURN_IF_NOT_BATTLE(BattlePerspective::INVALID); + if(player < 0) + return BattlePerspective::ALL_KNOWING; + if(player == getBattle()->sides[0]) + return BattlePerspective::LEFT_SIDE; + if(player == getBattle()->sides[1]) + return BattlePerspective::RIGHT_SIDE; + + tlog1 << "Cannot find player " << player << " in battle!\n"; + return BattlePerspective::INVALID; +} + +const CStack * CBattleInfoEssentials::battleActiveStack() const +{ + RETURN_IF_NOT_BATTLE(nullptr); + return battleGetStackByID(getBattle()->activeStack); +} + +const CStack* CBattleInfoEssentials::battleGetStackByID(int ID, bool onlyAlive) const +{ + RETURN_IF_NOT_BATTLE(nullptr); + + BOOST_FOREACH(auto s, battleGetAllStacks()) + if(s->ID == ID && (!onlyAlive || s->alive())) + return s; + + return nullptr; +} + +bool CBattleInfoEssentials::battleDoWeKnowAbout(ui8 side) const +{ + RETURN_IF_NOT_BATTLE(false); + auto p = battleGetMySide(); + return p == BattlePerspective::ALL_KNOWING || p == side; +} + +si8 CBattleInfoEssentials::battleTacticDist() const +{ + RETURN_IF_NOT_BATTLE(0); + return getBattle()->tacticDistance; +} + +si8 CBattleInfoEssentials::battleGetTacticsSide() const +{ + RETURN_IF_NOT_BATTLE(-1); + return getBattle()->tacticsSide; +} + +const CGHeroInstance * CBattleInfoEssentials::battleGetFightingHero(ui8 side) const +{ + RETURN_IF_NOT_BATTLE(nullptr); + if(side > 1) + { + tlog1 << "FIXME: " << __FUNCTION__ << " wrong argument!" << std::endl; + return nullptr; + } + + if(!battleDoWeKnowAbout(side)) + { + tlog1 << "FIXME: " << __FUNCTION__ << " access check " << std::endl; + return nullptr; + } + + return getBattle()->heroes[side]; +} + +int CBattleInfoEssentials::battleCastSpells(ui8 side) const +{ + RETURN_IF_NOT_BATTLE(-1); + return getBattle()->castSpells[side]; +} + +ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(int player, ECastingMode::ECastingMode mode) const +{ + RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID); + const ui8 side = playerToSide(player); + if(!battleDoWeKnowAbout(side)) + { + tlog3 << "You can't check if enemy can cast given spell!\n"; + return ESpellCastProblem::INVALID; + } + + switch (mode) + { + case ECastingMode::HERO_CASTING: + { + if(battleTacticDist()) + return ESpellCastProblem::ONGOING_TACTIC_PHASE; + if(battleCastSpells(side) > 0) + return ESpellCastProblem::ALREADY_CASTED_THIS_TURN; + + auto hero = battleGetFightingHero(side); + + if(!hero) + return ESpellCastProblem::NO_HERO_TO_CAST_SPELL; + if(!hero->getArt(ArtifactPosition::SPELLBOOK)) + return ESpellCastProblem::NO_SPELLBOOK; + if(hero->hasBonusOfType(Bonus::BLOCK_ALL_MAGIC)) + return ESpellCastProblem::MAGIC_IS_BLOCKED; + } + break; + default: + break; + } + + return ESpellCastProblem::OK; +} + +bool CBattleInfoEssentials::battleCanFlee(int player) const +{ + RETURN_IF_NOT_BATTLE(false); + ui8 mySide = playerToSide(player); + const CGHeroInstance *myHero = battleGetFightingHero(mySide), + *enemyHero = battleGetFightingHero(!mySide); + + //current player have no hero + if(!myHero) + return false; + + //TODo use bonus system + //ie. one of heroes is wearing shakles of war + if(NBonus::hasOfType(enemyHero, Bonus::ENEMY_CANT_ESCAPE) || NBonus::hasOfType(myHero, Bonus::ENEMY_CANT_ESCAPE)) + return false; + + //we are besieged defender + if(mySide == BattleSide::DEFENDER && battleGetSiegeLevel()) + { + auto town = battleGetDefendedTown(); + if(!(town->subID == 6 && town->hasBuilt(EBuilding::SPECIAL_1))) //not a stronghold with escape tunnel + return false; + } + + return true; +} + +ui8 CBattleInfoEssentials::playerToSide(int player) const +{ + RETURN_IF_NOT_BATTLE(-1); + int ret = vstd::find_pos(getBattle()->sides, player); + if(ret < 0) + tlog3 << "Cannot find side for player " << player << std::endl; + + return ret; +} + +ui8 CBattleInfoEssentials::battleGetSiegeLevel() const +{ + RETURN_IF_NOT_BATTLE(0); + return getBattle()->siege; +} + +bool CBattleInfoEssentials::battleCanSurrender(int player) const +{ + RETURN_IF_NOT_BATTLE(false); + //conditions like for fleeing + enemy must have a hero + return battleCanFlee(player) && battleGetFightingHero(!playerToSide(player)); +} + +bool CBattleInfoEssentials::battleHasHero(ui8 side) const +{ + RETURN_IF_NOT_BATTLE(false); + assert(side >= 0 && side < 2); + return getBattle()->heroes[side]; +} + +ui8 CBattleInfoEssentials::battleGetWallState(int partOfWall) const +{ + RETURN_IF_NOT_BATTLE(0); + if(getBattle()->siege == 0) + return 0; + + assert(partOfWall >= 0 && partOfWall < EWallParts::PARTS_COUNT); + return getBattle()->si.wallState[partOfWall]; +} + +si8 CBattleInfoCallback::battleHasWallPenalty( const CStack * stack, BattleHex destHex ) const +{ + RETURN_IF_NOT_BATTLE(false); + if (!battleGetSiegeLevel() || stack->hasBonusOfType(Bonus::NO_WALL_PENALTY)) + return false; + + const int wallInStackLine = lineToWallHex(stack->position.getY()); + const int wallInDestLine = lineToWallHex(destHex.getY()); + + const bool stackLeft = stack->position < 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 + row -= 2; + const int wallPos = lineToWallHex(row); + if (battleHexToWallPart(wallPos) != -1) //wall still exists or is indestructible + return true; + } + + return false; +} + +si8 CBattleInfoCallback::battleCanTeleportTo(const CStack * stack, BattleHex destHex, int telportLevel) const +{ + RETURN_IF_NOT_BATTLE(false); + if(getAccesibility().accessible(destHex, stack)) + return false; + + if (battleGetSiegeLevel() && telportLevel < 2) //check for wall + return sameSideOfWall(stack->position, destHex); + + return true; +} + +// std::vector CBattleInfoCallback::battleGetDistances(const CStack * stack, BattleHex hex /*= BattleHex::INVALID*/, BattleHex * predecessors /*= NULL*/) +// { +// // FIXME - This method is broken, hex argument is not used. However AI depends on that wrong behaviour. +// +// if(!hex.isValid()) +// hex = stack->position; +// +// std::vector ret(GameConstants::BFIELD_SIZE, -1); //fill initial ret with -1's +// +// if(!hex.isValid()) //stack has bad position? probably castle turret, return initial values (they can't move) +// return ret; +// +// bool ac[GameConstants::BFIELD_SIZE] = {0}; +// std::set occupyable; +// getBattle()->getAccessibilityMap(ac, stack->doubleWide(), stack->attackerOwned, false, occupyable, stack->hasBonusOfType(Bonus::FLYING), stack); +// BattleHex pr[GameConstants::BFIELD_SIZE]; +// int dist[GameConstants::BFIELD_SIZE]; +// getBattle()->makeBFS(stack->position, ac, pr, dist, stack->doubleWide(), stack->attackerOwned, stack->hasBonusOfType(Bonus::FLYING), false); +// +// for(int i=0; i CBattleInfoCallback::battleGetAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos /*= BattleHex::INVALID*/) const +{ + std::set attackedHexes; + RETURN_IF_NOT_BATTLE(attackedHexes); + + AttackableTiles at = getPotentiallyAttackableHexes(attacker, destinationTile, attackerPos); + + BOOST_FOREACH (BattleHex tile, at.hostileCreaturePositions) + { + const CStack * st = battleGetStackByPos(tile, true); + if(st && st->owner != attacker->owner) //only hostile stacks - does it work well with Berserk? + { + attackedHexes.insert(tile); + } + } + BOOST_FOREACH (BattleHex tile, at.friendlyCreaturePositions) + { + if(const CStack * st = battleGetStackByPos(tile, true)) //friendly stacks can also be damaged by Dragon Breath + { + attackedHexes.insert(tile); + } + } + return attackedHexes; +} + +si32 CBattleInfoCallback::battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode) const +{ + switch (mode) + { + case RANDOM_GENIE: + return getRandomBeneficialSpell(stack); //target + break; + case RANDOM_AIMED: + return getRandomCastedSpell(stack); //caster + break; + default: + tlog1 << "Incorrect mode of battleGetRandomSpell (" << mode <<")\n"; + return -1; + } +} + +const CStack* CBattleInfoCallback::battleGetStackByPos(BattleHex pos, bool onlyAlive) const +{ + RETURN_IF_NOT_BATTLE(nullptr); + BOOST_FOREACH(auto s, battleGetAllStacks()) + if(vstd::contains(s->getHexes(), pos) && (!onlyAlive || s->alive())) + return s; + + return nullptr; +} + +void CBattleInfoCallback::battleGetStackQueue(std::vector &out, const int howMany, const int turn /*= 0*/, int lastMoved /*= -1*/) const +{ + RETURN_IF_NOT_BATTLE(); + + //let's define a huge lambda + auto takeStack = [&](std::vector &st) -> const CStack* + { + const CStack *ret = NULL; + unsigned i, //fastest stack + j=0; //fastest stack of the other side + for(i = 0; i < st.size(); i++) + if(st[i]) + break; + + //no stacks left + if(i == st.size()) + return nullptr; + + const CStack *fastest = st[i], *other = NULL; + int bestSpeed = fastest->Speed(turn); + + if(fastest->attackerOwned != lastMoved) + { + ret = fastest; + } + else + { + for(j = i + 1; j < st.size(); j++) + { + if(!st[j]) continue; + if(st[j]->attackerOwned != lastMoved || st[j]->Speed(turn) != bestSpeed) + break; + } + + if(j >= st.size()) + { + ret = fastest; + } + else + { + other = st[j]; + if(other->Speed(turn) != bestSpeed) + ret = fastest; + else + ret = other; + } + } + + assert(ret); + if(ret == fastest) + st[i] = NULL; + else + st[j] = NULL; + + lastMoved = ret->attackerOwned; + return ret; + }; + + //We'll split creatures with remaining movement to 4 buckets + // [0] - turrets/catapult, + // [1] - normal (unmoved) creatures, other war machines, + // [2] - waited cres that had morale, + // [3] - rest of waited cres + std::vector phase[4]; + int toMove = 0; //how many stacks still has move + 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)) + { + out.push_back(active); + if(out.size() == howMany) + return; + } + + BOOST_FOREACH(auto s, battleGetAllStacks()) + { + if((turn <= 0 && !s->willMove()) //we are considering current round and stack won't move + || (turn > 0 && !s->canMove(turn)) //stack won't be able to move in later rounds + || (turn <= 0 && s == active && out.size() && s == out.front())) //it's active stack already added at the beginning of queue + { + continue; + } + + 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(vstd::contains(s->state, EBattleStackState::HAD_MORALE)) + p = 2; + else + p = 3; + } + else if(s->getCreature()->idNumber == 145 || s->getCreature()->idNumber == 149) //catapult and turrets are first + { + p = 0; + } + else + { + p = 1; + } + + phase[p].push_back(s); + toMove++; + } + + for(int i = 0; i < 4; i++) + boost::sort(phase[i], CMP_stack(i, turn > 0 ? turn : 0)); + + for(size_t i = 0; i < phase[0].size() && i < howMany; i++) + out.push_back(phase[0][i]); + + if(out.size() == howMany) + return; + + if(lastMoved == -1) + { + if(active) + { + if(out.size() && out.front() == active) + lastMoved = active->attackerOwned; + else + lastMoved = active->attackerOwned; + } + else + { + lastMoved = 0; + } + } + + int pi = 1; + while(out.size() < howMany) + { + const CStack *hlp = takeStack(phase[pi]); + if(!hlp) + { + pi++; + if(pi > 3) + { + //if(turn != 2) + battleGetStackQueue(out, howMany, turn + 1, lastMoved); + return; + } + } + else + { + out.push_back(hlp); + } + } +} + +void CBattleInfoCallback::battleGetStackCountOutsideHexes(bool *ac) const +{ + RETURN_IF_NOT_BATTLE(); + auto accessibility = getAccesibility(); + + for(int i = 0; i < accessibility.size(); i++) + ac[i] = (accessibility[i] == EAccessibility::ACCESSIBLE); +} + +std::vector CBattleInfoCallback::battleGetAvailableHexes(const CStack * stack, bool addOccupiable, std::vector * attackable) const +{ + std::vector ret; + + RETURN_IF_NOT_BATTLE(ret); + if(!stack->position.isValid()) //turrets + return ret; + + auto reachability = getReachability(stack); + + for (int i = 0; i < GameConstants::BFIELD_SIZE; ++i) + { + // If obstacles or other stacks makes movement impossible, it can't be helped. + if(!reachability.isReachable(i)) + continue; + + if(battleTacticDist() && battleGetTacticsSide() == !stack->attackerOwned) + { + //Stack has to perform tactic-phase movement -> can enter any reachable tile within given range + if(!isInTacticRange(i)) + continue; + } + else + { + //Not tactics phase -> destination must be reachable and within stack range. + if(reachability.distances[i] > stack->Speed(0, true)) + continue; + } + + ret.push_back(i); + + if(addOccupiable && stack->doubleWide()) + { + //If two-hex stack can stand on hex i then obviously it can occupy its second hex from that position + ret.push_back(stack->occupiedHex(i)); + } + } + + + if(attackable) + { + auto meleeAttackable = [&](BattleHex hex) -> bool + { + // Return true if given hex has at least one available neighbour. + // Available hexes are already present in ret vector. + auto availableNeighbor = boost::find_if(ret, [=] (BattleHex availableHex) + { return BattleHex::mutualPosition(hex, availableHex) >= 0; }); + + return availableNeighbor != ret.end(); + }; + + BOOST_FOREACH(const CStack * otherSt, battleAliveStacks(stack->attackerOwned)) + { + if(!otherSt->isValidTarget(false)) + continue; + + std::vector occupied = otherSt->getHexes(); + + if(battleCanShoot(stack, otherSt->position)) + { + attackable->insert(attackable->end(), occupied.begin(), occupied.end()); + continue; + } + + BOOST_FOREACH(BattleHex he, occupied) + { + if(meleeAttackable(he)) + attackable->push_back(he); + } + } + } + + //adding occupiable likely adds duplicates to ret -> clean it up + boost::sort(ret); + ret.erase(boost::unique(ret).end(), ret.end()); + return ret; +} + +bool CBattleInfoCallback::battleCanShoot(const CStack * stack, BattleHex dest) const +{ + RETURN_IF_NOT_BATTLE(false); + + if(battleTacticDist()) //no shooting during tactics + return false; + + const CStack *dst = battleGetStackByPos(dest); + + if(!stack || !dst) + return false; + + if(stack->hasBonusOfType(Bonus::FORGETFULL)) //forgetfulness + return false; + + if(stack->getCreature()->idNumber == 145 && dst) //catapult cannot attack creatures + return false; + + //const CGHeroInstance * stackHero = battleGetOwner(stack); + if(stack->hasBonusOfType(Bonus::SHOOTER)//it's shooter + && stack->owner != dst->owner + && dst->alive() + && (!battleIsStackBlocked(stack) || stack->hasBonusOfType(Bonus::FREE_SHOOTING)) + && stack->shots + ) + return true; + return false; +} + +TDmgRange CBattleInfoCallback::calculateDmgRange(const CStack* attacker, const CStack* defender, bool shooting, + ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg) const +{ + 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 +{ + double additiveBonus = 1.0, multBonus = 1.0, + minDmg = attacker->getMinDamage() * attackerCount, + maxDmg = attacker->getMaxDamage() * attackerCount; + + if(attacker->getCreature()->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; + } + } + + if(attacker->hasBonusOfType(Bonus::SIEGE_WEAPON) && attacker->getCreature()->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 + { + const Bonus *b = attacker->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; + } + + int attackDefenceDifference = 0; + if(attacker->hasBonusOfType(Bonus::GENERAL_ATTACK_REDUCTION)) + { + double multAttackReduction = attacker->valOfBonuses(Bonus::GENERAL_ATTACK_REDUCTION, -1024) / 100.0; + attackDefenceDifference = attacker->Attack() * multAttackReduction; + } + else + { + attackDefenceDifference = attacker->Attack(); + } + + if(attacker->hasBonusOfType(Bonus::ENEMY_DEFENCE_REDUCTION)) + { + double multDefenceReduction = (100 - attacker->valOfBonuses(Bonus::ENEMY_DEFENCE_REDUCTION, -1024)) / 100.0; + attackDefenceDifference -= defender->Defense() * multDefenceReduction; + } + else + { + attackDefenceDifference -= defender->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(); + else //bloodlust handling (etc.) + attackDefenceDifference += attacker->getBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK), Selector::effectRange(Bonus::ONLY_MELEE_FIGHT))->totalValue(); + + + if(attacker->getEffect(55)) //slayer handling + { + std::vector affectedIds; + int spLevel = attacker->getEffect(55)->val; + + for(int g = 0; g < VLC->creh->creatures.size(); ++g) + { + BOOST_FOREACH(const Bonus *b, VLC->creh->creatures[g]->getBonusList()) + { + if ( (b->type == Bonus::KING3 && spLevel >= 3) || //expert + (b->type == Bonus::KING2 && spLevel >= 2) || //adv + + (b->type == Bonus::KING1 && spLevel >= 0) ) //none or basic + + { + affectedIds.push_back(g); + break; + } + } + } + + for(ui32 g=0; ggetCreature()->idNumber == affectedIds[g]) + { + attackDefenceDifference += VLC->spellh->spells[55]->powers[attacker->getEffect(55)->val]; + break; + } + } + } + + //bonus from attack/defense skills + if(attackDefenceDifference < 0) //decreasing dmg + { + const double dec = std::min(0.025 * (-attackDefenceDifference), 0.7); + multBonus *= 1.0 - dec; + } + else //increasing dmg + { + const double inc = std::min(0.05 * attackDefenceDifference, 4.0); + additiveBonus += inc; + } + + + //applying jousting bonus + if( attacker->hasBonusOfType(Bonus::JOUSTING) && !defender->hasBonusOfType(Bonus::CHARGE_IMMUNITY) ) + additiveBonus += charge * 0.05; + + + //handling secondary abilities and artifacts giving premies to them + if(shooting) + additiveBonus += attacker->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::ARCHERY) / 100.0; + else + additiveBonus += attacker->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; + + //handling hate effect + additiveBonus += attacker->valOfBonuses(Bonus::HATE, defender->getCreature()->idNumber) / 100.; + + //luck bonus + if (lucky) + { + additiveBonus += 1.0; + } + + //ballista double dmg + if(ballistaDoubleDmg) + { + additiveBonus += 1.0; + } + + if (deathBlow) //Dread Knight and many WoGified creatures + { + additiveBonus += 1.0; + } + + //handling spell effects + if(!shooting && defender->hasBonusOfType(Bonus::GENERAL_DAMAGE_REDUCTION, 0)) //eg. shield + { + multBonus *= defender->valOfBonuses(Bonus::GENERAL_DAMAGE_REDUCTION, 0) / 100.0; + } + else if(shooting && defender->hasBonusOfType(Bonus::GENERAL_DAMAGE_REDUCTION, 1)) //eg. air shield + { + multBonus *= defender->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); + int curseBlessAdditiveModifier = blessEffects->totalValue() - curseEffects->totalValue(); + double curseMultiplicativePenalty = curseEffects->size() ? (*std::max_element(curseEffects->begin(), curseEffects->end(), &Bonus::compareByAdditionalInfo))->additionalInfo : 0; + + if(curseMultiplicativePenalty) //curse handling (partial, the rest is below) + { + multBonus *= 1.0 - curseMultiplicativePenalty/100; + } + + auto isAdvancedAirShield = [](const Bonus *bonus) + { + return bonus->source == Bonus::SPELL_EFFECT + && bonus->sid == Spells::AIR_SHIELD + && bonus->val >= SecSkillLevel::ADVANCED; + }; + + //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); + + if (shooting) + { + if (distPenalty || defender->hasBonus(isAdvancedAirShield)) + { + multBonus *= 0.5; + } + if (obstaclePenalty) + { + multBonus *= 0.5; //cumulative + } + } + if (!shooting && attacker->hasBonusOfType(Bonus::SHOOTER) && !attacker->hasBonusOfType(Bonus::NO_MELEE_PENALTY)) + { + multBonus *= 0.5; + } + + minDmg *= additiveBonus * multBonus; + maxDmg *= additiveBonus * multBonus; + + TDmgRange returnedVal; + + if(curseEffects->size()) //curse handling (rest) + { + minDmg += curseBlessAdditiveModifier; + returnedVal = std::make_pair(int(minDmg), int(minDmg)); + } + else if(blessEffects->size()) //bless handling + { + maxDmg += curseBlessAdditiveModifier; + returnedVal = std::make_pair(int(maxDmg), int(maxDmg)); + } + else + { + returnedVal = std::make_pair(int(minDmg), int(maxDmg)); + } + + //damage cannot be less than 1 + vstd::amax(returnedVal.first, 1); + vstd::amax(returnedVal.second, 1); + + return returnedVal; +} + +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 ui8 mySide = !attacker->attackerOwned; + + TDmgRange ret = calculateDmgRange(attacker, defender, shooting, 0, false, false, false); + + if(retaliationDmg) + { + if(shooting) + { + retaliationDmg->first = retaliationDmg->second = 0; + } + else + { + ui32 TDmgRange::* pairElems[] = {&TDmgRange::first, &TDmgRange::second}; + for (int i=0; i<2; ++i) + { + BattleStackAttacked bsa; + bsa.damageAmount = ret.*pairElems[i]; + retaliationDmg->*pairElems[!i] = calculateDmgRange(defender, attacker, bsa.newAmount, false, 0, false, false, false).*pairElems[!i]; + } + } + } + + return ret; +} + + +shared_ptr CBattleInfoCallback::battleGetObstacleOnPos(BattleHex tile, bool onlyBlocking /*= true*/) const +{ + RETURN_IF_NOT_BATTLE(shared_ptr()); + + BOOST_FOREACH(auto &obs, battleGetAllObstacles()) + { + if(vstd::contains(obs->getBlockedTiles(), tile) + || (!onlyBlocking && vstd::contains(obs->getAffectedTiles(), tile))) + { + return obs; + } + } + + return shared_ptr(); +} + +AccessibilityInfo CBattleInfoCallback::getAccesibility() const +{ + AccessibilityInfo ret; + ret.fill(EAccessibility::ACCESSIBLE); + + //removing accessibility for side columns of hexes + for(int y = 0; y < GameConstants::BFIELD_HEIGHT; y++) + { + ret[BattleHex(GameConstants::BFIELD_WIDTH - 1, y)] = EAccessibility::SIDE_COLUMN; + ret[BattleHex(0, y)] = EAccessibility::SIDE_COLUMN; + } + + //tiles occupied by standing stacks + BOOST_FOREACH(auto stack, battleAliveStacks()) + { + BOOST_FOREACH(auto hex, stack->getHexes()) + if(hex.isAvailable()) //towers can have <0 pos; we don't also want to overwrite side columns + ret[hex] = EAccessibility::ALIVE_STACK; + } + + //obstacles + BOOST_FOREACH(const auto &obst, battleGetAllObstacles()) + { + BOOST_FOREACH(auto hex, obst->getBlockedTiles()) + ret[hex] = EAccessibility::OBSTACLE; + } + + //walls + if(battleGetSiegeLevel() > 0) + { + static const int permanentlyLocked[] = {12, 45, 78, 112, 147, 165}; + BOOST_FOREACH(auto hex, permanentlyLocked) + ret[hex] = EAccessibility::UNAVAILABLE; + + //TODO likely duplicated logic + static const std::pair lockedIfNotDestroyed[] = //(which part of wall, which hex is blocked if this part of wall is not destroyed + {std::make_pair(2, BattleHex(182)), std::make_pair(3, BattleHex(130)), + std::make_pair(4, BattleHex(62)), std::make_pair(5, BattleHex(29))}; + + for(int b=0; b quicksands = getStoppers(params.perspective); + //const bool twoHexCreature = params.doubleWide; + + + std::queue hexq; //bfs queue + + //first element + hexq.push(params.startPosition); + ret.distances[params.startPosition] = 0; + + while(!hexq.empty()) //bfs loop + { + const BattleHex curHex = hexq.front(); + hexq.pop(); + + //walking stack can't step past the quicksands + //TODO what if second hex of two-hex creature enters quicksand + if(curHex != params.startPosition && vstd::contains(quicksands, curHex)) + continue; + + const int costToNeighbour = ret.distances[curHex] + 1; + BOOST_FOREACH(BattleHex neighbour, curHex.neighbouringTiles()) + { + const bool accessible = accessibility.accessible(neighbour, params.doubleWide, params.attackerOwned); + const int costFoundSoFar = ret.distances[neighbour]; + + if(accessible && costToNeighbour < costFoundSoFar) + { + hexq.push(neighbour); + ret.distances[neighbour] = costToNeighbour; + ret.predecessors[neighbour] = curHex; + } + } + } + + return ret; +} + +ReachabilityInfo CBattleInfoCallback::makeBFS(const CStack *stack) const +{ + return makeBFS(getAccesibility(), ReachabilityInfo::Parameters(stack)); +} + +std::set CBattleInfoCallback::getStoppers(BattlePerspective::BattlePerspective whichSidePerspective) const +{ + std::set ret; + RETURN_IF_NOT_BATTLE(ret); + + BOOST_FOREACH(auto &oi, battleGetAllObstacles(whichSidePerspective)) + { + if(battleIsObstacleVisibleForSide(*oi, whichSidePerspective)) + { + range::copy(oi->getStoppingTile(), vstd::set_inserter(ret)); + } + } + + return ret; +} + +std::pair CBattleInfoCallback::getNearestStack(const CStack * closest, boost::logic::tribool attackerOwned) const +{ + auto reachability = getReachability(closest); + + // I hate std::pairs with their undescriptive member names first / second + struct DistStack + { + int distanceToPred; + const CStack *stack; + }; + + std::vector stackPairs; //pairs <, stack> + for(int g=0; gID == closest->ID) //if there is not stack or we are the closest one + continue; + + if(boost::logic::indeterminate(attackerOwned) || atG->attackerOwned == attackerOwned) + { + if(reachability.isReachable(g)) + continue; + + DistStack hlp = {reachability.distances[reachability.predecessors[g]], atG}; + stackPairs.push_back(hlp); + } + } + + if(stackPairs.size() > 0) + { + auto comparator = [](DistStack lhs, DistStack rhs) { return lhs.distanceToPred < rhs.distanceToPred; }; + auto minimal = boost::min_element(stackPairs, comparator); + return std::make_pair(minimal->stack, reachability.predecessors[minimal->stack->position]); + } + + return std::make_pair(NULL, BattleHex::INVALID); +} + +si8 CBattleInfoCallback::battleGetTacticDist() const +{ + RETURN_IF_NOT_BATTLE(0); + + //TODO get rid of this method + if(battleDoWeKnowAbout(battleGetTacticsSide())) + return battleTacticDist(); + + return 0; +} + +bool CBattleInfoCallback::isInTacticRange(BattleHex dest) const +{ + RETURN_IF_NOT_BATTLE(false); + auto side = battleGetTacticsSide(); + auto dist = battleGetTacticDist(); + + return ((!side && dest.getX() > 0 && dest.getX() <= dist) + || (side && dest.getX() < GameConstants::BFIELD_WIDTH - 1 && dest.getX() >= GameConstants::BFIELD_WIDTH - dist - 1)); +} + +ReachabilityInfo CBattleInfoCallback::getReachability(const CStack *stack) const +{ + ReachabilityInfo::Parameters params(stack); + + if(!battleDoWeKnowAbout(!stack->attackerOwned)) + { + //Stack is held by enemy, we can't use his perspective to check for reachability. + tlog3 << "Falling back to our perspective for reachability lookup for " << stack->nodeName() << std::endl; + params.perspective = battleGetMySide(); + } + + return getReachability(params); +} + +ReachabilityInfo CBattleInfoCallback::getReachability(const ReachabilityInfo::Parameters ¶ms) const +{ + if(params.flying) + return getFlyingReachability(params); + else + return makeBFS(getAccesibility(), params); +} + +ReachabilityInfo CBattleInfoCallback::getFlyingReachability(const ReachabilityInfo::Parameters params) const +{ + ReachabilityInfo ret; + ret.accessibility = getAccesibility(); + + for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) + { + if(ret.accessibility.accessible(i, params.doubleWide, params.attackerOwned)) + { + ret.predecessors[i] = params.startPosition; + ret.distances[i] = BattleHex::getDistance(params.startPosition, i); + } + } + + return ret; +} + +AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos) const +{ + AttackableTiles at; + RETURN_IF_NOT_BATTLE(at); + + const int WN = GameConstants::BFIELD_WIDTH; + ui16 hex = (attackerPos != BattleHex::INVALID) ? attackerPos.hex : attacker->position.hex; //real or hypothetical (cursor) position + if (attacker->hasBonusOfType(Bonus::ATTACKS_ALL_ADJACENT)) + { + boost::copy(attacker->getSurroundingHexes(attackerPos), vstd::set_inserter(at.hostileCreaturePositions)); +// BOOST_FOREACH (BattleHex tile, attacker->getSurroundingHexes(attackerPos)) +// at.hostileCreaturePositions.insert(tile); + } + if (attacker->hasBonusOfType(Bonus::THREE_HEADED_ATTACK)) + { + std::vector hexes = attacker->getSurroundingHexes(attackerPos); + BOOST_FOREACH (BattleHex tile, hexes) + { + if ((BattleHex::mutualPosition(tile, destinationTile) > -1 && BattleHex::mutualPosition(tile, hex) > -1) //adjacent both to attacker's head and attacked tile + || tile == destinationTile) //or simply attacked directly + { + const CStack * st = battleGetStackByPos(tile, true); + if(st && st->owner != attacker->owner) //only hostile stacks - does it work well with Berserk? + { + at.hostileCreaturePositions.insert(tile); + } + } + } + } + if (attacker->hasBonusOfType(Bonus::TWO_HEX_ATTACK_BREATH)) + { + std::vector hexes; //only one, in fact + int pseudoVector = destinationTile.hex - hex; + switch (pseudoVector) + { + case 1: + case -1: + BattleHex::checkAndPush(destinationTile.hex + pseudoVector, hexes); + break; + case WN: //17 + case WN + 1: //18 + case -WN: //-17 + case -WN + 1: //-16 + BattleHex::checkAndPush(destinationTile.hex + pseudoVector + ((hex/WN)%2 ? 1 : -1 ), hexes); + break; + case WN-1: //16 + case -WN-1: //-18 + BattleHex::checkAndPush(destinationTile.hex + pseudoVector + ((hex/WN)%2 ? 1 : 0), hexes); + break; + } + BOOST_FOREACH (BattleHex tile, hexes) + { + //friendly stacks can also be damaged by Dragon Breath + if(const CStack * st = battleGetStackByPos(tile, true)) + at.friendlyCreaturePositions.insert(tile); + } + } + + return at; +} + +std::set CBattleInfoCallback::getAttackedCreatures(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos /*= BattleHex::INVALID*/) const +{ + std::set attackedCres; + RETURN_IF_NOT_BATTLE(attackedCres); + + AttackableTiles at = getPotentiallyAttackableHexes(attacker, destinationTile, attackerPos); + BOOST_FOREACH (BattleHex tile, at.hostileCreaturePositions) //all around & three-headed attack + { + const CStack * st = battleGetStackByPos(tile, true); + if(st && st->owner != attacker->owner) //only hostile stacks - does it work well with Berserk? + { + attackedCres.insert(st); + } + } + BOOST_FOREACH (BattleHex tile, at.friendlyCreaturePositions) + { + const CStack * st = battleGetStackByPos(tile, true); + if(st) //friendly stacks can also be damaged by Dragon Breath + { + attackedCres.insert(st); + } + } + return attackedCres; +} + +ReachabilityInfo::TDistances CBattleInfoCallback::battleGetDistances(const CStack * stack, BattleHex hex /*= BattleHex::INVALID*/, BattleHex * predecessors /*= NULL*/) const +{ + ReachabilityInfo::TDistances ret; + ret.fill(-1); + RETURN_IF_NOT_BATTLE(ret); + + ReachabilityInfo::Parameters params(stack); + params.startPosition = hex.isValid() ? hex : stack->position; + auto reachability = getReachability(params); + + boost::copy(reachability.distances, ret.begin()); + + if(predecessors) + for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) + predecessors[i] = reachability.predecessors[i]; + + return ret; +} + +si8 CBattleInfoCallback::battleHasDistancePenalty(const CStack * stack, BattleHex destHex) const +{ + RETURN_IF_NOT_BATTLE(false); + if(stack->hasBonusOfType(Bonus::NO_DISTANCE_PENALTY)) + return false; + + if(BattleHex::getDistance(stack->position, destHex) <= 10) + return false; + + const CStack * dstStack = battleGetStackByPos(destHex, false); + if(dstStack) + { + //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) + return false; + } + + return true; +} + +EWallParts::EWallParts CBattleInfoCallback::battleHexToWallPart(BattleHex hex) const +{ + RETURN_IF_NOT_BATTLE(EWallParts::INVALID); + return hexToWallPart(hex); +} + +ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const +{ + RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID); + + + // Get stack at destination hex -> subject of our spell. + const CStack * subject = battleGetStackByPos(dest, !spell->isRisingSpell()); //only alive if not rising spell + + if(subject) + { + if (spell->isPositive() && subject->hasBonusOfType(Bonus::RECEPTIVE)) //accept all positive spells + return ESpellCastProblem::OK; + + switch (spell->id) //TODO: more general logic for new spells? + { + case Spells::DESTROY_UNDEAD: + if (!subject->hasBonusOfType(Bonus::UNDEAD)) + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + break; + case Spells::DEATH_RIPPLE: + if (subject->hasBonusOfType(Bonus::SIEGE_WEAPON)) + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; //don't break here - undeads and war machines are immune, non-living are not + case Spells::BLESS: + case Spells::CURSE: //undeads are immune to bless & curse + if (subject->hasBonusOfType(Bonus::UNDEAD)) + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + break; + case Spells::HASTE: + case Spells::SLOW: + case Spells::TELEPORT: + case Spells::CLONE: + if (subject->hasBonusOfType(Bonus::SIEGE_WEAPON)) + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; //war machines are immune to some spells than involve movement + if (spell->id == Spells::CLONE && caster) //TODO: how about stacks casting Clone? + { + if (vstd::contains(subject->state, EBattleStackState::CLONED)) + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; //can't clone already cloned creature + int maxLevel = (std::max(caster->getSpellSchoolLevel(spell), (ui8)1) + 4); + int creLevel = subject->getCreature()->level; + if (maxLevel < creLevel) //tier 1-5 for basic, 1-6 for advanced, 1-7 for expert + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + } + break; + case Spells::FORGETFULNESS: + if (!subject->hasBonusOfType(Bonus::SHOOTER)) + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + break; + case Spells::DISPEL_HELPFUL_SPELLS: + { + TBonusListPtr spellBon = subject->getSpellBonuses(); + bool hasPositiveSpell = false; + BOOST_FOREACH(const Bonus * b, *spellBon) + { + if(VLC->spellh->spells[b->sid]->isPositive()) + { + hasPositiveSpell = true; + break; + } + } + if(!hasPositiveSpell) + { + return ESpellCastProblem::NO_SPELLS_TO_DISPEL; + } + } + break; + } + + const bool damageSpell = spell->isDamageSpell(); + auto battleTestElementalImmunity = [&](Bonus::BonusType element) -> bool //helper for battleisImmune + { + if (!spell->isPositive()) //negative or indifferent + { + if ((damageSpell && subject->hasBonusOfType(element, 2)) || subject->hasBonusOfType(element, 1)) + return true; + } + else if (spell->isPositive()) //positive + { + if (subject->hasBonusOfType(element, 0)) //must be immune to all spells + return true; + } + return false; + }; + + + if (damageSpell && subject->hasBonusOfType(Bonus::DIRECT_DAMAGE_IMMUNITY)) + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + + if (spell->fire) + { + if (battleTestElementalImmunity(Bonus::FIRE_IMMUNITY)) + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + } + if (spell->water) + { + if (battleTestElementalImmunity(Bonus::WATER_IMMUNITY)) + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + } + if (spell->earth) + { + if (battleTestElementalImmunity(Bonus::EARTH_IMMUNITY)) + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + } + if (spell->air) + { + if (battleTestElementalImmunity(Bonus::AIR_IMMUNITY)) + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + } + if (vstd::contains(VLC->spellh->mindSpells, spell->id)) + { + if (subject->hasBonusOfType(Bonus::MIND_IMMUNITY)) + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + } + + if (spell->isRisingSpell()) + { + if (subject->count >= subject->baseAmount) //TODO: calculate potential hp raised + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + } + + TBonusListPtr immunities = subject->getBonuses(Selector::type(Bonus::LEVEL_SPELL_IMMUNITY)); + if(subject->hasBonusOfType(Bonus::NEGATE_ALL_NATURAL_IMMUNITIES)) + { + immunities->remove_if([](const Bonus* b){ return b->source == Bonus::CREATURE_ABILITY; }); + } + + if(subject->hasBonusOfType(Bonus::SPELL_IMMUNITY, spell->id) + || ( immunities->size() > 0 && immunities->totalValue() >= spell->level && spell->level)) + { + return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; + } + } + else //no target stack on this tile + { + if(spell->getTargetType() == CSpell::CREATURE + || (spell->getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE + && mode == ECastingMode::HERO_CASTING + && caster + && caster->getSpellSchoolLevel(spell) < SecSkillLevel::EXPERT)) + { + return ESpellCastProblem::WRONG_SPELL_TARGET; + } + } + + return ESpellCastProblem::OK; +} + +ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell( int player, const CSpell * spell, ECastingMode::ECastingMode mode ) const +{ + RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID); + const ui8 side = playerToSide(player); + if(!battleDoWeKnowAbout(side)) + ESpellCastProblem::INVALID; + + ESpellCastProblem::ESpellCastProblem genProblem = battleCanCastSpell(player, mode); + if(genProblem != ESpellCastProblem::OK) + return genProblem; + + //Casting hero, set only if he is an actual caster. + const CGHeroInstance *castingHero = mode == ECastingMode::HERO_CASTING + ? battleGetFightingHero(side) + : nullptr; + + + switch(mode) + { + case ECastingMode::HERO_CASTING: + { + assert(castingHero); + if(!castingHero->canCastThisSpell(spell)) + return ESpellCastProblem::HERO_DOESNT_KNOW_SPELL; + if(castingHero->mana < battleGetSpellCost(spell, castingHero)) //not enough mana + return ESpellCastProblem::NOT_ENOUGH_MANA; + } + break; + } + + + if(spell->id < 10) //it's adventure spell (not combat)) + return ESpellCastProblem::ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL; + + //TODO? + //if(NBonus::hasOfType(heroes[1-cside], Bonus::SPELL_IMMUNITY, spell->id)) //non - casting hero provides immunity for this spell + // return ESpellCastProblem::SECOND_HEROS_SPELL_IMMUNITY; + if(spell->isNegative()) + { + bool allEnemiesImmune = true; + BOOST_FOREACH(auto enemyStack, battleAliveStacks(!side)) + { + if(!enemyStack->hasBonusOfType(Bonus::SPELL_IMMUNITY, spell->id)) + { + allEnemiesImmune = false; + break; + } + } + + if(allEnemiesImmune) + return ESpellCastProblem::NO_APPROPRIATE_TARGET; + } + + + if(battleMaxSpellLevel() < spell->level) //effect like Recanter's Cloak or Orb of Inhibition + return ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED; + + //IDs of summon elemental spells (fire, earth, water, air) + int spellIDs[] = { Spells::SUMMON_FIRE_ELEMENTAL, Spells::SUMMON_EARTH_ELEMENTAL, + Spells::SUMMON_WATER_ELEMENTAL, Spells::SUMMON_AIR_ELEMENTAL }; + //(fire, earth, water, air) elementals + int creIDs[] = {114, 113, 115, 112}; + + int arpos = vstd::find_pos(spellIDs, spell->id); + if(arpos < ARRAY_COUNT(spellIDs)) + { + //check if there are summoned elementals of other type + BOOST_FOREACH( const CStack * st, battleAliveStacks()) + if(vstd::contains(st->state, EBattleStackState::SUMMONED) && st->getCreature()->idNumber == creIDs[arpos]) + return ESpellCastProblem::ANOTHER_ELEMENTAL_SUMMONED; + } + + //checking if there exists an appropriate target + switch(spell->getTargetType()) + { + case CSpell::CREATURE: + case CSpell::CREATURE_EXPERT_MASSIVE: + if(mode == ECastingMode::HERO_CASTING) + { + const CGHeroInstance * caster = battleGetFightingHero(player); + bool targetExists = false; + 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) + { + targetExists = true; + break; + } + } + break; + case CSpell::NEUTRAL: + if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK) + { + targetExists = true; + break; + } + break; + case CSpell::NEGATIVE: + if(stack->owner != caster->getOwner()) + { + if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK) + { + targetExists = true; + break; + } + } + break; + } + } + if(!targetExists) + { + return ESpellCastProblem::NO_APPROPRIATE_TARGET; + } + } + break; + case CSpell::OBSTACLE: + break; + } + + return ESpellCastProblem::OK; +} + +ui32 CBattleInfoCallback::battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const +{ + RETURN_IF_NOT_BATTLE(-1); + //TODO should be replaced using bonus system facilities (propagation onto battle node) + + ui32 ret = caster->getSpellCost(sp); + + //checking for friendly stacks reducing cost of the spell and + //enemy stacks increasing it + si32 manaReduction = 0; + si32 manaIncrease = 0; + + + BOOST_FOREACH(auto stack, battleAliveStacks()) + { + if(stack->owner == caster->tempOwner && stack->hasBonusOfType(Bonus::CHANGES_SPELL_COST_FOR_ALLY) ) + { + vstd::amax(manaReduction, stack->valOfBonuses(Bonus::CHANGES_SPELL_COST_FOR_ALLY)); + } + if( stack->owner != caster->tempOwner && stack->hasBonusOfType(Bonus::CHANGES_SPELL_COST_FOR_ENEMY) ) + { + vstd::amax(manaIncrease, stack->valOfBonuses(Bonus::CHANGES_SPELL_COST_FOR_ENEMY)); + } + } + + return ret - manaReduction + manaIncrease; +} + +ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpellHere( int player, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest ) const +{ + RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID); + ESpellCastProblem::ESpellCastProblem moreGeneralProblem = battleCanCastThisSpell(player, spell, mode); + if(moreGeneralProblem != ESpellCastProblem::OK) + return moreGeneralProblem; + + if(spell->getTargetType() == CSpell::OBSTACLE) + { + //isObstacleOnTile(dest) + // + // + //TODO + //assert that it's remove obstacle + //rules whether we can remove spell-created obstacle + return ESpellCastProblem::NO_APPROPRIATE_TARGET; + } + + + //get dead stack if we cast resurrection or animate dead + const CStack *deadStack = getStackIf([dest](const CStack *s) { return !s->alive() && s->position == dest; }); + const CStack *aliveStack = getStackIf([dest](const CStack *s) { return s->alive() && s->position == dest;}); + + + if(spell->isRisingSpell()) + { + if(!deadStack && !aliveStack) + return ESpellCastProblem::NO_APPROPRIATE_TARGET; + if(spell->id == Spells::ANIMATE_DEAD && deadStack && !deadStack->hasBonusOfType(Bonus::UNDEAD)) + return ESpellCastProblem::NO_APPROPRIATE_TARGET; + if(deadStack && deadStack->owner != player) //you can resurrect only your own stacks //FIXME: it includes alive stacks as well + return ESpellCastProblem::NO_APPROPRIATE_TARGET; + } + else if(spell->getTargetType() == CSpell::CREATURE || spell->getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE) + { + if(!aliveStack) + return ESpellCastProblem::NO_APPROPRIATE_TARGET; + if(spell->isNegative() && aliveStack->owner == player) + return ESpellCastProblem::NO_APPROPRIATE_TARGET; + if(spell->isPositive() && aliveStack->owner != player) + return ESpellCastProblem::NO_APPROPRIATE_TARGET; + } + + + if (mode == ECastingMode::HERO_CASTING) + return battleIsImmune(battleGetFightingHero(playerToSide(player)), spell, mode, dest); + else + return battleIsImmune(NULL, spell, mode, dest); +} + +const CStack * CBattleInfoCallback::getStackIf(boost::function pred) const +{ + RETURN_IF_NOT_BATTLE(nullptr); + auto stacks = battleGetAllStacks(); + auto stackItr = range::find_if(stacks, pred); + return stackItr == stacks.end() + ? NULL + : *stackItr; +} + +bool CBattleInfoCallback::battleIsStackBlocked(const CStack * stack) const +{ + RETURN_IF_NOT_BATTLE(false); + + if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON)) //siege weapons cannot be blocked + return false; + + BOOST_FOREACH(const CStack * s, batteAdjacentCreatures(stack)) + { + if (s->owner != stack->owner) //blocked by enemy stack + return true; + } + return false; +} + +std::set CBattleInfoCallback:: batteAdjacentCreatures(const CStack * stack) const +{ + std::set stacks; + RETURN_IF_NOT_BATTLE(stacks); + + BOOST_FOREACH (BattleHex hex, stack->getSurroundingHexes()) + if(const CStack *neighbour = battleGetStackByPos(hex, true)) + stacks.insert(neighbour); + + return stacks; +} + +TSpell CBattleInfoCallback::getRandomBeneficialSpell(const CStack * subject) const +{ + RETURN_IF_NOT_BATTLE(-1); + std::vector possibleSpells; + + BOOST_FOREACH(const CSpell *spell, VLC->spellh->spells) + { + if (spell->isPositive()) //only positive + { + if (subject->hasBonusFrom(Bonus::SPELL_EFFECT, spell->id) + || battleCanCastThisSpellHere(subject->owner, spell, ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK) + continue; + + switch (spell->id) + { + case Spells::SHIELD: + case Spells::FIRE_SHIELD: // not if all enemy units are shooters + { + auto walker = getStackIf([&](const CStack *stack) //look for enemy, non-shooting stack + { + return stack->owner != subject->owner && !stack->shots; + }); + + if (!walker) + continue; + } + break; + case Spells::AIR_SHIELD: //only against active shooters + { + + auto shooter = getStackIf([&](const CStack *stack) //look for enemy, non-shooting stack + { + return stack->owner != subject->owner && stack->hasBonusOfType(Bonus::SHOOTER) && stack->shots; + }); + if (!shooter) + continue; + } + break; + case Spells::ANTI_MAGIC: + case Spells::MAGIC_MIRROR: + { + if (!battleHasHero(subject->attackerOwned)) //only if there is enemy hero + continue; + } + break; + case Spells::CURE: //only damaged units - what about affected by curse? + { + if (subject->firstHPleft >= subject->MaxHealth()) + continue; + } + break; + case Spells::BLOODLUST: + { + if (subject->shots) //if can shoot - only if enemy uits are adjacent + continue; + } + break; + case Spells::PRECISION: + { + if (!(subject->hasBonusOfType(Bonus::SHOOTER) && subject->shots)) + continue; + } + break; + case Spells::SLAYER://only if monsters are present + { + auto kingMonster = getStackIf([&](const CStack *stack) //look for enemy, non-shooting stack + { + return stack->owner != subject->owner + && (stack->hasBonus(Selector::type(Bonus::KING1) || Selector::type(Bonus::KING2) || Selector::type(Bonus::KING3))); + }); + + if (!kingMonster) + continue; + } + break; + case Spells::CLONE: //not allowed + continue; + break; + } + possibleSpells.push_back(spell->id); + } + } + + if (possibleSpells.size()) + return possibleSpells[rand() % possibleSpells.size()]; + else + return -1; +} + +TSpell CBattleInfoCallback::getRandomCastedSpell(const CStack * caster) const +{ + RETURN_IF_NOT_BATTLE(-1); + + TBonusListPtr bl = caster->getBonuses(Selector::type(Bonus::SPELLCASTER)); + if (!bl->size()) + return -1; + int totalWeight = 0; + BOOST_FOREACH(Bonus * b, *bl) + { + totalWeight += std::max(b->additionalInfo, 1); //minimal chance to cast is 1 + } + int randomPos = rand() % totalWeight; + BOOST_FOREACH(Bonus * b, *bl) + { + randomPos -= std::max(b->additionalInfo, 1); + if(randomPos < 0) + { + return b->subtype; + } + } + + return -1; +} + +int CBattleInfoCallback::battleGetSurrenderCost(int Player) const +{ + RETURN_IF_NOT_BATTLE(-3); + if(!battleCanSurrender(Player)) + return -1; + + int ret = 0; + double discount = 0; + BOOST_FOREACH(const CStack *s, battleAliveStacks(playerToSide(Player))) + if(s->base) //we pay for our stack that comes from our army slots - condition eliminates summoned cres and war machines + ret += s->getCreature()->cost[Res::GOLD] * s->count; + + if(const CGHeroInstance *h = battleGetFightingHero(playerToSide(Player))) + discount += h->valOfBonuses(Bonus::SURRENDER_DISCOUNT); + + ret *= (100.0 - discount) / 100.0; + vstd::amax(ret, 0); //no negative costs for >100% discounts (impossible in original H3 mechanics, but some day...) + return ret; +} + +si8 CBattleInfoCallback::battleMaxSpellLevel() const +{ + const CBonusSystemNode *node = NULL; + if(const CGHeroInstance *h = battleGetFightingHero(battleGetMySide())) + node = h; + //TODO else use battle node + if(!node) + return GameConstants::SPELL_LEVELS; + + //We can't "just get value" - it'd be 0 if there are bonuses (and all would be blocked) + auto b = node->getBonuses(Selector::type(Bonus::BLOCK_MAGIC_ABOVE)); + if(b->size()) + return b->totalValue(); + + return GameConstants::SPELL_LEVELS; +} +bool AccessibilityInfo::accessible(BattleHex tile, const CStack *stack) const +{ + return accessible(tile, stack->doubleWide(), stack->attackerOwned); +} + +bool AccessibilityInfo::accessible(BattleHex tile, bool doubleWide, bool attackerOwned) const +{ + // All hexes that stack would cover if standing on tile have to be accessible. + BOOST_FOREACH(auto hex, CStack::getHexes(tile, doubleWide, attackerOwned)) + if(!hex.isValid() || at(hex) != EAccessibility::ACCESSIBLE) + return false; + + return true; +} + +bool AccessibilityInfo::occupiable(const CStack *stack, BattleHex tile) const +{ + //obviously, we can occupy tile by standing on it + if(accessible(tile, stack)) + return true; + + if(stack->doubleWide()) + { + //Check the tile next to -> if stack stands there, it'll also occupy considered hex + const BattleHex anotherTile = tile + (stack->attackerOwned ? BattleHex::RIGHT : BattleHex::LEFT); + if(accessible(anotherTile, stack)) + return true; + } + + return false; +} + +ReachabilityInfo::Parameters::Parameters() +{ + stack = nullptr; + perspective = BattlePerspective::ALL_KNOWING; + attackerOwned = doubleWide = flying = false; +} + +ReachabilityInfo::Parameters::Parameters(const CStack *Stack) +{ + stack = Stack; + perspective = (BattlePerspective::BattlePerspective)(!Stack->attackerOwned); + startPosition = Stack->position; + doubleWide = stack->doubleWide(); + attackerOwned = stack->attackerOwned; + flying = stack->hasBonusOfType(Bonus::FLYING); +} + +ESpellCastProblem::ESpellCastProblem CPlayerBattleCallback::battleCanCastThisSpell(const CSpell * spell) const +{ + RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID); + return CBattleInfoCallback::battleCanCastThisSpell(player, spell, ECastingMode::HERO_CASTING); +} + +ESpellCastProblem::ESpellCastProblem CPlayerBattleCallback::battleCanCastThisSpell(const CSpell * spell, BattleHex destination) const +{ + RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID); + return battleCanCastThisSpellHere(player, spell, ECastingMode::HERO_CASTING, destination); +} + +ESpellCastProblem::ESpellCastProblem CPlayerBattleCallback::battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination) const +{ + RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID); + return battleCanCastThisSpellHere(player, spell, ECastingMode::CREATURE_ACTIVE_CASTING, destination); +} + +bool CPlayerBattleCallback::battleCanFlee() const +{ + RETURN_IF_NOT_BATTLE(false); + return CBattleInfoEssentials::battleCanFlee(player); +} + +TStacks CPlayerBattleCallback::battleGetStacks(EStackOwnership whose /*= MINE_AND_ENEMY*/, bool onlyAlive /*= true*/) const +{ + TStacks ret; + RETURN_IF_NOT_BATTLE(ret); + vstd::copy_if(battleGetAllStacks(), std::back_inserter(ret), [=](const CStack *s) -> bool + { + const bool ownerMatches = (whose == MINE_AND_ENEMY) + || (whose == ONLY_MINE && s->owner == player) + || (whose == ONLY_ENEMY && s->owner != player); + const bool alivenessMatches = s->alive() || !onlyAlive; + return ownerMatches && alivenessMatches; + }); + + return ret; +} + +int CPlayerBattleCallback::battleGetSurrenderCost() const +{ + RETURN_IF_NOT_BATTLE(-3) + return CBattleInfoCallback::battleGetSurrenderCost(player); +} + +bool CPlayerBattleCallback::battleCanCastSpell(ESpellCastProblem::ESpellCastProblem *outProblem /*= nullptr*/) const +{ + RETURN_IF_NOT_BATTLE(false); + auto problem = CBattleInfoCallback::battleCanCastSpell(player, ECastingMode::HERO_CASTING); + if(outProblem) + *outProblem = problem; + + return problem == ESpellCastProblem::OK; +} diff --git a/lib/CBattleCallback.h b/lib/CBattleCallback.h new file mode 100644 index 000000000..6460f25da --- /dev/null +++ b/lib/CBattleCallback.h @@ -0,0 +1,269 @@ +#pragma once +#include "BattleHex.h" + +class CGameState; +class CGTownInstance; +class CGHeroInstance; +class CStack; +class CSpell; +struct BattleInfo; +struct CObstacleInstance; + +namespace boost +{class shared_mutex;} + +namespace BattleSide +{ + enum {ATTACKER = 0, DEFENDER = 1}; +} + +typedef std::vector TStacks; + +class CBattleInfoEssentials; + +//Basic class for various callbacks (interfaces called by players to get info about game and so forth) +class DLL_LINKAGE CCallbackBase +{ + const BattleInfo *battle; //battle to which the player is engaged, NULL if none or not applicable + + const BattleInfo * getBattle() const + { + return battle; + } + +protected: + CGameState *gs; + int player; // -1 gives access to all information, otherwise callback provides only information "visible" for player + + CCallbackBase(CGameState *GS, int Player) + : gs(GS), player(Player) + {} + CCallbackBase() + : gs(NULL), player(-1) + {} + + void setBattle(const BattleInfo *B); + bool duringBattle() const; + +public: + boost::shared_mutex &getGsMutex(); //just return a reference to mutex, does not lock nor anything + + friend class CBattleInfoEssentials; +}; + + +struct DLL_LINKAGE AttackableTiles +{ + std::set hostileCreaturePositions; + std::set friendlyCreaturePositions; //for Dragon Breath + template void serialize(Handler &h, const int version) + { + h & hostileCreaturePositions & friendlyCreaturePositions; + } +}; + +//Accessibility is property of hex in battle. It doesn't depend on stack, side's perspective and so on. +namespace EAccessibility +{ + enum EAccessibility + { + ACCESSIBLE, + ALIVE_STACK, + OBSTACLE, + DESTRUCTIBLE_WALL, + GATE, //sieges -> gate opens only for defender stacks + UNAVAILABLE, //indestructible wall parts, special battlefields (like boat-to-boat) + SIDE_COLUMN //used for first and last columns of hexes that are unavailable but wat machines can stand there + }; +} + +typedef std::array TAccessibilityArray; + +struct DLL_LINKAGE AccessibilityInfo : TAccessibilityArray +{ + bool occupiable(const CStack *stack, BattleHex tile) const; + bool accessible(BattleHex tile, const CStack *stack) const; //checks for both tiles if stack is double wide + bool accessible(BattleHex tile, bool doubleWide, bool attackerOwned) const; //checks for both tiles if stack is double wide +}; + +namespace BattlePerspective +{ + enum BattlePerspective + { + INVALID = -2, + ALL_KNOWING = -1, + LEFT_SIDE, + RIGHT_SIDE + }; +} + +// Reachability info is result of BFS calculation. It's dependant on stack (it's owner, whether it's flying), +// startPosition and perpective. +struct DLL_LINKAGE ReachabilityInfo +{ + typedef std::array TDistances; + typedef std::array TPredecessors; + + static const int INFINITE_DIST = 1000000; + + struct DLL_LINKAGE Parameters + { + const CStack *stack; //stack for which calculation is mage => not required (kept for debugging mostly), following variables are enough + + bool attackerOwned; + bool doubleWide; + bool flying; + + BattleHex startPosition; //assumed position of stack + BattlePerspective::BattlePerspective perspective; //some obstacles (eg. quicksands) may be invisible for some side + + Parameters(); + Parameters(const CStack *Stack); + }; + + Parameters params; + AccessibilityInfo accessibility; + TDistances distances; + TPredecessors predecessors; + + ReachabilityInfo() + { + distances.fill(INFINITE_DIST); + predecessors.fill(BattleHex::INVALID); + } + + bool isReachable(BattleHex hex) const + { + return predecessors[hex].isValid(); + } +}; + +class DLL_LINKAGE CBattleInfoEssentials : public virtual CCallbackBase +{ +protected: + bool battleDoWeKnowAbout(ui8 side) const; +public: + enum EStackOwnership + { + ONLY_MINE, ONLY_ENEMY, MINE_AND_ENEMY + }; + + BattlePerspective::BattlePerspective battleGetMySide() const; + + ui8 battleTerrainType() const; + int battleGetBattlefieldType() const; // 1. sand/shore 2. sand/mesas 3. dirt/birches 4. dirt/hills 5. dirt/pines 6. grass/hills 7. grass/pines 8. lava 9. magic plains 10. snow/mountains 11. snow/trees 12. subterranean 13. swamp/trees 14. fiery fields 15. rock lands 16. magic clouds 17. lucid pools 18. holy ground 19. clover field 20. evil fog 21. "favourable winds" text on magic plains background 22. cursed ground 23. rough 24. ship to ship 25. ship + std::vector > battleGetAllObstacles(boost::optional perspective = boost::none) const; //returns all obstacles on the battlefield + TStacks battleGetAllStacks() const; //returns all stacks, alive or dead or undead or mechanical :) + bool battleHasNativeStack(ui8 side) const; + ui8 battleGetWallState(int partOfWall) const; //for determining state of a part of the wall; format: parameter [0] - keep, [1] - bottom tower, [2] - bottom wall, [3] - below gate, [4] - over gate, [5] - upper wall, [6] - uppert tower, [7] - gate; returned value: 1 - intact, 2 - damaged, 3 - destroyed; 0 - no battle + int battleGetMoatDmg() const; //what dmg unit will suffer if ending turn in the moat + const CGTownInstance * battleGetDefendedTown() const; //returns defended town if current battle is a siege, NULL instead + const CStack *battleActiveStack() const; + si8 battleTacticDist() const; //returns tactic distance in current tactics phase; 0 if not in tactics phase + si8 battleGetTacticsSide() const; //returns which side is in tactics phase, undefined if none (?) + bool battleCanFlee(int player) const; + bool battleCanSurrender(int player) const; + ui8 playerToSide(int player) const; + ui8 battleGetSiegeLevel() const; //returns 0 when there is no siege, 1 if fort, 2 is citadel, 3 is castle + bool battleHasHero(ui8 side) const; + int battleCastSpells(ui8 side) const; //how many spells has given side casted + const CGHeroInstance * battleGetFightingHero(ui8 side) const; + + //helpers + TStacks battleAliveStacks() const; + TStacks battleAliveStacks(ui8 side) const; + const CStack * battleGetStackByID(int ID, bool onlyAlive = true) const; //returns stack info by given ID + bool battleIsObstacleVisibleForSide(const CObstacleInstance & coi, BattlePerspective::BattlePerspective side) const; + //ESpellCastProblem::ESpellCastProblem battleCanCastSpell(int player, ECastingMode::ECastingMode mode) const; //Checks if player is able to cast spells (at all) at the moment +}; + +class DLL_LINKAGE CBattleInfoCallback : public virtual CBattleInfoEssentials +{ +public: + enum ERandomSpell + { + RANDOM_GENIE, RANDOM_AIMED + }; + + //battle + shared_ptr battleGetObstacleOnPos(BattleHex tile, bool onlyBlocking = true) const; //blocking obstacles makes tile inaccessible, others cause special effects (like Land Mines, Moat, Quicksands) + const CStack * battleGetStackByPos(BattleHex pos, bool onlyAlive = true) const; //returns stack info by given pos + void battleGetStackQueue(std::vector &out, const int howMany, const int turn = 0, int lastMoved = -1) const; + void battleGetStackCountOutsideHexes(bool *ac) const; // returns hexes which when in front of a stack cause us to move the amount box back + + //void getStackQueue( std::vector &out, int howMany ) const; //returns vector of stack in order of their move sequence + std::vector battleGetAvailableHexes(const CStack * stack, bool addOccupiable, std::vector * attackable = NULL) const; //returns numbers of hexes reachable by creature with id ID + + int battleGetSurrenderCost(int Player) const; //returns cost of surrendering battle, -1 if surrendering is not possible + ReachabilityInfo::TDistances battleGetDistances(const CStack * stack, BattleHex hex = BattleHex::INVALID, BattleHex * predecessors = NULL) const; //returns vector of distances to [dest hex number] + std::set battleGetAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID) const; + 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 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 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 battleHasWallPenalty(const CStack * stack, 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 + si8 battleMaxSpellLevel() const; //calculates minimum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned + ui32 battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //returns cost of given spell + ESpellCastProblem::ESpellCastProblem battleCanCastSpell(int player, ECastingMode::ECastingMode mode) const; //returns true if there are no general issues preventing from casting a 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 battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination) const; //determines if creature can cast a spell here + + si32 battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode) const; + TSpell getRandomBeneficialSpell(const CStack * subject) const; + TSpell getRandomCastedSpell(const CStack * caster) const; //called at the beginning of turn for Faerie Dragon + + //checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into acount general problems such as not having spellbook or mana points etc. + ESpellCastProblem::ESpellCastProblem battleIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; + + + const CStack * getStackIf(boost::function pred) const; + + si8 battleHasShootingPenalty(const CStack * stack, BattleHex destHex) + { + return battleHasDistancePenalty(stack, destHex) || battleHasWallPenalty(stack, destHex); + } + si8 battleCanTeleportTo(const CStack * stack, BattleHex destHex, int telportLevel) const; //checks if teleportation of given stack to given position can take place + + + //convenience methods using the ones above + bool isInTacticRange( BattleHex dest ) const; + si8 battleGetTacticDist() const; //returns tactic distance for calling player or 0 if this player is not in tactic phase (for ALL_KNOWING actual distance for tactic side) + + AttackableTiles getPotentiallyAttackableHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos) const; + std::set getAttackedCreatures(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID) const; //calculates range of multi-hex attacks + + ReachabilityInfo getReachability(const CStack *stack) const; + ReachabilityInfo getReachability(const ReachabilityInfo::Parameters ¶ms) const; + AccessibilityInfo getAccesibility() const; + std::pair getNearestStack(const CStack * closest, boost::logic::tribool attackerOwned) const; +protected: + ReachabilityInfo getFlyingReachability(const ReachabilityInfo::Parameters params) const; + ReachabilityInfo makeBFS(const AccessibilityInfo &accessibility, const ReachabilityInfo::Parameters params) const; + ReachabilityInfo makeBFS(const CStack *stack) const; //uses default parameters -> stack position and owner's perspective + std::set getStoppers(BattlePerspective::BattlePerspective whichSidePerspective) const; //get hexes with stopping obstacles (quicksands) + + +}; + +class DLL_LINKAGE CPlayerBattleCallback : public CBattleInfoCallback +{ +public: + bool battleCanFlee() const; //returns true if caller can flee from the battle + TStacks battleGetStacks(EStackOwnership whose = MINE_AND_ENEMY, bool onlyAlive = true) const; //returns stacks on battlefield + ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell) const; //determines if given spell can be casted (and returns problem description) + ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell, BattleHex destination) const; //if hero can cast spell here + ESpellCastProblem::ESpellCastProblem battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination) const; //determines if creature can cast a spell here + int battleGetSurrenderCost() const; //returns cost of surrendering battle, -1 if surrendering is not possible + + bool battleCanCastSpell(ESpellCastProblem::ESpellCastProblem *outProblem = nullptr) const; //returns true, if caller can cast a spell. If not, if pointer is given via arg, the reason will be written. +}; diff --git a/lib/CGameInterface.h b/lib/CGameInterface.h index 021374193..229a09675 100644 --- a/lib/CGameInterface.h +++ b/lib/CGameInterface.h @@ -16,7 +16,7 @@ using namespace boost::logic; class CCallback; -class CBattleCallback; +class CPlayerBattleCallback; class ICallback; class CGlobalAI; struct Component; @@ -61,7 +61,7 @@ public: std::string dllName; virtual ~CBattleGameInterface() {}; - virtual void init(CBattleCallback * CB){}; + virtual void init(CPlayerBattleCallback * 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; - CBattleCallback *cbc; + CPlayerBattleCallback *cbc; //battle interface virtual BattleAction activeStack(const CStack * stack); diff --git a/lib/CSpellHandler.cpp b/lib/CSpellHandler.cpp index be695d279..3f774e077 100644 --- a/lib/CSpellHandler.cpp +++ b/lib/CSpellHandler.cpp @@ -256,6 +256,10 @@ bool CSpell::isRisingSpell() const return vstd::contains(VLC->spellh->risingSpells, id); } +bool CSpell::isDamageSpell() const +{ + return vstd::contains(VLC->spellh->damageSpells, id); +} bool DLL_LINKAGE isInScreenRange(const int3 ¢er, const int3 &pos) { int3 diff = pos - center; diff --git a/lib/CSpellHandler.h b/lib/CSpellHandler.h index 48ec431bd..40692604a 100644 --- a/lib/CSpellHandler.h +++ b/lib/CSpellHandler.h @@ -22,7 +22,7 @@ class DLL_LINKAGE CSpell public: enum ETargetType {NO_TARGET, CREATURE, CREATURE_EXPERT_MASSIVE, OBSTACLE}; enum ESpellPositiveness {NEGATIVE = -1, NEUTRAL = 0, POSITIVE = 1}; - ui32 id; + TSpell id; std::string name; std::string abbName; //abbreviated name std::vector descriptions; //descriptions of spell for skill levels: 0 - none, 1 - basic, etc @@ -48,6 +48,7 @@ public: bool isPositive() const; bool isNegative() const; bool isRisingSpell() const; + bool isDamageSpell() const; template void serialize(Handler &h, const int version) { diff --git a/lib/Filesystem/CLodArchiveLoader.cpp b/lib/Filesystem/CLodArchiveLoader.cpp index 484744465..0e0b25b03 100644 --- a/lib/Filesystem/CLodArchiveLoader.cpp +++ b/lib/Filesystem/CLodArchiveLoader.cpp @@ -161,11 +161,11 @@ void CLodArchiveLoader::initSNDArchive(CFileInputStream & fileStream) SoundEntryBlock sndEntry = sndEntries[i]; ArchiveEntry entry; - //for some reason entries in snd have format NAME\0WAV\0\0\0.... - //we need to replace first \0 with dot and trim line + //for some reason entries in snd have format NAME\0WAVRUBBISH.... + //we need to replace first \0 with dot and take the 3 chars with extension (and drop the rest) entry.name = std::string(sndEntry.filename, 40); + entry.name.resize(entry.name.find_first_of('\0') + 4); //+4 because we take dot and 3-char extension entry.name[entry.name.find_first_of('\0')] = '.'; - entry.name.resize(entry.name.find_first_of('\0')); entry.offset = SDL_SwapLE32(sndEntry.offset); entry.realSize = SDL_SwapLE32(sndEntry.size); diff --git a/lib/GameConstants.h b/lib/GameConstants.h index a4cf0ba9d..90cc83e88 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -140,7 +140,9 @@ namespace ESpellCastProblem OK, NO_HERO_TO_CAST_SPELL, ALREADY_CASTED_THIS_TURN, NO_SPELLBOOK, ANOTHER_ELEMENTAL_SUMMONED, HERO_DOESNT_KNOW_SPELL, NOT_ENOUGH_MANA, ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL, SECOND_HEROS_SPELL_IMMUNITY, SPELL_LEVEL_LIMIT_EXCEEDED, NO_SPELLS_TO_DISPEL, - NO_APPROPRIATE_TARGET, STACK_IMMUNE_TO_SPELL, WRONG_SPELL_TARGET, ONGOING_TACTIC_PHASE + NO_APPROPRIATE_TARGET, STACK_IMMUNE_TO_SPELL, WRONG_SPELL_TARGET, ONGOING_TACTIC_PHASE, + MAGIC_IS_BLOCKED, //For Orb of Inhibition and similar - no casting at all + INVALID }; } @@ -172,6 +174,28 @@ namespace ECommander const int MAX_SKILL_LEVEL = 5; } +namespace EWallParts +{ + enum EWallParts + { + INDESTRUCTIBLE_PART = -2, INVALID = -1, + KEEP = 0, BOTTOM_TOWER, BOTTOM_WALL, BELOW_GATE, OVER_GATE, UPPER_WAL, UPPER_TOWER, GATE, + + PARTS_COUNT + }; +} + +namespace EWallState +{ + enum + { + NONE, //no wall + INTACT, + DAMAGED, + DESTROYED + }; +} + namespace Obj { enum @@ -224,6 +248,17 @@ namespace Obj }; } +namespace SecSkillLevel +{ + enum SecSkillLevel + { + NONE, + BASIC, + ADVANCED, + EXPERT + }; +} + //follows ERM BI (battle image) format namespace BattlefieldBI { diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index d097bca88..e7353aadc 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -134,10 +134,26 @@ int BonusList::totalValue() const if(hasIndepMin && hasIndepMax) assert(indepMin < indepMax); + + const int notIndepBonuses = boost::count_if(bonuses, [](const Bonus *b) + { + return b->valType != Bonus::INDEPENDENT_MAX && b->valType != Bonus::INDEPENDENT_MIN; + }); + if (hasIndepMax) - vstd::amax(valFirst, indepMax); + { + if(notIndepBonuses) + vstd::amax(valFirst, indepMax); + else + valFirst = indepMax; + } if (hasIndepMin) - vstd::amin(valFirst, indepMin); + { + if(notIndepBonuses) + vstd::amin(valFirst, indepMin); + else + valFirst = indepMin; + } return valFirst; } diff --git a/lib/HeroBonus.h b/lib/HeroBonus.h index 615a08702..6cdbfb55a 100644 --- a/lib/HeroBonus.h +++ b/lib/HeroBonus.h @@ -95,6 +95,8 @@ typedef boost::function CSelector; BONUS_NAME(SPELL_BEFORE_ATTACK) /* subtype - spell id, value - chance %, additional info % 1000 - level, (additional info)/1000 -> [0 - all attacks, 1 - shot only, 2 - melee only*/ \ BONUS_NAME(SPELL_RESISTANCE_AURA) /*eg. unicorns, value - resistance bonus in % for adjacent creatures*/ \ BONUS_NAME(LEVEL_SPELL_IMMUNITY) /*creature is immune to all spell with level below or equal to value of this bonus*/ \ + BONUS_NAME(BLOCK_MAGIC_ABOVE) /*blocks casting spells of the level > value */ \ + BONUS_NAME(BLOCK_ALL_MAGIC) /*blocks casting spells of the level > value */ \ BONUS_NAME(TWO_HEX_ATTACK_BREATH) /*eg. dragons*/ \ BONUS_NAME(SPELL_DAMAGE_REDUCTION) /*eg. golems; value - reduction in %, subtype - spell school; -1 - all, 0 - air, 1 - fire, 2 - water, 3 - earth*/ \ BONUS_NAME(NO_WALL_PENALTY) \ @@ -626,7 +628,8 @@ public: } enum ENodeTypes { - UNKNOWN, STACK_INSTANCE, STACK_BATTLE, SPECIALITY, ARTIFACT, CREATURE, ARTIFACT_INSTANCE, HERO, PLAYER, TEAM, TOWN_AND_VISITOR + UNKNOWN, STACK_INSTANCE, STACK_BATTLE, SPECIALITY, ARTIFACT, CREATURE, ARTIFACT_INSTANCE, HERO, PLAYER, TEAM, + TOWN_AND_VISITOR, BATTLE }; }; diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index a13d131e8..5a3174899 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -34,417 +34,6 @@ extern boost::rand48 ran; -boost::shared_mutex& CCallbackBase::getGsMutex() -{ - return *gs->mx; -} - -si8 CBattleInfoCallback::battleHasDistancePenalty( const CStack * stack, BattleHex destHex ) -{ - return gs->curB->hasDistancePenalty(stack, destHex); -} - -si8 CBattleInfoCallback::battleHasWallPenalty( const CStack * stack, BattleHex destHex ) -{ - return gs->curB->hasWallPenalty(stack, destHex); -} - -si8 CBattleInfoCallback::battleCanTeleportTo(const CStack * stack, BattleHex destHex, int telportLevel) -{ - return gs->curB->canTeleportTo(stack, destHex, telportLevel); -} - -std::vector CBattleInfoCallback::battleGetDistances(const CStack * stack, BattleHex hex /*= BattleHex::INVALID*/, BattleHex * predecessors /*= NULL*/) -{ - // FIXME - This method is broken, hex argument is not used. However AI depends on that wrong behaviour. - - if(!hex.isValid()) - hex = stack->position; - - std::vector ret(GameConstants::BFIELD_SIZE, -1); //fill initial ret with -1's - - if(!hex.isValid()) //stack has bad position? probably castle turret, return initial values (they can't move) - return ret; - - bool ac[GameConstants::BFIELD_SIZE] = {0}; - std::set occupyable; - gs->curB->getAccessibilityMap(ac, stack->doubleWide(), stack->attackerOwned, false, occupyable, stack->hasBonusOfType(Bonus::FLYING), stack); - BattleHex pr[GameConstants::BFIELD_SIZE]; - int dist[GameConstants::BFIELD_SIZE]; - gs->curB->makeBFS(stack->position, ac, pr, dist, stack->doubleWide(), stack->attackerOwned, stack->hasBonusOfType(Bonus::FLYING), false); - - for(int i=0; i CBattleInfoCallback::battleGetAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos /*= BattleHex::INVALID*/) -{ - if(!gs->curB) - { - - tlog1 << "battleGetAttackedHexes called when there is no battle!\n"; - std::set set; - return set; - } - - return gs->curB->getAttackedHexes(attacker, destinationTile, attackerPos); -} - -ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell( const CSpell * spell ) -{ - if(!gs->curB) - { - - tlog1 << "battleCanCastThisSpell called when there is no battle!\n"; - return ESpellCastProblem::NO_HERO_TO_CAST_SPELL; - } - - return gs->curB->battleCanCastThisSpell(player, spell, ECastingMode::HERO_CASTING); -} - -ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell(const CSpell * spell, BattleHex destination) -{ - if(!gs->curB) - { - - tlog1 << "battleCanCastThisSpell called when there is no battle!\n"; - return ESpellCastProblem::NO_HERO_TO_CAST_SPELL; - } - - return gs->curB->battleCanCastThisSpellHere(player, spell, ECastingMode::HERO_CASTING, destination); -} - -ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination) -{ - if(!gs->curB) - { - - tlog1 << "battleCanCastThisSpell called when there is no battle!\n"; - return ESpellCastProblem::NO_HERO_TO_CAST_SPELL; - } - - return gs->curB->battleCanCastThisSpellHere(player, spell, ECastingMode::CREATURE_ACTIVE_CASTING, destination); -} - -si32 CBattleInfoCallback::battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode) -{ - switch (mode) - { - case RANDOM_GENIE: - return gs->curB->getRandomBeneficialSpell(stack); //target - break; - case RANDOM_AIMED: - return gs->curB->getRandomCastedSpell(stack); //caster - break; - default: - tlog1 << "Incorrect mode of battleGetRandomSpell (" << mode <<")\n"; - return -1; - } -} - -si8 CBattleInfoCallback::battleGetTacticDist() -{ - if (!gs->curB) - { - tlog1 << "battleGetTacticDist called when no battle!\n"; - return 0; - } - - if (gs->curB->sides[gs->curB->tacticsSide] == player) - { - return gs->curB->tacticDistance; - } - return 0; -} - -ui8 CBattleInfoCallback::battleGetMySide() -{ - if (!gs->curB) - { - tlog1 << "battleGetMySide called when no battle!\n"; - return 0; - } - - return gs->curB->sides[1] == player; -} - -int CBattleInfoCallback::battleGetSurrenderCost() -{ - if (!gs->curB) - { - tlog1 << "battleGetSurrenderCost called when no battle!\n"; - return -1; - } - - return gs->curB->getSurrenderingCost(player); -} - -int CBattleInfoCallback::battleGetBattlefieldType() -{ - //boost::shared_lock lock(*gs->mx); - //return gs->battleGetBattlefieldType(); - - if(!gs->curB) - { - tlog2<<"battleGetBattlefieldType called when there is no battle!"<curB->battlefieldType; -} - -// int CBattleInfoCallback::battleGetObstaclesAtTile(BattleHex tile) //returns bitfield -// { -// //TODO - write -// return -1; -// } - -std::vector > CBattleInfoCallback::battleGetAllObstacles() -{ - //boost::shared_lock lock(*gs->mx); - std::vector > ret; - if(gs->curB) - { - BOOST_FOREACH(auto oi, gs->curB->obstacles) - { - if(player < 0 || gs->curB->isObstacleVisibleForSide(*oi, battleGetMySide())) - ret.push_back(oi); - } - } - - return ret; -} - -const CStack* CBattleInfoCallback::battleGetStackByID(int ID, bool onlyAlive) -{ - //boost::shared_lock lock(*gs->mx); - if(!gs->curB) return NULL; - return gs->curB->getStack(ID, onlyAlive); -} - -const CStack* CBattleInfoCallback::battleGetStackByPos(BattleHex pos, bool onlyAlive) -{ - //boost::shared_lock lock(*gs->mx); - return gs->curB->battleGetStack(pos, onlyAlive); -} - -BattleHex CBattleInfoCallback::battleGetPos(int stack) -{ - //boost::shared_lock lock(*gs->mx); - if(!gs->curB) - { - tlog2<<"battleGetPos called when there is no battle!"<curB->stacks.size(); ++g) - { - if(gs->curB->stacks[g]->ID == stack) - return gs->curB->stacks[g]->position; - } - return BattleHex::INVALID; -} - -TStacks CBattleInfoCallback::battleGetStacks(EStackOwnership whose /*= MINE_AND_ENEMY*/, bool onlyAlive /*= true*/) -{ - //boost::shared_lock lock(*gs->mx); - TStacks ret; - if(!gs->curB) //there is no battle - { - tlog2<<"battleGetStacks called when there is no battle!"<curB->stacks) - { - bool ownerMatches = (whose == MINE_AND_ENEMY) || (whose == ONLY_MINE && s->owner == player) || (whose == ONLY_ENEMY && s->owner != player); - bool alivenessMatches = s->alive() || !onlyAlive; - if(ownerMatches && alivenessMatches) - ret.push_back(s); - } - - return ret; -} - -void CBattleInfoCallback::getStackQueue( std::vector &out, int howMany ) -{ - if(!gs->curB) - { - tlog2 << "battleGetStackQueue called when there is not battle!" << std::endl; - return; - } - gs->curB->getStackQueue(out, howMany); -} - -void CBattleInfoCallback::battleGetStackCountOutsideHexes(bool *ac) -{ - if(!gs->curB) - { - tlog2<<"battleGetAvailableHexes called when there is no battle!"< ignored; - gs->curB->getAccessibilityMap(ac, false /*ignored*/, false, false, ignored, false /*ignored*/, NULL); - } -} - -std::vector CBattleInfoCallback::battleGetAvailableHexes(const CStack * stack, bool addOccupiable, std::vector * attackable) -{ - //boost::shared_lock lock(*gs->mx); - if(!gs->curB) - { - tlog2<<"battleGetAvailableHexes called when there is no battle!"<(); - } - return gs->curB->getAccessibility(stack, addOccupiable, attackable); - //return gs->battleGetRange(ID); -} - -bool CBattleInfoCallback::battleCanShoot(const CStack * stack, BattleHex dest) -{ - //boost::shared_lock lock(*gs->mx); - - if(!gs->curB) return false; - - return gs->curB->battleCanShoot(stack, dest); -} - -bool CBattleInfoCallback::battleCanCastSpell() -{ - if(!gs->curB) //there is no battle - return false; - - return gs->curB->battleCanCastSpell(player, ECastingMode::HERO_CASTING) == ESpellCastProblem::OK; -} - -bool CBattleInfoCallback::battleCanFlee() -{ - return gs->curB->battleCanFlee(player); -} - -const CGTownInstance *CBattleInfoCallback::battleGetDefendedTown() -{ - if(!gs->curB || gs->curB->town == NULL) - return NULL; - - return gs->curB->town; -} - -ui8 CBattleInfoCallback::battleGetWallState(int partOfWall) -{ - if(!gs->curB || gs->curB->siege == 0) - { - return 0; - } - return gs->curB->si.wallState[partOfWall]; -} - -int CBattleInfoCallback::battleGetWallUnderHex(BattleHex hex) -{ - if(!gs->curB || gs->curB->siege == 0) - { - return -1; - } - return gs->curB->hexToWallPart(hex); -} - -TDmgRange CBattleInfoCallback::battleEstimateDamage(const CStack * attacker, const CStack * defender, TDmgRange * retaliationDmg) -{ - if(!gs->curB) - return std::make_pair(0, 0); - - const CGHeroInstance * attackerHero, * defenderHero; - bool shooting = battleCanShoot(attacker, defender->position); - - if(gs->curB->sides[0] == attacker->owner) - { - attackerHero = gs->curB->heroes[0]; - defenderHero = gs->curB->heroes[1]; - } - else - { - attackerHero = gs->curB->heroes[1]; - defenderHero = gs->curB->heroes[0]; - } - - TDmgRange ret = gs->curB->calculateDmgRange(attacker, defender, attackerHero, defenderHero, shooting, 0, false, false, false); - - if(retaliationDmg) - { - if(shooting) - { - retaliationDmg->first = retaliationDmg->second = 0; - } - else - { - ui32 TDmgRange::* pairElems[] = {&TDmgRange::first, &TDmgRange::second}; - for (int i=0; i<2; ++i) - { - BattleStackAttacked bsa; - bsa.damageAmount = ret.*pairElems[i]; - retaliationDmg->*pairElems[!i] = gs->curB->calculateDmgRange(defender, attacker, bsa.newAmount, attacker->count, attackerHero, defenderHero, false, 0, false, false, false).*pairElems[!i]; - } - } - } - - return ret; -} - -ui8 CBattleInfoCallback::battleGetSiegeLevel() -{ - if(!gs->curB) - return 0; - - return gs->curB->siege; -} - -const CGHeroInstance * CBattleInfoCallback::battleGetFightingHero(ui8 side) const -{ - if(!gs->curB) - return 0; - - //TODO: this method should not exist... you shouldn't be able to get info about enemy hero - return gs->curB->heroes[side]; -} - -shared_ptr CBattleInfoCallback::battleGetObstacleOnPos(BattleHex tile, bool onlyBlocking /*= true*/) -{ - if(!gs->curB) - return shared_ptr(); - - BOOST_FOREACH(auto &obs, battleGetAllObstacles()) - { - if(vstd::contains(obs->getBlockedTiles(), tile) - || (!onlyBlocking && vstd::contains(obs->getAffectedTiles(), tile))) - { - return obs; - } - } - return shared_ptr(); -} - -int CBattleInfoCallback::battleGetMoatDmg() -{ - if(!gs->curB || !gs->curB->town) - return 0; - - //TODO move to config file - static const int dmgs[] = {70, 70, -1, - 90, 70, 90, - 70, 90, 70}; - if(gs->curB->town->subID < ARRAY_COUNT(dmgs)) - return dmgs[gs->curB->town->subID]; - return 0; -} - CGameState * CPrivilagedInfoCallback::gameState () { return gs; @@ -729,7 +318,7 @@ int CGameInfoCallback::getSpellCost(const CSpell * sp, const CGHeroInstance * ca ERROR_RET_VAL_IF(!canGetFullInfo(caster), "Cannot get info about caster!", -1); //if there is a battle if(gs->curB) - return gs->curB->getSpellCost(sp, caster); + return gs->curB->battleGetSpellCost(sp, caster); //if there is no battle return caster->getSpellCost(sp); @@ -784,7 +373,7 @@ bool CGameInfoCallback::getTownInfo( const CGObjectInstance *town, InfoAboutTown //TODO vision support if(town->ID == GameConstants::TOWNI_TYPE) dest.initFromTown(static_cast(town), detailed); - else if(town->ID == 33 || town->ID == 219) + else if(town->ID == Obj::GARRISON || town->ID == Obj::GARRISON2) dest.initFromArmy(static_cast(town), detailed); else return false; diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index a19da8239..1627e6873 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -3,10 +3,10 @@ #include "BattleHex.h" #include "../client/FunctionList.h" -#include "CObstacleInstance.h" #include "ResourceSet.h" #include "int3.h" #include "GameConstants.h" +#include "CBattleCallback.h" /* * IGameCallback.h, part of VCMI engine @@ -59,85 +59,6 @@ struct TeamState; struct QuestInfo; class CGCreature; -typedef std::vector TStacks; - -namespace boost -{class shared_mutex;} - -class DLL_LINKAGE CCallbackBase -{ -protected: - CGameState *gs; - int player; // -1 gives access to all information, otherwise limited to knowledge of given player - - CCallbackBase(CGameState *GS, int Player) - : gs(GS), player(Player) - {} - CCallbackBase() - : gs(NULL), player(-1) - {} - -public: - boost::shared_mutex &getGsMutex(); //just return a reference to mutex, does not lock nor anything -}; - -class DLL_LINKAGE CBattleInfoCallback : public virtual CCallbackBase -{ -public: - enum EStackOwnership - { - ONLY_MINE, ONLY_ENEMY, MINE_AND_ENEMY - }; - enum ERandomSpell - { - RANDOM_GENIE, RANDOM_AIMED - }; - - //battle - int battleGetBattlefieldType(); // 1. sand/shore 2. sand/mesas 3. dirt/birches 4. dirt/hills 5. dirt/pines 6. grass/hills 7. grass/pines 8. lava 9. magic plains 10. snow/mountains 11. snow/trees 12. subterranean 13. swamp/trees 14. fiery fields 15. rock lands 16. magic clouds 17. lucid pools 18. holy ground 19. clover field 20. evil fog 21. "favourable winds" text on magic plains background 22. cursed ground 23. rough 24. ship to ship 25. ship - //int battleGetObstaclesAtTile(BattleHex tile); //returns bitfield - shared_ptr battleGetObstacleOnPos(BattleHex tile, bool onlyBlocking = true); - std::vector > battleGetAllObstacles(); //returns all obstacles on the battlefield - const CStack * battleGetStackByID(int ID, bool onlyAlive = true); //returns stack info by given ID - const CStack * battleGetStackByPos(BattleHex pos, bool onlyAlive = true); //returns stack info by given pos - BattleHex battleGetPos(int stack); //returns position (tile ID) of stack - TStacks battleGetStacks(EStackOwnership whose = MINE_AND_ENEMY, bool onlyAlive = true); //returns stacks on battlefield - void getStackQueue( std::vector &out, int howMany ); //returns vector of stack in order of their move sequence - void battleGetStackCountOutsideHexes(bool *ac); // returns hexes which when in front of a stack cause us to move the amount box back - std::vector battleGetAvailableHexes(const CStack * stack, bool addOccupiable, std::vector * attackable = NULL); //returns numbers of hexes reachable by creature with id ID - std::vector battleGetDistances(const CStack * stack, BattleHex hex = BattleHex::INVALID, BattleHex * predecessors = NULL); //returns vector of distances to [dest hex number] - std::set battleGetAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID); - bool battleCanShoot(const CStack * stack, BattleHex dest); //returns true if unit with id ID can shoot to dest - bool battleCanCastSpell(); //returns true, if caller can cast a spell - ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell); //determines if given spell can be casted (and returns problem description) - ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell, BattleHex destination); //if hero can cast spell here - ESpellCastProblem::ESpellCastProblem battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination); //determines if creature can cast a spell here - si32 battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode); - bool battleCanFlee(); //returns true if caller can flee from the battle - int battleGetSurrenderCost(); //returns cost of surrendering battle, -1 if surrendering is not possible - const CGTownInstance * battleGetDefendedTown(); //returns defended town if current battle is a siege, NULL instead - ui8 battleGetWallState(int partOfWall); //for determining state of a part of the wall; format: parameter [0] - keep, [1] - bottom tower, [2] - bottom wall, [3] - below gate, [4] - over gate, [5] - upper wall, [6] - uppert tower, [7] - gate; returned value: 1 - intact, 2 - damaged, 3 - destroyed; 0 - no battle - int battleGetWallUnderHex(BattleHex hex); //returns part of destructible wall / gate / keep under given hex or -1 if not found - std::pair battleEstimateDamage(const CStack * attacker, const CStack * defender, std::pair * retaliationDmg = NULL); //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair - ui8 battleGetSiegeLevel(); //returns 0 when there is no siege, 1 if fort, 2 is citadel, 3 is castle - const CGHeroInstance * battleGetFightingHero(ui8 side) const; //returns hero corresponding to given side (0 - attacker, 1 - defender) - si8 battleHasDistancePenalty(const CStack * stack, BattleHex destHex); //checks if given stack has distance penalty - si8 battleHasWallPenalty(const CStack * stack, BattleHex destHex); //checks if given stack has wall penalty - si8 battleHasShootingPenalty(const CStack * stack, BattleHex destHex) - { - return battleHasDistancePenalty(stack, destHex) || battleHasWallPenalty(stack, destHex); - } - si8 battleCanTeleportTo(const CStack * stack, BattleHex destHex, int telportLevel); //checks if teleportation of given stack to given position can take place - si8 battleGetTacticDist(); //returns tactic distance for calling player or 0 if player is not in tactic phase - ui8 battleGetMySide(); //return side of player in battle (attacker/defender) - int battleGetMoatDmg(); //what dmg unit will suffer if ending turn in the moat - - //convenience methods using the ones above - TStacks battleGetAllStacks() //returns all stacks, alive or dead or undead or mechanical :) - { - return battleGetStacks(MINE_AND_ENEMY, false); - } -}; class DLL_LINKAGE CGameInfoCallback : public virtual CCallbackBase { diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 80e1d39c5..84841040a 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -1113,6 +1113,7 @@ void BattleStackMoved::applyGs( CGameState *gs ) DLL_LINKAGE void BattleStackAttacked::applyGs( CGameState *gs ) { CStack * at = gs->curB->getStack(stackAttacked); + assert(at); at->count = newAmount; at->firstHPleft = newHP; @@ -1334,16 +1335,13 @@ DLL_LINKAGE void StacksHealedOrResurrected::applyGs( CGameState *gs ) CStack * changedStack = gs->curB->getStack(healedStacks[g].stackID, false); //checking if we resurrect a stack that is under a living stack - std::vector access = gs->curB->getAccessibility(changedStack, true); - bool acc[GameConstants::BFIELD_SIZE]; - for(int h=0; halive() && !gs->curB->isAccessible(changedStack->position, acc, - changedStack->doubleWide(), changedStack->attackerOwned, - changedStack->hasBonusOfType(Bonus::FLYING), true)) + auto accessibility = gs->curB->getAccesibility(); + + if(!changedStack->alive() && !accessibility.accessible(changedStack->position, changedStack)) + { + tlog1 << "Cannot resurrect " << changedStack->nodeName() << " because hex " << changedStack->position << " is occupied!\n"; return; //position is already occupied + } //applying changes bool resurrected = !changedStack->alive(); //indicates if stack is resurrected or just healed diff --git a/lib/ResourceSet.h b/lib/ResourceSet.h index 5be61c1e6..707c4885d 100644 --- a/lib/ResourceSet.h +++ b/lib/ResourceSet.h @@ -1,8 +1,5 @@ #pragma once - -#include - typedef si32 TResource; typedef si64 TResourceCap; //to avoid overflow when adding integers. Signed values are easier to control. diff --git a/lib/VCMI_lib.vcxproj b/lib/VCMI_lib.vcxproj index 905598e38..21b4707e5 100644 --- a/lib/VCMI_lib.vcxproj +++ b/lib/VCMI_lib.vcxproj @@ -230,14 +230,13 @@ - - + @@ -245,15 +244,15 @@ + - - + @@ -283,14 +282,13 @@ - - + @@ -302,17 +300,18 @@ + - + diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index c204d81d7..eb97d2c62 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -531,8 +531,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer ConstTransitivePtr loserHero = battleResult.data->winner != 0 ? hero1 : hero2; std::vector arts; //display them in window - //TODO: display loot in window - if (result < BattleResult::SURRENDER && winnerHero) + if (result == BattleResult::NORMAL && winnerHero) { if (loserHero) { @@ -542,7 +541,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer MoveArtifact ma; ma.src = ArtifactLocation (loserHero, artSlot.first); const CArtifactInstance * art = ma.src.getArt(); - if (art && !art->artType->isBig()) // don't move war machines or locked arts (spellbook) + if (art && !art->artType->isBig() && art->id != ArtifactId::SPELLBOOK) // don't move war machines or locked arts (spellbook) { arts.push_back (art->artType->id); ma.dst = ArtifactLocation (winnerHero, art->firstAvailableSlot(winnerHero)); @@ -692,6 +691,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer addToSlot(StackLocation(winnerHero, necroSlot), raisedStack.type, raisedStack.count); } sendAndApply(&resultsApplied); + setBattle(nullptr); if(duel) { @@ -775,10 +775,10 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt if (!bat.shot()) //multiple-hex attack - only in meele { - std::set attackedCreatures = gs->curB->getAttackedCreatures(att, targetHex); + std::set attackedCreatures = gs->curB->getAttackedCreatures(att, targetHex); //TODO: get exact attacked hex for defender - BOOST_FOREACH(CStack * stack, attackedCreatures) + BOOST_FOREACH(const CStack * stack, attackedCreatures) { if (stack != def) //do not hit same stack twice { @@ -793,10 +793,10 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt bat.bsa.front().flags |= BattleStackAttacked::EFFECT; bat.bsa.front().effect = VLC->spellh->spells[bonus->subtype]->mainEffectAnim; //hopefully it does not interfere with any other effect? - std::set attackedCreatures = gs->curB->getAttackedCreatures(VLC->spellh->spells[bonus->subtype], bonus->val, att->owner, targetHex); + std::set attackedCreatures = gs->curB->getAttackedCreatures(VLC->spellh->spells[bonus->subtype], bonus->val, att->owner, targetHex); //TODO: get exact attacked hex for defender - BOOST_FOREACH(CStack * stack, attackedCreatures) + BOOST_FOREACH(const CStack * stack, attackedCreatures) { if (stack != def) //do not hit same stack twice { @@ -924,8 +924,8 @@ int CGameHandler::moveStack(int stack, BattleHex dest) { int ret = 0; - CStack *curStack = gs->curB->getStack(stack), - *stackAtEnd = gs->curB->getStackT(dest); + const CStack *curStack = gs->curB->battleGetStackByID(stack), + *stackAtEnd = gs->curB->battleGetStackByPos(dest); assert(curStack); assert(dest < GameConstants::BFIELD_SIZE); @@ -936,50 +936,30 @@ int CGameHandler::moveStack(int stack, BattleHex dest) } //initing necessary tables - bool accessibility[GameConstants::BFIELD_SIZE]; - std::vector accessible = gs->curB->getAccessibility(curStack, false, NULL, true); - for(int b=0; bdoubleWide() && !accessibility[dest]) + if(!stackAtEnd && curStack->doubleWide() && !accessibility.accessible(dest, curStack)) { if(curStack->attackerOwned) { - if(accessibility[dest+1]) + if(accessibility.accessible(dest+1, curStack)) dest += BattleHex::RIGHT; } else { - if(accessibility[dest-1]) + if(accessibility.accessible(dest-1, curStack)) dest += BattleHex::LEFT; } } - if((stackAtEnd && stackAtEnd!=curStack && stackAtEnd->alive()) || !accessibility[dest]) + if((stackAtEnd && stackAtEnd!=curStack && stackAtEnd->alive()) || !accessibility.accessible(dest, curStack)) + { + complain("Given destination is not accessible!"); return 0; - - bool accessibilityWithOccupyable[GameConstants::BFIELD_SIZE]; - std::vector accOc = gs->curB->getAccessibility(curStack, true, NULL, true); - for(int b=0; b curStack->creature->speed && !(stackAtEnd && dists[dest] == curStack->creature->speed+1)) //we can attack a stack if we can go to adjacent hex - // return false; - - std::pair< std::vector, int > path = gs->curB->getPath(curStack->position, dest, accessibilityWithOccupyable, curStack->hasBonusOfType(Bonus::FLYING), curStack->doubleWide(), curStack->attackerOwned); + + std::pair< std::vector, int > path = gs->curB->getPath(curStack->position, dest, curStack); ret = path.second; @@ -3220,6 +3200,40 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) tlog1 << "\tMaking action of type " << ba.actionType << std::endl; bool ok = true; + + const CStack *stack = battleGetStackByID(ba.stackNumber); //may be nullptr if action is not about stack + const bool isAboutActiveStack = stack && (stack == battleActiveStack()); + + switch(ba.actionType) + { + case BattleAction::WALK: //walk + case BattleAction::DEFEND: //defend + case BattleAction::WAIT: //wait + case BattleAction::WALK_AND_ATTACK: //walk or attack + case BattleAction::SHOOT: //shoot + case BattleAction::CATAPULT: //catapult + case BattleAction::STACK_HEAL: //healing with First Aid Tent + case BattleAction::DAEMON_SUMMONING: + case BattleAction::MONSTER_SPELL: + + if(!stack) + { + complain("No such stack!"); + return false; + } + if(!stack->alive()) + { + complain("This stack is dead: " + stack->nodeName()); + return false; + } + if(!isAboutActiveStack) + { + complain("Action has to be about active stack!"); + return false; + } + } + + switch(ba.actionType) { case BattleAction::END_TACTIC_PHASE: //wait @@ -3267,7 +3281,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) case BattleAction::SURRENDER: { int player = gs->curB->sides[ba.side]; - int cost = gs->curB->getSurrenderingCost(player); + int cost = gs->curB->battleGetSurrenderCost(player); if(cost < 0) complain("Cannot surrender!"); else if(getResource(player, Res::GOLD) < cost) @@ -3284,10 +3298,10 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) { StartAction start_action(ba); sendAndApply(&start_action); //start movement and attack - int startingPos = gs->curB->getStack(ba.stackNumber)->position; + int startingPos = gs->curB->battleGetStackByID(ba.stackNumber)->position; int distance = moveStack(ba.stackNumber, ba.destinationTile); - CStack *curStack = gs->curB->getStack(ba.stackNumber), - *stackAtEnd = gs->curB->getStackT(ba.additionalInfo); + const CStack *curStack = gs->curB->battleGetStackByID(ba.stackNumber), + *stackAtEnd = gs->curB->battleGetStackByPos(ba.additionalInfo); if(!curStack || !stackAtEnd) { @@ -3295,6 +3309,8 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) break; } + tlog5 << curStack->nodeName() << " will attack " << stackAtEnd->nodeName() << std::endl; + if(curStack->position != ba.destinationTile //we wasn't able to reach destination tile && !(curStack->doubleWide() && ( curStack->position == ba.destinationTile + (curStack->attackerOwned ? +1 : -1 ) ) @@ -3341,7 +3357,8 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) //counterattack if(!curStack->hasBonusOfType(Bonus::BLOCKS_RETALIATION) - && stackAtEnd->ableToRetaliate()) + && stackAtEnd->ableToRetaliate() + && curStack->alive()) //attacker may have died (fire shield) { BattleAttack bat; prepareAttack(bat, stackAtEnd, curStack, 0, curStack->position); @@ -3374,8 +3391,8 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) } case BattleAction::SHOOT: //shoot { - CStack *curStack = gs->curB->getStack(ba.stackNumber), - *destStack= gs->curB->getStackT(ba.destinationTile); + const CStack *curStack = gs->curB->battleGetStackByID(ba.stackNumber), + *destStack= gs->curB->battleGetStackByPos(ba.destinationTile); if( !gs->curB->battleCanShoot(curStack, ba.destinationTile) ) break; @@ -3423,7 +3440,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) const CGHeroInstance * attackingHero = gs->curB->heroes[ba.side]; CHeroHandler::SBallisticsLevelInfo sbi = VLC->heroh->ballistics[attackingHero->getSecSkillLevel(CGHeroInstance::BALLISTICS)]; - int attackedPart = gs->curB->hexToWallPart(ba.destinationTile); + int attackedPart = gs->curB->battleHexToWallPart(ba.destinationTile); if(attackedPart < 0) { complain("catapult tried to attack non-catapultable hex!"); @@ -3524,8 +3541,8 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) StartAction start_action(ba); sendAndApply(&start_action); const CGHeroInstance * attackingHero = gs->curB->heroes[ba.side]; - CStack *healer = gs->curB->getStack(ba.stackNumber), - *destStack = gs->curB->getStackT(ba.destinationTile); + const CStack *healer = gs->curB->battleGetStackByID(ba.stackNumber), + *destStack = gs->curB->battleGetStackByPos(ba.destinationTile); if(healer == NULL || destStack == NULL || !healer->hasBonusOfType(Bonus::HEALER)) { @@ -3567,8 +3584,8 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) StartAction start_action(ba); sendAndApply(&start_action); - CStack *summoner = gs->curB->getStack(ba.stackNumber), - *destStack = gs->curB->getStackT(ba.destinationTile, false); + const CStack *summoner = gs->curB->battleGetStackByID(ba.stackNumber), + *destStack = gs->curB->battleGetStackByPos(ba.destinationTile, false); BattleStackAdded bsa; bsa.attacker = summoner->attackerOwned; @@ -3603,7 +3620,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) StartAction start_action(ba); sendAndApply(&start_action); - CStack * stack = gs->curB->getStack(ba.stackNumber); + const CStack * stack = gs->curB->battleGetStackByID(ba.stackNumber); int spellID = ba.additionalInfo; BattleHex destination(ba.destinationTile); @@ -3852,7 +3869,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest if (caster) //calculate spell cost { - sc.spellCost = gs->curB->getSpellCost(VLC->spellh->spells[spellID], caster); + sc.spellCost = gs->curB->battleGetSpellCost(VLC->spellh->spells[spellID], caster); if (secHero && mode == ECastingMode::HERO_CASTING) //handle mana channel { @@ -3869,18 +3886,18 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest } //calculating affected creatures for all spells - std::set attackedCres; + std::set attackedCres; if (mode != ECastingMode::ENCHANTER_CASTING) { attackedCres = gs->curB->getAttackedCreatures(spell, spellLvl, casterColor, destination); - for(std::set::const_iterator it = attackedCres.begin(); it != attackedCres.end(); ++it) + for(std::set::const_iterator it = attackedCres.begin(); it != attackedCres.end(); ++it) { sc.affectedCres.insert((*it)->ID); } } else //enchanter - hit all possible stacks { - BOOST_FOREACH (CStack * stack, gs->curB->stacks) + BOOST_FOREACH (const CStack * stack, gs->curB->stacks) { /*if it's non negative spell and our unit or non positive spell and hostile unit */ if((!spell->isNegative() && stack->owner == casterColor) @@ -3898,7 +3915,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest sc.resisted = gs->curB->calculateResistedStacks(spell, caster, secHero, attackedCres, casterColor, mode, usedSpellPower, spellLvl); //calculating dmg to display - for(std::set::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it) + for(std::set::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it) { if(vstd::contains(sc.resisted, (*it)->ID)) //this creature resisted the spell continue; @@ -3976,7 +3993,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest } } int chainLightningModifier = 0; - for(std::set::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it) + for(auto it = attackedCres.begin(); it != attackedCres.end(); ++it) { if(vstd::contains(sc.resisted, (*it)->ID)) //this creature resisted the spell continue; @@ -4067,7 +4084,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest bonus = caster->getBonus(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, spellID)); si32 power = 0; - for(std::set::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it) + for(auto it = attackedCres.begin(); it != attackedCres.end(); ++it) { if(vstd::contains(sc.resisted, (*it)->ID)) //this creature resisted the spell continue; @@ -4154,7 +4171,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest StacksHealedOrResurrected shr; shr.lifeDrain = (ui8)false; shr.tentHealing = (ui8)false; - for(std::set::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it) + for(auto it = attackedCres.begin(); it != attackedCres.end(); ++it) { if(vstd::contains(sc.resisted, (*it)->ID) //this creature resisted the spell || (spellID == Spells::ANIMATE_DEAD && !(*it)->hasBonusOfType(Bonus::UNDEAD)) //we try to cast animate dead on living stack @@ -4172,7 +4189,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest hi.healedHP = gs->curB->calculateHealedHP(spell, usedSpellPower, spellLvl, *it); } else - hi.healedHP = gs->curB->calculateHealedHP(caster, spell, *it, gs->curB->getStack(selectedStack)); + hi.healedHP = gs->curB->calculateHealedHP(caster, spell, *it, gs->curB->battleGetStackByID(selectedStack)); hi.lowLevelResurrection = spellLvl <= 1; shr.healedStacks.push_back(hi); } @@ -4227,7 +4244,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest break; case Spells::CLONE: { - CStack * clonedStack = NULL; + const CStack * clonedStack = NULL; if (attackedCres.size()) clonedStack = *attackedCres.begin(); if (!clonedStack) @@ -4270,7 +4287,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest break; case Spells::DEATH_STARE: //handled in a bit different way { - for(std::set::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it) + for(auto it = attackedCres.begin(); it != attackedCres.end(); ++it) { if((*it)->hasBonusOfType(Bonus::UNDEAD) || (*it)->hasBonusOfType(Bonus::NON_LIVING)) //this creature is immune { @@ -4291,7 +4308,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest break; case Spells::ACID_BREATH_DAMAGE: //new effect, separate from acid breath defense reduction { - for(std::set::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it) //no immunities + for(auto it = attackedCres.begin(); it != attackedCres.end(); ++it) //no immunities { BattleStackAttacked bsa; bsa.flags |= BattleStackAttacked::EFFECT; @@ -4323,7 +4340,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest //Magic Mirror effect if (spell->isNegative() && mode != ECastingMode::MAGIC_MIRROR && spell->level && spell->range[0] == "0") //it is actual spell and can be reflected to single target, no recurrence { - for(std::set::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it) + for(auto it = attackedCres.begin(); it != attackedCres.end(); ++it) { int mirrorChance = (*it)->valOfBonuses(Bonus::MAGIC_MIRROR); if(mirrorChance > rand()%100) @@ -4388,7 +4405,7 @@ bool CGameHandler::makeCustomAction( BattleAction &ba ) ECastingMode::HERO_CASTING, NULL, ba.selectedStack); sendAndApply(&end_action); - if( !gs->curB->getStack(gs->curB->activeStack, false)->alive() ) + if( !gs->curB->battleGetStackByID(gs->curB->activeStack, false)->alive() ) { battleMadeAction.setn(true); } @@ -4426,11 +4443,11 @@ void CGameHandler::stackTurnTrigger(const CStack * st) { bool unbind = true; BonusList bl = *(st->getBonuses(Selector::type(Bonus::BIND_EFFECT))); - std::set stacks = gs->curB->getAdjacentCreatures(st); + std::set stacks = gs->curB-> batteAdjacentCreatures(st); BOOST_FOREACH(Bonus * b, bl) { - const CStack * stack = gs->curB->getStack(b->additionalInfo); //binding stack must be alive and adjacent + const CStack * stack = gs->curB->battleGetStackByID(b->additionalInfo); //binding stack must be alive and adjacent if (stack) { if (vstd::contains(stacks, stack)) //binding stack is still present @@ -4533,7 +4550,7 @@ void CGameHandler::stackTurnTrigger(const CStack * st) } } -void CGameHandler::handleDamageFromObstacle(const CObstacleInstance &obstacle, CStack * curStack) +void CGameHandler::handleDamageFromObstacle(const CObstacleInstance &obstacle, const CStack * curStack) { //we want to determine following vars depending on obstacle type int damage = -1; @@ -4552,7 +4569,7 @@ void CGameHandler::handleDamageFromObstacle(const CObstacleInstance &obstacle, C else if(obstacle.obstacleType == CObstacleInstance::LAND_MINE) { //You don't get hit by a Mine you can see. - if(gs->curB->isObstacleVisibleForSide(obstacle, side)) + if(gs->curB->battleIsObstacleVisibleForSide(obstacle, (BattlePerspective::BattlePerspective)side)) return; oneTimeObstacle = true; @@ -5168,7 +5185,7 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta { if (bat.bsa[g].newAmount > 0 && !bat.bsa[g].isSecondary()) //apply effects only to first target stack if it's alive { - oneOfAttacked = gs->curB->getStack(bat.bsa[g].stackAttacked); + oneOfAttacked = gs->curB->battleGetStackByID(bat.bsa[g].stackAttacked); break; } } @@ -5206,13 +5223,13 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta void CGameHandler::handleAttackBeforeCasting (const BattleAttack & bat) { - const CStack * attacker = gs->curB->getStack(bat.stackAttacking); + const CStack * attacker = gs->curB->battleGetStackByID(bat.stackAttacking); attackCasting(bat, Bonus::SPELL_BEFORE_ATTACK, attacker); //no detah stare / acid bretah needed? } void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat ) { - const CStack * attacker = gs->curB->getStack(bat.stackAttacking); + const CStack * attacker = gs->curB->battleGetStackByID(bat.stackAttacking); if (!attacker) //could be already dead return; attackCasting(bat, Bonus::SPELL_AFTER_ATTACK, attacker); @@ -5241,7 +5258,7 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat ) if (staredCreatures) { if (bat.bsa.size() && bat.bsa[0].newAmount > 0) //TODO: death stare was not originally available for multiple-hex attacks, but... - handleSpellCasting(79, 0, gs->curB->getStack(bat.bsa[0].stackAttacked)->position, + handleSpellCasting(79, 0, gs->curB->battleGetStackByID(bat.bsa[0].stackAttacked)->position, !attacker->attackerOwned, attacker->owner, NULL, NULL, staredCreatures, ECastingMode::AFTER_ATTACK_CASTING, attacker); } } @@ -5255,7 +5272,7 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat ) } if (acidDamage) { - handleSpellCasting(81, 0, gs->curB->getStack(bat.bsa[0].stackAttacked)->position, + handleSpellCasting(81, 0, gs->curB->battleGetStackByID(bat.bsa[0].stackAttacked)->position, !attacker->attackerOwned, attacker->owner, NULL, NULL, acidDamage * attacker->count, ECastingMode::AFTER_ATTACK_CASTING, attacker); } @@ -5716,6 +5733,7 @@ bool CGameHandler::swapStacks(const StackLocation &sl1, const StackLocation &sl2 void CGameHandler::runBattle() { + setBattle(gs->curB); assert(gs->curB); //TODO: pre-tactic stuff, call scripts etc. diff --git a/server/CGameHandler.h b/server/CGameHandler.h index f83bd982d..fb6eb67a3 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -201,7 +201,7 @@ public: int usedSpellPower, ECastingMode::ECastingMode mode, const CStack * stack, si32 selectedStack = -1); bool makeCustomAction(BattleAction &ba); void stackTurnTrigger(const CStack * stack); - void handleDamageFromObstacle(const CObstacleInstance &obstacle, CStack * curStack); //checks if obstacle is land mine and handles possible consequences + void handleDamageFromObstacle(const CObstacleInstance &obstacle, const CStack * curStack); //checks if obstacle is land mine and handles possible consequences void removeObstacle(const CObstacleInstance &obstacle); bool queryReply( ui32 qid, ui32 answer, ui8 player ); bool hireHero( const CGObjectInstance *obj, ui8 hid, ui8 player ); diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index 103b7ac9b..555605e10 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -251,7 +251,7 @@ bool MakeAction::applyGh( CGameHandler *gh ) if(gh->connections[b->sides[b->tacticsSide]] != c) ERROR_AND_RETURN; } - else if(gh->connections[b->getStack(b->activeStack)->owner] != c) + else if(gh->connections[b->battleGetStackByID(b->activeStack)->owner] != c) ERROR_AND_RETURN; return gh->makeBattleAction(ba); @@ -262,9 +262,10 @@ bool MakeCustomAction::applyGh( CGameHandler *gh ) const BattleInfo *b = GS(gh)->curB; if(!b) ERROR_AND_RETURN; if(b->tacticDistance) ERROR_AND_RETURN; - const CStack *active = GS(gh)->curB->getStack(GS(gh)->curB->activeStack); + const CStack *active = GS(gh)->curB->battleGetStackByID(GS(gh)->curB->activeStack); if(!active) ERROR_AND_RETURN; if(gh->connections[active->owner] != c) ERROR_AND_RETURN; + if(ba.actionType != BattleAction::HERO_SPELL) ERROR_AND_RETURN; return gh->makeCustomAction(ba); }