1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Stupid AI is capable of winning / losing battle.

This commit is contained in:
Michał W. Urbańczyk 2011-01-08 18:33:40 +00:00
parent 957f1764d7
commit a53ec23556
14 changed files with 336 additions and 153 deletions

View File

@ -75,7 +75,7 @@ void CBattleLogic::SetCurrentTurn(int turn)
void CBattleLogic::MakeStatistics(int currentCreatureId)
{
typedef std::vector<const CStack*> 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
{

View File

@ -2,6 +2,13 @@
#include "StupidAI.h"
#include "../../lib/BattleState.h"
#include "../../CCallback.h"
#include <boost/foreach.hpp>
#include <boost/bind.hpp>
#include "../../lib/CCreatureHandler.h"
#include <algorithm>
#include <boost/thread.hpp>
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<THex> 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<int> & 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<int> & 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<THex> avHexes = cb->battleGetAvailableHexes(stack, false);
std::vector<const CStack *> avEnemies;
for(int g=0; g<avHexes.size(); ++g)
std::vector<int> dists = cb->battleGetDistances(stack);
std::vector<EnemyInfo> 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<EnemyInfo>::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(stack->position % 17 < 5) //move army little towards enemy
{
THex dest = stack->position + !side*2 - 1;
print(stack->nodeName() + "will be moved to " + boost::lexical_cast<std::string>(dest));
return BattleAction::makeMove(stack, dest);
if(!vstd::contains(enemiesReachable, s))
enemiesUnreachable.push_back(s);
}
}
}
if(avEnemies.size())
if(enemiesShootable.size())
{
const CStack * enemy = avEnemies[0];
//shooting
if (cb->battleCanShoot(stack, enemy->position))
{
return BattleAction::makeShotAttack(stack, enemy);
const EnemyInfo &ei= *std::max_element(enemiesShootable.begin(), enemiesShootable.end(), isMoreProfitable);
return BattleAction::makeShotAttack(stack, ei.s);
}
else if(enemiesReachable.size())
{
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 goTowards(stack, ei.s->position);
}
//melee
return BattleAction::makeMeleeAttack(stack, enemy, avHexes);
}
return BattleAction::makeDefend(stack);
@ -149,3 +238,28 @@ void CStupidAI::print(const std::string &text) const
{
tlog0 << "CStupidAI [" << this <<"]: " << text << std::endl;
}
BattleAction CStupidAI::goTowards(const CStack * stack, THex hex)
{
THex realDest = hex;
int predecessors[BFIELD_SIZE];
std::vector<int> 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<THex> avHexes = cb->battleGetAvailableHexes(stack, false);
while(1)
{
assert(realDest.isValid());
if(vstd::contains(avHexes, hex))
return BattleAction::makeMove(stack, hex);
hex = predecessors[hex];
}
}

View File

@ -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 );
};

View File

@ -539,7 +539,7 @@ int CBattleCallback::battleGetPos(int stack)
return -1;
}
std::vector<const CStack*> CBattleCallback::battleGetStacks()
std::vector<const CStack*> CBattleCallback::battleGetStacks(bool onlyAlive /*= true*/)
{
boost::shared_lock<boost::shared_mutex> lock(*gs->mx);
std::vector<const CStack*> ret;
@ -550,6 +550,7 @@ std::vector<const CStack*> CBattleCallback::battleGetStacks()
}
BOOST_FOREACH(const CStack *s, gs->curB->stacks)
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<ui32, ui32> 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<ui32, ui32> 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()
@ -1040,3 +1059,30 @@ CBattleCallback::CBattleCallback(CGameState *GS, int Player, CClient *C )
player = Player;
cl = C;
}
std::vector<int> CBattleCallback::battleGetDistances(const CStack * stack, THex hex /*= THex::INVALID*/, int * predecessors /*= NULL*/)
{
if(!hex.isValid())
hex = stack->position;
std::vector<int> 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<BFIELD_SIZE; ++i)
{
if(pr[i] == -1)
ret.push_back(-1);
else
ret.push_back(dist[i]);
}
if(predecessors)
{
memcpy(predecessors, pr, BFIELD_SIZE * sizeof(int));
}
return ret;
}

View File

@ -82,16 +82,17 @@ public:
virtual const CStack * battleGetStackByPos(THex pos, bool onlyAlive = true)=0; //returns stack info by given pos
virtual int battleGetPos(int stack)=0; //returns position (tile ID) of stack
virtual int battleMakeAction(BattleAction* action)=0;//for casting spells by hero - DO NOT use it for moving active stack
virtual std::vector<const CStack*> battleGetStacks()=0; //returns stacks on battlefield
virtual std::vector<const CStack*> battleGetStacks(bool onlyAlive = true)=0; //returns stacks on battlefield
virtual void getStackQueue( std::vector<const CStack *> &out, int howMany )=0; //returns vector of stack in order of their move sequence
virtual std::vector<THex> battleGetAvailableHexes(const CStack * stack, bool addOccupiable)=0; //returns numbers of hexes reachable by creature with id ID
virtual std::vector<int> 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<ui32, ui32> 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 <min dmg, max dmg>
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 <min dmg, max dmg>
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<const CStack*> battleGetStacks() OVERRIDE; //returns stacks on battlefield
std::vector<const CStack*> battleGetStacks(bool onlyAlive = true) OVERRIDE; //returns stacks on battlefield
void getStackQueue( std::vector<const CStack *> &out, int howMany ) OVERRIDE; //returns vector of stack in order of their move sequence
std::vector<THex> battleGetAvailableHexes(const CStack * stack, bool addOccupiable) OVERRIDE; //reutrns numbers of hexes reachable by creature with id ID
std::vector<int> 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<ui32, ui32> 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 <min dmg, max dmg>
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 <min dmg, max dmg>
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

View File

@ -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: "<<dest<<" attacking stack pos: "<<attackingStackPosBeforeReturn<<" reversed shift: "<<reversedShift<<std::endl;
group = 11;
break;
}
return true;
@ -1129,7 +1135,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe
//initializing armies
this->army1 = army1;
this->army2 = army2;
std::vector<const CStack*> stacks = curInt->cb->battleGetStacks();
std::vector<const CStack*> 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<const CStack*> stacks = curInt->cb->battleGetStacks(); //used in a few places
std::vector<const CStack*> 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<ui32, ui32> estimatedDmg = curInt->cb->battleEstimateDamage(sactive->ID, shere->ID);
std::pair<ui32, ui32> 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<ui32, ui32> estimatedDmg = curInt->cb->battleEstimateDamage(sactive->ID, shere->ID);
std::pair<ui32, ui32> 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<const CStack*> stacks = curInt->cb->battleGetStacks();
std::vector<const CStack*> stacks = curInt->cb->battleGetStacks(false);
BOOST_FOREACH(const CStack *s, stacks)
{
//don't show animation when no HP is regenerated

View File

@ -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<ui32, ui32> TDmgRange;
#include "int3.h"
#include <map>
@ -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<typename inttype>
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;
}
}

View File

@ -30,34 +30,18 @@ BattleAction BattleAction::makeDefend(const CStack *stack)
return ba;
}
BattleAction BattleAction::makeMeleeAttack( const CStack *stack, const CStack * attacked, std::vector<THex> 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; g<reachableByAttacker.size(); ++g)
{
if (THex::mutualPosition(reachableByAttacker[g], attacked->position) >= 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;

View File

@ -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 <typename Handler> 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<THex> 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);
};

View File

@ -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<THex> 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>, int > BattleInfo::getPath(int start, int dest, bool
return std::make_pair(path, dist[dest]);
}
std::pair<ui32, ui32> 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<ui32, ui32> BattleInfo::calculateDmgRange( const CStack* attacker, con
minDmg *= additiveBonus * multBonus;
maxDmg *= additiveBonus * multBonus;
std::pair<ui32, ui32> returnedVal;
TDmgRange returnedVal;
if(attacker->getEffect(42)) //curse handling (rest)
{
@ -593,9 +594,14 @@ std::pair<ui32, ui32> 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<ui32, ui32> 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<const CStack *> &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)

View File

@ -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>, int > getPath(int start, int dest, bool*accessibility, bool flyingCreature, bool twoHex, bool attackerOwned); //returned value: pair<path, length>; length may be different than number of elements in path since flying vreatures jump between distant hexes
std::vector<THex> 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<ui32, ui32> 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 <min dmg, max dmg>
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 <min dmg, max dmg>
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 <min dmg, max dmg>
void calculateCasualties(std::map<ui32,si32> *casualties) const; //casualties are array of maps size 2 (attacker, defeneder), maps are (crid => amount)
std::set<CStack*> 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 <typename Handler> void serialize(Handler &h, const int version)
{
assert(isIndependentNode());

View File

@ -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();

View File

@ -19,7 +19,7 @@
#include "../lib/VCMIDirs.h"
#include "../client/CSoundBase.h"
#include "CGameHandler.h"
#include <boost/format.hpp>
/*
* 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<void(BattleResult*)> cb, const CGTownInstance *town /*= NULL*/ )
{
battleEndCallback = new boost::function<void(BattleResult*)>(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())

View File

@ -113,12 +113,11 @@ public:
////used only in endBattle - don't touch elsewhere
boost::function<void(BattleResult*)> * 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<CStack*> &stacks );
void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town);