From a53ec235562342bf71c843412197772b099ab538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=2E=20Urba=C5=84czyk?= Date: Sat, 8 Jan 2011 18:33:40 +0000 Subject: [PATCH] Stupid AI is capable of winning / losing battle. --- AI/GeniusAI/BattleLogic.cpp | 2 +- AI/StupidAI/StupidAI.cpp | 156 +++++++++++++++++++++++++++++++----- AI/StupidAI/StupidAI.h | 1 + CCallback.cpp | 60 ++++++++++++-- CCallback.h | 10 ++- client/CBattleInterface.cpp | 16 ++-- global.h | 19 ++++- lib/BattleAction.cpp | 24 +----- lib/BattleAction.h | 4 +- lib/BattleState.cpp | 83 +++++++++++++++---- lib/BattleState.h | 27 ++++--- lib/CGameState.cpp | 5 +- server/CGameHandler.cpp | 79 +++++------------- server/CGameHandler.h | 3 +- 14 files changed, 336 insertions(+), 153 deletions(-) diff --git a/AI/GeniusAI/BattleLogic.cpp b/AI/GeniusAI/BattleLogic.cpp index 4b16cc1bb..9187d8d77 100644 --- a/AI/GeniusAI/BattleLogic.cpp +++ b/AI/GeniusAI/BattleLogic.cpp @@ -75,7 +75,7 @@ void CBattleLogic::SetCurrentTurn(int turn) void CBattleLogic::MakeStatistics(int currentCreatureId) { typedef std::vector vector_stacks; - vector_stacks allStacks = m_cb->battleGetStacks(); + vector_stacks allStacks = m_cb->battleGetStacks(false); const CStack *currentStack = m_cb->battleGetStackByID(currentCreatureId); if(currentStack->position < 0) //turret { diff --git a/AI/StupidAI/StupidAI.cpp b/AI/StupidAI/StupidAI.cpp index dbe4df395..cf532efcd 100644 --- a/AI/StupidAI/StupidAI.cpp +++ b/AI/StupidAI/StupidAI.cpp @@ -2,6 +2,13 @@ #include "StupidAI.h" #include "../../lib/BattleState.h" #include "../../CCallback.h" +#include +#include +#include "../../lib/CCreatureHandler.h" +#include +#include + +IBattleCallback * cbc; CStupidAI::CStupidAI(void) : side(-1), cb(NULL) @@ -18,7 +25,7 @@ CStupidAI::~CStupidAI(void) void CStupidAI::init( IBattleCallback * CB ) { print("init called, saving ptr to IBattleCallback"); - cb = CB; + cbc = cb = CB; } void CStupidAI::actionFinished( const BattleAction *action ) @@ -31,39 +38,121 @@ void CStupidAI::actionStarted( const BattleAction *action ) print("actionStarted called"); } +struct EnemyInfo +{ + const CStack * s; + int adi, adr; + std::vector attackFrom; //for melee fight + EnemyInfo(const CStack * _s) : s(_s) + {} + void calcDmg(const CStack * ourStack) + { + TDmgRange retal, dmg = cbc->battleEstimateDamage(ourStack, s, &retal); + adi = (dmg.first + dmg.second) / 2; + adr = (retal.first + retal.second) / 2; + } + + bool operator==(const EnemyInfo& ei) const + { + return s == ei.s; + } +}; + +bool isMoreProfitable(const EnemyInfo &ei1, const EnemyInfo& ei2) +{ + return (ei1.adi-ei1.adr) < (ei2.adi - ei2.adr); +} + +int distToNearestNeighbour(THex hex, const std::vector & dists, THex *chosenHex = NULL) +{ + int ret = 1000000; + BOOST_FOREACH(THex n, hex.neighbouringTiles()) + { + if(dists[n] >= 0 && dists[n] < ret) + { + ret = dists[n]; + if(chosenHex) + *chosenHex = n; + } + } + + return ret; +} + +bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const std::vector & dists) +{ + return distToNearestNeighbour(ei1.s->position, dists) < distToNearestNeighbour(ei2.s->position, dists); +} + +static bool willSecondHexBlockMoreEnemyShooters(const THex &h1, const THex &h2) +{ + int shooters[2] = {0}; //count of shooters on hexes + + for(int i = 0; i < 2; i++) + BOOST_FOREACH(THex neighbour, (i ? h2 : h1).neighbouringTiles()) + if(const CStack *s = cbc->battleGetStackByPos(neighbour)) + if(s->getCreature()->isShooting()) + shooters[i]++; + + return shooters[0] < shooters[1]; +} + BattleAction CStupidAI::activeStack( const CStack * stack ) { + boost::this_thread::sleep(boost::posix_time::seconds(2)); print("activeStack called"); std::vector avHexes = cb->battleGetAvailableHexes(stack, false); - std::vector avEnemies; - for(int g=0; g dists = cb->battleGetDistances(stack); + std::vector enemiesShootable, enemiesReachable, enemiesUnreachable; + + BOOST_FOREACH(const CStack *s, cb->battleGetStacks()) { - const CStack * enemy = cb->battleGetStackByPos(avHexes[g]); - if (enemy) + if(s->owner != stack->owner) { - avEnemies.push_back(enemy); + if(cb->battleCanShoot(stack, s->position)) + { + enemiesShootable.push_back(s); + } + else + { + BOOST_FOREACH(THex hex, avHexes) + { + if(CStack::isMeleeAttackPossible(stack, s, hex)) + { + std::vector::iterator i = std::find(enemiesReachable.begin(), enemiesReachable.end(), s); + if(i == enemiesReachable.end()) + { + enemiesReachable.push_back(s); + i = enemiesReachable.begin() + (enemiesReachable.size() - 1); + } + + i->attackFrom.push_back(hex); + } + } + + if(!vstd::contains(enemiesReachable, s)) + enemiesUnreachable.push_back(s); + } } } - - if(stack->position % 17 < 5) //move army little towards enemy + if(enemiesShootable.size()) { - THex dest = stack->position + !side*2 - 1; - print(stack->nodeName() + "will be moved to " + boost::lexical_cast(dest)); - return BattleAction::makeMove(stack, dest); + const EnemyInfo &ei= *std::max_element(enemiesShootable.begin(), enemiesShootable.end(), isMoreProfitable); + return BattleAction::makeShotAttack(stack, ei.s); } - - if(avEnemies.size()) + else if(enemiesReachable.size()) { - const CStack * enemy = avEnemies[0]; - //shooting - if (cb->battleCanShoot(stack, enemy->position)) + const EnemyInfo &ei= *std::max_element(enemiesReachable.begin(), enemiesReachable.end(), &isMoreProfitable); + return BattleAction::makeMeleeAttack(stack, ei.s, *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), &willSecondHexBlockMoreEnemyShooters)); + } + else + { + const EnemyInfo &ei= *std::min_element(enemiesUnreachable.begin(), enemiesUnreachable.end(), boost::bind(isCloser, _1, _2, boost::ref(dists))); + if(distToNearestNeighbour(ei.s->position, dists) < BFIELD_SIZE) { - return BattleAction::makeShotAttack(stack, enemy); + return goTowards(stack, ei.s->position); } - - //melee - return BattleAction::makeMeleeAttack(stack, enemy, avHexes); } return BattleAction::makeDefend(stack); @@ -148,4 +237,29 @@ void CStupidAI::battleStacksRemoved(const BattleStacksRemoved & bsr) void CStupidAI::print(const std::string &text) const { tlog0 << "CStupidAI [" << this <<"]: " << text << std::endl; -} \ No newline at end of file +} + +BattleAction CStupidAI::goTowards(const CStack * stack, THex hex) +{ + THex realDest = hex; + int predecessors[BFIELD_SIZE]; + std::vector dists = cb->battleGetDistances(stack, hex); + if(distToNearestNeighbour(hex, dists, &realDest) > BFIELD_SIZE) + { + print("goTowards: Cannot reach"); + return BattleAction::makeDefend(stack); + } + + dists = cb->battleGetDistances(stack, realDest, predecessors); + std::vector avHexes = cb->battleGetAvailableHexes(stack, false); + + while(1) + { + assert(realDest.isValid()); + if(vstd::contains(avHexes, hex)) + return BattleAction::makeMove(stack, hex); + + hex = predecessors[hex]; + } +} + diff --git a/AI/StupidAI/StupidAI.h b/AI/StupidAI/StupidAI.h index ee7b16488..2590de7b4 100644 --- a/AI/StupidAI/StupidAI.h +++ b/AI/StupidAI/StupidAI.h @@ -31,5 +31,6 @@ public: void battleCatapultAttacked(const CatapultAttack & ca) OVERRIDE; //called when catapult makes an attack void battleStacksRemoved(const BattleStacksRemoved & bsr) OVERRIDE; //called when certain stack is completely removed from battlefield + BattleAction goTowards(const CStack * stack, THex hex ); }; diff --git a/CCallback.cpp b/CCallback.cpp index c2b059ef0..9bb20214d 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -539,7 +539,7 @@ int CBattleCallback::battleGetPos(int stack) return -1; } -std::vector CBattleCallback::battleGetStacks() +std::vector CBattleCallback::battleGetStacks(bool onlyAlive /*= true*/) { boost::shared_lock lock(*gs->mx); std::vector ret; @@ -550,7 +550,8 @@ std::vector CBattleCallback::battleGetStacks() } BOOST_FOREACH(const CStack *s, gs->curB->stacks) - ret.push_back(s); + if(s->alive() || !onlyAlive) + ret.push_back(s); return ret; } @@ -628,12 +629,13 @@ int CBattleCallback::battleGetWallUnderHex(int hex) return gs->curB->hexToWallPart(hex); } -std::pair CBattleCallback::battleEstimateDamage(int attackerID, int defenderID) +TDmgRange CBattleCallback::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->side1 == player) { @@ -646,10 +648,27 @@ std::pair CBattleCallback::battleEstimateDamage(int attackerID, int defenderHero = gs->curB->heroes[0]; } - const CStack * attacker = gs->curB->getStack(attackerID, false), - * defender = gs->curB->getStack(defenderID); + TDmgRange ret = gs->curB->calculateDmgRange(attacker, defender, attackerHero, defenderHero, shooting, 0, false); - return gs->curB->calculateDmgRange(attacker, defender, attackerHero, defenderHero, battleCanShoot(attacker, defender->position), 0, 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, false, false).*pairElems[!i]; + } + } + } + + return ret; } ui8 CBattleCallback::battleGetSiegeLevel() @@ -1039,4 +1058,31 @@ CBattleCallback::CBattleCallback(CGameState *GS, int Player, CClient *C ) gs = GS; player = Player; cl = C; -} \ No newline at end of file +} + +std::vector CBattleCallback::battleGetDistances(const CStack * stack, THex hex /*= THex::INVALID*/, int * predecessors /*= NULL*/) +{ + if(!hex.isValid()) + hex = stack->position; + + std::vector ret; + bool ac[BFIELD_SIZE]; + int pr[BFIELD_SIZE], dist[BFIELD_SIZE]; + gs->curB->makeBFS(stack->position, ac, pr, dist, stack->doubleWide(), stack->attackerOwned, stack->hasBonusOfType(Bonus::FLYING), false); + + for(int i=0; i battleGetStacks()=0; //returns stacks on battlefield + virtual std::vector battleGetStacks(bool onlyAlive = true)=0; //returns stacks on battlefield virtual void getStackQueue( std::vector &out, int howMany )=0; //returns vector of stack in order of their move sequence virtual std::vector battleGetAvailableHexes(const CStack * stack, bool addOccupiable)=0; //returns numbers of hexes reachable by creature with id ID + virtual std::vector battleGetDistances(const CStack * stack, THex hex = THex::INVALID, int * predecessors = NULL)=0; //returns vector of distances to [dest hex number] virtual bool battleCanShoot(const CStack * stack, THex dest)=0; //returns true if unit with id ID can shoot to dest virtual bool battleCanCastSpell()=0; //returns true, if caller can cast a spell virtual bool battleCanFlee()=0; //returns true if caller can flee from the battle virtual const CGTownInstance * battleGetDefendedTown()=0; //returns defended town if current battle is a siege, NULL instead virtual ui8 battleGetWallState(int partOfWall)=0; //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 virtual int battleGetWallUnderHex(int hex)=0; //returns part of destructible wall / gate / keep under given hex or -1 if not found - virtual std::pair battleEstimateDamage(int attackerID, int defenderID)=0; //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair + virtual TDmgRange battleEstimateDamage(const CStack * attacker, const CStack * defender, TDmgRange * retaliationDmg = NULL)=0; //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair virtual ui8 battleGetSiegeLevel()=0; //returns 0 when there is no siege, 1 if fort, 2 is citadel, 3 is castle virtual const CGHeroInstance * battleGetFightingHero(ui8 side) const =0; //returns hero corresponding to given side (0 - attacker, 1 - defender) virtual si8 battleHasDistancePenalty(const CStack * stack, THex destHex) =0; //checks if given stack has distance penalty @@ -209,16 +210,17 @@ public: const CStack * battleGetStackByPos(THex pos, bool onlyAlive = true) OVERRIDE; //returns stack info by given pos int battleGetPos(int stack) OVERRIDE; //returns position (tile ID) of stack int battleMakeAction(BattleAction* action) OVERRIDE;//for casting spells by hero - DO NOT use it for moving active stack - std::vector battleGetStacks() OVERRIDE; //returns stacks on battlefield + std::vector battleGetStacks(bool onlyAlive = true) OVERRIDE; //returns stacks on battlefield void getStackQueue( std::vector &out, int howMany ) OVERRIDE; //returns vector of stack in order of their move sequence std::vector battleGetAvailableHexes(const CStack * stack, bool addOccupiable) OVERRIDE; //reutrns numbers of hexes reachable by creature with id ID + std::vector battleGetDistances(const CStack * stack, THex hex = THex::INVALID, int * predecessors = NULL) OVERRIDE; //returns vector of distances to [dest hex number]; if predecessors is not null, it must point to BFIELD_SIZE * sizeof(int) of allocated memory bool battleCanShoot(const CStack * stack, THex dest) OVERRIDE; //returns true if unit with id ID can shoot to dest bool battleCanCastSpell() OVERRIDE; //returns true, if caller can cast a spell bool battleCanFlee() OVERRIDE; //returns true if caller can flee from the battle const CGTownInstance * battleGetDefendedTown() OVERRIDE; //returns defended town if current battle is a siege, NULL instead ui8 battleGetWallState(int partOfWall) OVERRIDE; //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(int hex) OVERRIDE; //returns part of destructible wall / gate / keep under given hex or -1 if not found - std::pair battleEstimateDamage(int attackerID, int defenderID) OVERRIDE; //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair + TDmgRange battleEstimateDamage(const CStack * attacker, const CStack * defender, TDmgRange * retaliationDmg = NULL) OVERRIDE; //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair ui8 battleGetSiegeLevel() OVERRIDE; //returns 0 when there is no siege, 1 if fort, 2 is citadel, 3 is castle const CGHeroInstance * battleGetFightingHero(ui8 side) const OVERRIDE; //returns hero corresponding ot given side (0 - attacker, 1 - defender) si8 battleHasDistancePenalty(const CStack * stack, THex destHex) OVERRIDE; //checks if given stack has distance penalty diff --git a/client/CBattleInterface.cpp b/client/CBattleInterface.cpp index a1bf102a6..d52002e1d 100644 --- a/client/CBattleInterface.cpp +++ b/client/CBattleInterface.cpp @@ -913,6 +913,10 @@ bool CMeleeAttack::init() static const int mutPosToGroup[] = {11, 11, 12, 13, 13, 12}; int mutPos = THex::mutualPosition(attackingStackPosBeforeReturn + reversedShift, dest); + if(mutPos == -1 && attackedStack->doubleWide()) + { + mutPos = THex::mutualPosition(attackingStackPosBeforeReturn + reversedShift, attackedStack->occupiedHex()); + } switch(mutPos) //attack direction { case 0: case 1: case 2: case 3: case 4: case 5: @@ -920,6 +924,8 @@ bool CMeleeAttack::init() break; default: tlog1<<"Critical Error! Wrong dest in stackAttacking! dest: "<army1 = army1; this->army2 = army2; - std::vector stacks = curInt->cb->battleGetStacks(); + std::vector stacks = curInt->cb->battleGetStacks(false); BOOST_FOREACH(const CStack *s, stacks) { newStack(s); @@ -1455,7 +1461,7 @@ void CBattleInterface::deactivate() void CBattleInterface::show(SDL_Surface * to) { - std::vector stacks = curInt->cb->battleGetStacks(); //used in a few places + std::vector stacks = curInt->cb->battleGetStacks(false); //used in a few places ++animCount; if(!to) //"evaluating" to to = screen; @@ -1806,7 +1812,7 @@ void CBattleInterface::mouseMoved(const SDL_MouseMotionEvent &sEvent) //setting console text char buf[500]; //calculating estimated dmg - std::pair estimatedDmg = curInt->cb->battleEstimateDamage(sactive->ID, shere->ID); + std::pair estimatedDmg = curInt->cb->battleEstimateDamage(sactive, shere); std::ostringstream estDmg; estDmg << estimatedDmg.first << " - " << estimatedDmg.second; //printing @@ -1956,7 +1962,7 @@ void CBattleInterface::mouseMoved(const SDL_MouseMotionEvent &sEvent) //setting console info char buf[500]; //calculating estimated dmg - std::pair estimatedDmg = curInt->cb->battleEstimateDamage(sactive->ID, shere->ID); + std::pair estimatedDmg = curInt->cb->battleEstimateDamage(sactive, shere); std::ostringstream estDmg; estDmg << estimatedDmg.first << " - " << estimatedDmg.second; //printing @@ -2273,7 +2279,7 @@ void CBattleInterface::stackAttacking( const CStack * attacker, THex dest, const void CBattleInterface::newRoundFirst( int round ) { //handle regeneration - std::vector stacks = curInt->cb->battleGetStacks(); + std::vector stacks = curInt->cb->battleGetStacks(false); BOOST_FOREACH(const CStack *s, stacks) { //don't show animation when no HP is regenerated diff --git a/global.h b/global.h index 199d80297..0ec278612 100644 --- a/global.h +++ b/global.h @@ -18,7 +18,7 @@ typedef boost::int16_t si16; //signed int 16 bits (2 bytes) typedef boost::int8_t si8; //signed int 8 bits (1 byte) typedef si64 expType; typedef ui16 spelltype; - +typedef std::pair TDmgRange; #include "int3.h" #include @@ -132,20 +132,26 @@ const int SPELLBOOK_GOLD_COST = 500; //for battle stacks' positions struct THex { + static const si16 INVALID = -1; enum EDir{RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT, LEFT, TOP_LEFT, TOP_RIGHT}; si16 hex; - THex() : hex(-1) {} + THex() : hex(INVALID) {} THex(si16 _hex) : hex(_hex) { - assert(hex >= 0 && hex < BFIELD_SIZE); + //assert(isValid()); } operator si16() const { return hex; } + bool isValid() const + { + return hex >= 0 && hex < BFIELD_SIZE; + } + template THex(inttype x, inttype y) { @@ -208,18 +214,25 @@ struct THex { case TOP_LEFT: setXY(y%2 ? x-1 : x, y-1); + break; case TOP_RIGHT: setXY(y%2 ? x : x+1, y-1); + break; case RIGHT: setXY(x+1, y); + break; case BOTTOM_RIGHT: setXY(y%2 ? x : x+1, y+1); + break; case BOTTOM_LEFT: setXY(y%2 ? x-1 : x, y+1); + break; case LEFT: setXY(x-1, y); + break; default: throw std::string("Disaster: wrong direction in THex::operator+=!\n"); + break; } } diff --git a/lib/BattleAction.cpp b/lib/BattleAction.cpp index 4a1880f04..bc636649e 100644 --- a/lib/BattleAction.cpp +++ b/lib/BattleAction.cpp @@ -30,34 +30,18 @@ BattleAction BattleAction::makeDefend(const CStack *stack) return ba; } -BattleAction BattleAction::makeMeleeAttack( const CStack *stack, const CStack * attacked, std::vector reachableByAttacker ) + +BattleAction BattleAction::makeMeleeAttack(const CStack *stack, const CStack * attacked, THex attackFrom /*= THex::INVALID*/) { BattleAction ba; ba.side = !stack->attackerOwned; ba.actionType = WALK_AND_ATTACK; ba.stackNumber = stack->ID; - ba.destinationTile = -1; - for (int g=0; gposition) >= 0 ) - { - ba.destinationTile = reachableByAttacker[g]; - break; - } - } - - if (ba.destinationTile == -1) - { - //we couldn't determine appropriate pos - //TODO: should we throw an exception? - return makeDefend(stack); - } - + ba.destinationTile = attackFrom; ba.additionalInfo = attacked->position; - return ba; -} +} BattleAction BattleAction::makeWait(const CStack *stack) { BattleAction ba; diff --git a/lib/BattleAction.h b/lib/BattleAction.h index 67aef18f9..5d702ebd0 100644 --- a/lib/BattleAction.h +++ b/lib/BattleAction.h @@ -25,7 +25,7 @@ struct DLL_EXPORT BattleAction }; ui8 actionType; //use ActionType enum for values //10 = Monster casts a spell (i.e. Faerie Dragons) 11 - Bad morale freeze 12 - stacks heals another stack - ui16 destinationTile; + THex destinationTile; si32 additionalInfo; // e.g. spell number if type is 1 || 10; tile to attack if type is 6 template void serialize(Handler &h, const int version) { @@ -36,7 +36,7 @@ struct DLL_EXPORT BattleAction static BattleAction makeDefend(const CStack *stack); static BattleAction makeWait(const CStack *stack); - static BattleAction makeMeleeAttack(const CStack *stack, const CStack * attacked, std::vector reachableByAttacker); + static BattleAction makeMeleeAttack(const CStack *stack, const CStack * attacked, THex attackFrom = THex::INVALID); static BattleAction makeShotAttack(const CStack *shooter, const CStack *target); static BattleAction makeMove(const CStack *stack, THex dest); }; diff --git a/lib/BattleState.cpp b/lib/BattleState.cpp index e4246b33c..de7c3596b 100644 --- a/lib/BattleState.cpp +++ b/lib/BattleState.cpp @@ -14,6 +14,7 @@ #include "CCreatureHandler.h" #include "CSpellHandler.h" #include "CTownHandler.h" +#include "NetPacks.h" /* * BattleState.h, part of VCMI engine @@ -326,7 +327,7 @@ std::vector BattleInfo::getAccessibility(const CStack * stack, bool addOcc return ret; } -bool BattleInfo::isStackBlocked(const CStack * stack) +bool BattleInfo::isStackBlocked(const CStack * stack) const { if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON)) //siege weapons cannot be blocked return false; @@ -377,11 +378,11 @@ std::pair< std::vector, int > BattleInfo::getPath(int start, int dest, bool return std::make_pair(path, dist[dest]); } -std::pair BattleInfo::calculateDmgRange( const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky ) +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 ) const { float additiveBonus=1.0f, multBonus=1.0f, - minDmg = attacker->getMinDamage() * attacker->count, - maxDmg = attacker->getMaxDamage() * attacker->count; + minDmg = attacker->getMinDamage() * attackerCount, + maxDmg = attacker->getMaxDamage() * attackerCount; if(attacker->getCreature()->idNumber == 149) //arrow turret { @@ -569,7 +570,7 @@ std::pair BattleInfo::calculateDmgRange( const CStack* attacker, con minDmg *= additiveBonus * multBonus; maxDmg *= additiveBonus * multBonus; - std::pair returnedVal; + TDmgRange returnedVal; if(attacker->getEffect(42)) //curse handling (rest) { @@ -593,9 +594,14 @@ std::pair BattleInfo::calculateDmgRange( const CStack* attacker, con return returnedVal; } +TDmgRange BattleInfo::calculateDmgRange(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky) const +{ + return calculateDmgRange(attacker, defender, attacker->count, defender->count, attackerHero, defendingHero, shooting, charge, lucky); +} + ui32 BattleInfo::calculateDmg( const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky ) { - std::pair range = calculateDmgRange(attacker, defender, attackerHero, defendingHero, shooting, charge, lucky); + TDmgRange range = calculateDmgRange(attacker, defender, attackerHero, defendingHero, shooting, charge, lucky); if(range.first != range.second) { @@ -1021,7 +1027,7 @@ void BattleInfo::getStackQueue( std::vector &out, int howMany, i } } -si8 BattleInfo::hasDistancePenalty( const CStack * stack, THex destHex ) +si8 BattleInfo::hasDistancePenalty( const CStack * stack, THex destHex ) const { struct HLP { @@ -1042,7 +1048,7 @@ si8 BattleInfo::hasDistancePenalty( const CStack * stack, THex destHex ) } -si8 BattleInfo::sameSideOfWall(int pos1, int pos2) +si8 BattleInfo::sameSideOfWall(int pos1, int pos2) const { int wallInStackLine = lineToWallHex(pos1/BFIELD_WIDTH); int wallInDestLine = lineToWallHex(pos2/BFIELD_WIDTH); @@ -1053,7 +1059,7 @@ si8 BattleInfo::sameSideOfWall(int pos1, int pos2) return stackLeft != destLeft; } -si8 BattleInfo::hasWallPenalty( const CStack* stack, THex destHex ) +si8 BattleInfo::hasWallPenalty( const CStack* stack, THex destHex ) const { if (siege == 0) { @@ -1067,7 +1073,7 @@ si8 BattleInfo::hasWallPenalty( const CStack* stack, THex destHex ) return !sameSideOfWall(stack->position, destHex); } -si8 BattleInfo::canTeleportTo(const CStack * stack, THex destHex, int telportLevel) +si8 BattleInfo::canTeleportTo(const CStack * stack, THex destHex, int telportLevel) const { bool ac[BFIELD_SIZE]; @@ -1107,7 +1113,7 @@ si8 BattleInfo::canTeleportTo(const CStack * stack, THex destHex, int telportLev // } // } -bool BattleInfo::battleCanShoot(const CStack * stack, THex dest) +bool BattleInfo::battleCanShoot(const CStack * stack, THex dest) const { const CStack *dst = getStackT(dest); @@ -1131,7 +1137,7 @@ bool BattleInfo::battleCanShoot(const CStack * stack, THex dest) return false; } -bool BattleInfo::battleCanFlee(int player) +bool BattleInfo::battleCanFlee(int player) const { if (player == side1) { @@ -1171,12 +1177,12 @@ const CStack * BattleInfo::battleGetStack(THex pos, bool onlyAlive) return NULL; } -const CGHeroInstance * BattleInfo::battleGetOwner(const CStack * stack) +const CGHeroInstance * BattleInfo::battleGetOwner(const CStack * stack) const { return heroes[!stack->attackerOwned]; } -si8 BattleInfo::battleMaxSpellLevel() +si8 BattleInfo::battleMaxSpellLevel() const { // if(!curB) //there is not battle // { @@ -1944,6 +1950,55 @@ std::string CStack::nodeName() const return oss.str(); } +void CStack::prepareAttacked(BattleStackAttacked &bsa) const +{ + bsa.killedAmount = bsa.damageAmount / MaxHealth(); + unsigned damageFirst = bsa.damageAmount % MaxHealth(); + + if( firstHPleft <= damageFirst ) + { + bsa.killedAmount++; + bsa.newHP = firstHPleft + MaxHealth() - damageFirst; + } + else + { + bsa.newHP = firstHPleft - damageFirst; + } + + if(count <= bsa.killedAmount) //stack killed + { + bsa.newAmount = 0; + bsa.flags |= 1; + bsa.killedAmount = count; //we cannot kill more creatures than we have + } + else + { + bsa.newAmount = count - bsa.killedAmount; + } +} + +bool CStack::isMeleeAttackPossible(const CStack * attacker, const CStack * defender, THex attackerPos /*= THex::INVALID*/, THex defenderPos /*= THex::INVALID*/) +{ + if (!attackerPos.isValid()) + { + attackerPos = attacker->position; + } + if (!defenderPos.isValid()) + { + defenderPos = defender->position; + } + + return + (THex::mutualPosition(attackerPos, defenderPos) >= 0) //front <=> front + || (attacker->doubleWide() //back <=> front + && THex::mutualPosition(attackerPos + (attacker->attackerOwned ? -1 : 1), defenderPos) >= 0) + || (defender->doubleWide() //front <=> back + && THex::mutualPosition(attackerPos, defenderPos + (defender->attackerOwned ? -1 : 1)) >= 0) + || (defender->doubleWide() && attacker->doubleWide()//back <=> back + && THex::mutualPosition(attackerPos + (attacker->attackerOwned ? -1 : 1), defenderPos + (defender->attackerOwned ? -1 : 1)) >= 0); + +} + bool CMP_stack::operator()( const CStack* a, const CStack* b ) { switch(phase) diff --git a/lib/BattleState.h b/lib/BattleState.h index 1ea4c98b8..8a4ccbcac 100644 --- a/lib/BattleState.h +++ b/lib/BattleState.h @@ -21,7 +21,7 @@ class CStack; class CArmedInstance; class CGTownInstance; class CStackInstance; - +struct BattleStackAttacked; struct DLL_EXPORT CObstacleInstance { @@ -84,10 +84,11 @@ struct DLL_EXPORT BattleInfo : public CBonusSystemNode std::pair< std::vector, int > getPath(int start, int 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) const; //returns vector of accessible tiles (taking into account the creature range) - bool isStackBlocked(const CStack * stack); //returns true if there is neighboring enemy stack + bool isStackBlocked(const CStack * stack) const; //returns true if there is neighboring enemy stack ui32 calculateDmg(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky); //charge - number of hexes travelled before attack (for champion's jousting) - std::pair calculateDmgRange(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky); //charge - number of hexes travelled before attack (for champion's jousting); returns pair + TDmgRange calculateDmgRange( const CStack* attacker, const CStack* defender, TQuantity attackerCount, TQuantity defenderCount, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky) 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) 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, int destinationTile); //calculates stack affected by given spell static int calculateSpellDuration(const CSpell * spell, const CGHeroInstance * caster, int usedSpellPower); @@ -100,16 +101,16 @@ struct DLL_EXPORT BattleInfo : public CBonusSystemNode 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; - si8 hasDistancePenalty(const CStack * stackID, THex destHex); //determines if given stack has distance penalty shooting given pos - si8 sameSideOfWall(int pos1, int pos2); //determines if given positions are on the same side of wall - si8 hasWallPenalty(const CStack * stack, THex destHex); //determines if given stack has wall penalty shooting given pos - si8 canTeleportTo(const CStack * stack, THex destHex, int telportLevel); //determines if given stack can teleport to given place - bool battleCanShoot(const CStack * stack, THex dest); //determines if stack with given ID shoot at the selected destination + si8 hasDistancePenalty(const CStack * stackID, THex 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, THex destHex) const; //determines if given stack has wall penalty shooting given pos + si8 canTeleportTo(const CStack * stack, THex destHex, int telportLevel) const; //determines if given stack can teleport to given place + bool battleCanShoot(const CStack * stack, THex dest) const; //determines if stack with given ID shoot at the selected destination - bool battleCanFlee(int player); //returns true if player can flee from the battle + bool battleCanFlee(int player) const; //returns true if player can flee from the battle const CStack * battleGetStack(THex pos, bool onlyAlive); //returns stack at given tile - const CGHeroInstance * battleGetOwner(const CStack * stack); //returns hero that owns given stack; NULL if none - si8 battleMaxSpellLevel(); //calculates maximum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, SPELL_LEVELS is returned + const CGHeroInstance * battleGetOwner(const CStack * stack) const; //returns hero that owns given stack; NULL if none + si8 battleMaxSpellLevel() const; //calculates maximum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, SPELL_LEVELS is returned void localInit(); static BattleInfo * BattleInfo::setupBattle( int3 tile, int terrain, int terType, const CArmedInstance *armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance *town ); }; @@ -167,9 +168,13 @@ public: return ret; } + static bool isMeleeAttackPossible(const CStack * attacker, const CStack * defender, THex attackerPos = THex::INVALID, THex defenderPos = THex::INVALID); + bool doubleWide() const; int occupiedHex() const; //returns number of occupied hex (not the position) if stack is double wide; otherwise -1 + void prepareAttacked(BattleStackAttacked &bsa) const; //requires bsa.damageAmout filled + template void serialize(Handler &h, const int version) { assert(isIndependentNode()); diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 6afa07193..acc0f1de9 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -905,11 +905,12 @@ void CGameState::init( StartInfo * si, ui32 checksum, int Seed ) h->subID = 1; h->initHero(1); h->initObj(); - //h->putStack(0, new CStackInstance(34, 5)); + h->setCreature(0, 110, 1); CGCreature *c = new CGCreature(); c->setOwner(1); - c->putStack(0, new CStackInstance(70, 6)); + c->putStack(0, new CStackInstance(69, 6)); + c->putStack(1, new CStackInstance(11, 3)); c->subID = 34; c->initObj(); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 41ab6daa0..5acabfd5b 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -19,7 +19,7 @@ #include "../lib/VCMIDirs.h" #include "../client/CSoundBase.h" #include "CGameHandler.h" - +#include /* * CGameHandler.cpp, part of VCMI engine @@ -309,8 +309,6 @@ void CGameHandler::changeSecSkill( int ID, int which, int val, bool abs/*=false* void CGameHandler::startBattle( const CArmedInstance *armies[2], int3 tile, const CGHeroInstance *heroes[2], bool creatureBank, boost::function cb, const CGTownInstance *town /*= NULL*/ ) { battleEndCallback = new boost::function(cb); - bEndArmy1 = armies[0]; - bEndArmy2 = armies[1]; { setupBattle(tile, armies, heroes, creatureBank, town); //initializes stacks, places creatures on battlefield, blocks and informs player interfaces } @@ -321,6 +319,9 @@ void CGameHandler::startBattle( const CArmedInstance *armies[2], int3 tile, cons void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2) { BattleResultsApplied resultsApplied; + + const CArmedInstance *bEndArmy1 = gs->curB->belligerents[0]; + const CArmedInstance *bEndArmy2 = gs->curB->belligerents[0]; resultsApplied.player1 = bEndArmy1->tempOwner; resultsApplied.player2 = bEndArmy2->tempOwner; @@ -425,33 +426,6 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer delete battleResult.data; } -void CGameHandler::prepareAttacked(BattleStackAttacked &bsa, const CStack *def) -{ - bsa.killedAmount = bsa.damageAmount / def->MaxHealth(); - unsigned damageFirst = bsa.damageAmount % def->MaxHealth(); - - if( def->firstHPleft <= damageFirst ) - { - bsa.killedAmount++; - bsa.newHP = def->firstHPleft + def->MaxHealth() - damageFirst; - } - else - { - bsa.newHP = def->firstHPleft - damageFirst; - } - - if(def->count <= bsa.killedAmount) //stack killed - { - bsa.newAmount = 0; - bsa.flags |= 1; - bsa.killedAmount = def->count; //we cannot kill more creatures than we have - } - else - { - bsa.newAmount = def->count - bsa.killedAmount; - } -} - void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CStack *def, int distance) { bat.bsa.clear(); @@ -480,7 +454,7 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt int dmg = bsa->damageAmount; - prepareAttacked(*bsa, def); + def->prepareAttacked(*bsa); //life drain handling if (att->hasBonusOfType(Bonus::LIFE_DRAIN)) @@ -515,7 +489,7 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt bsa->effect = 11; bsa->damageAmount = (dmg * def->valOfBonuses(Bonus::FIRE_SHIELD)) / 100; - prepareAttacked(*bsa, att); + att->prepareAttacked(*bsa); } } @@ -3026,21 +3000,21 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) bool ok = true; switch(ba.actionType) { - case 2: //walk + case BattleAction::WALK: //walk { sendAndApply(&StartAction(ba)); //start movement moveStack(ba.stackNumber,ba.destinationTile); //move sendAndApply(&EndAction()); break; } - case 3: //defend - case 8: //wait + case BattleAction::DEFEND: //defend + case BattleAction::WAIT: //wait { sendAndApply(&StartAction(ba)); sendAndApply(&EndAction()); break; } - case 4: //retreat/flee + case BattleAction::RETREAT: //retreat/flee { if( !gs->curB->battleCanFlee(ba.side ? gs->curB->side2 : gs->curB->side1) ) break; @@ -3053,7 +3027,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) battleResult.set(br); break; } - case 6: //walk or attack + case BattleAction::WALK_AND_ATTACK: //walk or attack { sendAndApply(&StartAction(ba)); //start movement and attack int startingPos = gs->curB->getStack(ba.stackNumber)->position; @@ -3075,37 +3049,20 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) break; } - if(curStack->ID == stackAtEnd->ID) //we should just move, it will be handled by following check + if(stackAtEnd && curStack->ID == stackAtEnd->ID) //we should just move, it will be handled by following check { stackAtEnd = NULL; } if(!stackAtEnd) { - std::ostringstream problem; - problem << "There is no stack on " << ba.additionalInfo << " tile (no attack)!"; - std::string probl = problem.str(); - tlog3 << probl << std::endl; - complain(probl); + complain(boost::str(boost::format("walk and attack error: no stack at additionalInfo tile (%d)!\n") % ba.additionalInfo)); ok = false; sendAndApply(&EndAction()); break; } - ui16 curpos = curStack->position, - enemypos = stackAtEnd->position; - - - if( !( - (THex::mutualPosition(curpos, enemypos) >= 0) //front <=> front - || (curStack->doubleWide() //back <=> front - && THex::mutualPosition(curpos + (curStack->attackerOwned ? -1 : 1), enemypos) >= 0) - || (stackAtEnd->doubleWide() //front <=> back - && THex::mutualPosition(curpos, enemypos + (stackAtEnd->attackerOwned ? -1 : 1)) >= 0) - || (stackAtEnd->doubleWide() && curStack->doubleWide()//back <=> back - && THex::mutualPosition(curpos + (curStack->attackerOwned ? -1 : 1), enemypos + (stackAtEnd->attackerOwned ? -1 : 1)) >= 0) - ) - ) + if( !CStack::isMeleeAttackPossible(curStack, stackAtEnd) ) { tlog3 << "Attack cannot be performed!"; sendAndApply(&EndAction()); @@ -3152,7 +3109,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) sendAndApply(&EndAction()); break; } - case 7: //shoot + case BattleAction::SHOOT: //shoot { CStack *curStack = gs->curB->getStack(ba.stackNumber), *destStack= gs->curB->getStackT(ba.destinationTile); @@ -3180,7 +3137,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) sendAndApply(&EndAction()); break; } - case 9: //catapult + case BattleAction::CATAPULT: //catapult { sendAndApply(&StartAction(ba)); const CGHeroInstance * attackingHero = gs->curB->heroes[ba.side]; @@ -3282,7 +3239,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) sendAndApply(&EndAction()); break; } - case 12: //healing + case BattleAction::STACK_HEAL: //healing { sendAndApply(&StartAction(ba)); const CGHeroInstance * attackingHero = gs->curB->heroes[ba.side]; @@ -3609,7 +3566,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, int destinatio bsa.damageAmount = gs->curB->calculateSpellDmg(spell, caster, *it, spellLvl, usedSpellPower); bsa.stackAttacked = (*it)->ID; bsa.attackerID = -1; - prepareAttacked(bsa,*it); + (*it)->prepareAttacked(bsa); si.stacks.push_back(bsa); } if(!si.stacks.empty()) diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 83f90106c..b2d53690e 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -113,12 +113,11 @@ public: ////used only in endBattle - don't touch elsewhere boost::function * battleEndCallback; - const CArmedInstance * bEndArmy1, * bEndArmy2; + //const CArmedInstance * bEndArmy1, * bEndArmy2; bool visitObjectAfterVictory; // void endBattle(int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2); //ends battle void prepareAttack(BattleAttack &bat, const CStack *att, const CStack *def, int distance); //distance - number of hexes travelled before attacking - void prepareAttacked(BattleStackAttacked &bsa, const CStack *def); void checkForBattleEnd( std::vector &stacks ); void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town);