From 69eee05ccc3d080f626567ea13d9c842e0943574 Mon Sep 17 00:00:00 2001 From: beegee1 Date: Sun, 8 Dec 2013 17:54:13 +0000 Subject: [PATCH] - Partially fixed mantis #1065 (Gate with hex 95 can't be attacked) - Fixed 'catapult tried to attack non-catapultable hex!' problem, now catapult attacks attackable wall parts only - Fixed problem that the server performed applying damage on a wall part twice - Added methods for checking what wall parts are attackable and if a wall part is potentially attackable - Added functionality to trace net packages - Added functionality to trace std::vectors - Added tracing for CatapultAttack(CPack) - Updated various toString methods to use {} instead of [] - Refactoring --- Global.h | 19 +++++-- client/battle/CBattleInterface.cpp | 10 ++-- lib/BattleAction.cpp | 2 +- lib/BattleHex.cpp | 2 +- lib/BattleState.cpp | 6 +-- lib/BattleState.h | 2 +- lib/CBattleCallback.cpp | 80 ++++++++++++++++++++---------- lib/CBattleCallback.h | 11 ++-- lib/Connection.cpp | 2 +- lib/GameConstants.h | 8 +-- lib/NetPacks.h | 12 +++-- lib/NetPacksLib.cpp | 21 ++++++++ server/CGameHandler.cpp | 68 +++++++++++++------------ 13 files changed, 156 insertions(+), 87 deletions(-) diff --git a/Global.h b/Global.h index 0821e1317..d83ca6781 100644 --- a/Global.h +++ b/Global.h @@ -226,14 +226,23 @@ template char (&_ArrayCountObj(const T (&)[N]))[N]; /* VCMI standard library */ /* ---------------------------------------------------------------------------- */ template -std::ostream &operator<<(std::ostream &out, const boost::optional &opt) +std::ostream & operator<<(std::ostream & out, const boost::optional & opt) { - if(opt) - return out << *opt; - else - return out<< "empty"; + if(opt) return out << *opt; + else return out << "empty"; } +template +std::ostream & operator<<(std::ostream & out, const std::vector & container) +{ + out << "["; + for(auto it = container.begin(); it != container.end(); ++it) + { + out << *it; + if(std::prev(container.end()) != it) out << ", "; + } + return out << "]"; +} namespace vstd { diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index 591988734..1060b1987 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -1156,14 +1156,12 @@ bool CBattleInterface::isTileAttackable(const BattleHex & number) const bool CBattleInterface::isCatapultAttackable(BattleHex hex) const { - if(!siegeH || tacticsMode) - return false; + if(!siegeH || tacticsMode) return false; - int wallUnder = curInt->cb->battleHexToWallPart(hex); - if(wallUnder < 0) //invalid or indestructible - return false; + auto wallPart = curInt->cb->battleHexToWallPart(hex); + if(!curInt->cb->isWallPartPotentiallyAttackable(wallPart)) return false; - auto state = curInt->cb->battleGetWallState(wallUnder); + auto state = curInt->cb->battleGetWallState(static_cast(wallPart)); return state != EWallState::DESTROYED && state != EWallState::NONE; } diff --git a/lib/BattleAction.cpp b/lib/BattleAction.cpp index 2cda10d59..0f93315b4 100644 --- a/lib/BattleAction.cpp +++ b/lib/BattleAction.cpp @@ -98,6 +98,6 @@ std::ostream & operator<<(std::ostream & os, const BattleAction & ba) std::stringstream actionTypeStream; actionTypeStream << ba.actionType; - return os << boost::str(boost::format("[BattleAction: side '%d', stackNumber '%d', actionType '%s', destinationTile '%s', additionalInfo '%d', selectedStack '%d']") + return os << boost::str(boost::format("{BattleAction: side '%d', stackNumber '%d', actionType '%s', destinationTile '%s', additionalInfo '%d', selectedStack '%d'}") % static_cast(ba.side) % ba.stackNumber % actionTypeStream.str() % ba.destinationTile % ba.additionalInfo % ba.selectedStack); } diff --git a/lib/BattleHex.cpp b/lib/BattleHex.cpp index 362a4ae8a..654197d44 100644 --- a/lib/BattleHex.cpp +++ b/lib/BattleHex.cpp @@ -161,5 +161,5 @@ BattleHex BattleHex::getClosestTile(bool attackerOwned, BattleHex initialPos, st std::ostream & operator<<(std::ostream & os, const BattleHex & hex) { - return os << boost::str(boost::format("[BattleHex: x '%d', y '%d', hex '%d']") % hex.getX() % hex.getY() % hex.hex); + return os << boost::str(boost::format("{BattleHex: x '%d', y '%d', hex '%d'}") % hex.getX() % hex.getY() % hex.hex); } diff --git a/lib/BattleState.cpp b/lib/BattleState.cpp index 4b8013e16..e4b3cd24b 100644 --- a/lib/BattleState.cpp +++ b/lib/BattleState.cpp @@ -386,13 +386,13 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, ETerrainType terrain, BFieldTyp if (!town->hasBuilt(BuildingID::CITADEL)) { - curB->si.wallState[EWallParts::KEEP] = EWallState::NONE; + curB->si.wallState[EWallPart::KEEP] = EWallState::NONE; } if (!town->hasBuilt(BuildingID::CASTLE)) { - curB->si.wallState[EWallParts::UPPER_TOWER] = EWallState::NONE; - curB->si.wallState[EWallParts::BOTTOM_TOWER] = EWallState::NONE; + curB->si.wallState[EWallPart::UPPER_TOWER] = EWallState::NONE; + curB->si.wallState[EWallPart::BOTTOM_TOWER] = EWallState::NONE; } } diff --git a/lib/BattleState.h b/lib/BattleState.h index 913d3c238..93a2cda0f 100644 --- a/lib/BattleState.h +++ b/lib/BattleState.h @@ -33,7 +33,7 @@ struct BattleStackAttacked; //only for use in BattleInfo struct DLL_LINKAGE SiegeInfo { - std::array wallState; + std::array wallState; // return EWallState decreased by value of damage points static EWallState::EWallState applyDamage(EWallState::EWallState state, unsigned int value) diff --git a/lib/CBattleCallback.cpp b/lib/CBattleCallback.cpp index 700166919..9be2f591d 100644 --- a/lib/CBattleCallback.cpp +++ b/lib/CBattleCallback.cpp @@ -54,38 +54,38 @@ namespace SiegeStuffThatShouldBeMovedToHandlers // <=== TODO return stackLeft != destLeft; } - //potentially attackable parts of wall - static const std::pair attackable[] = + // parts of wall + static const std::pair wallParts[] = { - 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_WALL), - 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) + std::make_pair(50, EWallPart::KEEP), + std::make_pair(183, EWallPart::BOTTOM_TOWER), + std::make_pair(182, EWallPart::BOTTOM_WALL), + std::make_pair(130, EWallPart::BELOW_GATE), + std::make_pair(62, EWallPart::OVER_GATE), + std::make_pair(29, EWallPart::UPPER_WALL), + std::make_pair(12, EWallPart::UPPER_TOWER), + std::make_pair(95, EWallPart::INDESTRUCTIBLE_PART_OF_GATE), + std::make_pair(96, EWallPart::GATE), + std::make_pair(45, EWallPart::INDESTRUCTIBLE_PART), + std::make_pair(78, EWallPart::INDESTRUCTIBLE_PART), + std::make_pair(112, EWallPart::INDESTRUCTIBLE_PART), + std::make_pair(147, EWallPart::INDESTRUCTIBLE_PART) }; - static EWallParts::EWallParts hexToWallPart(BattleHex hex) + static EWallPart::EWallPart hexToWallPart(BattleHex hex) { - for(auto & elem : attackable) + for(auto & elem : wallParts) { if(elem.first == hex) return elem.second; } - return EWallParts::INVALID; //not found! + return EWallPart::INVALID; //not found! } - static BattleHex WallPartToHex(EWallParts::EWallParts part) + static BattleHex WallPartToHex(EWallPart::EWallPart part) { - for(auto & elem : attackable) + for(auto & elem : wallParts) { if(elem.second == part) return elem.first; @@ -425,7 +425,7 @@ si8 CBattleInfoEssentials::battleGetWallState(int partOfWall) const if(getBattle()->siege == CGTownInstance::NONE) return EWallState::NONE; - assert(partOfWall >= 0 && partOfWall < EWallParts::PARTS_COUNT); + assert(partOfWall >= 0 && partOfWall < EWallPart::PARTS_COUNT); return getBattle()->si.wallState[partOfWall]; } @@ -452,8 +452,7 @@ si8 CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer *bonusBearer, B if (shooterPosition > destHex && ((destHex % GameConstants::BFIELD_WIDTH - shooterPosition % GameConstants::BFIELD_WIDTH) < 2)) //shooting up high row -= 2; const int wallPos = lineToWallHex(row); - if (battleHexToWallPart(wallPos) < 0) //wall still exists or is indestructible - return true; + if (!isWallPartPotentiallyAttackable(battleHexToWallPart(wallPos))) return true; } return false; @@ -1094,7 +1093,7 @@ AccessibilityInfo CBattleInfoCallback::getAccesibility() const } //gate -> should be before stacks - if(battleGetSiegeLevel() > 0 && battleGetWallState(EWallParts::GATE) != EWallState::DESTROYED) + if(battleGetSiegeLevel() > 0 && battleGetWallState(EWallPart::GATE) != EWallState::DESTROYED) { ret[95] = ret[96] = EAccessibility::GATE; //block gate's hexes } @@ -1514,18 +1513,45 @@ si8 CBattleInfoCallback::battleHasDistancePenalty(const IBonusBearer *bonusBeare return true; } -BattleHex CBattleInfoCallback::wallPartToBattleHex(EWallParts::EWallParts part) const +BattleHex CBattleInfoCallback::wallPartToBattleHex(EWallPart::EWallPart part) const { RETURN_IF_NOT_BATTLE(BattleHex::INVALID); return WallPartToHex(part); } -EWallParts::EWallParts CBattleInfoCallback::battleHexToWallPart(BattleHex hex) const +EWallPart::EWallPart CBattleInfoCallback::battleHexToWallPart(BattleHex hex) const { - RETURN_IF_NOT_BATTLE(EWallParts::INVALID); + RETURN_IF_NOT_BATTLE(EWallPart::INVALID); return hexToWallPart(hex); } +bool CBattleInfoCallback::isWallPartPotentiallyAttackable(EWallPart::EWallPart wallPart) const +{ + RETURN_IF_NOT_BATTLE(false); + return wallPart != EWallPart::INDESTRUCTIBLE_PART && wallPart != EWallPart::INDESTRUCTIBLE_PART_OF_GATE && + wallPart != EWallPart::INVALID; +} + +std::vector CBattleInfoCallback::getAttackableBattleHexes() const +{ + std::vector attackableBattleHexes; + RETURN_IF_NOT_BATTLE(attackableBattleHexes); + + for(auto & wallPartPair : wallParts) + { + if(isWallPartPotentiallyAttackable(wallPartPair.second)) + { + auto wallState = static_cast(battleGetWallState(static_cast(wallPartPair.second))); + if(wallState == EWallState::INTACT || wallState == EWallState::DAMAGED) + { + attackableBattleHexes.push_back(BattleHex(wallPartPair.first)); + } + } + } + + return attackableBattleHexes; +} + ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const { RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID); diff --git a/lib/CBattleCallback.h b/lib/CBattleCallback.h index b56c3077d..ced7748e3 100644 --- a/lib/CBattleCallback.h +++ b/lib/CBattleCallback.h @@ -170,7 +170,6 @@ public: std::vector > battleGetAllObstacles(boost::optional perspective = boost::none) const; //returns all obstacles on the battlefield TStacks battleGetAllStacks(bool includeTurrets = false) const; //returns all stacks, alive or dead or undead or mechanical :) bool battleHasNativeStack(ui8 side) const; - si8 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, nullptr instead const CStack *battleActiveStack() const; @@ -186,6 +185,10 @@ public: const CArmedInstance * battleGetArmyObject(ui8 side) const; InfoAboutHero battleGetHeroInfo(ui8 side) 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 + si8 battleGetWallState(int partOfWall) const; + //helpers TStacks battleAliveStacks() const; TStacks battleAliveStacks(ui8 side) const; @@ -251,8 +254,10 @@ public: si8 battleHasWallPenalty(const CStack * stack, BattleHex destHex) const; //checks if given stack has wall penalty si8 battleHasWallPenalty(const IBonusBearer *bonusBearer, BattleHex shooterPosition, BattleHex destHex) const; //checks if given stack has wall penalty - BattleHex wallPartToBattleHex(EWallParts::EWallParts part) const; - EWallParts::EWallParts battleHexToWallPart(BattleHex hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found + BattleHex wallPartToBattleHex(EWallPart::EWallPart part) const; + EWallPart::EWallPart battleHexToWallPart(BattleHex hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found + bool isWallPartPotentiallyAttackable(EWallPart::EWallPart wallPart) const; // returns true if the wall part is potentially attackable (independent of wall state), false if not + std::vector getAttackableBattleHexes() const; //*** 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 diff --git a/lib/Connection.cpp b/lib/Connection.cpp index 8a04eaaf5..d5512beaa 100644 --- a/lib/Connection.cpp +++ b/lib/Connection.cpp @@ -224,7 +224,7 @@ CPack * CConnection::retreivePack() boost::unique_lock lock(*rmx); logNetwork->traceStream() << "Listening... "; *this >> ret; - logNetwork->traceStream() << "\treceived server message of type " << typeid(*ret).name(); + logNetwork->traceStream() << "\treceived server message of type " << typeid(*ret).name() << ", data: " << ret; return ret; } diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 3cb479eb9..9e881a6b3 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -439,13 +439,13 @@ namespace ECommander const int MAX_SKILL_LEVEL = 5; } -namespace EWallParts +namespace EWallPart { - enum EWallParts + enum EWallPart { - INDESTRUCTIBLE_PART = -2, INVALID = -1, + INDESTRUCTIBLE_PART_OF_GATE = -3, INDESTRUCTIBLE_PART = -2, INVALID = -1, KEEP = 0, BOTTOM_TOWER, BOTTOM_WALL, BELOW_GATE, OVER_GATE, UPPER_WALL, UPPER_TOWER, GATE, - PARTS_COUNT + PARTS_COUNT /* This constant SHOULD always stay as the last item in the enum. */ }; } diff --git a/lib/NetPacks.h b/lib/NetPacks.h index 01c754691..ec9508897 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -48,10 +48,12 @@ struct CPack { logNetwork->errorStream() << "CPack serialized... this should not happen!"; } - void applyGs(CGameState *gs) - {}; + void applyGs(CGameState *gs) { } + virtual std::string toString() const { return boost::str(boost::format("{CPack: type '%d'}") % type); } }; +std::ostream & operator<<(std::ostream & out, const CPack * pack); + struct CPackForClient : public CPack { CPackForClient(){type = 1;}; @@ -1678,6 +1680,8 @@ struct CatapultAttack : public CPackForClient //3015 ui8 attackedPart; ui8 damageDealt; + DLL_LINKAGE std::string toString() const; + template void serialize(Handler &h, const int version) { h & destinationTile & attackedPart & damageDealt; @@ -1688,9 +1692,9 @@ struct CatapultAttack : public CPackForClient //3015 DLL_LINKAGE void applyGs(CGameState *gs); void applyCl(CClient *cl); + DLL_LINKAGE std::string toString() const override; std::vector< AttackInfo > attackedParts; - int attacker; //if -1, then a spell caused this template void serialize(Handler &h, const int version) @@ -1699,6 +1703,8 @@ struct CatapultAttack : public CPackForClient //3015 } }; +DLL_LINKAGE std::ostream & operator<<(std::ostream & out, const CatapultAttack::AttackInfo & attackInfo); + struct BattleStacksRemoved : public CPackForClient //3016 { BattleStacksRemoved(){type = 3016;} diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 1f27600b2..7cc0b909d 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -35,6 +35,11 @@ #undef max #endif +std::ostream & operator<<(std::ostream & out, const CPack * pack) +{ + return out << pack->toString(); +} + DLL_LINKAGE void SetResource::applyGs( CGameState *gs ) { assert(player < PlayerColor::PLAYER_LIMIT); @@ -1508,6 +1513,22 @@ DLL_LINKAGE void CatapultAttack::applyGs( CGameState *gs ) } } +DLL_LINKAGE std::string CatapultAttack::AttackInfo::toString() const +{ + return boost::str(boost::format("{AttackInfo: destinationTile '%d', attackedPart '%d', damageDealt '%d'}") + % destinationTile % static_cast(attackedPart) % static_cast(damageDealt)); +} + +DLL_LINKAGE std::ostream & operator<<(std::ostream & out, const CatapultAttack::AttackInfo & attackInfo) +{ + return out << attackInfo.toString(); +} + +DLL_LINKAGE std::string CatapultAttack::toString() const +{ + return boost::str(boost::format("{CatapultAttack: attackedParts '%s', attacker '%d'}") % attackedParts % attacker); +} + DLL_LINKAGE void BattleStacksRemoved::applyGs( CGameState *gs ) { if(!gs->curB) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index f64e1e6fa..11b5126f4 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3476,21 +3476,21 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) } case Battle::CATAPULT: { - auto getCatapultHitChance = [&](EWallParts::EWallParts part, const CHeroHandler::SBallisticsLevelInfo & sbi) -> int + auto getCatapultHitChance = [&](EWallPart::EWallPart part, const CHeroHandler::SBallisticsLevelInfo & sbi) -> int { switch(part) { - case EWallParts::GATE: + case EWallPart::GATE: return sbi.gate; - case EWallParts::KEEP: + case EWallPart::KEEP: return sbi.keep; - case EWallParts::BOTTOM_TOWER: - case EWallParts::UPPER_TOWER: + case EWallPart::BOTTOM_TOWER: + case EWallPart::UPPER_TOWER: return sbi.tower; - case EWallParts::BOTTOM_WALL: - case EWallParts::BELOW_GATE: - case EWallParts::OVER_GATE: - case EWallParts::UPPER_WALL: + case EWallPart::BOTTOM_WALL: + case EWallPart::BELOW_GATE: + case EWallPart::OVER_GATE: + case EWallPart::UPPER_WALL: return sbi.wall; default: return 0; @@ -3504,8 +3504,8 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) const CGHeroInstance * attackingHero = gs->curB->battleGetFightingHero(ba.side); CHeroHandler::SBallisticsLevelInfo sbi = VLC->heroh->ballistics.at(attackingHero->getSecSkillLevel(SecondarySkill::BALLISTICS)); - EWallParts::EWallParts desiredTarget = gs->curB->battleHexToWallPart(ba.destinationTile); - if(desiredTarget < 0) + auto wallPart = gs->curB->battleHexToWallPart(ba.destinationTile); + if(!gs->curB->isWallPartPotentiallyAttackable(wallPart)) { complain("catapult tried to attack non-catapultable hex!"); break; @@ -3514,7 +3514,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) //in successive iterations damage is dealt but not yet subtracted from wall's HPs auto ¤tHP = gs->curB->si.wallState; - if (currentHP.at(desiredTarget) == EWallState::DESTROYED || currentHP.at(desiredTarget) == EWallState::NONE) + if (currentHP.at(wallPart) == EWallState::DESTROYED || currentHP.at(wallPart) == EWallState::NONE) { complain("catapult tried to attack already destroyed wall part!"); break; @@ -3523,7 +3523,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) for(int g=0; g allowedTargets; + std::vector allowedTargets; for (size_t i=0; i< currentHP.size(); i++) { if (currentHP.at(i) != EWallState::DESTROYED && currentHP.at(i) != EWallState::NONE) - allowedTargets.push_back(EWallParts::EWallParts(i)); + allowedTargets.push_back(EWallPart::EWallPart(i)); } if (allowedTargets.empty()) break; @@ -3569,32 +3569,30 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) { if(dmgRand <= dmgChance[damage]) { - currentHP[attackedPart] = SiegeInfo::applyDamage(EWallState::EWallState(currentHP.at(attackedPart)), damage); - attack.damageDealt = damage; break; } } // attacked tile may have changed - update destination - attack.destinationTile = gs->curB->wallPartToBattleHex(EWallParts::EWallParts(attack.attackedPart)); + attack.destinationTile = gs->curB->wallPartToBattleHex(EWallPart::EWallPart(attack.attackedPart)); logGlobal->traceStream() << "Catapult attacks " << (int)attack.attackedPart << " dealing " << (int)attack.damageDealt << " damage"; //removing creatures in turrets / keep if one is destroyed - if(attack.damageDealt > 0 && (attackedPart == EWallParts::KEEP || - attackedPart == EWallParts::BOTTOM_TOWER || attackedPart == EWallParts::UPPER_TOWER)) + if(attack.damageDealt > 0 && (attackedPart == EWallPart::KEEP || + attackedPart == EWallPart::BOTTOM_TOWER || attackedPart == EWallPart::UPPER_TOWER)) { int posRemove = -1; switch(attackedPart) { - case EWallParts::KEEP: + case EWallPart::KEEP: posRemove = -2; break; - case EWallParts::BOTTOM_TOWER: + case EWallPart::BOTTOM_TOWER: posRemove = -3; break; - case EWallParts::UPPER_TOWER: + case EWallPart::UPPER_TOWER: posRemove = -4; break; } @@ -5975,20 +5973,26 @@ void CGameHandler::runBattle() if(next->getCreature()->idNumber == CreatureID::CATAPULT && (!curOwner || curOwner->getSecSkillLevel(SecondarySkill::BALLISTICS) == 0)) //catapult, hero has no ballistics { - BattleAction attack; - static const int wallHexes[] = {50, 183, 182, 130, 62, 29, 12, 95}; + const auto & attackableBattleHexes = curB.getAttackableBattleHexes(); - attack.destinationTile = wallHexes[ rand()%ARRAY_COUNT(wallHexes) ]; - attack.actionType = Battle::CATAPULT; - attack.additionalInfo = 0; - attack.side = !next->attackerOwned; - attack.stackNumber = next->ID; + if(!attackableBattleHexes.empty()) + { + BattleAction attack; + attack.destinationTile = attackableBattleHexes[rand() % attackableBattleHexes.size()]; + attack.actionType = Battle::CATAPULT; + attack.additionalInfo = 0; + attack.side = !next->attackerOwned; + attack.stackNumber = next->ID; - makeAutomaticAction(next, attack); + makeAutomaticAction(next, attack); + } + else + { + makeStackDoNothing(next); + } continue; } - if(next->getCreature()->idNumber == CreatureID::FIRST_AID_TENT) { std::vector< const CStack * > possibleStacks;